jetpants 0.7.10 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +7 -3
- data/bin/jetpants +67 -33
- data/doc/commands.rdoc +2 -0
- data/doc/configuration.rdoc +3 -2
- data/doc/jetpants_collins.rdoc +1 -1
- data/doc/plugins.rdoc +4 -0
- data/doc/requirements.rdoc +1 -1
- data/lib/jetpants.rb +21 -4
- data/lib/jetpants/db.rb +4 -0
- data/lib/jetpants/db/client.rb +10 -4
- data/lib/jetpants/db/import_export.rb +28 -11
- data/lib/jetpants/db/privileges.rb +59 -10
- data/lib/jetpants/db/replication.rb +69 -20
- data/lib/jetpants/db/server.rb +41 -19
- data/lib/jetpants/db/state.rb +99 -8
- data/lib/jetpants/host.rb +33 -35
- data/lib/jetpants/monkeypatch.rb +9 -0
- data/lib/jetpants/pool.rb +14 -7
- data/lib/jetpants/shard.rb +10 -12
- data/lib/jetpants/table.rb +4 -0
- data/lib/jetpants/topology.rb +28 -9
- data/plugins/jetpants_collins/db.rb +12 -0
- data/plugins/jetpants_collins/jetpants_collins.rb +34 -7
- data/plugins/jetpants_collins/pool.rb +6 -2
- data/plugins/jetpants_collins/topology.rb +44 -6
- data/plugins/simple_tracker/topology.rb +1 -0
- metadata +33 -29
data/README.rdoc
CHANGED
@@ -76,12 +76,16 @@ If you have a question that isn't covered here, please feel free to email the au
|
|
76
76
|
|
77
77
|
== CREDITS:
|
78
78
|
|
79
|
-
* <b>Evan Elias</b>: Lead developer
|
80
|
-
* <b>Dallas Marlow</b>:
|
79
|
+
* <b>Evan Elias</b>: Lead developer
|
80
|
+
* <b>Dallas Marlow</b>: Developer
|
81
|
+
* <b>Bob Patterson Jr</b>: Developer
|
82
|
+
* <b>Tom Christ</b>: Developer
|
83
|
+
|
84
|
+
Special thanks to <b>Tim Ellis</b> for testing and bug reports.
|
81
85
|
|
82
86
|
== LICENSE:
|
83
87
|
|
84
|
-
Copyright
|
88
|
+
Copyright 2013 Tumblr, Inc.
|
85
89
|
|
86
90
|
Licensed under the Apache License, Version 2.0 (the "License");
|
87
91
|
you may not use this file except in compliance with the License.
|
data/bin/jetpants
CHANGED
@@ -4,8 +4,6 @@
|
|
4
4
|
# module in a Thor command processor, providing a command-line interface to
|
5
5
|
# common Jetpants functionality.
|
6
6
|
|
7
|
-
jetpants_base_dir = File.expand_path(File.dirname(__FILE__) + '/..')
|
8
|
-
$:.unshift File.join(jetpants_base_dir, 'lib')
|
9
7
|
%w[thor pry highline/import colored].each {|g| require g}
|
10
8
|
|
11
9
|
module Jetpants
|
@@ -109,7 +107,7 @@ module Jetpants
|
|
109
107
|
|
110
108
|
error "Unable to determine a node to demote and a node to promote" unless demoted.kind_of?(Jetpants::DB) && promoted.kind_of?(Jetpants::DB)
|
111
109
|
error "Node to promote #{promoted} is not a slave of node to demote #{demoted}" unless promoted.master == demoted
|
112
|
-
error "
|
110
|
+
error "The chosen node cannot be promoted. Please choose another." unless promoted.promotable_to_master?
|
113
111
|
|
114
112
|
inform "Going to DEMOTE existing master #{demoted} and PROMOTE new master #{promoted}."
|
115
113
|
error "Aborting." unless agree "Proceed? [yes/no]: "
|
@@ -124,12 +122,20 @@ module Jetpants
|
|
124
122
|
|
125
123
|
|
126
124
|
desc 'summary', 'display information about a node in the context of its pool'
|
127
|
-
method_option :node, :desc => 'IP address of node to query'
|
125
|
+
method_option :node, :desc => 'IP address of node to query, or name of pool'
|
128
126
|
def summary
|
129
|
-
node =
|
130
|
-
node
|
131
|
-
|
132
|
-
|
127
|
+
node = options[:node] || ask('Please enter node IP or name of pool: ')
|
128
|
+
if is_ip? node
|
129
|
+
node = node.to_db
|
130
|
+
node.pool(true).probe
|
131
|
+
describe node
|
132
|
+
node.pool(true).summary(true)
|
133
|
+
else
|
134
|
+
pool = Jetpants.topology.pool(node)
|
135
|
+
raise "#{node} is neither an IP address nor a pool name" unless pool
|
136
|
+
pool.probe
|
137
|
+
pool.summary(true)
|
138
|
+
end
|
133
139
|
end
|
134
140
|
|
135
141
|
|
@@ -140,7 +146,7 @@ module Jetpants
|
|
140
146
|
Jetpants.pools.each &:summary
|
141
147
|
|
142
148
|
# We could do this more compactly using DB#role, but it would incorrectly
|
143
|
-
#
|
149
|
+
# double-count nodes involved in a shard split
|
144
150
|
counts = {master: 0, active_slave: 0, standby_slave: 0, backup_slave: 0}
|
145
151
|
Jetpants.pools.each do |p|
|
146
152
|
counts[:master] += 1
|
@@ -170,10 +176,10 @@ module Jetpants
|
|
170
176
|
def pools_compact
|
171
177
|
puts
|
172
178
|
Jetpants.shards.each do |s|
|
173
|
-
puts "[%-15s] %8s to %-11s = %
|
179
|
+
puts "[%-15s] %8s to %-11s = %4s GB" % [s.ip, s.min_id, s.max_id, s.data_set_size(true)]
|
174
180
|
end
|
175
181
|
Jetpants.functional_partitions.each do |p|
|
176
|
-
puts "[%-15s] %-23s = %
|
182
|
+
puts "[%-15s] %-23s = %4s GB" % [p.ip, p.name, p.data_set_size(true)]
|
177
183
|
end
|
178
184
|
puts
|
179
185
|
end
|
@@ -187,17 +193,32 @@ module Jetpants
|
|
187
193
|
|
188
194
|
desc 'clone_slave', 'clone a standby slave'
|
189
195
|
method_option :source, :desc => 'IP of node to clone from'
|
190
|
-
method_option :target, :desc => 'IP of node to clone to'
|
196
|
+
method_option :target, :desc => 'IP of node(s) to clone to'
|
191
197
|
def clone_slave
|
192
198
|
puts "This task clones the data set of a standby slave."
|
193
199
|
source = ask_node('Please enter IP of node to clone from: ', options[:source])
|
200
|
+
source.master.probe if source.master # fail early if there are any replication issues in this pool
|
194
201
|
describe source
|
195
202
|
|
196
|
-
|
203
|
+
puts "You may clone to particular IP address(es), or can type \"spare\" to claim a node from the spare pool."
|
204
|
+
target = options[:target] || ask('Please enter comma-separated list of targets (IPs or "spare") to clone to: ')
|
205
|
+
target = 'spare' if target.strip == '' || target.split(',').length == 0
|
206
|
+
spares_needed = target.split(',').count {|t| t.strip.upcase == 'SPARE'}
|
207
|
+
if spares_needed > 0
|
208
|
+
spares_available = Jetpants.topology.count_spares(role: :standby_slave, like: source)
|
209
|
+
raise "Not enough spare machines with role of standby slave! Requested #{spares_needed} but only have #{spares_available} available." if spares_needed > spares_available
|
210
|
+
claimed_spares = Jetpants.topology.claim_spares(spares_needed, role: :standby_slave, like: source)
|
211
|
+
end
|
212
|
+
|
197
213
|
targets = target.split(',').map do |ip|
|
198
214
|
ip.strip!
|
199
|
-
|
200
|
-
|
215
|
+
if is_ip? ip
|
216
|
+
ip.to_db
|
217
|
+
elsif ip == '' || ip.upcase == 'SPARE'
|
218
|
+
claimed_spares.shift
|
219
|
+
else
|
220
|
+
error "target (#{ip}) does not appear to be an IP."
|
221
|
+
end
|
201
222
|
end
|
202
223
|
|
203
224
|
source.start_mysql if ! source.running?
|
@@ -205,6 +226,7 @@ module Jetpants
|
|
205
226
|
|
206
227
|
targets.each do |t|
|
207
228
|
error "target #{t} already has a master; please clear out node (including in asset tracker) before proceeding" if t.master
|
229
|
+
error "target #{t} is running a different version of MySQL than source #{source}! Cannot proceed with clone operation." if t.version_cmp(source) != 0
|
208
230
|
end
|
209
231
|
|
210
232
|
source.enslave_siblings!(targets)
|
@@ -213,11 +235,6 @@ module Jetpants
|
|
213
235
|
puts "Cloning complete."
|
214
236
|
Jetpants.topology.write_config
|
215
237
|
end
|
216
|
-
def self.after_clone_slave
|
217
|
-
reminders(
|
218
|
-
'Add the new host(s) to trending and monitoring.'
|
219
|
-
)
|
220
|
-
end
|
221
238
|
|
222
239
|
|
223
240
|
desc 'activate_slave', 'turn a standby slave into an active slave'
|
@@ -256,18 +273,26 @@ module Jetpants
|
|
256
273
|
desc 'destroy_slave', 'remove a standby slave from its pool'
|
257
274
|
method_option :node, :desc => 'IP of standby slave to remove'
|
258
275
|
def destroy_slave
|
276
|
+
# Permit slaves with broken replication to be destroyed
|
277
|
+
Jetpants.verify_replication = false
|
259
278
|
puts "This task removes a standby/backup slave from its pool entirely. THIS IS PERMANENT, ie, it does a RESET SLAVE on the target."
|
260
279
|
node = ask_node('Please enter node IP: ', options[:node])
|
261
280
|
describe node
|
262
|
-
|
281
|
+
if node.running? && node.available?
|
282
|
+
raise "Node is not a standby or backup slave" unless (node.is_standby? || node.for_backups?)
|
283
|
+
else
|
284
|
+
puts "Please note that we cannot run a RESET SLAVE on the node, because MySQL is not running or is otherwise unreachable."
|
285
|
+
puts "If the node is not permanently dead, you will have to run this manually."
|
286
|
+
end
|
263
287
|
raise "Aborting" unless ask('Please type YES in all capital letters to confirm removing node from its pool: ') == 'YES'
|
264
288
|
node.pool.remove_slave!(node)
|
289
|
+
node.revoke_all_access!
|
265
290
|
end
|
266
291
|
|
267
292
|
|
268
293
|
desc 'defrag_slave', 'export and re-import data set on a standby slave'
|
269
294
|
method_option :node, :desc => 'IP of standby slave to defragment'
|
270
|
-
def
|
295
|
+
def defrag_slave
|
271
296
|
puts "This task exports all data on a standby/backup slave and then re-imports it."
|
272
297
|
node = ask_node('Please enter node IP: ', options[:node])
|
273
298
|
describe node
|
@@ -358,7 +383,6 @@ module Jetpants
|
|
358
383
|
end
|
359
384
|
def self.after_shard_split
|
360
385
|
reminders(
|
361
|
-
'Trending and monitoring setup, as needed.',
|
362
386
|
'Proceed to next step: jetpants shard_split_child_reads'
|
363
387
|
)
|
364
388
|
end
|
@@ -415,16 +439,25 @@ module Jetpants
|
|
415
439
|
desc 'shard_cutover', 'truncate the current last shard range, and add a new shard after it'
|
416
440
|
method_option :cutover_id, :desc => 'Minimum ID of new last shard being created'
|
417
441
|
def shard_cutover
|
418
|
-
# ensure the spares are available before beginning
|
419
|
-
raise "Not enough total spare machines!" unless Jetpants.topology.count_spares >= Jetpants.standby_slaves_per_pool + 1
|
420
|
-
raise "Not enough standby_slave role spare machines!" unless Jetpants.topology.count_spares(role: 'standby_slave') >= Jetpants.standby_slaves_per_pool
|
421
|
-
raise "Cannot find a spare master-role machine!" unless Jetpants.topology.count_spares(role: 'master') >= 1
|
422
|
-
|
423
442
|
cutover_id = options[:cutover_id] || ask('Please enter min ID of the new shard to be created: ')
|
424
443
|
cutover_id = cutover_id.to_i
|
425
444
|
last_shard = Jetpants.topology.shards.select {|s| s.max_id == 'INFINITY' && s.in_config?}.first
|
426
445
|
last_shard_master = last_shard.master
|
427
446
|
|
447
|
+
# Simple sanity-check that the cutover ID is greater than the current last shard's MIN id.
|
448
|
+
# (in a later release, this may be improved to also ensure it's greater than any sharding
|
449
|
+
# key value in use on the last shard.)
|
450
|
+
raise "Specified cutover ID is too low!" unless cutover_id > last_shard.min_id
|
451
|
+
|
452
|
+
# Ensure enough spare nodes are available before beginning.
|
453
|
+
# We supply the *previous* last shard as context for counting/claiming spares
|
454
|
+
# because the new last shard can't be created yet (chicken-and-egg problem -- master
|
455
|
+
# must exist before we create the pool). The assumption is the hardware spec
|
456
|
+
# of the new last shard and previous last shard will be the same.
|
457
|
+
raise "Not enough total spare machines!" unless Jetpants.topology.count_spares(like: last_shard_master) >= Jetpants.standby_slaves_per_pool + 1
|
458
|
+
raise "Not enough standby_slave role spare machines!" unless Jetpants.topology.count_spares(role: :standby_slave, like: last_shard_master) >= Jetpants.standby_slaves_per_pool
|
459
|
+
raise "Cannot find a spare master-role machine!" unless Jetpants.topology.count_spares(role: :master, like: last_shard_master) >= 1
|
460
|
+
|
428
461
|
# In asset tracker, remove the last shard pool and replace it with a new pool. The new pool
|
429
462
|
# has the same master/slaves but now has a non-infinity max ID.
|
430
463
|
last_shard.state = :recycle
|
@@ -433,11 +466,13 @@ module Jetpants
|
|
433
466
|
last_shard_replace.sync_configuration
|
434
467
|
Jetpants.topology.pools << last_shard_replace
|
435
468
|
|
436
|
-
# Now put another new shard after that one
|
437
|
-
|
438
|
-
|
469
|
+
# Now put another new shard after that one. (See earlier comment as to why we're supplying
|
470
|
+
# the pool from the old last shard. This param is just used to select the right type of hardware,
|
471
|
+
# NOT to actually set the pool of the returned object.)
|
472
|
+
new_last_shard_master = Jetpants.topology.claim_spare(role: :master, like: last_shard_master)
|
473
|
+
new_last_shard_master.disable_read_only! if new_last_shard_master.running?
|
439
474
|
if Jetpants.standby_slaves_per_pool > 0
|
440
|
-
new_last_shard_slaves = Jetpants.topology.claim_spares(Jetpants.standby_slaves_per_pool, role:
|
475
|
+
new_last_shard_slaves = Jetpants.topology.claim_spares(Jetpants.standby_slaves_per_pool, role: :standby_slave, like: last_shard_master)
|
441
476
|
new_last_shard_slaves.each do |x|
|
442
477
|
x.change_master_to new_last_shard_master
|
443
478
|
x.resume_replication
|
@@ -458,7 +493,6 @@ module Jetpants
|
|
458
493
|
end
|
459
494
|
def self.after_shard_cutover
|
460
495
|
reminders(
|
461
|
-
'Trending and monitoring setup, as needed.',
|
462
496
|
'Commit/push the configuration in version control.',
|
463
497
|
'Deploy the configuration to all machines.',
|
464
498
|
)
|
data/doc/commands.rdoc
CHANGED
@@ -16,6 +16,8 @@ The copy method in \Jetpants uses a combination of tar, netcat (nc), and whichev
|
|
16
16
|
|
17
17
|
This command does not require an asset tracker plugin, but DOES require that all your database nodes have installed whichever compression binary you specified in the Jetpants config file.
|
18
18
|
|
19
|
+
If you are using an asset tracker, when prompted for which nodes to clone TO, you may type "spare" (or equivalently just hit ENTER without typing any input) to claim a spare node with role STANDBY_SLAVE. You may clone to multiple spares at once by supplying comma-separated input like "spare, spare, spare". You can mix-and-match with supplying IP addresses of particular hosts as well.
|
20
|
+
|
19
21
|
|
20
22
|
== Master/slave state changes
|
21
23
|
|
data/doc/configuration.rdoc
CHANGED
@@ -4,13 +4,13 @@
|
|
4
4
|
|
5
5
|
At least one of these files must exist for \Jetpants to function properly.
|
6
6
|
|
7
|
-
If both exist, the user configuration will be merged on top of the global configuration,
|
7
|
+
If both exist, the user configuration will be merged on top of the global configuration, this is a "deep" merge. So if the user a "plugins" section this will be combined with the global "plugins" section.
|
8
8
|
|
9
9
|
For an example global configuration file, please see the included <tt>etc/jetpants.yaml.sample</tt> file.
|
10
10
|
|
11
11
|
== Configuration settings
|
12
12
|
|
13
|
-
max_concurrency:: Maximum threads to use in import/export operations, or equivalently the maximum connection pool size per database host (default:
|
13
|
+
max_concurrency:: Maximum threads to use in import/export operations, or equivalently the maximum connection pool size per database host. You will need to tune this to your specific database hardware, especially number of CPU cores and number/type of disks (default: 20)
|
14
14
|
standby_slaves_per_pool:: Minimum number of standby slaves to keep in every pool (default: 2)
|
15
15
|
mysql_schema:: database name (mandatory)
|
16
16
|
mysql_app_user:: mysql user that your application uses (mandatory)
|
@@ -24,6 +24,7 @@ compress_with:: command line to perform compression during large fil
|
|
24
24
|
decompress_with:: command line to perform decompression during large file copy operations; see below (default: false)
|
25
25
|
export_location:: directory to use for data dumping (default: '/tmp')
|
26
26
|
verify_replication:: raise exception if the actual replication topology differs from Jetpants' understanding of it (ie, disagreement between asset tracker and probed state), or if MySQL's two replication threads are in different states (one running and the other stopped) on a DB node. (default: true. master promotion tool ignores this, since the demoted master may legitimately be dead/offline)
|
27
|
+
private_interface:: name of private interface on your servers, such as eth0 or bond0. Not used by any core \Jetpants commands, but can be useful in plugings that wrap tcpdump, or code calling the Jetpants::Host.local method. (default: 'bond0')
|
27
28
|
plugins:: hash of plugin name => arbitrary plugin data, usually a nested hash of settings (default: \{})
|
28
29
|
ssh_keys:: array of SSH private key file locations, if not using standard id_dsa or id_rsa. Passed directly to Net::SSH.start's :keys parameter (default: nil)
|
29
30
|
sharded_tables:: array of name => \{sharding_key=>X, chunks=>Y} hashes, describing all tables on shards. Required by shard split/rebuild processes (default: \[])
|
data/doc/jetpants_collins.rdoc
CHANGED
@@ -38,7 +38,7 @@ This plugin also makes some assumptions about the way in which you use \Collins,
|
|
38
38
|
* All MySQL database server hosts that are in-use will have a STATUS of either ALLOCATED or MAINTENANCE.
|
39
39
|
* All MySQL database server hosts that are in-use will have a POOL set matching the name of their pool/shard, and a SECONDARY_ROLE set matching their \Jetpants role within the pool (MASTER, ACTIVE_SLAVE, STANDBY_SLAVE, or BACKUP_SLAVE).
|
40
40
|
* You can initially assign PRIMARY_ROLE, STATUS, POOL, and SECONDARY_ROLE to database servers somewhat automatically; see GETTING STARTED, below.
|
41
|
-
* All database server hosts that are "spares" (not yet in use, but ready for use in shard splits, shard cutover, or slave cloning) need to have a STATUS of PROVISIONED. These nodes must meet the requirements of spares as defined by the REQUIREMENTS doc that comes with \Jetpants. They should NOT have a POOL set
|
41
|
+
* All database server hosts that are "spares" (not yet in use, but ready for use in shard splits, shard cutover, or slave cloning) need to have a STATUS of PROVISIONED. These nodes must meet the requirements of spares as defined by the REQUIREMENTS doc that comes with \Jetpants. They should NOT have a POOL or SECONDARY_ROLE set in advance; if they do, it will be ignored -- we treat all spares as identical. That said, you can implement custom logic to respect POOL or SECONDARY_ROLE (or any other Collins attribute) by overriding Topology#process_spare_selector_options in a custom plugin loaded after jetpants_collins.
|
42
42
|
* Database server hosts may optionally have an attribute called SLAVE_WEIGHT. The default weight, if omitted, is 100. This field has no effect in \Jetpants, but can be used by your custom configuration generator as needed, if your application supports a notion of different weights for slave selection.
|
43
43
|
* Arbitrary metadata regarding pools and shards will be stored in assets with a TYPE of CONFIGURATION. These assets will have a POOL matching the pool's name, a TAG matching the pool's name but prefixed with 'mysql-', a STATUS reflecting the pool's state, and a PRIMARY_ROLE of either MYSQL_POOL or MYSQL_SHARD depending on the type of pool. You can make jetpants_collins create these automatically; see GETTING STARTED, below.
|
44
44
|
|
data/doc/plugins.rdoc
CHANGED
@@ -56,6 +56,10 @@ You may also want to override or implement these, though it's not strictly manda
|
|
56
56
|
* Jetpants::Pool#after_remove_slave!
|
57
57
|
* If your implementation of Jetpants::Pool#sync_configuration works by iterating over the current slaves of the pool's master, it won't correctly handle the <tt>jetpants destroy_slave</tt> command,
|
58
58
|
or anything else that calls Jetpants::DB#disable_replication! which does a RESET SLAVE. You can instead handle that special case here.
|
59
|
+
* Jetpants::DB#stop_query_kiler and Jetpants::DB#start_query_killer
|
60
|
+
* If you use a query killer, you'll want to override these methods to start and stop it. stop_query_killer is called prior to a long-running query (SELECT ... INTO OUTFILE, LOAD DATA INFILE, SELECT COUNT(*), etc) and start_query_killer is called after the operation is complete.
|
61
|
+
* Jetpants::DB#disable_monitoring and Jetpants::DB#enable_monitoring
|
62
|
+
* If you use monitoring software, you can override these methods to prevent alerts from \Jetpants operations. \Jetpants calls disable_monitoring prior to performing maintenance operations on a node, such as stopping MySQL prior to cloning a slave, splitting a shard, etc. It calls enable_monitoring once the operation is complete.
|
59
63
|
|
60
64
|
== How to write a plugin
|
61
65
|
|
data/doc/requirements.rdoc
CHANGED
@@ -14,7 +14,7 @@ Plugins may freely override these assumptions, and upstream patches are very wel
|
|
14
14
|
* For example, \jetpants requires the mysql2 gem, which in turn needs MySQL development C headers in order to compile.
|
15
15
|
* MySQL (or compatible, like Percona Server), version 5.1 or higher.
|
16
16
|
* Required Linux binaries that must be installed and in root's PATH on all of your database machines:
|
17
|
-
* <tt>service</tt>, a wrapper around init scripts, supporting syntax <tt>service mysql start</tt>, <tt>service mysql status</tt>, etc. Some distros include this by default (typically as /sbin/service or /usr/sbin/service) while others offer it as a package. Implementation varies slightly between distros; currently \Jetpants expects <tt>service mysql status</tt> output to include either "not running" (RHEL/Centos) or "stop/waiting" (Ubuntu) in the output if the MySQL server is not running.
|
17
|
+
* <tt>service</tt>, a wrapper around init scripts, supporting syntax <tt>service mysql start</tt>, <tt>service mysql status</tt>, etc. Some distros include this by default (typically as /sbin/service or /usr/sbin/service) while others offer it as a package. Implementation varies slightly between distros; currently \Jetpants expects <tt>service mysql status</tt> output to include either "not running" (RHEL/Centos) or "stop/waiting" (Ubuntu) in the output if the MySQL server is not running. Additionally, <tt>service</tt> must pass extra parameters through to the daemon -- <tt>service mysql start --skip-networking</tt> should pass <tt>--skip-networking</tt> to mysqld.
|
18
18
|
* <tt>nc</tt>, also known as netcat, a tool for piping data to or from a socket.
|
19
19
|
* Whichever compression tool you've specified in the \Jetpants config file for the <tt>compress_with</tt> and <tt>decompress_with</tt> options, if any. (if omitted, compression will not be used for file copy operations.)
|
20
20
|
* InnoDB / Percona XtraDB for storage engine. \Jetpants has not been tested with MyISAM, since \Jetpants is geared towards huge tables, and MyISAM is generally a bad fit.
|
data/lib/jetpants.rb
CHANGED
@@ -21,7 +21,7 @@ module Jetpants
|
|
21
21
|
# Establish default configuration values, and then merge in whatever we find globally
|
22
22
|
# in /etc/jetpants.yaml and per-user in ~/.jetpants.yaml
|
23
23
|
@config = {
|
24
|
-
'max_concurrency' =>
|
24
|
+
'max_concurrency' => 20, # max threads/conns per database
|
25
25
|
'standby_slaves_per_pool' => 2, # number of standby slaves in every pool
|
26
26
|
'mysql_schema' => 'test', # database name
|
27
27
|
'mysql_app_user' => 'appuser', # mysql user for application
|
@@ -38,10 +38,27 @@ module Jetpants
|
|
38
38
|
'sharded_tables' => [], # array of name => {sharding_key=>X, chunks=>Y} hashes
|
39
39
|
'compress_with' => false, # command line to use for compression in large file transfers
|
40
40
|
'decompress_with' => false, # command line to use for decompression in large file transfers
|
41
|
+
'private_interface' => 'bond0', # network interface corresponding to private IP
|
41
42
|
}
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
|
44
|
+
config_paths = ["/etc/jetpants.yaml", "~/.jetpants.yml", "~/.jetpants.yaml"]
|
45
|
+
config_loaded = false
|
46
|
+
|
47
|
+
config_paths.each do |path|
|
48
|
+
begin
|
49
|
+
overrides = YAML.load_file(File.expand_path path)
|
50
|
+
@config.deep_merge! overrides
|
51
|
+
config_loaded = true
|
52
|
+
rescue Errno::ENOENT => error
|
53
|
+
rescue ArgumentError => error
|
54
|
+
puts "YAML parsing error in configuration file #{path} : #{error.message}\n\n"
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
unless config_loaded
|
60
|
+
puts "Could not find any readable configuration files at either /etc/jetpants.yaml or ~/.jetpants.yaml\n\n"
|
61
|
+
exit
|
45
62
|
end
|
46
63
|
|
47
64
|
class << self
|
data/lib/jetpants/db.rb
CHANGED
@@ -66,6 +66,10 @@ module Jetpants
|
|
66
66
|
# These get set upon DB#connect being run
|
67
67
|
@user = nil
|
68
68
|
@schema = nil
|
69
|
+
|
70
|
+
# This is ephemeral, only known to Jetpants if you previously called
|
71
|
+
# DB#start_mysql or DB#restart_mysql in this process
|
72
|
+
@options = []
|
69
73
|
end
|
70
74
|
|
71
75
|
###### Host methods ########################################################
|
data/lib/jetpants/db/client.rb
CHANGED
@@ -41,14 +41,19 @@ module Jetpants
|
|
41
41
|
# Initializes (or re-initializes) the connection pool upon first use or upon
|
42
42
|
# requesting a different user or schema. Note that we only maintain one connection
|
43
43
|
# pool per DB.
|
44
|
-
# Valid options include :user, :pass, :schema, :max_conns or omit
|
45
|
-
# defaults.
|
44
|
+
# Valid options include :user, :pass, :schema, :max_conns, :after_connect or omit
|
45
|
+
# these to use defaults.
|
46
46
|
def connect(options={})
|
47
|
+
if @options.include? '--skip-networking'
|
48
|
+
output 'Skipping connection attempt because server started with --skip-networking'
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
47
52
|
options[:user] ||= app_credentials[:user]
|
48
53
|
options[:schema] ||= app_schema
|
49
54
|
|
50
55
|
return @db if @db && @user == options[:user] && @schema == options[:schema]
|
51
|
-
|
56
|
+
|
52
57
|
disconnect if @db
|
53
58
|
|
54
59
|
@db = Sequel.connect(
|
@@ -58,7 +63,8 @@ module Jetpants
|
|
58
63
|
:user => options[:user],
|
59
64
|
:password => options[:pass] || app_credentials[:pass],
|
60
65
|
:database => options[:schema],
|
61
|
-
:max_connections => options[:max_conns] || Jetpants.max_concurrency
|
66
|
+
:max_connections => options[:max_conns] || Jetpants.max_concurrency,
|
67
|
+
:after_connect => options[:after_connect] )
|
62
68
|
@user = options[:user]
|
63
69
|
@schema = options[:schema]
|
64
70
|
@db
|
@@ -101,14 +101,29 @@ module Jetpants
|
|
101
101
|
# run after export_data (in the same process), import_data will
|
102
102
|
# automatically confirm that the import counts match the previous export
|
103
103
|
# counts.
|
104
|
+
#
|
104
105
|
# Creates a 'jetpants' db user with FILE permissions for the duration of the
|
105
106
|
# import.
|
107
|
+
#
|
108
|
+
# Note: import will be substantially faster if you disable binary logging
|
109
|
+
# before the import, and re-enable it after the import. You also must set
|
110
|
+
# InnoDB's autoinc lock mode to 2 in order to do a chunked import with
|
111
|
+
# auto-increment tables. You can achieve all this by calling
|
112
|
+
# DB#restart_mysql '--skip-log-bin', '--skip-log-slave-updates', '--innodb-autoinc-lock-mode=2'
|
113
|
+
# prior to importing data, and then clear those settings by calling
|
114
|
+
# DB#restart_mysql with no params after done importing data.
|
106
115
|
def import_data(tables, min_id=false, max_id=false)
|
116
|
+
disable_read_only!
|
107
117
|
import_export_user = 'jetpants'
|
108
118
|
create_user(import_export_user)
|
109
119
|
grant_privileges(import_export_user) # standard privs
|
110
120
|
grant_privileges(import_export_user, '*', 'FILE') # FILE global privs
|
111
|
-
|
121
|
+
|
122
|
+
# Disable unique checks upon connecting. This has to be done at the :after_connect level in Sequel
|
123
|
+
# to guarantee it's being run on every connection in the conn pool. This is mysql2-specific.
|
124
|
+
disable_unique_checks_proc = Proc.new {|mysql2_client| mysql2_client.query 'SET unique_checks = 0'}
|
125
|
+
|
126
|
+
reconnect(user: import_export_user, after_connect: disable_unique_checks_proc)
|
112
127
|
|
113
128
|
import_counts = {}
|
114
129
|
tables.each {|t| import_counts[t.name] = import_table_data t, min_id, max_id}
|
@@ -258,9 +273,7 @@ module Jetpants
|
|
258
273
|
|
259
274
|
disable_monitoring
|
260
275
|
stop_query_killer
|
261
|
-
|
262
|
-
restart_mysql
|
263
|
-
pause_replication if is_slave?
|
276
|
+
restart_mysql '--skip-log-bin', '--skip-log-slave-updates', '--innodb-autoinc-lock-mode=2', '--skip-slave-start'
|
264
277
|
|
265
278
|
# Automatically detect missing min/max. Assumes that all tables' primary keys
|
266
279
|
# are on the same scale, so this may be non-ideal, but better than just erroring.
|
@@ -283,11 +296,9 @@ module Jetpants
|
|
283
296
|
import_schemata!
|
284
297
|
alter_schemata if respond_to? :alter_schemata
|
285
298
|
import_data tables, min_id, max_id
|
286
|
-
|
287
|
-
resume_replication if is_slave?
|
288
|
-
enable_binary_logging
|
299
|
+
|
289
300
|
restart_mysql
|
290
|
-
catch_up_to_master
|
301
|
+
catch_up_to_master if is_slave?
|
291
302
|
start_query_killer
|
292
303
|
enable_monitoring
|
293
304
|
end
|
@@ -303,15 +314,21 @@ module Jetpants
|
|
303
314
|
destinations = {}
|
304
315
|
targets.each do |t|
|
305
316
|
destinations[t] = t.mysql_directory
|
306
|
-
|
307
|
-
raise "Over 100 MB of existing MySQL data on target #{t}, aborting copy!" if existing_size > 100000000
|
317
|
+
raise "Over 100 MB of existing MySQL data on target #{t}, aborting copy!" if t.data_set_size > 100000000
|
308
318
|
end
|
309
319
|
[self, targets].flatten.concurrent_each {|t| t.stop_query_killer; t.stop_mysql}
|
310
320
|
targets.concurrent_each {|t| t.ssh_cmd "rm -rf #{t.mysql_directory}/ib_logfile*"}
|
321
|
+
|
322
|
+
# Construct the list of files and dirs to copy. We include ib_lru_dump if present
|
323
|
+
# (ie, if using Percona Server with innodb_buffer_pool_restore_at_startup enabled)
|
324
|
+
# since this will greatly improve warm-up time of the cloned nodes
|
325
|
+
files = ['ibdata1', 'mysql', 'test', app_schema]
|
326
|
+
files << 'ib_lru_dump' if ssh_cmd("test -f #{mysql_directory}/ib_lru_dump 2>/dev/null; echo $?").chomp.to_i == 0
|
327
|
+
|
311
328
|
fast_copy_chain(mysql_directory,
|
312
329
|
destinations,
|
313
330
|
port: 3306,
|
314
|
-
files:
|
331
|
+
files: files,
|
315
332
|
overwrite: true)
|
316
333
|
[self, targets].flatten.concurrent_each {|t| t.start_mysql; t.start_query_killer}
|
317
334
|
end
|