global_uid 3.7.1 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/global_uid.rb +19 -8
- data/lib/global_uid/active_record_extension.rb +10 -16
- data/lib/global_uid/allocator.rb +69 -0
- data/lib/global_uid/base.rb +28 -161
- data/lib/global_uid/configuration.rb +67 -0
- data/lib/global_uid/error_tracker.rb +7 -0
- data/lib/global_uid/migration_extension.rb +15 -6
- data/lib/global_uid/server.rb +106 -0
- metadata +51 -6
- data/lib/global_uid/server_variables.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a02789fdd302a6dbc207c590c60ce5b5909f8430e3432813af90d0373efd71b
|
4
|
+
data.tar.gz: 4cf5cd96f7b8855b546a1741c67dac5db6bb9b42cca3105bbe741423a2f2e66e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e36b732bbea40d81423498b9ec9365e39688601691524b615a9b1cc0955b5dbcbf7b9788128bcb5673b08ccd37c49b44a7990d8a62f578358cd9ca8116d17fb
|
7
|
+
data.tar.gz: 73b1d6ff9808d94c14e1618b7da2137e0ad9c2b02442941b788519ebdc77daec7bfc8be67715ec16294062ea8ae334c1b437a198342f096623bd4f124585385c
|
data/lib/global_uid.rb
CHANGED
@@ -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,20 @@ 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
|
-
|
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
|
+
# @private
|
27
|
+
def self.reset_configuration
|
28
|
+
@configuration = nil
|
29
|
+
end
|
14
30
|
end
|
15
31
|
|
16
32
|
ActiveRecord::Base.send(:include, GlobalUid::ActiveRecordExtension)
|
@@ -26,10 +42,5 @@ if ActiveRecord::VERSION::MAJOR >= 5
|
|
26
42
|
ActiveRecord::InternalMetadata.disable_global_uid
|
27
43
|
end
|
28
44
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
if ActiveRecord::VERSION::MAJOR >= 4
|
34
|
-
ActiveRecord::SchemaDumper.send(:prepend, GlobalUid::SchemaDumperExtension)
|
35
|
-
end
|
45
|
+
ActiveRecord::Associations::Builder::HasAndBelongsToMany.send(:include, GlobalUid::HasAndBelongsToManyBuilderExtension)
|
46
|
+
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
|
11
|
+
return if GlobalUid.configuration.disabled?
|
12
12
|
return if self.class.global_uid_disabled
|
13
13
|
|
14
|
-
|
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
|
41
|
-
GlobalUid::Base.
|
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
|
45
|
-
GlobalUid::Base.
|
36
|
+
def generate_many_uids(count)
|
37
|
+
GlobalUid::Base.with_servers do |server|
|
38
|
+
return server.allocate(self, count: count)
|
39
|
+
end
|
46
40
|
end
|
47
41
|
|
48
42
|
def disable_global_uid
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module GlobalUid
|
2
|
+
class Allocator
|
3
|
+
attr_reader :recent_allocations, :max_window_size, :incrementing_by, :connection
|
4
|
+
|
5
|
+
def initialize(incrementing_by:, connection:)
|
6
|
+
@recent_allocations = []
|
7
|
+
@max_window_size = 5
|
8
|
+
@incrementing_by = incrementing_by
|
9
|
+
@connection = connection
|
10
|
+
validate_connection_increment
|
11
|
+
end
|
12
|
+
|
13
|
+
def allocate_one(table)
|
14
|
+
identifier = connection.insert("REPLACE INTO #{table} (stub) VALUES ('a')")
|
15
|
+
allocate(identifier)
|
16
|
+
end
|
17
|
+
|
18
|
+
def allocate_many(table, count:)
|
19
|
+
return [] unless count > 0
|
20
|
+
|
21
|
+
increment_by = validate_connection_increment
|
22
|
+
|
23
|
+
start_id = connection.insert("REPLACE INTO #{table} (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}"
|
38
|
+
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
|
+
|
51
|
+
def validate_connection_increment
|
52
|
+
db_increment = connection.select_value("SELECT @@auto_increment_increment")
|
53
|
+
|
54
|
+
if db_increment != incrementing_by
|
55
|
+
alert(InvalidIncrementException.new("Configured: '#{incrementing_by}', Found: '#{db_increment}' on '#{connection.current_database}'"))
|
56
|
+
end
|
57
|
+
|
58
|
+
db_increment
|
59
|
+
end
|
60
|
+
|
61
|
+
def alert(exception)
|
62
|
+
if GlobalUid.configuration.suppress_increment_exceptions?
|
63
|
+
GlobalUid.configuration.notifier.call(exception)
|
64
|
+
else
|
65
|
+
raise exception
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/global_uid/base.rb
CHANGED
@@ -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,176 +15,54 @@ module GlobalUid
|
|
26
15
|
Thread.current["global_uid_servers_#{$$}"] = s
|
27
16
|
end
|
28
17
|
|
29
|
-
def self.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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.
|
100
|
-
|
101
|
-
|
102
|
-
|
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] }
|
107
|
-
end
|
108
|
-
|
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)
|
34
|
+
def self.with_servers
|
35
|
+
self.servers ||= init_server_info
|
36
|
+
servers = self.servers.each(&:connect)
|
127
37
|
|
128
|
-
if
|
129
|
-
servers
|
38
|
+
if GlobalUid.configuration.connection_shuffling?
|
39
|
+
servers.shuffle! # subsequent requests are made against different servers
|
130
40
|
end
|
131
41
|
|
132
|
-
raise NoServersAvailableException if servers.empty?
|
133
|
-
|
134
42
|
errors = []
|
135
|
-
servers.each do |
|
43
|
+
servers.each do |server|
|
136
44
|
begin
|
137
|
-
yield
|
45
|
+
yield server if server.active?
|
138
46
|
rescue TimeoutException, Exception => e
|
139
|
-
|
47
|
+
GlobalUid.configuration.notifier.call(e)
|
140
48
|
errors << e
|
141
|
-
|
142
|
-
|
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?
|
148
|
-
servers.each do |
|
149
|
-
|
55
|
+
if servers.all?(&:disconnected?)
|
56
|
+
servers.each do |server|
|
57
|
+
server.update_retry_at(0)
|
150
58
|
end
|
151
|
-
|
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
|
155
|
-
end
|
156
|
-
|
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!"
|
175
|
-
end
|
176
|
-
|
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
|
185
|
-
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]
|
65
|
+
servers
|
199
66
|
end
|
200
67
|
|
201
68
|
def self.id_table_from_name(name)
|
@@ -0,0 +1,67 @@
|
|
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 :disabled?, :disabled
|
15
|
+
alias_method :connection_shuffling?, :connection_shuffling
|
16
|
+
alias_method :suppress_increment_exceptions?, :suppress_increment_exceptions
|
17
|
+
|
18
|
+
# Set defaults
|
19
|
+
def initialize
|
20
|
+
# Timeout (in seconds) for connecting to a global UID server
|
21
|
+
@connection_timeout = 3
|
22
|
+
|
23
|
+
# Duration (in seconds) to wait before attempting another connection to UID server
|
24
|
+
@connection_retry = 600 # 10 minutes
|
25
|
+
|
26
|
+
# An object that will be notified in case of a UID server failure.
|
27
|
+
# The object is expected to respond to `#call`, which will be passed one argument of type `Exception`.
|
28
|
+
@notifier = GlobalUid::ErrorTracker.new
|
29
|
+
|
30
|
+
# Timeout (in seconds) for retrieving a global UID from a server before moving to the next server
|
31
|
+
@query_timeout = 10
|
32
|
+
|
33
|
+
# Used for validation, compared with the value on the alloc servers to prevent allocation of duplicate IDs
|
34
|
+
# NB: The value configured here does not dictate the value on your alloc server and must remain in
|
35
|
+
# sync with the value of auto_increment_increment in the database.
|
36
|
+
@increment_by = 5
|
37
|
+
|
38
|
+
# Disable GlobalUid entirely
|
39
|
+
@disabled = false
|
40
|
+
|
41
|
+
# The same allocation server is used each time `with_servers` is called
|
42
|
+
@connection_shuffling = false
|
43
|
+
|
44
|
+
# Suppress configuration validation, allowing updates to auto_increment_increment while alloc servers in use.
|
45
|
+
# The InvalidIncrementException will be swallowed and logged when suppressed
|
46
|
+
@suppress_increment_exceptions = false
|
47
|
+
|
48
|
+
# The name of the alloc DB servers, defined in your database.yml
|
49
|
+
# e.g. ["id_server_1", "id_server_2"]
|
50
|
+
@id_servers = []
|
51
|
+
|
52
|
+
# The storage engine used during GloblaUid table creation
|
53
|
+
# Supported and tested: InnoDB, MyISAM
|
54
|
+
@storage_engine = "MyISAM"
|
55
|
+
end
|
56
|
+
|
57
|
+
def id_servers=(value)
|
58
|
+
raise "More servers configured than increment_by: #{value.size} > #{increment_by} -- this will create duplicate IDs." if value.size > increment_by
|
59
|
+
@id_servers = value
|
60
|
+
end
|
61
|
+
|
62
|
+
def id_servers
|
63
|
+
raise "You haven't configured any id servers" if @id_servers.empty?
|
64
|
+
@id_servers
|
65
|
+
end
|
66
|
+
end
|
67
|
+
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
|
6
|
+
uid_enabled = !(GlobalUid.configuration.disabled? || options[:use_global_uid] == false)
|
7
7
|
|
8
|
-
# rules for stripping out auto_increment -- enabled
|
9
|
-
remove_auto_increment = uid_enabled && !
|
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,24 @@ 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.
|
20
|
+
GlobalUid::Base.with_servers do |server|
|
21
|
+
server.create_uid_table!(
|
22
|
+
name: id_table_name,
|
23
|
+
uid_type: options[:uid_type] || "bigint(21) UNSIGNED",
|
24
|
+
start_id: options[:start_id] || 1,
|
25
|
+
storage_engine: GlobalUid.configuration.storage_engine
|
26
|
+
)
|
27
|
+
end
|
21
28
|
end
|
22
29
|
|
23
30
|
end
|
24
31
|
|
25
32
|
def drop_table(name, options = {})
|
26
|
-
if !GlobalUid
|
33
|
+
if !GlobalUid.configuration.disabled? && options[:use_global_uid] == true
|
27
34
|
id_table_name = options[:global_uid_table] || GlobalUid::Base.id_table_from_name(name)
|
28
|
-
GlobalUid::Base.
|
35
|
+
GlobalUid::Base.with_servers do |server|
|
36
|
+
server.drop_uid_table!(name: id_table_name)
|
37
|
+
end
|
29
38
|
end
|
30
39
|
super(name, options)
|
31
40
|
end
|
@@ -0,0 +1,106 @@
|
|
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
|
+
@allocator = nil
|
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
|
+
@allocator = Allocator.new(incrementing_by: increment_by, connection: @connection) 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
|
+
@allocator = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_uid_table!(name:, uid_type:, start_id:, storage_engine:)
|
49
|
+
connection.execute("CREATE TABLE IF NOT EXISTS `#{name}` (
|
50
|
+
`id` #{uid_type} NOT NULL AUTO_INCREMENT,
|
51
|
+
`stub` char(1) NOT NULL DEFAULT '',
|
52
|
+
PRIMARY KEY (`id`),
|
53
|
+
UNIQUE KEY `stub` (`stub`)
|
54
|
+
) ENGINE=#{storage_engine}")
|
55
|
+
|
56
|
+
# prime the pump on each server
|
57
|
+
connection.execute("INSERT IGNORE INTO `#{name}` VALUES(#{start_id}, 'a')")
|
58
|
+
end
|
59
|
+
|
60
|
+
def drop_uid_table!(name:)
|
61
|
+
connection.execute("DROP TABLE IF EXISTS `#{name}`")
|
62
|
+
end
|
63
|
+
|
64
|
+
def allocate(klass, count: 1)
|
65
|
+
# TODO: Replace Timeout.timeout with DB level timeout
|
66
|
+
# Timeout.timeout is unpredictable
|
67
|
+
Timeout.timeout(query_timeout, TimeoutException) do
|
68
|
+
if count == 1
|
69
|
+
allocator.allocate_one(klass.global_uid_table)
|
70
|
+
else
|
71
|
+
allocator.allocate_many(klass.global_uid_table, count: count)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
attr_accessor :connection_retry, :connection_timeout, :retry_at, :increment_by, :query_timeout, :allocator
|
79
|
+
|
80
|
+
def retry_connection?
|
81
|
+
return Time.now > retry_at if retry_at
|
82
|
+
|
83
|
+
update_retry_at(connection_retry)
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
def mysql2_connection(name)
|
88
|
+
raise "No id server '#{name}' configured in database.yml" unless ActiveRecord::Base.configurations.to_h.has_key?(name)
|
89
|
+
config = ActiveRecord::Base.configurations.to_h[name]
|
90
|
+
c = config.symbolize_keys
|
91
|
+
|
92
|
+
raise "No global_uid support for adapter #{c[:adapter]}" if c[:adapter] != 'mysql2'
|
93
|
+
|
94
|
+
Timeout.timeout(connection_timeout, ConnectionTimeoutException) do
|
95
|
+
ActiveRecord::Base.mysql2_connection(config)
|
96
|
+
end
|
97
|
+
rescue ConnectionTimeoutException => e
|
98
|
+
GlobalUid.configuration.notifier.call(ConnectionTimeoutException.new("Timed out establishing a connection to #{name}"))
|
99
|
+
nil
|
100
|
+
rescue Exception => e
|
101
|
+
GlobalUid.configuration.notifier.call(StandardError.new("establishing a connection to #{name}: #{e.message}"))
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: global_uid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin Quorning
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2020-
|
14
|
+
date: 2020-04-28 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,11 +212,14 @@ 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/
|
222
|
+
- lib/global_uid/server.rb
|
178
223
|
homepage: https://github.com/zendesk/global_uid
|
179
224
|
licenses:
|
180
225
|
- MIT
|
@@ -190,11 +235,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
190
235
|
version: '2.4'
|
191
236
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
237
|
requirements:
|
193
|
-
- - "
|
238
|
+
- - ">"
|
194
239
|
- !ruby/object:Gem::Version
|
195
|
-
version:
|
240
|
+
version: 1.3.1
|
196
241
|
requirements: []
|
197
|
-
rubygems_version: 3.1.
|
242
|
+
rubygems_version: 3.1.2
|
198
243
|
signing_key:
|
199
244
|
specification_version: 4
|
200
245
|
summary: GUID
|
@@ -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
|