gizzmo 0.11.2 → 0.11.3
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.
- data/VERSION +1 -1
- data/gizzmo.gemspec +8 -4
- data/lib/gizzard.rb +1 -0
- data/lib/gizzard/commands.rb +84 -103
- data/lib/gizzard/nameserver.rb +24 -3
- data/lib/gizzard/rebalancer.rb +125 -0
- data/lib/gizzard/thrift.rb +4 -2
- data/lib/gizzard/transformation.rb +36 -5
- data/lib/gizzard/transformation_scheduler.rb +19 -16
- data/lib/gizzmo.rb +20 -20
- data/test/gizzmo_spec.rb +251 -130
- data/test/nameserver_spec.rb +1 -1
- data/test/scheduler_spec.rb +10 -10
- data/test/spec_helper.rb +26 -5
- data/test/test_server/project/build/Project.scala +1 -1
- data/test/test_server/project/plugins/Plugins.scala +1 -1
- data/test/transformation_spec.rb +8 -0
- metadata +16 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.11.
|
1
|
+
0.11.3
|
data/gizzmo.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{gizzmo}
|
8
|
-
s.version = "0.11.
|
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-
|
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.
|
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::
|
99
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
96
100
|
else
|
97
101
|
end
|
98
102
|
else
|
data/lib/gizzard.rb
CHANGED
data/lib/gizzard/commands.rb
CHANGED
@@ -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
|
-
|
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.
|
709
|
+
inject([]) { |h, (t, fs)| fs.each { |f| h << [f.inspect, t] }; h }.
|
803
710
|
sort.
|
804
|
-
each { |a| puts "%
|
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
|
data/lib/gizzard/nameserver.rb
CHANGED
@@ -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
|
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
|
-
|
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 =
|
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
|