gizzmo 0.11.2 → 0.11.3

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.11.2
1
+ 0.11.3
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{gizzmo}
8
- s.version = "0.11.2"
8
+ s.version = "0.11.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kyle Maxwell"]
12
- s.date = %q{2011-01-06}
12
+ s.date = %q{2011-01-26}
13
13
  s.description = %q{Gizzmo is a command-line client for managing gizzard clusters.}
14
14
  s.email = %q{kmaxwell@twitter.com}
15
15
  s.executables = ["gizzmo", "setup_shards"]
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  "lib/gizzard/digest.rb",
32
32
  "lib/gizzard/migrator.rb",
33
33
  "lib/gizzard/nameserver.rb",
34
+ "lib/gizzard/rebalancer.rb",
34
35
  "lib/gizzard/shard_template.rb",
35
36
  "lib/gizzard/thrift.rb",
36
37
  "lib/gizzard/transformation.rb",
@@ -76,7 +77,7 @@ Gem::Specification.new do |s|
76
77
  s.homepage = %q{http://github.com/twitter/gizzmo}
77
78
  s.rdoc_options = ["--charset=UTF-8"]
78
79
  s.require_paths = ["lib"]
79
- s.rubygems_version = %q{1.3.6}
80
+ s.rubygems_version = %q{1.3.7}
80
81
  s.summary = %q{Gizzmo is a command-line client for managing gizzard clusters.}
81
82
  s.test_files = [
82
83
  "test/gizzmo_spec.rb",
@@ -85,6 +86,9 @@ Gem::Specification.new do |s|
85
86
  "test/scheduler_spec.rb",
86
87
  "test/shard_template_spec.rb",
87
88
  "test/spec_helper.rb",
89
+ "test/test_server/target/gen-rb/test_server.rb",
90
+ "test/test_server/target/gen-rb/test_server_constants.rb",
91
+ "test/test_server/target/gen-rb/test_server_types.rb",
88
92
  "test/transformation_spec.rb"
89
93
  ]
90
94
 
@@ -92,7 +96,7 @@ Gem::Specification.new do |s|
92
96
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
93
97
  s.specification_version = 3
94
98
 
95
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
99
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
96
100
  else
97
101
  end
98
102
  else
@@ -7,3 +7,4 @@ require "gizzard/shard_template"
7
7
  require "gizzard/migrator"
8
8
  require "gizzard/transformation"
9
9
  require "gizzard/digest"
10
+ require "gizzard/rebalancer"
@@ -317,28 +317,6 @@ module Gizzard
317
317
  end
318
318
  end
319
319
 
320
- class RepairCommand < Command
321
- def run
322
- args = @argv.dup.map{|a| a.split(/\s+/)}.flatten
323
- pairs = []
324
- loop do
325
- a = args.shift
326
- b = args.shift
327
- break unless a && b
328
- pairs << [a, b]
329
- end
330
- pairs.each do |master, slave|
331
- puts "#{master} #{slave}"
332
- mprefixes = manager.shards_for_hostname(master).map{|s| s.id.table_prefix}
333
- sprefixes = manager.shards_for_hostname(slave).map{|s| s.id.table_prefix}
334
- delta = mprefixes - sprefixes
335
- delta.each do |prefix|
336
- puts "gizzmo copy #{master}/#{prefix} #{slave}/#{prefix}"
337
- end
338
- end
339
- end
340
- end
341
-
342
320
  class WrapCommand < Command
343
321
  def self.derive_wrapper_shard_id(shard_info, wrapping_class_name)
344
322
  suffix = "_" + wrapping_class_name.split(".").last.downcase.gsub("shard", "")
@@ -366,84 +344,6 @@ module Gizzard
366
344
  end
367
345
  end
368
346
 
369
- class RebalanceCommand < Command
370
-
371
- class NamedArray < Array
372
- attr_reader :name
373
- def initialize(name)
374
- @name = name
375
- end
376
- end
377
-
378
- def run
379
- help! "No shards specified" if @argv.empty?
380
- shards = []
381
- command_options.write_only_shard ||= "com.twitter.gizzard.shards.WriteOnlyShard"
382
- additional_hosts = (command_options.hosts || "").split(/[\s,]+/)
383
- exclude_hosts = (command_options.exclude_hosts || "").split(/[\s,]+/)
384
- ids = @argv.map{|arg| ShardId.new(*arg.split("/")) rescue nil }.compact
385
- by_host = ids.inject({}) do |memo, id|
386
- memo[id.hostname] ||= NamedArray.new(id.hostname)
387
- memo[id.hostname] << id
388
- memo
389
- end
390
-
391
- additional_hosts.each do |host|
392
- by_host[host] ||= NamedArray.new(host)
393
- end
394
-
395
- exclude_hosts.each do |host|
396
- by_host[host] ||= NamedArray.new(host)
397
- end
398
-
399
- sets = by_host.values
400
- exclude_sets = exclude_hosts.map{|host| by_host[host]}
401
- target_sets = sets - exclude_sets
402
-
403
- exclude_sets.each do |set|
404
- while set.length > 0
405
- sorted = target_sets.sort_by{|s| s.length}
406
- shortest = sorted.first
407
- shortest.push set.pop
408
- end
409
- end
410
-
411
- exclude_sets.each do |set|
412
- while set.length > 0
413
- sorted = target_sets.sort_by{|s| s.length}
414
- shortest = sorted.first
415
- shortest.push set.pop
416
- end
417
- end
418
-
419
- begin
420
- sorted = target_sets.sort_by{|s| s.length }
421
- longest = sorted.last
422
- shortest = sorted.first
423
- shortest.push longest.pop
424
- end while longest.length > shortest.length + 1
425
-
426
- shard_info = nil
427
- sets.each do |set|
428
- host = set.name
429
- set.each do |id|
430
- if id.hostname != host
431
- shard_info ||= manager.get_shard(id)
432
- old = id.to_unix
433
- id.hostname = host
434
- shards << [old, id.to_unix]
435
- end
436
- end
437
- end
438
-
439
- new_shards = shards.map{|(old, new)| new }
440
-
441
- puts "gizzmo create #{shard_info.class_name} -s '#{shard_info.source_type}' -d '#{shard_info.destination_type}' #{new_shards.join(" ")}"
442
- puts "gizzmo wrap #{command_options.write_only_shard} #{new_shards.join(" ")}"
443
- shards.map { |(old, new)| puts "gizzmo copy #{old} #{new}" }
444
- end
445
- end
446
-
447
347
  class PairCommand < Command
448
348
  def run
449
349
  ids = []
@@ -791,17 +691,29 @@ module Gizzard
791
691
  end
792
692
  end
793
693
 
694
+ class TablesCommand < Command
695
+ def run
696
+ puts manager.list_tables.join(" ")
697
+ end
698
+ end
699
+
794
700
  class TopologyCommand < Command
795
701
  def run
796
- templates = manager.manifest(*global_options.tables).templates.inject({}) do |h, (t, fs)|
702
+ manifest = manager.manifest(*global_options.tables)
703
+ templates = manifest.templates.inject({}) do |h, (t, fs)|
797
704
  h.update t.to_config => fs
798
705
  end
799
706
 
800
707
  if command_options.forwardings
801
708
  templates.
802
- inject([]) { |h, (t, fs)| fs.each { |f| h << [f.base_id, t] }; h }.
709
+ inject([]) { |h, (t, fs)| fs.each { |f| h << [f.inspect, t] }; h }.
803
710
  sort.
804
- each { |a| puts "%25d\t%s" % a }
711
+ each { |a| puts "%s\t%s" % a }
712
+ elsif command_options.root_shards
713
+ templates.
714
+ inject([]) { |a, (t, fs)| fs.each { |f| a << [f.shard_id.inspect, t] }; a }.
715
+ sort.
716
+ each { |a| puts "%s\t%s" % a }
805
717
  else
806
718
  templates.
807
719
  map { |(t, fs)| [fs.length, t] }.
@@ -830,6 +742,11 @@ module Gizzard
830
742
 
831
743
  scheduler_options[:quiet] = be_quiet
832
744
 
745
+ if transformation.noop?
746
+ puts "Nothing to do!"
747
+ exit
748
+ end
749
+
833
750
  unless be_quiet
834
751
  puts transformation.inspect
835
752
  puts ""
@@ -869,6 +786,70 @@ module Gizzard
869
786
  transformations[transformation] = trees
870
787
  end
871
788
 
789
+ transformations.reject! {|t,trees| t.noop? or trees.empty? }
790
+
791
+ if transformations.empty?
792
+ puts "Nothing to do!"
793
+ exit
794
+ end
795
+
796
+ base_name = transformations.values.first.values.first.id.table_prefix.split('_').first
797
+
798
+ unless be_quiet
799
+ transformations.sort.each do |transformation, trees|
800
+ puts transformation.inspect
801
+ puts "Applied to #{trees.length} shards:"
802
+ trees.keys.sort.each {|f| puts " #{f.inspect}" }
803
+ end
804
+ puts ""
805
+ end
806
+
807
+ unless global_options.force
808
+ print "Continue? (y/n) "; $stdout.flush
809
+ exit unless $stdin.gets.chomp == "y"
810
+ puts ""
811
+ end
812
+
813
+ Gizzard.schedule! manager,
814
+ base_name,
815
+ transformations,
816
+ scheduler_options
817
+ end
818
+ end
819
+
820
+ class RebalanceCommand < Command
821
+ def run
822
+ help!("must have an even number of arguments") unless @argv.length % 2 == 0
823
+
824
+ scheduler_options = command_options.scheduler_options || {}
825
+ manifest = manager.manifest(*global_options.tables)
826
+ copy_wrapper = scheduler_options[:copy_wrapper]
827
+ be_quiet = global_options.force && command_options.quiet
828
+ transformations = {}
829
+
830
+ scheduler_options[:quiet] = be_quiet
831
+
832
+ dest_templates_and_weights = {}
833
+
834
+ @argv.each_slice(2) do |(weight_s, to_template_s)|
835
+ to = ShardTemplate.parse(to_template_s)
836
+ weight = weight_s.to_i
837
+
838
+ dest_templates_and_weights[to] = weight
839
+ end
840
+
841
+ transformations = global_options.tables.inject({}) do |all, table|
842
+ trees = manifest.trees.reject {|(f, s)| f.table_id != table }
843
+ rebalancer = Rebalancer.new(trees, dest_templates_and_weights, copy_wrapper)
844
+
845
+ all.update(rebalancer.transformations) {|t,a,b| a.merge b }
846
+ end
847
+
848
+ if transformations.empty?
849
+ puts "Nothing to do!"
850
+ exit
851
+ end
852
+
872
853
  base_name = transformations.values.first.values.first.id.table_prefix.split('_').first
873
854
 
874
855
  unless be_quiet
@@ -1,6 +1,17 @@
1
1
  module Gizzard
2
2
  Shard = Struct.new(:info, :children, :weight)
3
3
 
4
+ module ParallelMap
5
+ def parallel_map(enumerable, &block)
6
+ enumerable.map do |elem|
7
+ Thread.new { Thread.current[:result] = block.call(elem) }
8
+ end.map do |thread|
9
+ thread.join
10
+ thread[:result]
11
+ end
12
+ end
13
+ end
14
+
4
15
  class Shard
5
16
  class << self
6
17
  def canonical_table_prefix(enum, table_id = nil, base_prefix = "shard")
@@ -75,6 +86,7 @@ module Gizzard
75
86
  end
76
87
 
77
88
  class Nameserver
89
+ include ParallelMap
78
90
 
79
91
  DEFAULT_PORT = 7917
80
92
  DEFAULT_RETRIES = 20
@@ -96,8 +108,16 @@ module Gizzard
96
108
  ids.map {|id| with_retry { client.get_shard(id) } }
97
109
  end
98
110
 
111
+ def reload_updated_forwardings
112
+ parallel_map all_clients do |c|
113
+ with_retry { c.reload_updated_forwardings }
114
+ end
115
+ end
116
+
99
117
  def reload_config
100
- all_clients.each {|c| with_retry { c.reload_config } }
118
+ parallel_map all_clients do |c|
119
+ with_retry { c.reload_config }
120
+ end
101
121
  end
102
122
 
103
123
  def copy_shard(from_shard_id, to_shard_id)
@@ -154,11 +174,12 @@ module Gizzard
154
174
  end
155
175
 
156
176
  class Manifest
157
- attr_reader :forwardings, :links, :shard_infos, :trees, :templates
177
+ include ParallelMap
158
178
 
179
+ attr_reader :forwardings, :links, :shard_infos, :trees, :templates
159
180
 
160
181
  def initialize(nameserver, table_ids)
161
- states = table_ids.map {|id| nameserver.dump_nameserver(id) }
182
+ states = nameserver.dump_nameserver(table_ids)
162
183
 
163
184
  @forwardings = states.map {|s| s.forwardings }.flatten
164
185
 
@@ -0,0 +1,125 @@
1
+ require 'set'
2
+
3
+ module Gizzard
4
+ class Rebalancer
5
+ TemplateAndTree = Struct.new(:template, :forwarding, :tree)
6
+ Bucket = Struct.new(:template, :approx_shards, :set)
7
+
8
+ class Bucket
9
+ def balance; set.length - approx_shards end
10
+ def add(e); set.add(e) end
11
+ def merge(es); set.merge(es) end
12
+ def delete(e); set.delete(e) end
13
+ end
14
+
15
+ # steps for rebalancing.
16
+ #
17
+ # 1. get a list of forwarding/template associations
18
+ # 2. get a list of destination templates and weights
19
+ # 3. order shards by weight. (ascending or descending)
20
+ # 4. put shards in destinations based on reducing number of copies required.
21
+ # 5.
22
+
23
+ def initialize(forwardings_to_trees, dest_templates_and_weights, wrapper)
24
+ @copy_dest_wrapper = wrapper
25
+ @shards = forwardings_to_trees.map do |forwarding, tree|
26
+ TemplateAndTree.new(tree.template, forwarding, tree)
27
+ end.flatten
28
+
29
+ @dest_templates = dest_templates_and_weights.keys
30
+
31
+ total_shards = @shards.length
32
+ total_weight = dest_templates_and_weights.values.inject {|a,b| a + b }
33
+
34
+ @result = dest_templates_and_weights.map do |template, weight|
35
+ weight_fraction = weight / total_weight.to_f
36
+ approx_shards = total_shards * weight_fraction
37
+
38
+ Bucket.new template, approx_shards, Set.new
39
+ end
40
+ end
41
+
42
+ def home!
43
+
44
+ # list of [template, shards] in descending length of shards
45
+ templates_to_shards =
46
+ @shards.inject({}) do |h, shard|
47
+ (h[shard.template] ||= []) << shard; h
48
+ end.sort_by {|(_,ss)| ss.length * -1 }
49
+
50
+ templates_to_shards.each do |(template, shards)|
51
+ descendants = memoized_concrete_descendants(template)
52
+
53
+ most_similar_buckets = []
54
+ last_cost = nil
55
+
56
+ @result.each do |bucket|
57
+ cost = (memoized_concrete_descendants(bucket.template) - descendants).length
58
+ last_cost = cost if last_cost.nil?
59
+
60
+ if cost == last_cost
61
+ most_similar_buckets << bucket
62
+ elsif cost < last_cost
63
+ last_cost = cost
64
+ most_similar_buckets = [bucket]
65
+ end
66
+ end
67
+
68
+ dest_bucket = most_similar_buckets.sort_by {|b| b.balance }.first
69
+
70
+ dest_bucket.merge shards
71
+ end
72
+ end
73
+
74
+ def rebalance!
75
+ while bucket_disparity > 1
76
+ ordered = ordered_buckets
77
+ move_shard ordered.first.template, ordered.last.set.each {|e| break e }
78
+ end
79
+ end
80
+
81
+ def transformations
82
+ return @transformations if @transformations
83
+
84
+ home!
85
+ rebalance!
86
+
87
+ @transformations = {}
88
+ @result.each do |bucket|
89
+ bucket.set.each do |shard|
90
+ trans = Transformation.new(shard.template, bucket.template, @copy_dest_wrapper)
91
+ forwardings_to_trees = (@transformations[trans] ||= {})
92
+
93
+ forwardings_to_trees.update(shard.forwarding => shard.tree)
94
+ end
95
+ end
96
+
97
+ @transformations.reject! {|t, _| t.noop? }
98
+ @transformations
99
+ end
100
+
101
+ def ordered_buckets
102
+ @result.sort_by {|bucket| bucket.balance }
103
+ end
104
+
105
+ def memoized_concrete_descendants(t)
106
+ @memoized_concrete_descendants ||= {}
107
+ @memoized_concrete_descendants[t] ||= t.concrete_descendants
108
+ end
109
+
110
+ def bucket_disparity
111
+ ordered = ordered_buckets
112
+ ordered.last.balance - ordered.first.balance
113
+ end
114
+
115
+ def move_shard(template, shard)
116
+ @result.each do |bucket|
117
+ if bucket.template == template
118
+ bucket.add shard
119
+ else
120
+ bucket.delete shard
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end