switchman 1.13.3 → 2.0.0

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/switchman/shard.rb +743 -11
  3. data/db/migrate/20130328224244_create_default_shard.rb +1 -1
  4. data/lib/switchman/active_record/abstract_adapter.rb +2 -6
  5. data/lib/switchman/active_record/association.rb +35 -12
  6. data/lib/switchman/active_record/attribute_methods.rb +3 -0
  7. data/lib/switchman/active_record/base.rb +47 -9
  8. data/lib/switchman/active_record/calculations.rb +2 -1
  9. data/lib/switchman/active_record/connection_handler.rb +23 -20
  10. data/lib/switchman/active_record/connection_pool.rb +17 -15
  11. data/lib/switchman/active_record/log_subscriber.rb +8 -12
  12. data/lib/switchman/active_record/migration.rb +9 -0
  13. data/lib/switchman/active_record/model_schema.rb +1 -1
  14. data/lib/switchman/active_record/postgresql_adapter.rb +22 -47
  15. data/lib/switchman/active_record/query_cache.rb +17 -107
  16. data/lib/switchman/active_record/query_methods.rb +4 -0
  17. data/lib/switchman/active_record/relation.rb +5 -5
  18. data/lib/switchman/active_record/statement_cache.rb +4 -25
  19. data/lib/switchman/active_record/table_definition.rb +2 -2
  20. data/lib/switchman/active_support/cache.rb +16 -0
  21. data/lib/switchman/connection_pool_proxy.rb +38 -22
  22. data/lib/switchman/database_server.rb +32 -19
  23. data/lib/switchman/default_shard.rb +1 -0
  24. data/lib/switchman/engine.rb +16 -14
  25. data/lib/switchman/{shackles → guard_rail}/relation.rb +5 -5
  26. data/lib/switchman/{shackles.rb → guard_rail.rb} +4 -4
  27. data/lib/switchman/r_spec_helper.rb +7 -7
  28. data/lib/switchman/sharded_instrumenter.rb +1 -1
  29. data/lib/switchman/test_helper.rb +3 -3
  30. data/lib/switchman/version.rb +1 -1
  31. data/lib/switchman.rb +1 -1
  32. data/lib/tasks/switchman.rake +23 -17
  33. metadata +35 -22
  34. data/app/models/switchman/shard_internal.rb +0 -714
@@ -1,714 +0,0 @@
1
- require 'switchman/database_server'
2
- require 'switchman/default_shard'
3
- require 'switchman/environment'
4
- require 'switchman/errors'
5
-
6
- module Switchman
7
- class Shard < ::ActiveRecord::Base
8
- # ten trillion possible ids per shard. yup.
9
- IDS_PER_SHARD = 10_000_000_000_000
10
-
11
- CATEGORIES =
12
- {
13
- # special cased to mean all other models
14
- :primary => nil,
15
- # special cased to not allow activating a shard other than the default
16
- :unsharded => [Shard]
17
- }
18
- private_constant :CATEGORIES
19
- @connection_specification_name = @shard_category = :unsharded
20
-
21
- if defined?(::ProtectedAttributes)
22
- attr_accessible :default, :name, :database_server
23
- end
24
-
25
- # only allow one default
26
- validates_uniqueness_of :default, :if => lambda { |s| s.default? }
27
-
28
- after_save :clear_cache
29
- after_destroy :clear_cache
30
-
31
- after_rollback :on_rollback
32
-
33
- scope :primary, -> { where(name: nil).order(:database_server_id, :id).distinct_on(:database_server_id) }
34
-
35
- class << self
36
- def categories
37
- CATEGORIES.keys
38
- end
39
-
40
- def default(reload_deprecated = false, reload: false, with_fallback: false)
41
- reload = reload_deprecated if reload_deprecated
42
- if !@default || reload
43
- # Have to create a dummy object so that several key methods still work
44
- # (it's easier to do this in one place here, and just assume that sharding
45
- # is up and running everywhere else). This includes for looking up the
46
- # default shard itself. This also needs to be a local so that this method
47
- # can be re-entrant
48
- default = DefaultShard.instance
49
-
50
- # if we already have a default shard in place, and the caller wants
51
- # to use it as a fallback, use that instead of the dummy instance
52
- if with_fallback && @default
53
- default = @default
54
- end
55
-
56
- # the first time we need a dummy dummy for re-entrancy to avoid looping on ourselves
57
- @default ||= default
58
-
59
- # Now find the actual record, if it exists; rescue the fake default if the table doesn't exist
60
- @default = begin
61
- find_cached("default_shard") { Shard.where(default: true).take } || default
62
- rescue
63
- default
64
- end
65
-
66
- # rebuild current shard activations - it might have "another" default shard serialized there
67
- active_shards.replace(active_shards.map do |category, shard|
68
- shard = Shard.lookup((!shard || shard.default?) ? 'default' : shard.id)
69
- [category, shard]
70
- end.to_h)
71
-
72
- activate!(primary: @default) if active_shards.empty?
73
-
74
- # make sure this is not erroneously cached
75
- if @default.database_server.instance_variable_defined?(:@primary_shard)
76
- @default.database_server.remove_instance_variable(:@primary_shard)
77
- end
78
- end
79
- @default
80
- end
81
-
82
- def current(category = :primary)
83
- active_shards[category] || Shard.default
84
- end
85
-
86
- def activate(shards)
87
- old_shards = activate!(shards)
88
- yield
89
- ensure
90
- active_shards.merge!(old_shards) if old_shards
91
- end
92
-
93
- def activate!(shards)
94
- old_shards = nil
95
- currently_active_shards = active_shards
96
- shards.each do |category, shard|
97
- next if category == :unsharded
98
- unless currently_active_shards[category] == shard
99
- old_shards ||= {}
100
- old_shards[category] = currently_active_shards[category]
101
- currently_active_shards[category] = shard
102
- end
103
- end
104
- old_shards
105
- end
106
-
107
- def lookup(id)
108
- id_i = id.to_i
109
- return current if id_i == current.id || id == 'self'
110
- return default if id_i == default.id || id.nil? || id == 'default'
111
- id = id_i
112
- raise ArgumentError if id == 0
113
-
114
- unless cached_shards.has_key?(id)
115
- cached_shards[id] = Shard.default.activate do
116
- find_cached(['shard', id]) { find_by(id: id) }
117
- end
118
- end
119
- cached_shards[id]
120
- end
121
-
122
- def clear_cache
123
- cached_shards.clear
124
- end
125
-
126
- # ==== Parameters
127
- #
128
- # * +shards+ - an array or relation of Shards to iterate over
129
- # * +categories+ - an array of categories to activate
130
- # * +options+ -
131
- # :parallel - true/false to execute in parallel, or a integer of how many
132
- # sub-processes per database server. Note that parallel
133
- # invocation currently uses forking, so should be used sparingly
134
- # because errors are not raised, and you cannot get results back
135
- # :max_procs - only run this many parallel processes at a time
136
- # :exception - :ignore, :raise, :defer (wait until the end and raise the first
137
- # error), or a proc
138
- def with_each_shard(*args)
139
- raise ArgumentError, "wrong number of arguments (#{args.length} for 0...3)" if args.length > 3
140
-
141
- unless default.is_a?(Shard)
142
- return Array.wrap(yield)
143
- end
144
-
145
- options = args.extract_options!
146
- if args.length == 1
147
- if Array === args.first && args.first.first.is_a?(Symbol)
148
- categories = args.first
149
- else
150
- scope = args.first
151
- end
152
- else
153
- scope, categories = args
154
- end
155
-
156
- parallel = case options[:parallel]
157
- when true
158
- 1
159
- when false, nil
160
- 0
161
- else
162
- options[:parallel]
163
- end
164
- options.delete(:parallel)
165
-
166
- scope ||= Shard.all
167
- if ::ActiveRecord::Relation === scope && scope.order_values.empty?
168
- scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
169
- end
170
-
171
- if parallel > 0
172
- max_procs = determine_max_procs(options.delete(:max_procs), parallel)
173
- if ::ActiveRecord::Relation === scope
174
- # still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
175
- database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
176
- map(&:database_server).compact.uniq
177
- parallel = [(max_procs.to_f / database_servers.count).ceil, parallel].min if max_procs
178
-
179
- scopes = Hash[database_servers.map do |server|
180
- server_scope = server.shards.merge(scope)
181
- if parallel == 1
182
- subscopes = [server_scope]
183
- else
184
- subscopes = []
185
- total = server_scope.count
186
- ranges = []
187
- server_scope.find_ids_in_ranges(:batch_size => (total.to_f / parallel).ceil) do |min, max|
188
- ranges << [min, max]
189
- end
190
- # create a half-open range on the last one
191
- ranges.last[1] = nil
192
- ranges.each do |min, max|
193
- subscope = server_scope.where("id>=?", min)
194
- subscope = subscope.where("id<=?", max) if max
195
- subscopes << subscope
196
- end
197
- end
198
- [server, subscopes]
199
- end]
200
- else
201
- scopes = scope.group_by(&:database_server)
202
- if parallel > 1
203
- parallel = [(max_procs.to_f / scopes.count).ceil, parallel].min if max_procs
204
- scopes = Hash[scopes.map do |(server, shards)|
205
- [server, shards.in_groups(parallel, false).compact]
206
- end]
207
- else
208
- scopes = Hash[scopes.map { |(server, shards)| [server, [shards]] }]
209
- end
210
- end
211
-
212
- exception_pipes = []
213
- pids = []
214
- out_fds = []
215
- err_fds = []
216
- pid_to_name_map = {}
217
- fd_to_name_map = {}
218
- errors = []
219
-
220
- wait_for_output = lambda do |out_fds, err_fds, fd_to_name_map|
221
- ready, _ = IO.select(out_fds + err_fds)
222
- ready.each do |fd|
223
- if fd.eof?
224
- fd.close
225
- out_fds.delete(fd)
226
- err_fds.delete(fd)
227
- next
228
- end
229
- line = fd.readline
230
- puts "#{fd_to_name_map[fd]}: #{line}"
231
- end
232
- end
233
-
234
- # only one process; don't bother forking
235
- if scopes.length == 1 && parallel == 1
236
- return with_each_shard(scopes.first.last.first, categories, options) { yield }
237
- end
238
-
239
- # clear connections prior to forking (no more queries will be executed in the parent,
240
- # and we want them gone so that we don't accidentally use them post-fork doing something
241
- # silly like dealloc'ing prepared statements)
242
- ::ActiveRecord::Base.clear_all_connections!
243
-
244
- scopes.each do |server, subscopes|
245
- subscopes.each_with_index do |subscope, idx|
246
- if subscopes.length > 1
247
- name = "#{server.id} #{idx + 1}"
248
- else
249
- name = server.id
250
- end
251
-
252
- exception_pipe = IO.pipe
253
- exception_pipes << exception_pipe
254
- pid, io_in, io_out, io_err = Open4.pfork4(lambda do
255
- begin
256
- Switchman.config[:on_fork_proc]&.call
257
-
258
- # set a pretty name for the process title, up to 128 characters
259
- # (we don't actually know the limit, depending on how the process
260
- # was started)
261
- # first, simplify the binary name by stripping directories,
262
- # then truncate arguments as necessary
263
- bin = File.basename(Process.argv0)
264
- max_length = 128 - bin.length - name.length - 3
265
- args = ARGV.join(" ")
266
- if max_length >= 0
267
- args = args[0..max_length]
268
- end
269
- new_title = [bin, args, name].join(" ")
270
- Process.setproctitle(new_title)
271
-
272
- with_each_shard(subscope, categories, options) { yield }
273
- exception_pipe.last.close
274
- rescue => e
275
- begin
276
- dumped = Marshal.dump(e)
277
- rescue
278
- # couldn't dump the exception; create a copy with just
279
- # the message and the backtrace
280
- e2 = e.class.new(e.message)
281
- e2.set_backtrace(e.backtrace)
282
- e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
283
- dumped = Marshal.dump(e2)
284
- end
285
- exception_pipe.last.set_encoding(dumped.encoding)
286
- exception_pipe.last.write(dumped)
287
- exception_pipe.last.flush
288
- exception_pipe.last.close
289
- exit! 1
290
- end
291
- end)
292
- exception_pipe.last.close
293
- pids << pid
294
- io_in.close # don't care about writing to stdin
295
- out_fds << io_out
296
- err_fds << io_err
297
- pid_to_name_map[pid] = name
298
- fd_to_name_map[io_out] = name
299
- fd_to_name_map[io_err] = name
300
-
301
- while max_procs && pids.count >= max_procs
302
- while max_procs && out_fds.count >= max_procs
303
- # wait for output if we've hit the max_procs limit
304
- wait_for_output.call(out_fds, err_fds, fd_to_name_map)
305
- end
306
- # we've gotten all the output from one fd so wait for its child process to exit
307
- found_pid, status = Process.wait2
308
- pids.delete(found_pid)
309
- errors << pid_to_name_map[found_pid] if status.exitstatus != 0
310
- end
311
- end
312
- end
313
-
314
- while out_fds.any? || err_fds.any?
315
- wait_for_output.call(out_fds, err_fds, fd_to_name_map)
316
- end
317
- pids.each do |pid|
318
- _, status = Process.waitpid2(pid)
319
- errors << pid_to_name_map[pid] if status.exitstatus != 0
320
- end
321
-
322
- # check for an exception; we only re-raise the first one
323
- exception_pipes.each do |exception_pipe|
324
- begin
325
- serialized_exception = exception_pipe.first.read
326
- next if serialized_exception.empty?
327
- exception = Marshal.load(serialized_exception)
328
- raise exception
329
- ensure
330
- exception_pipe.first.close
331
- end
332
- end
333
-
334
- unless errors.empty?
335
- raise ParallelShardExecError.new("The following subprocesses did not exit cleanly: #{errors.sort.join(", ")}")
336
- end
337
- return
338
- end
339
-
340
- categories ||= []
341
-
342
- previous_shard = nil
343
- close_connections_if_needed = lambda do |shard|
344
- # prune the prior connection unless it happened to be the same
345
- if previous_shard && shard != previous_shard && !previous_shard.database_server.shareable?
346
- previous_shard.activate do
347
- ::Shackles.activated_environments.each do |env|
348
- ::Shackles.activate(env) do
349
- if ::ActiveRecord::Base.connected? && ::ActiveRecord::Base.connection.open_transactions == 0
350
- ::ActiveRecord::Base.connection_pool.current_pool.disconnect!
351
- end
352
- end
353
- end
354
- end
355
- end
356
- end
357
-
358
- result = []
359
- exception = nil
360
- scope.each do |shard|
361
- # shard references a database server that isn't configured in this environment
362
- next unless shard.database_server
363
- close_connections_if_needed.call(shard)
364
- shard.activate(*categories) do
365
- begin
366
- result.concat Array.wrap(yield)
367
- rescue
368
- case options[:exception]
369
- when :ignore
370
- when :defer
371
- exception ||= $!
372
- when Proc
373
- options[:exception].call
374
- when :raise
375
- raise
376
- else
377
- raise
378
- end
379
- end
380
- end
381
- previous_shard = shard
382
- end
383
- close_connections_if_needed.call(Shard.current)
384
- raise exception if exception
385
- result
386
- end
387
-
388
- def partition_by_shard(array, partition_proc = nil)
389
- shard_arrays = {}
390
- array.each do |object|
391
- partition_object = partition_proc ? partition_proc.call(object) : object
392
- case partition_object
393
- when Shard
394
- shard = partition_object
395
- when ::ActiveRecord::Base
396
- if partition_object.respond_to?(:associated_shards)
397
- partition_object.associated_shards.each do |a_shard|
398
- shard_arrays[a_shard] ||= []
399
- shard_arrays[a_shard] << object
400
- end
401
- next
402
- else
403
- shard = partition_object.shard
404
- end
405
- when Integer, /^\d+$/, /^(\d+)~(\d+)$/
406
- local_id, shard = Shard.local_id_for(partition_object)
407
- local_id ||= partition_object
408
- object = local_id if !partition_proc
409
- end
410
- shard ||= Shard.current
411
- shard_arrays[shard] ||= []
412
- shard_arrays[shard] << object
413
- end
414
- # TODO: use with_each_shard (or vice versa) to get
415
- # connection management and parallelism benefits
416
- shard_arrays.inject([]) do |results, (shard, objects)|
417
- results.concat shard.activate { Array.wrap(yield objects) }
418
- end
419
- end
420
-
421
- # converts an AR object, integral id, string id, or string short-global-id to a
422
- # integral id. nil if it can't be interpreted
423
- def integral_id_for(any_id)
424
- if any_id.is_a?(::Arel::Nodes::Casted)
425
- any_id = any_id.val
426
- elsif any_id.is_a?(::Arel::Nodes::BindParam) && ::Rails.version >= "5.2"
427
- any_id = any_id.value.value_before_type_cast
428
- end
429
-
430
- case any_id
431
- when ::ActiveRecord::Base
432
- any_id.id
433
- when /^(\d+)~(\d+)$/
434
- local_id = $2.to_i
435
- # doesn't make sense to have a double-global id
436
- return nil if local_id > IDS_PER_SHARD
437
- $1.to_i * IDS_PER_SHARD + local_id
438
- when Integer, /^\d+$/
439
- any_id.to_i
440
- else
441
- nil
442
- end
443
- end
444
-
445
- # takes an id-ish, and returns a local id and the shard it's
446
- # local to. [nil, nil] if it can't be interpreted. [id, nil]
447
- # if it's already a local ID. [nil, nil] if it's a well formed
448
- # id, but the shard it refers to does not exist
449
- NIL_NIL_ID = [nil, nil].freeze
450
- def local_id_for(any_id)
451
- id = integral_id_for(any_id)
452
- return NIL_NIL_ID unless id
453
- if id < IDS_PER_SHARD
454
- [id, nil]
455
- elsif shard = lookup(id / IDS_PER_SHARD)
456
- [id % IDS_PER_SHARD, shard]
457
- else
458
- NIL_NIL_ID
459
- end
460
- end
461
-
462
- # takes an id-ish, and returns an integral id relative to
463
- # target_shard. returns nil if it can't be interpreted,
464
- # or the integral version of the id if it refers to a shard
465
- # that does not exist
466
- def relative_id_for(any_id, source_shard, target_shard)
467
- integral_id = integral_id_for(any_id)
468
- local_id, shard = local_id_for(integral_id)
469
- return integral_id unless local_id
470
- shard ||= source_shard
471
- return local_id if shard == target_shard
472
- shard.global_id_for(local_id)
473
- end
474
-
475
- # takes an id-ish, and returns a shortened global
476
- # string id if global, and itself if local.
477
- # returns any_id itself if it can't be interpreted
478
- def short_id_for(any_id)
479
- local_id, shard = local_id_for(any_id)
480
- return any_id unless local_id
481
- return local_id unless shard
482
- "#{shard.id}~#{local_id}"
483
- end
484
-
485
- # takes an id-ish, and returns an integral global id.
486
- # returns nil if it can't be interpreted
487
- def global_id_for(any_id, source_shard = nil)
488
- id = integral_id_for(any_id)
489
- return any_id unless id
490
- if id >= IDS_PER_SHARD
491
- id
492
- else
493
- source_shard ||= Shard.current
494
- source_shard.global_id_for(id)
495
- end
496
- end
497
-
498
- def shard_for(any_id, source_shard = nil)
499
- return any_id.shard if any_id.is_a?(::ActiveRecord::Base)
500
- _, shard = local_id_for(any_id)
501
- shard || source_shard || Shard.current
502
- end
503
-
504
- # given the provided option, determines whether we need to (and whether
505
- # it's possible) to determine a reasonable default.
506
- def determine_max_procs(max_procs_input, parallel_input=2)
507
- max_procs = nil
508
- if max_procs_input
509
- max_procs = max_procs_input.to_i
510
- max_procs = nil if max_procs == 0
511
- else
512
- return 1 if parallel_input.nil? || parallel_input < 1
513
- cpus = Environment.cpu_count
514
- if cpus && cpus > 0
515
- max_procs = cpus * parallel_input
516
- end
517
- end
518
-
519
- return max_procs
520
- end
521
-
522
- private
523
- # in-process caching
524
- def cached_shards
525
- @cached_shards ||= {}.compare_by_identity
526
- end
527
-
528
- def add_to_cache(shard)
529
- cached_shards[shard.id] = shard
530
- end
531
-
532
- def remove_from_cache(shard)
533
- cached_shards.delete(shard.id)
534
- end
535
-
536
- def find_cached(key)
537
- # can't simply cache the AR object since Shard has a custom serializer
538
- # that calls this method
539
- attributes = Switchman.cache.fetch(key) { yield&.attributes }
540
- return nil unless attributes
541
-
542
- shard = Shard.new
543
- attributes.each do |attr, value|
544
- shard.send(:"#{attr}=", value) if shard.respond_to?(:"#{attr}=")
545
- end
546
- shard.clear_changes_information
547
- shard.instance_variable_set(:@new_record, false)
548
- # connection info doesn't exist in database.yml;
549
- # pretend the shard doesn't exist either
550
- shard = nil unless shard.database_server
551
- shard
552
- end
553
-
554
- def active_shards
555
- Thread.current[:active_shards] ||= {}.compare_by_identity
556
- end
557
- end
558
-
559
- def name
560
- unless instance_variable_defined?(:@name)
561
- # protect against re-entrancy
562
- @name = nil
563
- @name = read_attribute(:name) || default_name
564
- end
565
- @name
566
- end
567
-
568
- def name=(name)
569
- write_attribute(:name, @name = name)
570
- remove_instance_variable(:@name) if name == nil
571
- end
572
-
573
- def database_server
574
- @database_server ||= DatabaseServer.find(self.database_server_id)
575
- end
576
-
577
- def database_server=(database_server)
578
- self.database_server_id = database_server.id
579
- @database_server = database_server
580
- end
581
-
582
- def primary?
583
- self == database_server.primary_shard
584
- end
585
-
586
- def description
587
- [database_server.id, name].compact.join(':')
588
- end
589
-
590
- # Shards are always on the default shard
591
- def shard
592
- Shard.default
593
- end
594
-
595
- def activate(*categories)
596
- shards = hashify_categories(categories)
597
- Shard.activate(shards) do
598
- yield
599
- end
600
- end
601
-
602
- # for use from console ONLY
603
- def activate!(*categories)
604
- shards = hashify_categories(categories)
605
- Shard.activate!(shards)
606
- nil
607
- end
608
-
609
- # custom serialization, since shard is self-referential
610
- def _dump(depth)
611
- self.id.to_s
612
- end
613
-
614
- def self._load(str)
615
- lookup(str.to_i)
616
- end
617
-
618
- def drop_database
619
- raise("Cannot drop the database of the default shard") if self.default?
620
- return unless read_attribute(:name)
621
-
622
- begin
623
- adapter = self.database_server.config[:adapter]
624
- sharding_config = Switchman.config || {}
625
- drop_statement = sharding_config[adapter]&.[](:drop_statement)
626
- drop_statement ||= sharding_config[:drop_statement]
627
- if drop_statement
628
- drop_statement = Array(drop_statement).dup.
629
- map { |statement| statement.gsub('%{name}', self.name) }
630
- end
631
-
632
- case adapter
633
- when 'mysql', 'mysql2'
634
- self.activate do
635
- ::Shackles.activate(:deploy) do
636
- drop_statement ||= "DROP DATABASE #{self.name}"
637
- Array(drop_statement).each do |stmt|
638
- ::ActiveRecord::Base.connection.execute(stmt)
639
- end
640
- end
641
- end
642
- when 'postgresql'
643
- self.activate do
644
- ::Shackles.activate(:deploy) do
645
- # Shut up, Postgres!
646
- conn = ::ActiveRecord::Base.connection
647
- old_proc = conn.raw_connection.set_notice_processor {}
648
- begin
649
- drop_statement ||= "DROP SCHEMA #{self.name} CASCADE"
650
- Array(drop_statement).each do |stmt|
651
- ::ActiveRecord::Base.connection.execute(stmt)
652
- end
653
- ensure
654
- conn.raw_connection.set_notice_processor(&old_proc) if old_proc
655
- end
656
- end
657
- end
658
- end
659
- rescue
660
- logger.info "Drop failed: #{$!}"
661
- end
662
- end
663
-
664
- # takes an id local to this shard, and returns a global id
665
- def global_id_for(local_id)
666
- return nil unless local_id
667
- local_id + self.id * IDS_PER_SHARD
668
- end
669
-
670
- # skip global_id.hash
671
- def hash
672
- id.hash
673
- end
674
-
675
- def destroy
676
- raise("Cannot destroy the default shard") if self.default?
677
- super
678
- end
679
-
680
- private
681
-
682
- def clear_cache
683
- Shard.default.activate do
684
- Switchman.cache.delete(['shard', id].join('/'))
685
- Switchman.cache.delete("default_shard") if default?
686
- end
687
- end
688
-
689
- def default_name
690
- database_server.shard_name(self)
691
- end
692
-
693
- def on_rollback
694
- # make sure all connection pool proxies are referencing valid pools
695
- ::ActiveRecord::Base.connection_handler.connection_pools.each do |pool|
696
- next unless pool.is_a?(ConnectionPoolProxy)
697
- ::Shackles.activated_environments.each do |env|
698
- ::Shackles.activate(env) do
699
- pool.current_pool
700
- end
701
- end
702
- end
703
- end
704
-
705
- def hashify_categories(categories)
706
- if categories.empty?
707
- { :primary => self }
708
- else
709
- categories.inject({}) { |h, category| h[category] = self; h }
710
- end
711
- end
712
-
713
- end
714
- end