yugabytedb-ysql 0.5 → 0.7
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/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.
|