yugabytedb-ysql 0.5 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +9 -3
- data/lib/ysql/load_balance_service.rb +94 -17
- data/lib/ysql/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d02d007bdda7a17a7174d81c231913a7ab329d21be1f679a665c8313111c68e
|
4
|
+
data.tar.gz: 0742f42f8f4f315659e55db9b51064b1af236e6ebca876cab0436f300b7b3e74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34e979bb7eaed1425377a3b305ae51e002dc472c4bf4605b8b94ee403bf0b626d7e603c325a3f9fb421dc6fc1137111586987eeefba402545f4426e2d2173779
|
7
|
+
data.tar.gz: 4092d1001dd0c47946d2c256b3a09b96f19b7daba7c1f1ea436266bb7a330f43f837184fd2a86f4f3d60d19cd7909102aa5796df49bf909361d536969fa50d74
|
data/Gemfile
CHANGED
@@ -12,6 +12,7 @@ group :development, :test do
|
|
12
12
|
gem "rdoc", "~> 6.4"
|
13
13
|
gem "rspec", "~> 3.5"
|
14
14
|
gem 'concurrent-ruby', require: 'concurrent'
|
15
|
+
gem 'logger'
|
15
16
|
# "bigdecimal" is a gem on ruby-3.4+ and it's optional for ruby-pg.
|
16
17
|
# Specs should succeed without it, but 4 examples are then excluded.
|
17
18
|
# gem "bigdecimal", "~> 3.0"
|
data/README.md
CHANGED
@@ -16,7 +16,13 @@ This is similar to 'Cluster Awareness' but uses those servers which are part of
|
|
16
16
|
|
17
17
|
### Connection Properties added for load balancing
|
18
18
|
|
19
|
-
- _load_balance_
|
19
|
+
- _load_balance_ - Starting with version 0.6, it expects one of **false, any (same as true), only-primary, only-rr, prefer-primary and prefer-rr** as its possible values. The default value for _load_balance_ property is `false`.
|
20
|
+
- _false_ - No connection load balancing. Behaviour is similar to vanilla ruby-pg driver
|
21
|
+
- _any_ - Same as value _true_. Distribute connections equally across all nodes in the cluster, irrespective of its type (`primary` or `read-replica`)
|
22
|
+
- _only-primary_ - Create connections equally across only the primary nodes of the cluster
|
23
|
+
- _only-rr_ - Create connections equally across only the read-replica nodes of the cluster
|
24
|
+
- _prefer-primary_ - Create connections equally across primary cluster nodes. If none available, on any available read replica node in the cluster
|
25
|
+
- _prefer-rr_ - Create connections equally across read replica nodes of the cluster. If none available, on any available primary cluster node
|
20
26
|
- _topology_keys_ - It takes a comma separated geo-location values. A single geo-location can be given as 'cloud.region.zone'. Multiple geo-locations too can be specified, separated by comma (`,`). Optionally, you can also register your preference for particular geo-locations by appending the preference value with prefix `:`. For example, `cloud.regionA.zoneA:1,cloud.regionA.zoneB:2`.
|
21
27
|
- _yb_servers_refresh_interval_ - Minimum time interval, in seconds, between two attempts to refresh the information about cluster nodes. This is checked only when a new connection is requested. Default is 300. Valid values are integers between 0 and 600. Value 0 means refresh for each connection request. Any value outside this range is ignored and the default is used.
|
22
28
|
- _fallback_to_topology_keys_only_ - When set to true, the driver does not attempt to connect to nodes outside of the geo-locations specified via _topology_keys_. Default value is false.
|
@@ -83,9 +89,9 @@ The driver attempts connection to servers in the first fallback placement(s) if
|
|
83
89
|
then it attempts to connect to servers in the second fallback placement(s), if specified. This continues until the driver finds a server to connect to, else an error is returned to the application.
|
84
90
|
And this repeats for each connection request.
|
85
91
|
|
86
|
-
###
|
92
|
+
### Using with ActiveRecord
|
87
93
|
|
88
|
-
- The load balancing feature of the Ruby Smart driver for YugabyteDB
|
94
|
+
- The load balancing feature of the Ruby Smart driver for YugabyteDB can be used with ActiveRecord - the ORM tool for Ruby apps - via its [adapter for YugabyteDB](https://github.com/yugabyte/activerecord-yugabytedb-adapter).
|
89
95
|
|
90
96
|
Rest of the README is from upstream repository.
|
91
97
|
|
@@ -1,11 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'ysql' unless defined?( YSQL )
|
3
3
|
require 'concurrent'
|
4
|
+
require 'logger'
|
4
5
|
|
5
6
|
class YSQL::LoadBalanceService
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
class << self
|
9
|
+
attr_accessor :logger
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set up a default logger
|
13
|
+
self.logger = Logger.new(STDOUT)
|
14
|
+
self.logger.level = Logger::WARN
|
15
|
+
|
16
|
+
LBProperties = Struct.new(:lb_value, :placements_info, :refresh_interval, :fallback_to_tk_only, :failed_host_reconnect_delay)
|
17
|
+
Node = Struct.new(:host, :port, :cloud, :region, :zone, :public_ip, :count, :is_down, :down_since, :node_type)
|
9
18
|
CloudPlacement = Struct.new(:cloud, :region, :zone)
|
10
19
|
@@mutex = Concurrent::ReentrantReadWriteLock.new
|
11
20
|
@@last_refresh_time = -1
|
@@ -27,6 +36,7 @@ class YSQL::LoadBalanceService
|
|
27
36
|
info = @@cluster_info[host]
|
28
37
|
unless info.nil?
|
29
38
|
info.count -= 1
|
39
|
+
logger.debug "decrement_connection_count(): count for #{host} updated to #{info.count}"
|
30
40
|
if info.count < 0
|
31
41
|
# Can go negative if we are here because of a connection that was created in a non-LB fashion
|
32
42
|
info.count = 0
|
@@ -40,6 +50,7 @@ class YSQL::LoadBalanceService
|
|
40
50
|
end
|
41
51
|
|
42
52
|
def self.connect_to_lb_hosts(lb_props, iopts)
|
53
|
+
logger.debug "connect_to_lb_hosts(): lb_props = #{lb_props}"
|
43
54
|
refresh_done = false
|
44
55
|
@@mutex.acquire_write_lock
|
45
56
|
begin
|
@@ -48,16 +59,21 @@ class YSQL::LoadBalanceService
|
|
48
59
|
if @@control_connection == nil
|
49
60
|
begin
|
50
61
|
@@control_connection = create_control_connection(iopts)
|
62
|
+
logger.debug "connect_to_lb_hosts(): created control connection to #{@@control_connection.host}"
|
51
63
|
rescue
|
52
64
|
return nil
|
53
65
|
end
|
54
66
|
end
|
55
67
|
begin
|
56
68
|
refresh_yb_servers(lb_props.failed_host_reconnect_delay, @@control_connection)
|
69
|
+
logger.debug "connect_to_lb_hosts(): refreshed yb_servers metadata"
|
57
70
|
refresh_done = true
|
58
71
|
rescue => err
|
59
72
|
if iopts[:host] == @@control_connection.host
|
60
73
|
if @@cluster_info[iopts[:host]]
|
74
|
+
if @@cluster_info[iopts[:host]].is_down
|
75
|
+
logger.debug "connect_to_lb_hosts(): Marking #{@@control_connection.host} as DOWN"
|
76
|
+
end
|
61
77
|
@@cluster_info[iopts[:host]].is_down = true
|
62
78
|
@@cluster_info[iopts[:host]].down_since = Time.now.to_i
|
63
79
|
end
|
@@ -73,6 +89,7 @@ class YSQL::LoadBalanceService
|
|
73
89
|
end
|
74
90
|
end
|
75
91
|
@@control_connection = create_control_connection(iopts)
|
92
|
+
logger.debug "connect_to_lb_hosts(): created control connection to #{@@control_connection.host} in rescue"
|
76
93
|
end
|
77
94
|
end
|
78
95
|
end
|
@@ -81,20 +98,43 @@ class YSQL::LoadBalanceService
|
|
81
98
|
end
|
82
99
|
success = false
|
83
100
|
new_request = true
|
101
|
+
strict_preference = true
|
84
102
|
placement_index = 1
|
85
103
|
until success
|
86
104
|
@@mutex.acquire_write_lock
|
87
105
|
begin
|
88
|
-
|
106
|
+
if strict_preference
|
107
|
+
host_port = get_least_loaded_server(lb_props.placements_info, lb_props.fallback_to_tk_only, new_request, placement_index, lb_props.lb_value, strict_preference)
|
108
|
+
else
|
109
|
+
host_port = get_least_loaded_server(nil, lb_props.fallback_to_tk_only, new_request, placement_index, lb_props.lb_value, strict_preference)
|
110
|
+
end
|
89
111
|
new_request = false
|
90
112
|
ensure
|
91
113
|
@@mutex.release_write_lock
|
92
114
|
end
|
93
115
|
unless host_port
|
94
|
-
|
116
|
+
if (lb_props.lb_value == "only-primary" || lb_props.lb_value == "only-rr" )
|
117
|
+
raise(YSQL::Error, "No node found for load_balance=#{lb_props.lb_value}")
|
118
|
+
elsif strict_preference && (lb_props.lb_value == "prefer-primary" || lb_props.lb_value == "prefer-rr")
|
119
|
+
@@mutex.acquire_write_lock
|
120
|
+
begin
|
121
|
+
host_port = get_least_loaded_server(nil, lb_props.fallback_to_tk_only, new_request, placement_index, lb_props.lb_value, strict_preference)
|
122
|
+
ensure
|
123
|
+
@@mutex.release_write_lock
|
124
|
+
end
|
125
|
+
unless host_port
|
126
|
+
strict_preference = false
|
127
|
+
placement_index = 1
|
128
|
+
next
|
129
|
+
end
|
130
|
+
else
|
131
|
+
logger.debug "connect_to_lb_hosts(): lb_host not found for load_balance=#{lb_props.lb_value}"
|
132
|
+
break
|
133
|
+
end
|
95
134
|
end
|
96
135
|
lb_host = host_port[0]
|
97
136
|
lb_port = host_port[1]
|
137
|
+
logger.debug "connect_to_lb_hosts(): lb_host #{lb_host}"
|
98
138
|
placement_index = host_port[2]
|
99
139
|
if lb_host.empty?
|
100
140
|
break
|
@@ -109,6 +149,9 @@ class YSQL::LoadBalanceService
|
|
109
149
|
rescue => e
|
110
150
|
@@mutex.acquire_write_lock
|
111
151
|
begin
|
152
|
+
if @@cluster_info[lb_host].is_down
|
153
|
+
logger.debug "connect_to_lb_hosts(): Marking #{lb_host} as DOWN"
|
154
|
+
end
|
112
155
|
@@cluster_info[lb_host].is_down = true
|
113
156
|
@@cluster_info[lb_host].down_since = Time.now.to_i
|
114
157
|
@@cluster_info[lb_host].count -= 1
|
@@ -133,6 +176,9 @@ class YSQL::LoadBalanceService
|
|
133
176
|
success = true
|
134
177
|
rescue => e
|
135
178
|
if @@cluster_info[iopts[:host]]
|
179
|
+
if @@cluster_info[iopts[:host]].is_down
|
180
|
+
logger.debug "create_control_connection(): Marking #{iopts[:host]} as DOWN"
|
181
|
+
end
|
136
182
|
@@cluster_info[iopts[:host]].is_down = true
|
137
183
|
@@cluster_info[iopts[:host]].down_since = Time.now.to_i
|
138
184
|
end
|
@@ -161,6 +207,7 @@ class YSQL::LoadBalanceService
|
|
161
207
|
region = row['region']
|
162
208
|
zone = row['zone']
|
163
209
|
public_ip = row['public_ip']
|
210
|
+
node_type = row['node_type']
|
164
211
|
public_ip = resolve_host(public_ip)[0][0] if public_ip
|
165
212
|
if not public_ip.nil? and not public_ip.empty?
|
166
213
|
found_public_ip = true
|
@@ -179,12 +226,15 @@ class YSQL::LoadBalanceService
|
|
179
226
|
if old
|
180
227
|
if old.is_down
|
181
228
|
if Time.now.to_i - old.down_since > failed_host_reconnect_delay_secs
|
229
|
+
unless old.is_down
|
230
|
+
logger.debug "refresh_yb_servers(): Marking #{host} as UP"
|
231
|
+
end
|
182
232
|
old.is_down = false
|
183
233
|
end
|
184
234
|
@@cluster_info[host] = old
|
185
235
|
end
|
186
236
|
else
|
187
|
-
node = Node.new(host, port, cloud, region, zone, public_ip, 0, false, 0)
|
237
|
+
node = Node.new(host, port, cloud, region, zone, public_ip, 0, false, 0, node_type)
|
188
238
|
@@cluster_info[host] = node
|
189
239
|
end
|
190
240
|
end
|
@@ -196,21 +246,37 @@ class YSQL::LoadBalanceService
|
|
196
246
|
@@last_refresh_time = Time.now.to_i
|
197
247
|
end
|
198
248
|
|
199
|
-
def self.
|
249
|
+
def self.is_node_type_acceptable(node_type, lb_value, strict_preference)
|
250
|
+
case lb_value
|
251
|
+
when "true", "any"
|
252
|
+
true
|
253
|
+
when "only-primary"
|
254
|
+
node_type == "primary"
|
255
|
+
when "only-rr"
|
256
|
+
node_type == "read_replica"
|
257
|
+
when "prefer-primary"
|
258
|
+
node_type == "primary" || (!strict_preference && node_type == "read_replica")
|
259
|
+
when "prefer-rr"
|
260
|
+
node_type == "read_replica" || (!strict_preference && node_type == "primary")
|
261
|
+
else
|
262
|
+
false
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def self.get_least_loaded_server(allowed_placements, fallback_to_tk_only, new_request, placement_index, lb_value, strict_preference)
|
200
267
|
current_index = 1
|
201
268
|
selected = Array.new
|
202
269
|
unless allowed_placements.nil? # topology-aware
|
203
|
-
|
270
|
+
logger.debug "get_least_loaded_server(): topology_keys given = #{allowed_placements}"
|
204
271
|
(placement_index..10).each { |idx|
|
205
272
|
current_index = idx
|
206
273
|
selected.clear
|
207
274
|
min_connections = 1000000 # Using some really high value
|
208
275
|
@@cluster_info.each do |host, node_info|
|
209
|
-
|
210
|
-
|
276
|
+
if !node_info.is_down && !allowed_placements[idx].nil?
|
277
|
+
if is_node_type_acceptable(node_info.node_type, lb_value, strict_preference)
|
211
278
|
allowed_placements[idx].each do |cp|
|
212
279
|
if cp[0] == node_info.cloud && cp[1] == node_info.region && (cp[2] == node_info.zone || cp[2] == "*")
|
213
|
-
eligible_hosts << host
|
214
280
|
if node_info.count < min_connections
|
215
281
|
min_connections = node_info.count
|
216
282
|
selected.clear
|
@@ -231,12 +297,11 @@ class YSQL::LoadBalanceService
|
|
231
297
|
end
|
232
298
|
|
233
299
|
if allowed_placements.nil? || (selected.empty? && !fallback_to_tk_only) # cluster-aware || fallback_to_tk_only = false
|
234
|
-
|
235
|
-
end
|
300
|
+
logger.debug "get_least_loaded_server(): topology_keys not given or no nodes found for given topology_keys"
|
236
301
|
min_connections = 1000000 # Using some really high value
|
237
302
|
selected = Array.new
|
238
303
|
@@cluster_info.each do |host, node_info|
|
239
|
-
|
304
|
+
if !node_info.is_down && is_node_type_acceptable(node_info.node_type, lb_value, strict_preference)
|
240
305
|
if node_info.count < min_connections
|
241
306
|
min_connections = node_info.count
|
242
307
|
selected.clear
|
@@ -254,14 +319,16 @@ class YSQL::LoadBalanceService
|
|
254
319
|
index = rand(selected.size)
|
255
320
|
selected_node = selected[index]
|
256
321
|
@@cluster_info[selected_node].count += 1
|
322
|
+
selected_port = @@cluster_info[selected_node].port
|
257
323
|
if !@@useHostColumn.nil? && !@@useHostColumn
|
258
324
|
selected_node = @@cluster_info[selected_node].public_ip
|
259
325
|
end
|
260
|
-
Array[selected_node,
|
326
|
+
Array[selected_node, selected_port, current_index]
|
261
327
|
end
|
262
328
|
end
|
263
329
|
|
264
330
|
def self.parse_lb_args_from_url(conn_string)
|
331
|
+
logger.debug "parse_lb_args_from_url(): conn_string = #{conn_string}"
|
265
332
|
string_parts = conn_string.split('?', -1)
|
266
333
|
if string_parts.length != 2
|
267
334
|
return conn_string, nil
|
@@ -293,7 +360,7 @@ class YSQL::LoadBalanceService
|
|
293
360
|
|
294
361
|
base_string = base_string.chop if base_string[-1] == "&"
|
295
362
|
base_string = base_string.chop if base_string[-1] == "?"
|
296
|
-
if not lb_props.empty? and lb_props[:load_balance].to_s.downcase
|
363
|
+
if not lb_props.empty? and is_lb_enabled(lb_props[:load_balance].to_s.downcase)
|
297
364
|
return base_string, parse_connect_lb_args(lb_props)
|
298
365
|
else
|
299
366
|
return base_string, nil
|
@@ -301,15 +368,25 @@ class YSQL::LoadBalanceService
|
|
301
368
|
end
|
302
369
|
end
|
303
370
|
|
371
|
+
def self.is_lb_enabled(lb)
|
372
|
+
case lb
|
373
|
+
when "true", "any", "only-primary", "prefer-primary", "only-rr", "prefer-rr"
|
374
|
+
true
|
375
|
+
else
|
376
|
+
false
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
304
380
|
def self.parse_connect_lb_args(hash_arg)
|
381
|
+
logger.debug "parse_connect_lb_args(): hash_arg = #{hash_arg}"
|
305
382
|
lb = hash_arg.delete(:load_balance)
|
306
383
|
tk = hash_arg.delete(:topology_keys)
|
307
384
|
ri = hash_arg.delete(:yb_servers_refresh_interval)
|
308
385
|
ttl = hash_arg.delete(:failed_host_reconnect_delay_secs)
|
309
386
|
fb = hash_arg.delete(:fallback_to_topology_keys_only)
|
310
387
|
|
311
|
-
if lb
|
312
|
-
lb_properties = LBProperties.new(nil, 300, false, 5)
|
388
|
+
if is_lb_enabled(lb.to_s.downcase)
|
389
|
+
lb_properties = LBProperties.new(lb.to_s.downcase, nil, 300, false, 5)
|
313
390
|
if tk
|
314
391
|
lb_properties.placements_info = Hash.new
|
315
392
|
tk_parts = tk.split(',', -1)
|
data/lib/ysql/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yugabytedb-ysql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.7'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Granger
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2025-01-17 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: Pg_YugabyteDB is the Ruby interface to the PostgreSQL-compatible YugabyteDB.
|
16
16
|
It works with YugabyteDB 2.20 and later.
|