Skip to content

Commit e5bbc19

Browse files
author
tbchrist
committed
Merge pull request #24 from tumblr/0.10.0
0.10.0
2 parents 67315bc + 77929d7 commit e5bbc19

31 files changed

+555
-146
lines changed

bin/jetpants

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,13 @@ module Jetpants
268268
end
269269
end
270270

271-
spares_needed.map do |role, needed|
271+
spares_needed.each do |role, needed|
272272
next if needed == 0
273273
available = Jetpants.topology.count_spares(role: "#{role}_slave".to_sym, like: compare)
274274
raise "Not enough spare machines with role of #{role} slave! Requested #{needed} but only have #{available} available." if needed > available
275275
end
276276

277-
spares_needed.map do |role, needed|
277+
spares_needed.each do |role, needed|
278278
next if needed == 0
279279
targets.concat Jetpants.topology.claim_spares(needed, role: "#{role}_slave".to_sym, like: compare)
280280
end
@@ -407,10 +407,17 @@ module Jetpants
407407
method_option :max_id, :desc => 'Maximum ID of parent shard to split'
408408
method_option :ranges, :desc => 'Optional comma-separated list of ranges per child ie "1000-1999,2000-2499" (default if omitted: split evenly)'
409409
method_option :count, :desc => 'How many child shards to split the parent into (only necessary if the ranges option is omitted)'
410+
method_option :shard_pool, :desc => 'The sharding pool for which to perform the split'
410411
def shard_split
411412
shard_min = options[:min_id] || ask('Please enter min ID of the parent shard: ')
412413
shard_max = options[:max_id] || ask('Please enter max ID of the parent shard: ')
413-
s = Jetpants.topology.shard shard_min, shard_max
414+
415+
shard_pool = options[:shard_pool] || ask("Please enter the sharding pool for which to perform the split (enter for default pool, #{Jetpants.topology.default_shard_pool}): ")
416+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
417+
418+
output "Using shard pool `#{shard_pool}`"
419+
420+
s = Jetpants.topology.shard(shard_min, shard_max, shard_pool)
414421

415422
raise "Shard not found" unless s
416423
raise "Shard isn't in ready state" unless s.state == :ready
@@ -503,10 +510,14 @@ module Jetpants
503510

504511
desc 'shard_cutover', 'truncate the current last shard range, and add a new shard after it'
505512
method_option :cutover_id, :desc => 'Minimum ID of new last shard being created'
513+
method_option :shard_pool, :desc => 'The sharding pool for which to perform the cutover'
506514
def shard_cutover
507515
cutover_id = options[:cutover_id] || ask('Please enter min ID of the new shard to be created: ')
508516
cutover_id = cutover_id.to_i
509-
last_shard = Jetpants.topology.shards.select {|s| s.max_id == 'INFINITY' && s.in_config?}.first
517+
shard_pool = options[:shard_pool] || ask("Please enter the sharding pool for which to perform the split (enter for default pool, #{Jetpants.topology.default_shard_pool}): ")
518+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
519+
520+
last_shard = Jetpants.topology.shards(shard_pool).select {|s| s.max_id == 'INFINITY' && s.in_config?}.first
510521
last_shard_master = last_shard.master
511522

512523
# Simple sanity-check that the cutover ID is greater than the current last shard's MIN id.
@@ -528,7 +539,7 @@ module Jetpants
528539
# has the same master/slaves but now has a non-infinity max ID.
529540
last_shard.state = :recycle
530541
last_shard.sync_configuration
531-
last_shard_replace = Shard.new(last_shard.min_id, cutover_id - 1, last_shard_master)
542+
last_shard_replace = Shard.new(last_shard.min_id, cutover_id - 1, last_shard_master, :ready, shard_pool)
532543
last_shard_replace.sync_configuration
533544
Jetpants.topology.add_pool last_shard_replace
534545

@@ -557,12 +568,12 @@ module Jetpants
557568
end
558569
end
559570

560-
new_last_shard = Shard.new(cutover_id, 'INFINITY', new_last_shard_master)
571+
new_last_shard = Shard.new(cutover_id, 'INFINITY', new_last_shard_master, :ready, shard_pool)
561572
new_last_shard.sync_configuration
562573
Jetpants.topology.add_pool new_last_shard
563574

564575
# Create tables on the new shard's master, obtaining schema from previous last shard
565-
tables = Table.from_config 'sharded_tables'
576+
tables = Table.from_config('sharded_tables', shard_pool)
566577
last_shard_master.export_schemata tables
567578
last_shard_master.host.fast_copy_chain(Jetpants.export_location, new_last_shard_master.host, files: ["create_tables_#{last_shard_master.port}.sql"])
568579
new_last_shard_master.import_schemata!
@@ -581,9 +592,12 @@ module Jetpants
581592
method_option :min_id, :desc => 'Minimum ID of shard involved in master promotion'
582593
method_option :max_id, :desc => 'Maximum ID of shard involved in master promotion'
583594
method_option :new_master, :desc => 'New node to become master of the shard'
595+
method_option :shard_pool, :desc => 'The sharding pool for which to perform the promotion'
584596
def shard_promote_master
597+
shard_pool = options[:shard_pool] || ask("Please enter the sharding pool for which to perform the master promotion (enter for default, #{Jetpants.topology.default_shard_pool}): ")
598+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
585599
# find the shard we are going to do master promotion on
586-
s = ask_shard_being_promoted(:prep, options[:max_id], options[:max_id])
600+
s = ask_shard_being_promoted(:prep, options[:max_id], options[:max_id], shard_pool)
587601

588602
new_master = ask_node "Please enter the IP of the new master for #{s}: ", options[:new_master]
589603
raise "New master node #{new_master} is not currently a slave in shard #{s}" unless s.slaves && s.slaves.include?(new_master)
@@ -610,9 +624,12 @@ module Jetpants
610624
desc 'shard_promote_master_reads', 'Lockless shard master promotion (step 2 of 4): move reads to new master'
611625
method_option :min_id, :desc => 'Minimum ID of shard involved in master promotion'
612626
method_option :max_id, :desc => 'Maximum ID of shard involved in master promotion'
627+
method_option :shard_pool, :desc => 'The sharding pool for which to perform the promotion'
613628
def shard_promote_master_reads
629+
shard_pool = options[:shard_pool] || ask("Please enter the sharding pool for which to perform the master promotion (enter for default pool, #{Jetpants.topology.default_shard_pool}): ")
630+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
614631
# find the shard we are going to do master promotion on
615-
s = ask_shard_being_promoted(:prep, options[:max_id], options[:max_id])
632+
s = ask_shard_being_promoted(:prep, options[:max_id], options[:max_id], shard_pool)
616633

617634
# at this point we only have one slave, which is the new master
618635
new_master = s.master.slaves.last
@@ -637,8 +654,11 @@ module Jetpants
637654
end
638655

639656
desc 'shard_promote_master_writes', 'Lockless shard master promotion (step 3 of 4): move writes to new master'
657+
method_option :shard_pool, :desc => 'The sharding pool for which to perform the promotion'
640658
def shard_promote_master_writes
641-
s = ask_shard_being_promoted :writes
659+
shard_pool = options[:shard_pool] || ask("Please enter the sharding pool for which to perform the master promotion (enter for default pool, #{Jetpants.topology.default_shard_pool}): ")
660+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
661+
s = ask_shard_being_promoted(:writes, nil, nil, shard_pool)
642662
if s.state != :child
643663
raise "Shard #{s} is in wrong state to perform this action! Expected :child, found #{s.state}"
644664
end
@@ -659,8 +679,11 @@ module Jetpants
659679
end
660680

661681
desc 'shard_promote_master_cleanup', 'Lockless shard master promotion (step 4 of 4): clean up shard and eject old master'
682+
method_option :shard_pool, :desc => 'The sharding pool for which to perform the promotion'
662683
def shard_promote_master_cleanup
663-
s = ask_shard_being_promoted :cleanup
684+
shard_pool = options[:shard_pool] || ask("Please enter the sharding pool for which to perform the master promotion (enter for default pool, #{Jetpants.topology.default_shard_pool}): ")
685+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
686+
s = ask_shard_being_promoted(:cleanup, nil, nil, shard_pool)
664687
if s.state != :needs_cleanup
665688
raise "Shard #{s} is in wrong state to perform this action! Expected :needs_cleanup, found #{s.state}"
666689
end
@@ -794,13 +817,17 @@ module Jetpants
794817
output 'Which shard would you like to perform this action on?'
795818
shard_min = ask('Please enter min ID of the shard: ')
796819
shard_max = ask('Please enter max ID of the shard: ')
797-
s = Jetpants.topology.shard shard_min, shard_max
820+
shard_pool = ask("Please enter the sharding pool which to perform the action on (enter for default pool, #{Jetpants.topology.default_shard_pool}): ")
821+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
822+
s = Jetpants.topology.shard(shard_min, shard_max, shard_pool)
798823
raise 'Shard not found' unless s
799824
s
800825
end
801826

802827
def ask_shard_being_split
803-
shards_being_split = Jetpants.shards.select {|s| s.children.count > 0}
828+
shard_pool = ask("Enter shard pool to take action on (enter for default pool, #{Jetpants.topology.default_shard_pool}):")
829+
shard_pool = Jetpants.topology.default_shard_pool if shard_pool.empty?
830+
shards_being_split = Jetpants.shards(shard_pool).select {|s| s.children.count > 0}
804831
if shards_being_split.count == 0
805832
raise 'No shards are currently being split. You can only use this task after running "jetpants shard_split".'
806833
elsif shards_being_split.count == 1
@@ -814,9 +841,9 @@ module Jetpants
814841
s
815842
end
816843

817-
def ask_shard_being_promoted(stage = :prep, min_id = nil, max_id = nil)
844+
def ask_shard_being_promoted(stage = :prep, min_id = nil, max_id = nil, shard_pool)
818845
if stage == :writes || stage == :cleanup
819-
shards_being_promoted = Jetpants.shards.select do |s|
846+
shards_being_promoted = Jetpants.shards(shard_pool).select do |s|
820847
[:reads, :child, :needs_cleanup].include?(s.state) && !s.parent && s.master.master
821848
end
822849

@@ -836,7 +863,7 @@ module Jetpants
836863
max_id = ask("Enter max id of shard to perform master promotion: ")
837864
max_id = Integer(max_id) rescue max_id.upcase
838865

839-
s = Jetpants.topology.shard(min_id, max_id)
866+
s = Jetpants.topology.shard(min_id, max_id, shard_pool)
840867
end
841868
raise "Invalid shard selected!" unless s.is_a? Shard
842869

lib/jetpants.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
module Jetpants; end
1010

1111
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'jetpants'), File.join(File.dirname(__FILE__), '..', 'plugins')
12-
%w(output callback table host db pool topology shard monkeypatch commandsuite).each {|g| require g}
12+
%w(output callback table host db pool topology shard shardpool monkeypatch commandsuite).each {|g| require g}
1313

1414
# Since Jetpants is extremely multi-threaded, we need to force uncaught exceptions to
1515
# kill all threads in order to have any kind of sane error handling.
@@ -37,7 +37,7 @@ module Jetpants
3737
'verify_replication' => true, # raise exception if the 2 repl threads are in different states, or if actual repl topology differs from Jetpants' understanding of it
3838
'plugins' => {}, # hash of plugin name => arbitrary plugin data (usually a nested hash of settings)
3939
'ssh_keys' => nil, # array of SSH key file locations
40-
'sharded_tables' => [], # array of name => {sharding_key=>X, chunks=>Y} hashes
40+
'sharded_tables' => [], # hash of {shard_pool => {name => {sharding_key=>X, chunks=>Y}} hashes
4141
'compress_with' => false, # command line to use for compression in large file transfers
4242
'decompress_with' => false, # command line to use for decompression in large file transfers
4343
'private_interface' => 'bond0', # network interface corresponding to private IP
@@ -48,6 +48,14 @@ module Jetpants
4848
'log_file' => '/var/log/jetpants.log', # where to log all output from the jetpants commands
4949
'local_private_interface' => nil, # local network interface corresponding to private IP of the machine jetpants is running on
5050
'free_mem_min_mb' => 0, # Minimum amount of free memory in MB to be maintained on the node while performing the task (eg. network copy)
51+
'default_shard_pool' => nil, # default pool for sharding operations
52+
'import_without_indices' => false,
53+
'ssl_ca_path' => '/var/lib/mysql/ca.pem',
54+
'ssl_client_cert_path' => '/var/lib/mysql/client-cert.pem',
55+
'ssl_client_key_path' => '/var/lib/mysql/client-key.pem',
56+
'encrypt_with' => false, # command line stream encryption binary
57+
'decrypt_with' => false, # command line stream decryption binary
58+
'encrypt_file_transfers' => false # flag to use stream encryption
5159
}
5260

5361
config_paths = ["/etc/jetpants.yaml", "~/.jetpants.yml", "~/.jetpants.yaml"]
@@ -156,5 +164,6 @@ def with_retries(retries = nil, max_retry_backoff = nil)
156164

157165
# Finally, initialize topology object
158166
@topology = Topology.new
167+
@topology.load_shard_pools unless @config['lazy_load_pools']
159168
@topology.load_pools unless @config['lazy_load_pools']
160169
end

lib/jetpants/db/import_export.rb

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def rebuild!(tables=false, min_id=false, max_id=false)
274274

275275
p = pool
276276
if p.is_a?(Shard)
277-
tables ||= Table.from_config 'sharded_tables'
277+
tables ||= Table.from_config('sharded_tables', p.shard_pool.name)
278278
min_id ||= p.min_id
279279
max_id ||= p.max_id if p.max_id != 'INFINITY'
280280
end
@@ -307,8 +307,40 @@ def rebuild!(tables=false, min_id=false, max_id=false)
307307
export_schemata tables
308308
export_data tables, min_id, max_id
309309
import_schemata!
310-
alter_schemata if respond_to? :alter_schemata
310+
if respond_to? :alter_schemata
311+
alter_schemata
312+
# re-retrieve table metadata in the case that we alter the tables
313+
pool.probe_tables
314+
tables = pool.tables.select{|t| pool.tables.map(&:name).include?(t.name)}
315+
end
316+
317+
index_list = {}
318+
db_prefix = "USE #{app_schema};"
319+
320+
if Jetpants.import_without_indices
321+
tables.each do |t|
322+
index_list[t] = t.indexes
323+
324+
t.indexes.each do |index_name, index_info|
325+
drop_idx_cmd = t.drop_index_query(index_name)
326+
output "Dropping index #{index_name} from #{t.name} prior to import"
327+
mysql_root_cmd("#{db_prefix}#{drop_idx_cmd}")
328+
end
329+
end
330+
end
331+
311332
import_data tables, min_id, max_id
333+
334+
if Jetpants.import_without_indices
335+
index_list.each do |table, indexes|
336+
next if indexes.keys.empty?
337+
338+
create_idx_cmd = table.create_index_query(indexes)
339+
index_names = indexes.keys.join(", ")
340+
output "Recreating indexes #{index_names} for #{table.name} after import"
341+
mysql_root_cmd("#{db_prefix}#{create_idx_cmd}")
342+
end
343+
end
312344

313345
restart_mysql
314346
catch_up_to_master if is_slave?
@@ -345,8 +377,9 @@ def clone_to!(*targets)
345377
targets.concurrent_each {|t| t.ssh_cmd "rm -rf #{t.mysql_directory}/ib_logfile*"}
346378

347379
files = (databases + ['ibdata1', app_schema]).uniq
380+
files += ['*.tokudb', 'tokudb.*', 'log*.tokulog*'] if ssh_cmd("test -f #{mysql_directory}/tokudb.environment 2>/dev/null; echo $?").chomp.to_i == 0
348381
files << 'ib_lru_dump' if ssh_cmd("test -f #{mysql_directory}/ib_lru_dump 2>/dev/null; echo $?").chomp.to_i == 0
349-
382+
350383
fast_copy_chain(mysql_directory, destinations, :port => 3306, :files => files, :overwrite => true)
351384
clone_settings_to!(*targets)
352385

lib/jetpants/db/replication.rb

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,39 @@ def change_master_to(new_master, option_hash={})
2828

2929
repl_user = option_hash[:user] || replication_credentials[:user]
3030
repl_pass = option_hash[:password] || replication_credentials[:pass]
31+
use_ssl = new_master.use_ssl_replication? && use_ssl_replication?
3132

3233
pause_replication if @master && !@repl_paused
33-
result = mysql_root_cmd "CHANGE MASTER TO " +
34+
cmd_str = "CHANGE MASTER TO " +
3435
"MASTER_HOST='#{new_master.ip}', " +
3536
"MASTER_PORT=#{new_master.port}, " +
3637
"MASTER_LOG_FILE='#{logfile}', " +
3738
"MASTER_LOG_POS=#{pos}, " +
3839
"MASTER_USER='#{repl_user}', " +
3940
"MASTER_PASSWORD='#{repl_pass}'"
40-
41-
output "Changing master to #{new_master} with coordinates (#{logfile}, #{pos}). #{result}"
41+
42+
if use_ssl
43+
ssl_ca_path = option_hash[:ssl_ca_path] || Jetpants.ssl_ca_path
44+
ssl_client_cert_path = option_hash[:ssl_client_cert_path] || Jetpants.ssl_client_cert_path
45+
ssl_client_key_path = option_hash[:ssl_client_key_path] || Jetpants.ssl_client_key_path
46+
47+
cmd_str += ", MASTER_SSL=1"
48+
cmd_str += ", MASTER_SSL_CA='#{ssl_ca_path}'" if ssl_ca_path
49+
50+
if ssl_client_cert_path && ssl_client_key_path
51+
cmd_str +=
52+
", MASTER_SSL_CERT='#{ssl_client_cert_path}', " +
53+
"MASTER_SSL_KEY='#{ssl_client_key_path}'"
54+
end
55+
end
56+
57+
result = mysql_root_cmd cmd_str
58+
59+
msg = "Changing master to #{new_master}"
60+
msg += " using SSL" if use_ssl
61+
msg += " with coordinates (#{logfile}, #{pos}). #{result}"
62+
output msg
63+
4264
@master.slaves.delete(self) if @master rescue nil
4365
@master = new_master
4466
@repl_paused = true

lib/jetpants/db/schema.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ def detect_table_schema(table_name)
2424
'columns' => connection.schema(table_name).map{|schema| schema[0]}
2525
}
2626

27+
if pool.is_a? Shard
28+
config_params = Jetpants.send('sharded_tables')[pool.shard_pool.name.downcase]
29+
30+
unless(config_params[table_name].nil?)
31+
params.merge!(config_params[table_name])
32+
end
33+
end
34+
2735
Table.new(table_name, params)
2836
end
2937

lib/jetpants/db/server.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def stop_mysql
1111
output "Attempting to shutdown MySQL"
1212
disconnect if @db
1313
output service(:stop, 'mysql')
14-
running = ssh_cmd "netstat -ln | grep ':#{@port}' | wc -l"
14+
running = ssh_cmd "netstat -ln | grep \":#{@port}\\s\" | wc -l"
1515
raise "[#{@ip}] Failed to shut down MySQL: Something is still listening on port #{@port}" unless running.chomp == '0'
1616
@options = []
1717
@running = false

lib/jetpants/db/state.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,11 @@ def avg_buffer_pool_hit_rate
363363
((buffer_pool_hit_rate.split[4].to_f * 100) / buffer_pool_hit_rate.split[6].to_f).round(2)
364364
end
365365

366+
# Determine whether a server should use ssl as a replication source
367+
def use_ssl_replication?
368+
global_variables[:have_ssl] && global_variables[:have_ssl].downcase == "yes"
369+
end
370+
366371
###### Private methods #####################################################
367372

368373
private

0 commit comments

Comments
 (0)