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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9347ec836b745a18b8488faaa92531bc2e5a11fd03bb3c663768a447ee6fd85
4
- data.tar.gz: cb090813d96a65fdcbcfa99f0bcb19e45eaf9e642efc8f1d05db7ac546779d4b
3
+ metadata.gz: 1d02d007bdda7a17a7174d81c231913a7ab329d21be1f679a665c8313111c68e
4
+ data.tar.gz: 0742f42f8f4f315659e55db9b51064b1af236e6ebca876cab0436f300b7b3e74
5
5
  SHA512:
6
- metadata.gz: aacb77ed16eb757206c6aa9541e54edd1639f5da2b2f5351ac707ad5fb07583a820ebc08ef7be439108f4d7deedf39e68a5202a2d4094fddbc4ef4bd5420bcaf
7
- data.tar.gz: 5c82688254d30c875d44696174c5928496404d518300c6de0f709bc28d924b13f4c394d501f993018f1b4f1d2eacabc12427959e6ff25d80ad28e1affec26daa
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_ - It expects **true/false** as its possible values. The 'load_balance' property needs to be set to 'true' to enable cluster-awareness.
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
- ### Limitations
92
+ ### Using with ActiveRecord
87
93
 
88
- - The load balancing feature of the Ruby Smart driver for YugabyteDB does not work with ActiveRecords - the ORM tool for Ruby apps.
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
- LBProperties = Struct.new(:placements_info, :refresh_interval, :fallback_to_tk_only, :failed_host_reconnect_delay)
8
- Node = Struct.new(:host, :port, :cloud, :region, :zone, :public_ip, :count, :is_down, :down_since)
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
- host_port = get_least_loaded_server(lb_props.placements_info, lb_props.fallback_to_tk_only, new_request, placement_index)
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
- break
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.get_least_loaded_server(allowed_placements, fallback_to_tk_only, new_request, placement_index)
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
- eligible_hosts = Array.new
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
- unless node_info.is_down
210
- unless allowed_placements[idx].nil?
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
- unless allowed_placements.nil?
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
- unless node_info.is_down
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, @@cluster_info[selected_node].port, current_index]
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 == "true"
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 && lb.to_s.downcase == "true"
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
@@ -1,5 +1,5 @@
1
1
  module YSQL
2
2
  # Library version
3
3
  PG_VERSION = '1.5.6'
4
- VERSION = '0.5'
4
+ VERSION = '0.7'
5
5
  end
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.5'
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: 2024-08-23 00:00:00.000000000 Z
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.