pinot-client 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +66 -0
- data/lib/pinot/config.rb +8 -2
- data/lib/pinot/connection_factory.rb +6 -2
- data/lib/pinot/transport.rb +42 -15
- data/lib/pinot/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 819824203883477127807cc1a86d8535f7721883dfaec85423c5b333dd453b6e
|
|
4
|
+
data.tar.gz: dff26b09b035ad4d217a63b2b661677d4b47922760f2f140a6cda942d9383a20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 606903f919bd310a8b1c208206db2c4ac25b5522ee26079435966f95f171573b0800266970ca3643c29528f506cf2a2c392b7161f7f2c3a174c313732a53721e
|
|
7
|
+
data.tar.gz: bc99e8c6ba493de6a534009e557915f9e72a57298129837750c49d8e1026dc8785822b079c63d05c42842a5b800930c44ac415bdf487e622fe1518f5cbbbf867
|
data/README.md
CHANGED
|
@@ -62,6 +62,30 @@ The client periodically polls the controller's `/v2/brokers/tables` API and auto
|
|
|
62
62
|
client = Pinot.from_controller("localhost:9000")
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
### Via gRPC
|
|
66
|
+
|
|
67
|
+
Requires the `grpc` gem (`gem "grpc", "~> 1.65"` in your Gemfile).
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
grpc_config = Pinot::GrpcConfig.new(
|
|
71
|
+
broker_list: ["localhost:8090"],
|
|
72
|
+
timeout: 10,
|
|
73
|
+
extra_metadata: { "Authorization" => "Bearer <token>" }
|
|
74
|
+
)
|
|
75
|
+
config = Pinot::ClientConfig.new(grpc_config: grpc_config)
|
|
76
|
+
client = Pinot.from_config(config)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### From ZooKeeper (dynamic broker discovery)
|
|
80
|
+
|
|
81
|
+
Requires the `zk` gem (`gem "zk"` in your Gemfile). Watches `/EXTERNALVIEW/brokerResource` and automatically picks up broker changes.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
zk_config = Pinot::ZookeeperConfig.new(zk_path: "localhost:2181")
|
|
85
|
+
config = Pinot::ClientConfig.new(zookeeper_config: zk_config)
|
|
86
|
+
client = Pinot.from_config(config)
|
|
87
|
+
```
|
|
88
|
+
|
|
65
89
|
### From a `ClientConfig`
|
|
66
90
|
|
|
67
91
|
```ruby
|
|
@@ -79,6 +103,8 @@ config = Pinot::ClientConfig.new(
|
|
|
79
103
|
client = Pinot.from_config(config)
|
|
80
104
|
```
|
|
81
105
|
|
|
106
|
+
`validate!` raises `Pinot::ConfigurationError` early if the config is invalid.
|
|
107
|
+
|
|
82
108
|
### With TLS
|
|
83
109
|
|
|
84
110
|
```ruby
|
|
@@ -111,6 +137,20 @@ config = Pinot::ClientConfig.new(
|
|
|
111
137
|
client = Pinot.from_config(config)
|
|
112
138
|
```
|
|
113
139
|
|
|
140
|
+
## Instrumentation
|
|
141
|
+
|
|
142
|
+
Hook into every query execution for metrics, tracing, or alerting:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
Pinot::Instrumentation.on_query = lambda do |event|
|
|
146
|
+
puts "#{event[:table]} #{event[:query][0..50]} " \
|
|
147
|
+
"#{event[:duration_ms].round(1)}ms " \
|
|
148
|
+
"success=#{event[:success]}"
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The event hash contains: `:table`, `:query`, `:duration_ms` (Float), `:success` (Boolean), `:error` (Exception or nil).
|
|
153
|
+
|
|
114
154
|
## Executing Queries
|
|
115
155
|
|
|
116
156
|
### Simple SQL
|
|
@@ -148,6 +188,19 @@ config = Pinot::ClientConfig.new(
|
|
|
148
188
|
client = Pinot.from_config(config)
|
|
149
189
|
```
|
|
150
190
|
|
|
191
|
+
### Per-request timeout
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
# Global default via config
|
|
195
|
+
config = Pinot::ClientConfig.new(
|
|
196
|
+
broker_list: ["localhost:8000"],
|
|
197
|
+
query_timeout_ms: 5000
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# One-off override per query
|
|
201
|
+
resp = client.execute_sql_with_timeout("myTable", "SELECT * FROM myTable", 3000)
|
|
202
|
+
```
|
|
203
|
+
|
|
151
204
|
### Trace
|
|
152
205
|
|
|
153
206
|
```ruby
|
|
@@ -182,6 +235,19 @@ rescue Pinot::Error => e
|
|
|
182
235
|
end
|
|
183
236
|
```
|
|
184
237
|
|
|
238
|
+
## Retry
|
|
239
|
+
|
|
240
|
+
Configure automatic retry with exponential backoff for transient errors (connection reset, timeout, HTTP 503):
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
config = Pinot::ClientConfig.new(
|
|
244
|
+
broker_list: ["localhost:8000"],
|
|
245
|
+
max_retries: 3,
|
|
246
|
+
retry_interval_ms: 200 # base interval; doubles each attempt (200ms, 400ms, 800ms)
|
|
247
|
+
)
|
|
248
|
+
client = Pinot.from_config(config)
|
|
249
|
+
```
|
|
250
|
+
|
|
185
251
|
## Reading Results
|
|
186
252
|
|
|
187
253
|
`execute_sql` returns a `Pinot::BrokerResponse`. Results are in `result_table`:
|
data/lib/pinot/config.rb
CHANGED
|
@@ -22,7 +22,9 @@ module Pinot
|
|
|
22
22
|
class ClientConfig
|
|
23
23
|
attr_accessor :broker_list, :http_timeout, :query_timeout_ms, :extra_http_header,
|
|
24
24
|
:use_multistage_engine, :controller_config, :logger, :tls_config,
|
|
25
|
-
:grpc_config, :zookeeper_config
|
|
25
|
+
:grpc_config, :zookeeper_config,
|
|
26
|
+
:max_retries, # Integer, default 0 (no retry)
|
|
27
|
+
:retry_interval_ms # Integer ms base interval, default 200
|
|
26
28
|
|
|
27
29
|
def initialize(
|
|
28
30
|
broker_list: [],
|
|
@@ -34,7 +36,9 @@ module Pinot
|
|
|
34
36
|
logger: nil,
|
|
35
37
|
tls_config: nil,
|
|
36
38
|
grpc_config: nil,
|
|
37
|
-
zookeeper_config: nil
|
|
39
|
+
zookeeper_config: nil,
|
|
40
|
+
max_retries: 0,
|
|
41
|
+
retry_interval_ms: 200
|
|
38
42
|
)
|
|
39
43
|
@broker_list = broker_list
|
|
40
44
|
@http_timeout = http_timeout
|
|
@@ -47,6 +51,8 @@ module Pinot
|
|
|
47
51
|
@grpc_config = grpc_config
|
|
48
52
|
@zookeeper_config = zookeeper_config
|
|
49
53
|
@query_timeout_ms = query_timeout_ms
|
|
54
|
+
@max_retries = max_retries
|
|
55
|
+
@retry_interval_ms = retry_interval_ms
|
|
50
56
|
end
|
|
51
57
|
|
|
52
58
|
def validate!
|
|
@@ -37,7 +37,9 @@ module Pinot
|
|
|
37
37
|
transport = JsonHttpTransport.new(
|
|
38
38
|
http_client: inner,
|
|
39
39
|
extra_headers: config.extra_http_header || {},
|
|
40
|
-
logger: config.logger
|
|
40
|
+
logger: config.logger,
|
|
41
|
+
max_retries: config.max_retries || 0,
|
|
42
|
+
retry_interval_ms: config.retry_interval_ms || 200
|
|
41
43
|
)
|
|
42
44
|
|
|
43
45
|
return Connection.new(
|
|
@@ -53,7 +55,9 @@ module Pinot
|
|
|
53
55
|
transport = JsonHttpTransport.new(
|
|
54
56
|
http_client: inner,
|
|
55
57
|
extra_headers: config.extra_http_header || {},
|
|
56
|
-
logger: config.logger
|
|
58
|
+
logger: config.logger,
|
|
59
|
+
max_retries: config.max_retries || 0,
|
|
60
|
+
retry_interval_ms: config.retry_interval_ms || 200
|
|
57
61
|
)
|
|
58
62
|
|
|
59
63
|
selector = build_selector(config, inner)
|
data/lib/pinot/transport.rb
CHANGED
|
@@ -108,33 +108,60 @@ module Pinot
|
|
|
108
108
|
"Content-Type" => "application/json; charset=utf-8"
|
|
109
109
|
}.freeze
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
RETRYABLE_ERRORS = [
|
|
112
|
+
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
|
113
|
+
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout
|
|
114
|
+
].freeze
|
|
115
|
+
|
|
116
|
+
def initialize(http_client:, extra_headers: {}, timeout_ms: nil, logger: nil,
|
|
117
|
+
max_retries: 0, retry_interval_ms: 200)
|
|
112
118
|
@http_client = http_client
|
|
113
119
|
@extra_headers = extra_headers
|
|
114
120
|
@timeout_ms = timeout_ms
|
|
115
121
|
@logger = logger
|
|
122
|
+
@max_retries = max_retries
|
|
123
|
+
@retry_interval_ms = retry_interval_ms
|
|
116
124
|
end
|
|
117
125
|
|
|
118
126
|
def execute(broker_address, request)
|
|
119
127
|
logger.debug "Pinot query to #{broker_address}: #{request.query}"
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
headers = DEFAULT_HEADERS
|
|
124
|
-
.merge(@extra_headers)
|
|
125
|
-
.merge("X-Correlation-Id" => SecureRandom.uuid)
|
|
129
|
+
attempts = 0
|
|
130
|
+
max_attempts = (@max_retries || 0) + 1
|
|
126
131
|
|
|
127
|
-
|
|
132
|
+
begin
|
|
133
|
+
attempts += 1
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
url = build_url(broker_address, request.query_format)
|
|
136
|
+
body = build_body(request)
|
|
137
|
+
headers = DEFAULT_HEADERS
|
|
138
|
+
.merge(@extra_headers)
|
|
139
|
+
.merge("X-Correlation-Id" => SecureRandom.uuid)
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
resp = @http_client.post(url, body: body, headers: headers)
|
|
142
|
+
|
|
143
|
+
if resp.code == "503"
|
|
144
|
+
logger.error "Pinot broker returned HTTP #{resp.code}"
|
|
145
|
+
raise TransportError, "http exception with HTTP status code #{resp.code}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
unless resp.code.to_i == 200
|
|
149
|
+
logger.error "Pinot broker returned HTTP #{resp.code}"
|
|
150
|
+
raise TransportError, "http exception with HTTP status code #{resp.code}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
begin
|
|
154
|
+
BrokerResponse.from_json(resp.body)
|
|
155
|
+
rescue JSON::ParserError => e
|
|
156
|
+
raise e.message
|
|
157
|
+
end
|
|
158
|
+
rescue TransportError, *RETRYABLE_ERRORS => e
|
|
159
|
+
if attempts < max_attempts
|
|
160
|
+
sleep_ms = (@retry_interval_ms || 200) * (2 ** (attempts - 1))
|
|
161
|
+
sleep(sleep_ms / 1000.0)
|
|
162
|
+
retry
|
|
163
|
+
end
|
|
164
|
+
raise
|
|
138
165
|
end
|
|
139
166
|
end
|
|
140
167
|
|
data/lib/pinot/version.rb
CHANGED