pinot-client 1.3.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6d0692d6f1de1178eb0c27cb2f69e3f6575362b1163e58032538b2b003e13cb
4
- data.tar.gz: d9ee2eb20b541cfe54ad640b223425ed16697e869b031349d021ef89c8bab5af
3
+ metadata.gz: bb872f6cbcc1f34f16a68006eac8acea39229bdc9bd90f9f44a3369ed9c9fb59
4
+ data.tar.gz: b86cde24abaa6343f9984b3106830e9f00e25656050a4f577629942c3e0067c2
5
5
  SHA512:
6
- metadata.gz: d66f070cd740df59a8351724b954b902633052e0a94c0cffcc377cba887329709955b0c93075f5f90ea820abe453aa716b4f6eae80600ce90015eda669c33dd0
7
- data.tar.gz: '03345279b3e4ae8621fa75a4222dce2988cf61255663b659c7307296a9506680f29f804938ec1b5d28326bf15e4e5c4cf5984e71233b2e0fcf255f6123f0fa36'
6
+ metadata.gz: afc088a4df6eaa77e6141fa051d7f2e1f3afa1aa8d75555ed2291dfc84fe159a9538ad86621d452bc510066576a331d6e23135aa93529f672bf912ba46ed5b0c
7
+ data.tar.gz: b34bcb378ab7a6bca383545b79e090273ed0b67d4e5356699461b02bbbaaafd7e7580073e492bbb7f43cf988c77ba500e361e1954c53a990aab5be6d785cae4b
data/README.md CHANGED
@@ -277,6 +277,10 @@ Environment variables:
277
277
  | `BROKER_HOST` | `127.0.0.1` | Pinot broker hostname |
278
278
  | `BROKER_PORT` | `8000` | Pinot broker HTTP port |
279
279
 
280
+ ## Changelog
281
+
282
+ See [CHANGELOG.md](CHANGELOG.md) for a full history of releases.
283
+
280
284
  ## License
281
285
 
282
286
  Apache License 2.0 — see [LICENSE](LICENSE).
data/lib/pinot/config.rb CHANGED
@@ -1,4 +1,14 @@
1
1
  module Pinot
2
+ class ZookeeperConfig
3
+ attr_accessor :zk_path, # "host:port" or "host1:port,host2:port/chroot"
4
+ :session_timeout_ms # default 30000
5
+
6
+ def initialize(zk_path:, session_timeout_ms: 30_000)
7
+ @zk_path = zk_path
8
+ @session_timeout_ms = session_timeout_ms
9
+ end
10
+ end
11
+
2
12
  class ControllerConfig
3
13
  attr_accessor :controller_address, :update_freq_ms, :extra_controller_api_headers
4
14
 
@@ -10,28 +20,55 @@ module Pinot
10
20
  end
11
21
 
12
22
  class ClientConfig
13
- attr_accessor :broker_list, :http_timeout, :extra_http_header,
23
+ attr_accessor :broker_list, :http_timeout, :query_timeout_ms, :extra_http_header,
14
24
  :use_multistage_engine, :controller_config, :logger, :tls_config,
15
- :grpc_config
25
+ :grpc_config, :zookeeper_config
16
26
 
17
27
  def initialize(
18
28
  broker_list: [],
19
29
  http_timeout: nil,
30
+ query_timeout_ms: nil,
20
31
  extra_http_header: {},
21
32
  use_multistage_engine: false,
22
33
  controller_config: nil,
23
34
  logger: nil,
24
35
  tls_config: nil,
25
- grpc_config: nil
36
+ grpc_config: nil,
37
+ zookeeper_config: nil
26
38
  )
27
39
  @broker_list = broker_list
28
40
  @http_timeout = http_timeout
41
+ @query_timeout_ms = query_timeout_ms
29
42
  @extra_http_header = extra_http_header
30
43
  @use_multistage_engine = use_multistage_engine
31
44
  @controller_config = controller_config
32
45
  @logger = logger
33
46
  @tls_config = tls_config
34
47
  @grpc_config = grpc_config
48
+ @zookeeper_config = zookeeper_config
49
+ end
50
+
51
+ def validate!
52
+ sources = [
53
+ !broker_list.empty?,
54
+ !controller_config.nil?,
55
+ !zookeeper_config.nil?,
56
+ !grpc_config.nil?
57
+ ].count(true)
58
+
59
+ if sources == 0
60
+ raise ConfigurationError, "ClientConfig requires at least one of: broker_list, controller_config, zookeeper_config, or grpc_config"
61
+ end
62
+
63
+ if !http_timeout.nil? && http_timeout <= 0
64
+ raise ConfigurationError, "http_timeout must be positive, got: #{http_timeout}"
65
+ end
66
+
67
+ if !query_timeout_ms.nil? && query_timeout_ms <= 0
68
+ raise ConfigurationError, "query_timeout_ms must be positive, got: #{query_timeout_ms}"
69
+ end
70
+
71
+ self
35
72
  end
36
73
  end
37
74
  end
@@ -23,9 +23,11 @@ module Pinot
23
23
  end
24
24
 
25
25
  def execute_sql(table, query)
26
- logger.debug "Executing SQL on table=#{table}: #{query}"
27
- broker = @broker_selector.select_broker(table)
28
- @transport.execute(broker, build_request(query))
26
+ Pinot::Instrumentation.instrument(table: table, query: query) do
27
+ logger.debug "Executing SQL on table=#{table}: #{query}"
28
+ broker = @broker_selector.select_broker(table)
29
+ @transport.execute(broker, build_request(query))
30
+ end
29
31
  rescue => e
30
32
  raise "unable to execute SQL on table #{table}: #{e.message}"
31
33
  end
@@ -12,6 +12,8 @@ module Pinot
12
12
  end
13
13
 
14
14
  def self.from_config(config, http_client: nil)
15
+ config.validate!
16
+
15
17
  if config.grpc_config
16
18
  transport = GrpcTransport.new(config.grpc_config)
17
19
  selector = SimpleBrokerSelector.new(config.grpc_config.broker_list)
@@ -27,6 +29,25 @@ module Pinot
27
29
  return conn
28
30
  end
29
31
 
32
+ if config.zookeeper_config
33
+ selector = ZookeeperBrokerSelector.new(zk_path: config.zookeeper_config.zk_path)
34
+ selector.init
35
+
36
+ inner = http_client || HttpClient.new(timeout: config.http_timeout, tls_config: config.tls_config)
37
+ transport = JsonHttpTransport.new(
38
+ http_client: inner,
39
+ extra_headers: config.extra_http_header || {},
40
+ logger: config.logger
41
+ )
42
+
43
+ return Connection.new(
44
+ transport: transport,
45
+ broker_selector: selector,
46
+ use_multistage_engine: config.use_multistage_engine || false,
47
+ logger: config.logger
48
+ )
49
+ end
50
+
30
51
  inner = http_client || HttpClient.new(timeout: config.http_timeout, tls_config: config.tls_config)
31
52
 
32
53
  transport = JsonHttpTransport.new(
@@ -0,0 +1,36 @@
1
+ module Pinot
2
+ module Instrumentation
3
+ # Called around every query execution.
4
+ # Implement by setting Pinot::Instrumentation.on_query = proc { |event| ... }
5
+ # event is a Hash:
6
+ # :table => String
7
+ # :query => String
8
+ # :duration_ms => Float
9
+ # :success => Boolean
10
+ # :error => Exception or nil
11
+
12
+ def self.on_query=(callback)
13
+ @on_query = callback
14
+ end
15
+
16
+ def self.on_query
17
+ @on_query
18
+ end
19
+
20
+ def self.instrument(table:, query:)
21
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22
+ result = yield
23
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
24
+ notify(table: table, query: query, duration_ms: duration_ms, success: true, error: nil)
25
+ result
26
+ rescue => e
27
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
28
+ notify(table: table, query: query, duration_ms: duration_ms, success: false, error: e)
29
+ raise
30
+ end
31
+
32
+ def self.notify(event)
33
+ @on_query&.call(event)
34
+ end
35
+ end
36
+ end
data/lib/pinot/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pinot
2
- VERSION = "1.3.0"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -0,0 +1,80 @@
1
+ require "json"
2
+ require "set"
3
+ require_relative "table_aware_broker_selector"
4
+ require_relative "errors"
5
+
6
+ module Pinot
7
+ class ZookeeperBrokerSelector < TableAwareBrokerSelector
8
+ # ZK path where Pinot stores broker external view
9
+ BROKER_EXTERNAL_VIEW_PATH = "/EXTERNALVIEW/brokerResource"
10
+
11
+ def initialize(zk_path:, zk_client: nil)
12
+ super()
13
+ @zk_path = zk_path # e.g. "localhost:2181"
14
+ @zk_client = zk_client # injectable for testing
15
+ end
16
+
17
+ def init
18
+ @zk = @zk_client || build_zk_client
19
+ fetch_and_update
20
+ setup_watcher
21
+ end
22
+
23
+ private
24
+
25
+ def build_zk_client
26
+ begin
27
+ require "zk"
28
+ rescue LoadError
29
+ raise ConfigurationError, "The 'zk' gem is required to use ZookeeperBrokerSelector. Add it to your Gemfile: gem \"zk\""
30
+ end
31
+ ZK.new(@zk_path)
32
+ end
33
+
34
+ def fetch_and_update
35
+ data, _stat = @zk.get(BROKER_EXTERNAL_VIEW_PATH)
36
+ parsed = JSON.parse(data)
37
+ all_brokers, table_map = parse_external_view(parsed)
38
+ update_broker_data(all_brokers, table_map)
39
+ end
40
+
41
+ def setup_watcher
42
+ @zk.register(BROKER_EXTERNAL_VIEW_PATH) do |event|
43
+ fetch_and_update rescue nil
44
+ setup_watcher # re-register watch after each trigger
45
+ end
46
+ # Set initial watch
47
+ @zk.exists?(BROKER_EXTERNAL_VIEW_PATH, watch: true)
48
+ end
49
+
50
+ def parse_external_view(data)
51
+ # Pinot external view format:
52
+ # { "mapFields": { "tableName_OFFLINE": { "Broker_host_port": "ONLINE" } } }
53
+ map_fields = data["mapFields"] || {}
54
+
55
+ all_brokers = Set.new
56
+ table_map = {}
57
+
58
+ map_fields.each do |raw_table, broker_map|
59
+ # Strip _OFFLINE / _REALTIME suffix from the table key, matching Go's extractTableName
60
+ table_name = raw_table.sub(/_OFFLINE\z/, "").sub(/_REALTIME\z/, "")
61
+ brokers = []
62
+ broker_map.each do |broker_key, state|
63
+ next unless state == "ONLINE"
64
+ # Broker key format: Broker_<hostname>_<port>
65
+ # Use the last segment as port and the second-to-last as host
66
+ parts = broker_key.split("_")
67
+ next if parts.length < 2
68
+ port = parts.last
69
+ next unless port =~ /\A\d+\z/
70
+ host = parts[-2]
71
+ brokers << "#{host}:#{port}"
72
+ end
73
+ table_map[table_name] = brokers
74
+ all_brokers.merge(brokers)
75
+ end
76
+
77
+ [all_brokers.to_a, table_map]
78
+ end
79
+ end
80
+ end
data/lib/pinot.rb CHANGED
@@ -5,6 +5,7 @@ require "bigdecimal"
5
5
  require "securerandom"
6
6
 
7
7
  require_relative "pinot/errors"
8
+ require_relative "pinot/instrumentation"
8
9
  require_relative "pinot/version"
9
10
  require_relative "pinot/logger"
10
11
  require_relative "pinot/config"
@@ -27,3 +28,8 @@ begin
27
28
  rescue LoadError
28
29
  # grpc gem not available; GrpcTransport disabled
29
30
  end
31
+
32
+ begin
33
+ require_relative "pinot/zookeeper_broker_selector"
34
+ rescue LoadError
35
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pinot-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xiang Fu
@@ -84,6 +84,7 @@ files:
84
84
  - lib/pinot/errors.rb
85
85
  - lib/pinot/grpc_config.rb
86
86
  - lib/pinot/grpc_transport.rb
87
+ - lib/pinot/instrumentation.rb
87
88
  - lib/pinot/logger.rb
88
89
  - lib/pinot/prepared_statement.rb
89
90
  - lib/pinot/proto/broker_service.proto
@@ -96,6 +97,7 @@ files:
96
97
  - lib/pinot/tls_config.rb
97
98
  - lib/pinot/transport.rb
98
99
  - lib/pinot/version.rb
100
+ - lib/pinot/zookeeper_broker_selector.rb
99
101
  homepage: https://github.com/startreedata/ruby-pinot-client
100
102
  licenses:
101
103
  - Apache-2.0