pinot-client 1.21.0 → 1.22.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/lib/pinot/circuit_breaker.rb +115 -0
- data/lib/pinot/config.rb +14 -5
- data/lib/pinot/connection.rb +11 -2
- data/lib/pinot/connection_factory.rb +14 -2
- data/lib/pinot/version.rb +1 -1
- data/lib/pinot.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8fa5d08fb08bee353252e2da56932948941faa5fdbdd4f3410e5ff8887755e04
|
|
4
|
+
data.tar.gz: c8bb9cbe06c5d209d8f9f63031776fc604a153730b4be207b7fd6d8a3b69e5f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d5c85b83effd2ce352b09c03d350eedd817415a18994ed23ef7be972f80dfb8d72cbbbace9c83bfddedb095b6695af0b8b4538f2f0023ee40844e6b554849a9
|
|
7
|
+
data.tar.gz: c450ffb393a8798ae3ea3e608cf2cb976bdf5aeb6f7145121764c41bed33069807fdb3087d02c3963552f79568fb6f2020088794cccf47f97218d10fe7041c99
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module Pinot
|
|
2
|
+
# Per-broker circuit breaker. States: CLOSED (normal), OPEN (rejecting), HALF_OPEN (probing).
|
|
3
|
+
#
|
|
4
|
+
# failure_threshold - consecutive failures before opening (default 5)
|
|
5
|
+
# open_timeout - seconds to stay OPEN before moving to HALF_OPEN (default 30)
|
|
6
|
+
class CircuitBreaker
|
|
7
|
+
CLOSED = :closed
|
|
8
|
+
OPEN = :open
|
|
9
|
+
HALF_OPEN = :half_open
|
|
10
|
+
|
|
11
|
+
BrokerCircuitOpenError = Class.new(BrokerNotFoundError)
|
|
12
|
+
|
|
13
|
+
attr_reader :state, :failure_count
|
|
14
|
+
|
|
15
|
+
def initialize(failure_threshold: 5, open_timeout: 30)
|
|
16
|
+
@failure_threshold = failure_threshold
|
|
17
|
+
@open_timeout = open_timeout
|
|
18
|
+
@mutex = Mutex.new
|
|
19
|
+
@state = CLOSED
|
|
20
|
+
@failure_count = 0
|
|
21
|
+
@opened_at = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Call the block; record success/failure and enforce open-circuit rejection.
|
|
25
|
+
def call(broker_address)
|
|
26
|
+
@mutex.synchronize { check_state! }
|
|
27
|
+
begin
|
|
28
|
+
result = yield
|
|
29
|
+
@mutex.synchronize { on_success }
|
|
30
|
+
result
|
|
31
|
+
rescue BrokerUnavailableError, Errno::ECONNRESET, Errno::ECONNREFUSED,
|
|
32
|
+
Errno::ETIMEDOUT, Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout => e
|
|
33
|
+
@mutex.synchronize { on_failure }
|
|
34
|
+
raise
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def open?
|
|
39
|
+
@mutex.synchronize { @state == OPEN }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def reset
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
@state = CLOSED
|
|
45
|
+
@failure_count = 0
|
|
46
|
+
@opened_at = nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def check_state!
|
|
53
|
+
return if @state == CLOSED
|
|
54
|
+
|
|
55
|
+
if @state == OPEN
|
|
56
|
+
if elapsed_since_open >= @open_timeout
|
|
57
|
+
@state = HALF_OPEN
|
|
58
|
+
else
|
|
59
|
+
raise BrokerCircuitOpenError, "circuit open for broker (#{remaining_open_time.ceil}s remaining)"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
# HALF_OPEN: allow the probe through
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def on_success
|
|
66
|
+
@state = CLOSED
|
|
67
|
+
@failure_count = 0
|
|
68
|
+
@opened_at = nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def on_failure
|
|
72
|
+
@failure_count += 1
|
|
73
|
+
if @state == HALF_OPEN || @failure_count >= @failure_threshold
|
|
74
|
+
@state = OPEN
|
|
75
|
+
@opened_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def elapsed_since_open
|
|
80
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @opened_at
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def remaining_open_time
|
|
84
|
+
@open_timeout - elapsed_since_open
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Registry of per-broker CircuitBreakers, shared across transport calls.
|
|
89
|
+
class CircuitBreakerRegistry
|
|
90
|
+
def initialize(failure_threshold: 5, open_timeout: 30)
|
|
91
|
+
@failure_threshold = failure_threshold
|
|
92
|
+
@open_timeout = open_timeout
|
|
93
|
+
@breakers = {}
|
|
94
|
+
@mutex = Mutex.new
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def for(broker_address)
|
|
98
|
+
@mutex.synchronize do
|
|
99
|
+
@breakers[broker_address] ||= CircuitBreaker.new(
|
|
100
|
+
failure_threshold: @failure_threshold,
|
|
101
|
+
open_timeout: @open_timeout
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def open?(broker_address)
|
|
107
|
+
@mutex.synchronize { @breakers[broker_address]&.open? || false }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Remove all state (useful for testing).
|
|
111
|
+
def reset_all
|
|
112
|
+
@mutex.synchronize { @breakers.clear }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/pinot/config.rb
CHANGED
|
@@ -23,10 +23,13 @@ module Pinot
|
|
|
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
25
|
:grpc_config, :zookeeper_config,
|
|
26
|
-
:max_retries,
|
|
27
|
-
:retry_interval_ms,
|
|
28
|
-
:pool_size,
|
|
29
|
-
:keep_alive_timeout
|
|
26
|
+
:max_retries, # Integer, default 0 (no retry)
|
|
27
|
+
:retry_interval_ms, # Integer ms base interval, default 200
|
|
28
|
+
:pool_size, # max idle connections per broker, default 5
|
|
29
|
+
:keep_alive_timeout, # seconds before idle connection is reaped, default 30
|
|
30
|
+
:circuit_breaker_enabled, # bool, default false
|
|
31
|
+
:circuit_breaker_threshold, # consecutive failures before opening, default 5
|
|
32
|
+
:circuit_breaker_timeout # seconds circuit stays open, default 30
|
|
30
33
|
|
|
31
34
|
def initialize(
|
|
32
35
|
broker_list: [],
|
|
@@ -42,7 +45,10 @@ module Pinot
|
|
|
42
45
|
max_retries: 0,
|
|
43
46
|
retry_interval_ms: 200,
|
|
44
47
|
pool_size: nil,
|
|
45
|
-
keep_alive_timeout: nil
|
|
48
|
+
keep_alive_timeout: nil,
|
|
49
|
+
circuit_breaker_enabled: false,
|
|
50
|
+
circuit_breaker_threshold: 5,
|
|
51
|
+
circuit_breaker_timeout: 30
|
|
46
52
|
)
|
|
47
53
|
@broker_list = broker_list
|
|
48
54
|
@http_timeout = http_timeout
|
|
@@ -58,6 +64,9 @@ module Pinot
|
|
|
58
64
|
@retry_interval_ms = retry_interval_ms
|
|
59
65
|
@pool_size = pool_size
|
|
60
66
|
@keep_alive_timeout = keep_alive_timeout
|
|
67
|
+
@circuit_breaker_enabled = circuit_breaker_enabled
|
|
68
|
+
@circuit_breaker_threshold = circuit_breaker_threshold
|
|
69
|
+
@circuit_breaker_timeout = circuit_breaker_timeout
|
|
61
70
|
end
|
|
62
71
|
|
|
63
72
|
def validate!
|
data/lib/pinot/connection.rb
CHANGED
|
@@ -4,13 +4,15 @@ module Pinot
|
|
|
4
4
|
class Connection
|
|
5
5
|
attr_accessor :query_timeout_ms
|
|
6
6
|
|
|
7
|
-
def initialize(transport:, broker_selector:, use_multistage_engine: false, logger: nil,
|
|
7
|
+
def initialize(transport:, broker_selector:, use_multistage_engine: false, logger: nil,
|
|
8
|
+
query_timeout_ms: nil, circuit_breaker_registry: nil)
|
|
8
9
|
@transport = transport
|
|
9
10
|
@broker_selector = broker_selector
|
|
10
11
|
@use_multistage_engine = use_multistage_engine
|
|
11
12
|
@trace = false
|
|
12
13
|
@logger = logger
|
|
13
14
|
@query_timeout_ms = query_timeout_ms
|
|
15
|
+
@circuit_breaker_registry = circuit_breaker_registry
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def use_multistage_engine=(val)
|
|
@@ -30,7 +32,9 @@ module Pinot
|
|
|
30
32
|
logger.debug "Executing SQL on table=#{table}: #{query}"
|
|
31
33
|
broker = @broker_selector.select_broker(table)
|
|
32
34
|
effective_timeout = query_timeout_ms || @query_timeout_ms
|
|
33
|
-
|
|
35
|
+
run_with_circuit_breaker(broker) do
|
|
36
|
+
@transport.execute(broker, build_request(query, timeout_ms: effective_timeout))
|
|
37
|
+
end
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
|
|
@@ -97,6 +101,11 @@ module Pinot
|
|
|
97
101
|
|
|
98
102
|
private
|
|
99
103
|
|
|
104
|
+
def run_with_circuit_breaker(broker, &block)
|
|
105
|
+
return yield unless @circuit_breaker_registry
|
|
106
|
+
@circuit_breaker_registry.for(broker).call(broker, &block)
|
|
107
|
+
end
|
|
108
|
+
|
|
100
109
|
def logger
|
|
101
110
|
@logger || Pinot::Logging.logger
|
|
102
111
|
end
|
|
@@ -49,7 +49,8 @@ module Pinot
|
|
|
49
49
|
broker_selector: selector,
|
|
50
50
|
use_multistage_engine: config.use_multistage_engine || false,
|
|
51
51
|
logger: config.logger,
|
|
52
|
-
query_timeout_ms: config.query_timeout_ms
|
|
52
|
+
query_timeout_ms: config.query_timeout_ms,
|
|
53
|
+
circuit_breaker_registry: build_circuit_breaker_registry(config)
|
|
53
54
|
)
|
|
54
55
|
end
|
|
55
56
|
|
|
@@ -71,7 +72,8 @@ module Pinot
|
|
|
71
72
|
broker_selector: selector,
|
|
72
73
|
use_multistage_engine: config.use_multistage_engine || false,
|
|
73
74
|
logger: config.logger,
|
|
74
|
-
query_timeout_ms: config.query_timeout_ms
|
|
75
|
+
query_timeout_ms: config.query_timeout_ms,
|
|
76
|
+
circuit_breaker_registry: build_circuit_breaker_registry(config)
|
|
75
77
|
)
|
|
76
78
|
|
|
77
79
|
selector.init
|
|
@@ -96,4 +98,14 @@ module Pinot
|
|
|
96
98
|
end
|
|
97
99
|
end
|
|
98
100
|
private_class_method :build_selector
|
|
101
|
+
|
|
102
|
+
def self.build_circuit_breaker_registry(config)
|
|
103
|
+
return nil unless config.circuit_breaker_enabled
|
|
104
|
+
|
|
105
|
+
CircuitBreakerRegistry.new(
|
|
106
|
+
failure_threshold: config.circuit_breaker_threshold || 5,
|
|
107
|
+
open_timeout: config.circuit_breaker_timeout || 30
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
private_class_method :build_circuit_breaker_registry
|
|
99
111
|
end
|
data/lib/pinot/version.rb
CHANGED
data/lib/pinot.rb
CHANGED
|
@@ -18,6 +18,7 @@ require_relative "pinot/table_aware_broker_selector"
|
|
|
18
18
|
require_relative "pinot/controller_response"
|
|
19
19
|
require_relative "pinot/controller_based_broker_selector"
|
|
20
20
|
require_relative "pinot/transport"
|
|
21
|
+
require_relative "pinot/circuit_breaker"
|
|
21
22
|
require_relative "pinot/connection"
|
|
22
23
|
require_relative "pinot/prepared_statement"
|
|
23
24
|
require_relative "pinot/connection_factory"
|
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.
|
|
4
|
+
version: 1.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Xiang Fu
|
|
@@ -104,6 +104,7 @@ files:
|
|
|
104
104
|
- README.md
|
|
105
105
|
- lib/pinot.rb
|
|
106
106
|
- lib/pinot/broker_selector.rb
|
|
107
|
+
- lib/pinot/circuit_breaker.rb
|
|
107
108
|
- lib/pinot/config.rb
|
|
108
109
|
- lib/pinot/connection.rb
|
|
109
110
|
- lib/pinot/connection_factory.rb
|