global_uid 3.7.1 → 4.0.1

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: 66c3d7277c02d403b85ba5a0c89357d1fa23d3c80817678226fc9e0ffd945e99
4
- data.tar.gz: a922b9dfec3c753668e4cf5b14fbc2c795cb0d57b7d728fbbd85f5a725e18ada
3
+ metadata.gz: bc0bc131f043570b57245aa1b807b4f7e74dd87aefc17db5b403d2d7e615f5e5
4
+ data.tar.gz: a0048d1ebec4af921f0fee12db3ade0e37bfcc5af282f94e61f197fdb182ff17
5
5
  SHA512:
6
- metadata.gz: f3101805f0c413c2ece2aba4cab83e890e9bc4966304074cb21fea7f12ba0ba5f9f7d6d50ae89ceae81c8178f5717f3791151c14c8f41da3aebc858f2a9a7278
7
- data.tar.gz: c575ec0cd1cd707bfb7aa18e4acad03fc4423a070553dc5b1d06288f0d541084efaaca12fc60144461b45538ce0a369ba564899500604e6c58e3191130efd689
6
+ metadata.gz: df66f3a63d4b00c946e76f41cb18817e3790ba229be243ba1abf6cbc555cec1d778316782afdb87ac9769d47cef151b41e3a3b0f47b8640b0ccc6fb9e5f0dcbe
7
+ data.tar.gz: 987393d901a7d49897a4362c58a88d851695af84fdc137c307781acdc3b21dd00537608d241814d7293e4d12e3c26fe3ad96dfa085228ecbcff6d6edfb69fe1f
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  require "global_uid/base"
3
+ require "global_uid/allocator"
4
+ require "global_uid/server"
5
+ require "global_uid/configuration"
6
+ require "global_uid/error_tracker"
3
7
  require "global_uid/active_record_extension"
4
8
  require "global_uid/has_and_belongs_to_many_builder_extension"
5
9
  require "global_uid/migration_extension"
@@ -9,8 +13,36 @@ module GlobalUid
9
13
  class NoServersAvailableException < StandardError ; end
10
14
  class ConnectionTimeoutException < StandardError ; end
11
15
  class TimeoutException < StandardError ; end
16
+ class InvalidIncrementException < StandardError ; end
12
17
 
13
- autoload :ServerVariables, "global_uid/server_variables"
18
+ def self.configuration
19
+ @configuration ||= GlobalUid::Configuration.new
20
+ end
21
+
22
+ def self.configure
23
+ yield configuration if block_given?
24
+ end
25
+
26
+ def self.disable!
27
+ self.configuration.disabled = true
28
+ end
29
+
30
+ def self.enable!
31
+ self.configuration.disabled = false
32
+ end
33
+
34
+ def self.enabled?
35
+ !self.disabled?
36
+ end
37
+
38
+ def self.disabled?
39
+ self.configuration.disabled
40
+ end
41
+
42
+ # @private
43
+ def self.reset_configuration
44
+ @configuration = nil
45
+ end
14
46
  end
15
47
 
16
48
  ActiveRecord::Base.send(:include, GlobalUid::ActiveRecordExtension)
@@ -26,10 +58,5 @@ if ActiveRecord::VERSION::MAJOR >= 5
26
58
  ActiveRecord::InternalMetadata.disable_global_uid
27
59
  end
28
60
 
29
- if ActiveRecord::VERSION::STRING >= '4.1.0'
30
- ActiveRecord::Associations::Builder::HasAndBelongsToMany.send(:include, GlobalUid::HasAndBelongsToManyBuilderExtension)
31
- end
32
-
33
- if ActiveRecord::VERSION::MAJOR >= 4
34
- ActiveRecord::SchemaDumper.send(:prepend, GlobalUid::SchemaDumperExtension)
35
- end
61
+ ActiveRecord::Associations::Builder::HasAndBelongsToMany.send(:include, GlobalUid::HasAndBelongsToManyBuilderExtension)
62
+ ActiveRecord::SchemaDumper.send(:prepend, GlobalUid::SchemaDumperExtension)
@@ -8,20 +8,10 @@ module GlobalUid
8
8
  end
9
9
 
10
10
  def global_uid_before_create
11
- return if GlobalUid::Base.global_uid_options[:disabled]
11
+ return if GlobalUid.disabled?
12
12
  return if self.class.global_uid_disabled
13
13
 
14
- global_uid = nil
15
- realtime = Benchmark::realtime do
16
- global_uid = self.class.generate_uid
17
- end
18
-
19
- if GlobalUid::Base.global_uid_options[:dry_run]
20
- ActiveRecord::Base.logger.info("GlobalUid dry-run: #{self.class.name}\t#{global_uid}\t#{"%.4f" % realtime}")
21
- return
22
- end
23
-
24
- self.id = global_uid
14
+ self.id = self.class.generate_uid
25
15
  end
26
16
 
27
17
  module ClassMethods
@@ -37,12 +27,16 @@ module GlobalUid
37
27
  @global_uid_disabled
38
28
  end
39
29
 
40
- def generate_uid(options = {})
41
- GlobalUid::Base.get_uid_for_class(self, options)
30
+ def generate_uid
31
+ GlobalUid::Base.with_servers do |server|
32
+ return server.allocate(self)
33
+ end
42
34
  end
43
35
 
44
- def generate_many_uids(count, options = {})
45
- GlobalUid::Base.get_many_uids_for_class(self, count, options)
36
+ def generate_many_uids(count)
37
+ GlobalUid::Base.with_servers do |server|
38
+ return Array(server.allocate(self, count: count))
39
+ end
46
40
  end
47
41
 
48
42
  def disable_global_uid
@@ -54,7 +48,7 @@ module GlobalUid
54
48
  end
55
49
 
56
50
  def global_uid_table
57
- GlobalUid::Base.id_table_from_name(self.table_name)
51
+ @_global_uid_table ||= GlobalUid::Base.id_table_from_name(self.table_name)
58
52
  end
59
53
  end
60
54
  end
@@ -0,0 +1,51 @@
1
+ module GlobalUid
2
+ class Allocator
3
+ attr_reader :recent_allocations, :max_window_size, :incrementing_by, :connection, :table_name
4
+
5
+ def initialize(incrementing_by:, connection:, table_name:)
6
+ @recent_allocations = []
7
+ @max_window_size = 5
8
+ @incrementing_by = incrementing_by
9
+ @connection = connection
10
+ @table_name = table_name
11
+ end
12
+
13
+ def allocate_one
14
+ identifier = connection.insert("REPLACE INTO #{table_name} (stub) VALUES ('a')")
15
+ allocate(identifier)
16
+ end
17
+
18
+ def allocate_many(count:)
19
+ return [] unless count > 0
20
+
21
+ increment_by = connection.select_value("SELECT @@auto_increment_increment")
22
+
23
+ start_id = connection.insert("REPLACE INTO #{table_name} (stub) VALUES " + (["('a')"] * count).join(','))
24
+ identifiers = start_id.step(start_id + (count - 1) * increment_by, increment_by).to_a
25
+ identifiers.each { |identifier| allocate(identifier) }
26
+ identifiers
27
+ end
28
+
29
+ private
30
+
31
+ def allocate(identifier)
32
+ recent_allocations.shift if recent_allocations.size >= max_window_size
33
+ recent_allocations << identifier
34
+
35
+ if !valid_allocation?
36
+ db_increment = connection.select_value("SELECT @@auto_increment_increment")
37
+ message = "Configured: '#{incrementing_by}', Found: '#{db_increment}' on '#{connection.current_database}'. Recently allocated IDs: #{recent_allocations} using table '#{table_name}'"
38
+ GlobalUid::Base.alert(InvalidIncrementException.new(message))
39
+ end
40
+
41
+ identifier
42
+ end
43
+
44
+ def valid_allocation?
45
+ recent_allocations[1..-1].all? do |identifier|
46
+ (identifier > recent_allocations[0]) &&
47
+ (identifier - recent_allocations[0]) % incrementing_by == 0
48
+ end
49
+ end
50
+ end
51
+ end
@@ -6,17 +6,6 @@ require "timeout"
6
6
 
7
7
  module GlobalUid
8
8
  class Base
9
- GLOBAL_UID_DEFAULTS = {
10
- :connection_timeout => 3,
11
- :connection_retry => 10.minutes,
12
- :notifier => Proc.new { |exception, message| ActiveRecord::Base.logger.error("GlobalUID error: #{exception} #{message}") },
13
- :query_timeout => 10,
14
- :increment_by => 5, # This will define the maximum number of servers that you can have
15
- :disabled => false,
16
- :per_process_affinity => true,
17
- :dry_run => false
18
- }
19
-
20
9
  def self.servers
21
10
  # Thread local storage is inheritted on `fork`, include the pid
22
11
  Thread.current["global_uid_servers_#{$$}"]
@@ -26,180 +15,66 @@ module GlobalUid
26
15
  Thread.current["global_uid_servers_#{$$}"] = s
27
16
  end
28
17
 
29
- def self.create_uid_tables(id_table_name, options={})
30
- type = options[:uid_type] || "bigint(21) UNSIGNED"
31
- start_id = options[:start_id] || 1
32
-
33
- engine_stmt = "ENGINE=#{global_uid_options[:storage_engine] || "MyISAM"}"
34
-
35
- with_connections do |connection|
36
- connection.execute("CREATE TABLE IF NOT EXISTS `#{id_table_name}` (
37
- `id` #{type} NOT NULL AUTO_INCREMENT,
38
- `stub` char(1) NOT NULL DEFAULT '',
39
- PRIMARY KEY (`id`),
40
- UNIQUE KEY `stub` (`stub`)
41
- ) #{engine_stmt}")
42
-
43
- # prime the pump on each server
44
- connection.execute("INSERT IGNORE INTO `#{id_table_name}` VALUES(#{start_id}, 'a')")
45
- end
46
- end
47
-
48
- def self.drop_uid_tables(id_table_name, options={})
49
- with_connections do |connection|
50
- connection.execute("DROP TABLE IF EXISTS `#{id_table_name}`")
51
- end
52
- end
53
-
54
- def self.new_connection(name, connection_timeout, offset, increment_by)
55
- raise "No id server '#{name}' configured in database.yml" unless ActiveRecord::Base.configurations.to_h.has_key?(name)
56
- config = ActiveRecord::Base.configurations.to_h[name]
57
- c = config.symbolize_keys
58
-
59
- raise "No global_uid support for adapter #{c[:adapter]}" if c[:adapter] != 'mysql2'
60
-
61
- begin
62
- Timeout.timeout(connection_timeout, ConnectionTimeoutException) do
63
- ActiveRecord::Base.mysql2_connection(config)
64
- end
65
- rescue ConnectionTimeoutException => e
66
- notify e, "Timed out establishing a connection to #{name}"
67
- nil
68
- rescue Exception => e
69
- notify e, "establishing a connection to #{name}: #{e.message}"
70
- nil
71
- end
72
- end
73
-
74
- def self.init_server_info(options)
75
- id_servers = self.global_uid_servers
76
-
77
- raise "You haven't configured any id servers" if id_servers.nil? or id_servers.empty?
78
- raise "More servers configured than increment_by: #{id_servers.size} > #{options[:increment_by]} -- this will create duplicate IDs." if id_servers.size > options[:increment_by]
79
-
80
- offset = 1
81
-
82
- id_servers.map do |name, i|
83
- info = {}
84
- info[:cx] = nil
85
- info[:name] = name
86
- info[:retry_at] = nil
87
- info[:offset] = offset
88
- info[:rand] = rand
89
- info[:new?] = true
90
- offset +=1
91
- info
92
- end
18
+ def self.init_server_info
19
+ GlobalUid.configuration.id_servers.map do |name|
20
+ GlobalUid::Server.new(name,
21
+ increment_by: GlobalUid.configuration.increment_by,
22
+ connection_retry: GlobalUid.configuration.connection_retry,
23
+ connection_timeout: GlobalUid.configuration.connection_timeout,
24
+ query_timeout: GlobalUid.configuration.query_timeout
25
+ )
26
+ end.shuffle # so each process uses a random server
93
27
  end
94
28
 
95
29
  def self.disconnect!
30
+ servers.each(&:disconnect!) unless servers.nil?
96
31
  self.servers = nil
97
32
  end
98
33
 
99
- def self.setup_connections!(options)
100
- connection_timeout = options[:connection_timeout]
101
- increment_by = options[:increment_by]
34
+ def self.with_servers
35
+ self.servers ||= init_server_info
36
+ servers = self.servers.each(&:connect)
102
37
 
103
- if self.servers.nil?
104
- self.servers = init_server_info(options)
105
- # sorting here sets up each process to have affinity to a particular server.
106
- self.servers = self.servers.sort_by { |s| s[:rand] }
38
+ if GlobalUid.configuration.connection_shuffling?
39
+ servers.shuffle! # subsequent requests are made against different servers
107
40
  end
108
41
 
109
- self.servers.each do |info|
110
- next if info[:cx]
111
-
112
- if info[:new?] || ( info[:retry_at] && Time.now > info[:retry_at] )
113
- info[:new?] = false
114
-
115
- connection = new_connection(info[:name], connection_timeout, info[:offset], increment_by)
116
- info[:cx] = connection
117
- info[:retry_at] = Time.now + options[:connection_retry] if connection.nil?
118
- end
119
- end
120
-
121
- self.servers
122
- end
123
-
124
- def self.with_connections(options = {})
125
- options = self.global_uid_options.merge(options)
126
- servers = setup_connections!(options)
127
-
128
- if !options[:per_process_affinity]
129
- servers = servers.sort_by { rand } #yes, I know it's not true random.
130
- end
131
-
132
- raise NoServersAvailableException if servers.empty?
133
-
134
42
  errors = []
135
- servers.each do |s|
43
+ servers.each do |server|
136
44
  begin
137
- yield s[:cx] if s[:cx]
45
+ yield server if server.active?
138
46
  rescue TimeoutException, Exception => e
139
- notify e, "#{e.message}"
47
+ GlobalUid.configuration.notifier.call(e)
140
48
  errors << e
141
- s[:cx] = nil
142
- s[:retry_at] = Time.now + 1.minute
49
+ server.disconnect!
50
+ server.update_retry_at(1.minute)
143
51
  end
144
52
  end
145
53
 
146
54
  # in the case where all servers are gone, put everyone back in.
147
- if servers.all? { |info| info[:cx].nil? }
148
- servers.each do |info|
149
- info[:retry_at] = Time.now - 5.minutes
55
+ if servers.all?(&:disconnected?)
56
+ servers.each do |server|
57
+ server.update_retry_at(0)
150
58
  end
151
- raise NoServersAvailableException, "Errors hit: #{errors.map(&:to_s).join(',')}"
59
+ message = errors.empty? ? "" : "Errors hit: #{errors.map(&:to_s).join(', ')}"
60
+ exception = NoServersAvailableException.new(message)
61
+ GlobalUid.configuration.notifier.call(exception)
62
+ raise exception
152
63
  end
153
64
 
154
- servers.map { |s| s[:cx] }.compact
65
+ servers
155
66
  end
156
67
 
157
- def self.notify(exception, message)
158
- if self.global_uid_options[:notifier]
159
- self.global_uid_options[:notifier].call(exception, message)
160
- end
161
- end
162
-
163
- def self.get_connections(options = {})
164
- with_connections {}
165
- end
166
-
167
- def self.get_uid_for_class(klass, options = {})
168
- with_connections do |connection|
169
- Timeout.timeout(self.global_uid_options[:query_timeout], TimeoutException) do
170
- id = connection.insert("REPLACE INTO #{klass.global_uid_table} (stub) VALUES ('a')")
171
- return id
172
- end
173
- end
174
- raise NoServersAvailableException, "All global UID servers are gone!"
68
+ def self.id_table_from_name(name)
69
+ "#{name}_ids".to_sym
175
70
  end
176
71
 
177
- def self.get_many_uids_for_class(klass, count, options = {})
178
- return [] unless count > 0
179
- with_connections do |connection|
180
- Timeout.timeout(self.global_uid_options[:query_timeout], TimeoutException) do
181
- increment_by = connection.select_value("SELECT @@auto_increment_increment")
182
- start_id = connection.insert("REPLACE INTO #{klass.global_uid_table} (stub) VALUES " + (["('a')"] * count).join(','))
183
- return start_id.step(start_id + (count-1) * increment_by, increment_by).to_a
184
- end
72
+ def self.alert(exception)
73
+ if GlobalUid.configuration.suppress_increment_exceptions?
74
+ GlobalUid.configuration.notifier.call(exception)
75
+ else
76
+ raise exception
185
77
  end
186
- raise NoServersAvailableException, "All global UID servers are gone!"
187
- end
188
-
189
- def self.global_uid_options=(options)
190
- @global_uid_options = GLOBAL_UID_DEFAULTS.merge(options.symbolize_keys)
191
- end
192
-
193
- def self.global_uid_options
194
- @global_uid_options
195
- end
196
-
197
- def self.global_uid_servers
198
- self.global_uid_options[:id_servers]
199
- end
200
-
201
- def self.id_table_from_name(name)
202
- "#{name}_ids".to_sym
203
78
  end
204
79
  end
205
80
  end
@@ -0,0 +1,66 @@
1
+ module GlobalUid
2
+ class Configuration
3
+
4
+ attr_accessor :connection_timeout
5
+ attr_accessor :connection_retry
6
+ attr_accessor :notifier
7
+ attr_accessor :query_timeout
8
+ attr_accessor :increment_by
9
+ attr_accessor :disabled
10
+ attr_accessor :connection_shuffling
11
+ attr_accessor :suppress_increment_exceptions
12
+ attr_accessor :storage_engine
13
+
14
+ alias_method :connection_shuffling?, :connection_shuffling
15
+ alias_method :suppress_increment_exceptions?, :suppress_increment_exceptions
16
+
17
+ # Set defaults
18
+ def initialize
19
+ # Timeout (in seconds) for connecting to a global UID server
20
+ @connection_timeout = 3
21
+
22
+ # Duration (in seconds) to wait before attempting another connection to UID server
23
+ @connection_retry = 600 # 10 minutes
24
+
25
+ # An object that will be notified in case of a UID server failure.
26
+ # The object is expected to respond to `#call`, which will be passed one argument of type `Exception`.
27
+ @notifier = GlobalUid::ErrorTracker.new
28
+
29
+ # Timeout (in seconds) for retrieving a global UID from a server before moving to the next server
30
+ @query_timeout = 10
31
+
32
+ # Used for validation, compared with the value on the alloc servers to prevent allocation of duplicate IDs
33
+ # NB: The value configured here does not dictate the value on your alloc server and must remain in
34
+ # sync with the value of auto_increment_increment in the database.
35
+ @increment_by = 5
36
+
37
+ # Disable GlobalUid entirely
38
+ @disabled = false
39
+
40
+ # The same allocation server is used each time `with_servers` is called
41
+ @connection_shuffling = false
42
+
43
+ # Suppress configuration validation, allowing updates to auto_increment_increment while alloc servers in use.
44
+ # The InvalidIncrementException will be swallowed and logged when suppressed
45
+ @suppress_increment_exceptions = false
46
+
47
+ # The name of the alloc DB servers, defined in your database.yml
48
+ # e.g. ["id_server_1", "id_server_2"]
49
+ @id_servers = []
50
+
51
+ # The storage engine used during GloblaUid table creation
52
+ # Supported and tested: InnoDB, MyISAM
53
+ @storage_engine = "MyISAM"
54
+ end
55
+
56
+ def id_servers=(value)
57
+ raise "More servers configured than increment_by: #{value.size} > #{increment_by} -- this will create duplicate IDs." if value.size > increment_by
58
+ @id_servers = value
59
+ end
60
+
61
+ def id_servers
62
+ raise "You haven't configured any id servers" if @id_servers.empty?
63
+ @id_servers
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,7 @@
1
+ module GlobalUid
2
+ class ErrorTracker
3
+ def call(exception)
4
+ ActiveRecord::Base.logger.error("GlobalUID error: #{exception.class} #{exception.message}")
5
+ end
6
+ end
7
+ end
@@ -3,10 +3,10 @@ module GlobalUid
3
3
  module MigrationExtension
4
4
 
5
5
  def create_table(name, options = {}, &blk)
6
- uid_enabled = !(GlobalUid::Base.global_uid_options[:disabled] || options[:use_global_uid] == false)
6
+ uid_enabled = GlobalUid.enabled? && options[:use_global_uid] != false
7
7
 
8
- # rules for stripping out auto_increment -- enabled, not dry-run, and not a "PK-less" table
9
- remove_auto_increment = uid_enabled && !GlobalUid::Base.global_uid_options[:dry_run] && !(options[:id] == false)
8
+ # rules for stripping out auto_increment -- enabled and not a "PK-less" table
9
+ remove_auto_increment = uid_enabled && !(options[:id] == false)
10
10
 
11
11
  options.merge!(:id => false) if remove_auto_increment
12
12
 
@@ -17,15 +17,23 @@ module GlobalUid
17
17
 
18
18
  if uid_enabled
19
19
  id_table_name = options[:global_uid_table] || GlobalUid::Base.id_table_from_name(name)
20
- GlobalUid::Base.create_uid_tables(id_table_name, options)
20
+ GlobalUid::Base.with_servers do |server|
21
+ server.create_uid_table!(
22
+ name: id_table_name,
23
+ uid_type: options[:uid_type],
24
+ start_id: options[:start_id]
25
+ )
26
+ end
21
27
  end
22
28
 
23
29
  end
24
30
 
25
31
  def drop_table(name, options = {})
26
- if !GlobalUid::Base.global_uid_options[:disabled] && options[:use_global_uid] == true
32
+ if GlobalUid.enabled? && options[:use_global_uid] == true
27
33
  id_table_name = options[:global_uid_table] || GlobalUid::Base.id_table_from_name(name)
28
- GlobalUid::Base.drop_uid_tables(id_table_name,options)
34
+ GlobalUid::Base.with_servers do |server|
35
+ server.drop_uid_table!(name: id_table_name)
36
+ end
29
37
  end
30
38
  super(name, options)
31
39
  end
@@ -0,0 +1,121 @@
1
+ module GlobalUid
2
+ class Server
3
+
4
+ attr_accessor :connection, :name
5
+
6
+ def initialize(name, increment_by:, connection_retry:, connection_timeout:, query_timeout:)
7
+ @connection = nil
8
+ @name = name
9
+ @retry_at = nil
10
+ @allocators = {}
11
+ @increment_by = increment_by
12
+ @connection_retry = connection_retry
13
+ @connection_timeout = connection_timeout
14
+ @query_timeout = query_timeout
15
+ end
16
+
17
+ def connect
18
+ return @connection if active? || !retry_connection?
19
+ @connection = mysql2_connection(name)
20
+
21
+ begin
22
+ validate_connection_increment if active?
23
+ rescue InvalidIncrementException => e
24
+ GlobalUid.configuration.notifier.call(e)
25
+ disconnect!
26
+ end
27
+
28
+ @connection
29
+ end
30
+
31
+ def active?
32
+ !disconnected?
33
+ end
34
+
35
+ def disconnected?
36
+ @connection.nil?
37
+ end
38
+
39
+ def update_retry_at(seconds)
40
+ @retry_at = Time.now + seconds
41
+ end
42
+
43
+ def disconnect!
44
+ @connection = nil
45
+ @allocators = {}
46
+ end
47
+
48
+ def create_uid_table!(name:, uid_type: nil, start_id: nil)
49
+ uid_type ||= "bigint(21) UNSIGNED"
50
+ start_id ||= 1
51
+
52
+ connection.execute("CREATE TABLE IF NOT EXISTS `#{name}` (
53
+ `id` #{uid_type} NOT NULL AUTO_INCREMENT,
54
+ `stub` char(1) NOT NULL DEFAULT '',
55
+ PRIMARY KEY (`id`),
56
+ UNIQUE KEY `stub` (`stub`)
57
+ ) ENGINE=#{GlobalUid.configuration.storage_engine}")
58
+
59
+ # prime the pump on each server
60
+ connection.execute("INSERT IGNORE INTO `#{name}` VALUES(#{start_id}, 'a')")
61
+ end
62
+
63
+ def drop_uid_table!(name:)
64
+ connection.execute("DROP TABLE IF EXISTS `#{name}`")
65
+ end
66
+
67
+ def allocate(klass, count: 1)
68
+ # TODO: Replace Timeout.timeout with DB level timeout
69
+ # Timeout.timeout is unpredictable
70
+ Timeout.timeout(query_timeout, TimeoutException) do
71
+ if count == 1
72
+ allocator(klass).allocate_one
73
+ else
74
+ allocator(klass).allocate_many(count: count)
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ attr_accessor :connection_retry, :connection_timeout, :retry_at, :increment_by, :query_timeout, :allocators
82
+
83
+ def allocator(klass)
84
+ table_name = klass.global_uid_table
85
+ @allocators[table_name] ||= Allocator.new(incrementing_by: increment_by, connection: connection, table_name: table_name)
86
+ end
87
+
88
+ def retry_connection?
89
+ return Time.now > retry_at if retry_at
90
+
91
+ update_retry_at(connection_retry)
92
+ true
93
+ end
94
+
95
+ def mysql2_connection(name)
96
+ raise "No id server '#{name}' configured in database.yml" unless ActiveRecord::Base.configurations.to_h.has_key?(name)
97
+ config = ActiveRecord::Base.configurations.to_h[name]
98
+ c = config.symbolize_keys
99
+
100
+ raise "No global_uid support for adapter #{c[:adapter]}" if c[:adapter] != 'mysql2'
101
+
102
+ Timeout.timeout(connection_timeout, ConnectionTimeoutException) do
103
+ ActiveRecord::Base.mysql2_connection(config)
104
+ end
105
+ rescue ConnectionTimeoutException => e
106
+ GlobalUid.configuration.notifier.call(ConnectionTimeoutException.new("Timed out establishing a connection to #{name}"))
107
+ nil
108
+ rescue Exception => e
109
+ GlobalUid.configuration.notifier.call(StandardError.new("establishing a connection to #{name}: #{e.message}"))
110
+ nil
111
+ end
112
+
113
+ def validate_connection_increment
114
+ db_increment = connection.select_value("SELECT @@auto_increment_increment")
115
+
116
+ if db_increment != increment_by
117
+ GlobalUid::Base.alert(InvalidIncrementException.new("Configured: '#{increment_by}', Found: '#{db_increment}' on '#{connection.current_database}'"))
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,44 @@
1
+ module GlobalUid
2
+ module TestSupport
3
+ # Tables should be created through the MigrationExtension but
4
+ # if you want to manually create and drop the '_id' tables,
5
+ # you can do so via this module
6
+ class << self
7
+ def create_uid_tables(tables: [], uid_type: nil, start_id: nil)
8
+ return if GlobalUid.disabled?
9
+
10
+ GlobalUid::Base.with_servers do |server|
11
+ tables.each do |table|
12
+ server.create_uid_table!(
13
+ name: GlobalUid::Base.id_table_from_name(table),
14
+ uid_type: uid_type,
15
+ start_id: start_id
16
+ )
17
+ end
18
+ end
19
+ end
20
+
21
+ def drop_uid_tables(tables: [])
22
+ return if GlobalUid.disabled?
23
+
24
+ GlobalUid::Base.with_servers do |server|
25
+ tables.each do |table|
26
+ server.drop_uid_table!(
27
+ name: GlobalUid::Base.id_table_from_name(table)
28
+ )
29
+ end
30
+ end
31
+ end
32
+
33
+ def recreate_uid_tables(tables: [], uid_type: nil, start_id: nil)
34
+ return if GlobalUid.disabled?
35
+
36
+ drop_uid_tables(tables: tables)
37
+ create_uid_tables(tables: tables, uid_type: nil, start_id: start_id)
38
+
39
+ # Reset the servers, clearing any allocations from memory
40
+ GlobalUid::Base.disconnect!
41
+ end
42
+ end
43
+ end
44
+ end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: global_uid
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.1
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Quorning
8
8
  - Gabe Martin-Dempesy
9
9
  - Pierre Schambacher
10
10
  - Ben Osheroff
11
- autorequire:
11
+ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2020-03-17 00:00:00.000000000 Z
14
+ date: 2020-11-12 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activerecord
@@ -117,6 +117,20 @@ dependencies:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
119
  version: '0'
120
+ - !ruby/object:Gem::Dependency
121
+ name: minitest-line
122
+ requirement: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ type: :development
128
+ prerelease: false
129
+ version_requirements: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
120
134
  - !ruby/object:Gem::Dependency
121
135
  name: mocha
122
136
  requirement: !ruby/object:Gem::Requirement
@@ -131,6 +145,20 @@ dependencies:
131
145
  - - ">="
132
146
  - !ruby/object:Gem::Version
133
147
  version: '0'
148
+ - !ruby/object:Gem::Dependency
149
+ name: benchmark-ips
150
+ requirement: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ type: :development
156
+ prerelease: false
157
+ version_requirements: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
134
162
  - !ruby/object:Gem::Dependency
135
163
  name: bump
136
164
  requirement: !ruby/object:Gem::Requirement
@@ -159,6 +187,20 @@ dependencies:
159
187
  - - ">="
160
188
  - !ruby/object:Gem::Version
161
189
  version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: pry
192
+ requirement: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ type: :development
198
+ prerelease: false
199
+ version_requirements: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
162
204
  description: GUIDs for sharded models
163
205
  email:
164
206
  - bquorning@zendesk.com
@@ -170,16 +212,20 @@ extra_rdoc_files: []
170
212
  files:
171
213
  - lib/global_uid.rb
172
214
  - lib/global_uid/active_record_extension.rb
215
+ - lib/global_uid/allocator.rb
173
216
  - lib/global_uid/base.rb
217
+ - lib/global_uid/configuration.rb
218
+ - lib/global_uid/error_tracker.rb
174
219
  - lib/global_uid/has_and_belongs_to_many_builder_extension.rb
175
220
  - lib/global_uid/migration_extension.rb
176
221
  - lib/global_uid/schema_dumper_extension.rb
177
- - lib/global_uid/server_variables.rb
222
+ - lib/global_uid/server.rb
223
+ - lib/global_uid/test_support.rb
178
224
  homepage: https://github.com/zendesk/global_uid
179
225
  licenses:
180
226
  - MIT
181
227
  metadata: {}
182
- post_install_message:
228
+ post_install_message:
183
229
  rdoc_options: []
184
230
  require_paths:
185
231
  - lib
@@ -194,8 +240,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
240
  - !ruby/object:Gem::Version
195
241
  version: '0'
196
242
  requirements: []
197
- rubygems_version: 3.1.1
198
- signing_key:
243
+ rubygems_version: 3.0.3
244
+ signing_key:
199
245
  specification_version: 4
200
246
  summary: GUID
201
247
  test_files: []
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
- # This module is good for testing and development, not so much for production.
3
- # Please note that this is unreliable -- if you lose your CX to the server
4
- # and auto-reconnect, you will be utterly hosed. Much better to dedicate a server
5
- # or two to the cause, and set their auto_increment_increment globally.
6
- #
7
- # You can include this module in tests like this:
8
- # GlobalUid::Base.extend(GlobalUid::ServerVariables)
9
- #
10
- module GlobalUid
11
- module ServerVariables
12
-
13
- def self.extended(base)
14
- base.singleton_class.send(:alias_method, :new_connection_without_server_variables, :new_connection)
15
- base.singleton_class.send(:alias_method, :new_connection, :new_connection_with_server_variables)
16
- end
17
-
18
- def new_connection_with_server_variables(name, connection_timeout, offset, increment_by)
19
- con = new_connection_without_server_variables(name, connection_timeout, offset, increment_by)
20
-
21
- if con
22
- con.execute("set @@auto_increment_increment = #{increment_by}")
23
- con.execute("set @@auto_increment_offset = #{offset}")
24
- end
25
-
26
- con
27
- end
28
-
29
- end
30
- end