jetpants 0.7.6 → 0.7.8
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/jetpants +26 -9
- data/doc/commands.rdoc +1 -1
- data/doc/faq.rdoc +1 -1
- data/doc/plugins.rdoc +6 -26
- data/doc/requirements.rdoc +15 -6
- data/lib/jetpants.rb +4 -0
- data/lib/jetpants/db/privileges.rb +2 -2
- data/lib/jetpants/db/replication.rb +19 -2
- data/lib/jetpants/db/state.rb +19 -5
- data/lib/jetpants/host.rb +9 -5
- data/lib/jetpants/pool.rb +2 -1
- data/lib/jetpants/shard.rb +15 -8
- data/plugins/simple_tracker/commandsuite.rb +62 -0
- data/plugins/simple_tracker/shard.rb +10 -0
- data/plugins/simple_tracker/simple_tracker.rb +2 -2
- data/plugins/simple_tracker/topology.rb +3 -2
- metadata +3 -2
data/bin/jetpants
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This is the jetpants command suite toolkit executable. It wraps the Jetpants
|
4
|
+
# module in a Thor command processor, providing a command-line interface to
|
5
|
+
# common Jetpants functionality.
|
6
|
+
|
2
7
|
jetpants_base_dir = File.expand_path(File.dirname(__FILE__) + '/..')
|
3
8
|
$:.unshift File.join(jetpants_base_dir, 'lib')
|
4
9
|
%w[thor pry highline/import colored].each {|g| require g}
|
@@ -130,11 +135,19 @@ module Jetpants
|
|
130
135
|
|
131
136
|
desc 'pools', 'display a full summary of every pool in the topology'
|
132
137
|
def pools
|
133
|
-
Jetpants.
|
138
|
+
Jetpants.functional_partitions.concurrent_each &:probe
|
139
|
+
Jetpants.shards.reject {|s| s.parent}.concurrent_each &:probe
|
134
140
|
Jetpants.pools.each &:summary
|
135
141
|
|
142
|
+
# We could do this more compactly using DB#role, but it would incorrectly
|
143
|
+
# handle counts for shards in the middle of a split
|
136
144
|
counts = {master: 0, active_slave: 0, standby_slave: 0, backup_slave: 0}
|
137
|
-
Jetpants.pools.
|
145
|
+
Jetpants.pools.each do |p|
|
146
|
+
counts[:master] += 1
|
147
|
+
counts[:active_slave] += p.active_slaves.count
|
148
|
+
counts[:backup_slave] += p.backup_slaves.count
|
149
|
+
counts[:standby_slave] += p.standby_slaves.count
|
150
|
+
end
|
138
151
|
|
139
152
|
puts
|
140
153
|
puts "%4d global pools" % Jetpants.functional_partitions.count
|
@@ -252,14 +265,14 @@ module Jetpants
|
|
252
265
|
end
|
253
266
|
|
254
267
|
|
255
|
-
desc '
|
256
|
-
method_option :node, :desc => 'IP of standby slave to
|
268
|
+
desc 'defrag_slave', 'export and re-import data set on a standby slave'
|
269
|
+
method_option :node, :desc => 'IP of standby slave to defragment'
|
257
270
|
def rebuild_slave
|
258
271
|
puts "This task exports all data on a standby/backup slave and then re-imports it."
|
259
272
|
node = ask_node('Please enter node IP: ', options[:node])
|
260
273
|
describe node
|
261
274
|
raise "Node is not a standby or backup slave" unless (node.is_standby? || node.for_backups?)
|
262
|
-
raise "Cannot
|
275
|
+
raise "Cannot defragment non-shard slaves from command suite yet; use jetpants console instead" unless node.pool.is_a?(Shard)
|
263
276
|
node.rebuild!
|
264
277
|
end
|
265
278
|
|
@@ -312,6 +325,7 @@ module Jetpants
|
|
312
325
|
|
313
326
|
raise "Shard not found" unless s
|
314
327
|
raise "Shard isn't in ready state" unless s.state == :ready
|
328
|
+
raise "Cannot split the last shard (max ID of infinity), use \"jetpants shard_cutover\" instead" if s.max_id == 'INFINITY'
|
315
329
|
|
316
330
|
ranges = options[:ranges] || ask('Optionally enter comma-separated list of ranges per child ie "1000-1999,2000-2499" [default=even split]: ')
|
317
331
|
ranges = false if ranges.strip == ''
|
@@ -421,10 +435,13 @@ module Jetpants
|
|
421
435
|
|
422
436
|
# Now put another new shard after that one
|
423
437
|
new_last_shard_master = Jetpants.topology.claim_spare(role: 'master')
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
x
|
438
|
+
new_last_shard_master.disable_read_only! if (new_last_shard_master.running? && new_last_shard_master.read_only?)
|
439
|
+
if Jetpants.standby_slaves_per_pool > 0
|
440
|
+
new_last_shard_slaves = Jetpants.topology.claim_spares(Jetpants.standby_slaves_per_pool, role: 'standby_slave')
|
441
|
+
new_last_shard_slaves.each do |x|
|
442
|
+
x.change_master_to new_last_shard_master
|
443
|
+
x.resume_replication
|
444
|
+
end
|
428
445
|
end
|
429
446
|
new_last_shard = Shard.new(cutover_id, 'INFINITY', new_last_shard_master)
|
430
447
|
new_last_shard.sync_configuration
|
data/doc/commands.rdoc
CHANGED
@@ -114,7 +114,7 @@ These commands display status information about a particular node, pool, or the
|
|
114
114
|
|
115
115
|
== Miscellaneous
|
116
116
|
|
117
|
-
<b><tt>jetpants
|
117
|
+
<b><tt>jetpants defrag_slave</tt></b> exports all data on a standby slave, drops tables, recreates tables, and then re-imports the data. This is useful for defragmenting a node. Currently it only works on shard pool slaves; this may change in a future release.
|
118
118
|
|
119
119
|
<b><tt>jetpants regen_config</tt></b> regenerates your application's configuration file, assuming you are using an asset tracker. This isn't terribly useful in most cases, because any commands that make configuration changes will do this automatically. However, you may want to run this after manually making topology changes via <tt>jetpants console</tt>.
|
120
120
|
|
data/doc/faq.rdoc
CHANGED
@@ -55,7 +55,7 @@ Rebalancing range-based shards can be accomplished quickly as long as the primar
|
|
55
55
|
|
56
56
|
The main downside to the range-based approach is lack of even distribution of "hot" data. If a small handful of users on a given shard are using a disproportionate amount of resources, there's no way to move _only_ those users to a different shard. For this reason, range-based sharding can work best for "long-tail" sites where the majority of activity is created by the majority of common users.
|
57
57
|
|
58
|
-
|
58
|
+
\Jetpants only supports range-based sharding at this time, but for background, here are some alternative approaches:
|
59
59
|
|
60
60
|
* <b>Modulus or hash</b>: Apply a function to your sharding key to determine which shard the data lives on.
|
61
61
|
|
data/doc/plugins.rdoc
CHANGED
@@ -18,32 +18,12 @@ To use simple_tracker, be sure to define its <tt>tracker_data_file_path</tt> (wh
|
|
18
18
|
|
19
19
|
For an example of how to configure the plugin, please see the bottom of etc/jetpants.yaml.sample.
|
20
20
|
|
21
|
-
When you first start using simple_tracker, there will be no pools, shards, or spare machines.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# but they will all be assumed to be standby slaves by
|
28
|
-
# default. You have to tell Jetpants which ones are active
|
29
|
-
# slaves:
|
30
|
-
p.has_active_slave('10.45.67.226')
|
31
|
-
|
32
|
-
# save it
|
33
|
-
p.sync_configuration
|
34
|
-
|
35
|
-
# Create a shard + save it
|
36
|
-
s = Shard.new(1, 100000, '10.45.67.160', :ready)
|
37
|
-
s.sync_configuration
|
38
|
-
|
39
|
-
# add some spare machines
|
40
|
-
Jetpants.topology.tracker.spares << '10.45.67.130'
|
41
|
-
Jetpants.topology.tracker.spares << '10.45.67.132'
|
42
|
-
|
43
|
-
# If you're using jetpants console, this works too, since console runs in Jetpants
|
44
|
-
# module namespace, which then delegates missing methods to its topology object:
|
45
|
-
tracker.spares << '10.45.67.134'
|
46
|
-
|
21
|
+
When you first start using simple_tracker, there will be no pools, shards, or spare machines. If you're fluent in Ruby, you can start to add some via <tt>jetpants console</tt>, or you can write a custom script to import data from another source or config file. Alternatively, you can use these commands that simple_tracker adds to the \Jetpants command suite:
|
22
|
+
|
23
|
+
* <tt>jetpants add_pool</tt>
|
24
|
+
* <tt>jetpants add_shard</tt>
|
25
|
+
* <tt>jetpants add_spare</tt>
|
26
|
+
|
47
27
|
|
48
28
|
=== Methods to override
|
49
29
|
|
data/doc/requirements.rdoc
CHANGED
@@ -10,18 +10,21 @@ Plugins may freely override these assumptions, and upstream patches are very wel
|
|
10
10
|
* If your OS bundles a non-upgradeable 1.8 Ruby, we recommend using {rvm}[https://rvm.io/] to manage multiple Ruby versions.
|
11
11
|
* To date, \Jetpants has only been tested on the MRI/YARV implementation of Ruby. We hope to add JRuby compatibility in the near future.
|
12
12
|
* Several Ruby gem module dependencies. If you're new to Ruby, the easiest way to install everything at once is <tt>gem install jetpants</tt>.
|
13
|
+
* Some of these gems require building native extensions, which means they need devel headers in order to build.
|
14
|
+
* For example, \jetpants requires the mysql2 gem, which in turn needs MySQL development C headers in order to compile.
|
13
15
|
* MySQL (or compatible, like Percona Server), version 5.1 or higher.
|
14
|
-
*
|
15
|
-
*
|
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.
|
18
|
+
* <tt>nc</tt>, also known as netcat, a tool for piping data to or from a socket.
|
19
|
+
* <tt>pigz</tt>, an open-source single-binary parallel gzip tool by Mark Adler. A future version of \Jetpants will allow pluggable compression tools, but at present we strictly use <tt>pigz</tt> for compression in all file copy operations.
|
16
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.
|
17
21
|
* All MySQL instances run on port 3306, with only one instance per logical machine.
|
18
|
-
* A plugin could override this easily, but would require you to use the --report-host option on all slaves, so that crawling the replication topology is possible. It would also have to override various methods that specify the MySQL init script location, config file location, data directory, etc.
|
22
|
+
* A plugin could override this easily, but would require you to use the --report-host option on all slaves running MySQL 5.1, so that crawling the replication topology is possible. It would also have to override various methods that specify the MySQL init script location, config file location, data directory, etc.
|
19
23
|
* Since there's no "standard" layout for multi-instance MySQL, this won't ever be part of the \Jetpants core, but we may include one implementation as a bundled plugin in a future release.
|
20
|
-
* Master-Master replication is not in use. It presents a large number of additional failure states which make automation quite difficult, since transaction IDs are not global in MySQL.
|
24
|
+
* Master-Master replication is not in use. It presents a large number of additional failure states which make automation quite difficult, since transaction IDs are not global in MySQL. This restriction will be lifted if/when we add MySQL 5.6 support.
|
21
25
|
* Unusual replication topologies -- special-purpose slaves, ring topologies, hierarchical replication -- are not in use. Plugins could certainly override this by adding additional types of slaves, but there are too many options for this to be part of the \Jetpants core.
|
22
26
|
* You must be running \Jetpants as a UNIX user with access to an SSH private key, capable of connecting as root to any host in your database topology. If this key has a nonstandard name or location, you must specify this using the <tt>ssh_keys</tt> option in the Jetpants configuration file. SSH agent forwarding should work fine automatically, but be careful of its interaction with UNIX tools like <tt>screen</tt>.
|
23
27
|
* The root MySQL user must be able to access the database on localhost via MySQL's UNIX socket. You may specify the root password with Jetpants' <tt>mysql_root_password</tt> option in the configuration file. Or if all of your database hosts has a <tt>/root/.my.cnf</tt> file specifying the root password, you can omit <tt>mysql_root_password</tt> in the configuration file.
|
24
|
-
* You must have <tt>pigz</tt> (an open-source parallel gzip tool by Mark Adler) installed on all database hosts. A future version of \Jetpants will allow pluggable compression tools, but at present we strictly use <tt>pigz</tt> for compression in all file copy operations.
|
25
28
|
|
26
29
|
|
27
30
|
== Database topology
|
@@ -33,6 +36,8 @@ We define a database <b>pool</b> as 1 master and 0 or more slaves. Each slave fa
|
|
33
36
|
|
34
37
|
At Tumblr we ensure that all pools have exactly 2 standby slaves. This establishes a minimum level of redundancy at 3 copies of each row. If a master or active slave fails, we use \Jetpants to promote one standby slave in its place, and use the other standby slave to quickly clone a replacement. (\Jetpants clones nodes by stopping the MySQL daemon and then copying the data set; this is substantially faster than using a hot-copy solution, especially on very large and/or very actively written data sets.)
|
35
38
|
|
39
|
+
You may alter the two-standbys-per-pool requirement by tuning the <tt>standby_slaves_per_pool</tt> setting in the \Jetpants configuration file.
|
40
|
+
|
36
41
|
We no longer use dedicated backup slaves at Tumblr, although we still offer support in \Jetpants for them.
|
37
42
|
|
38
43
|
== Sharding scheme
|
@@ -43,7 +48,7 @@ For example, say your application's natural sharding key is <tt>user_id</tt>. Th
|
|
43
48
|
|
44
49
|
We prefer this range-based scheme due to its simplicity. The ranges can be expressed in a language-neutral JSON or YAML format, which can be shared between application stacks easily. \Jetpants solves all data rebalancing problems for you. There's no need to "pre-allocate" thousands of tiny shards, nor do you need an external lookup service which would be a bottleneck and a single point of failure.
|
45
50
|
|
46
|
-
Under this sharding scheme, there will always be a "last" shard, with a range of X to infinity. All new users (or whatever your sharding key is) will have their data placed on this shard.
|
51
|
+
Under this sharding scheme, there will always be a "last" shard, with a range of X to infinity. All new users (or whatever your sharding key is) will have their data placed on this shard. \Jetpants cannot split this last shard, but instead it can truncate its range to no longer have an infinite max_id -- ie, truncate its range such that it now handles IDs X through Y, where Y is an ID "in the future" (sufficiently higher than your current max ID such that you won't hit ID Y for another few days). \Jetpants then adds a new last shard which handles IDs (Y+1) to infinity. Please see the <tt>jetpants shard_cutover</tt> command.
|
47
52
|
|
48
53
|
|
49
54
|
== Assumptions for shard-split logic
|
@@ -55,4 +60,8 @@ In order for \Jetpants to be able to rebalance your shards efficiently, we also
|
|
55
60
|
* Uniform MySQL configuration between masters and slaves: log-slave-updates, unique server-id, generic log-bin and relay-log names, replication user/grants everywhere.
|
56
61
|
* Redundant rows temporarily on the wrong shard don’t matter to your application. Hopefully these would only matter if you're doing aggregate queries (like COUNTs) over the whole data set... but if your data set is big enough to shard, these already don't scale, so it shouldn't really matter.
|
57
62
|
|
63
|
+
By default, the <tt>standby_slaves_per_pool</tt> config option is set to 2. This means each shard has 3 machines: 1 master and 2 standby slaves. Therefore, splitting a shard into N pieces will require 3*N spare machines, because \Jetpants does not split the shard "in-place". After the process is completely finished, you'll get back the original shard's 3 machines to re-use or cancel.
|
64
|
+
|
65
|
+
A "spare" machine in \Jetpants should be in a clean-slate state: MySQL should be installed and have the proper grants and root password, but there should be no data on these machines, and they should not be slaving. \Jetpants will set up replication appropriately when it assigns the nodes to their appropriate pools.
|
66
|
+
|
58
67
|
For more information on the shard split process implemented by \Jetpants, including diagrams of each stage of the process, please see {Evan Elias's presentation at Velocity Europe 2011}[https://github.com/tumblr/jetpants/blob/master/doc/VelocityEurope2011Presentation.pdf?raw=true], starting at slide 19.
|
data/lib/jetpants.rb
CHANGED
@@ -8,11 +8,11 @@ module Jetpants
|
|
8
8
|
# Create a MySQL user. If you omit parameters, the defaults from Jetpants'
|
9
9
|
# configuration will be used instead. Does not automatically grant any
|
10
10
|
# privileges; use DB#grant_privileges for that.
|
11
|
-
def create_user(username=false,
|
11
|
+
def create_user(username=false, password=false, skip_binlog=false)
|
12
12
|
username ||= app_credentials[:user]
|
13
|
-
database ||= app_schema
|
14
13
|
password ||= app_credentials[:pass]
|
15
14
|
commands = []
|
15
|
+
commands << 'SET sql_log_bin = 0' if skip_binlog
|
16
16
|
Jetpants.mysql_grant_ips.each do |ip|
|
17
17
|
commands << "CREATE USER '#{username}'@'#{ip}' IDENTIFIED BY '#{password}'"
|
18
18
|
end
|
@@ -64,11 +64,28 @@ module Jetpants
|
|
64
64
|
end
|
65
65
|
alias start_replication resume_replication
|
66
66
|
|
67
|
-
# Permanently disables replication
|
67
|
+
# Permanently disables replication. Clears out the SHOW SLAVE STATUS output
|
68
|
+
# entirely in MySQL versions that permit this.
|
68
69
|
def disable_replication!
|
69
70
|
raise "This DB object has no master" unless master
|
70
71
|
output "Disabling replication; this db is no longer a slave."
|
71
|
-
|
72
|
+
|
73
|
+
ver = version_tuple
|
74
|
+
|
75
|
+
# MySQL < 5.5: allows master_host='', which clears out SHOW SLAVE STATUS
|
76
|
+
if ver[0] == 5 && ver[1] < 5
|
77
|
+
output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_host=''; RESET SLAVE"
|
78
|
+
|
79
|
+
# MySQL 5.5.16+: allows RESET SLAVE ALL, which clears out SHOW SLAVE STATUS
|
80
|
+
elsif ver[0] >= 5 && (ver[0] > 5 || ver[1] >= 5) && (ver[0] > 5 || ver[1] > 5 || ver[2] >= 16)
|
81
|
+
output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_user='test'; RESET SLAVE ALL"
|
82
|
+
|
83
|
+
# Other versions: no safe way to clear out SHOW SLAVE STATUS. Still set master_user to 'test'
|
84
|
+
# so that we know to ignore the slave status output.
|
85
|
+
else
|
86
|
+
output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_user='test'; RESET SLAVE"
|
87
|
+
end
|
88
|
+
|
72
89
|
@master.slaves.delete(self) rescue nil
|
73
90
|
@master = nil
|
74
91
|
@repl_paused = nil
|
data/lib/jetpants/db/state.rb
CHANGED
@@ -143,7 +143,14 @@ module Jetpants
|
|
143
143
|
variables
|
144
144
|
end
|
145
145
|
end
|
146
|
-
|
146
|
+
|
147
|
+
# Returns an array of integers representing the version of the MySQL server.
|
148
|
+
# For example, Percona Server 5.5.27-rel28.1-log would return [5, 5, 27]
|
149
|
+
def version_tuple
|
150
|
+
raise "Cannot determine version of a stopped MySQL instance" unless running?
|
151
|
+
global_variables[:version].split('.', 3).map &:to_i
|
152
|
+
end
|
153
|
+
|
147
154
|
# Returns the Jetpants::Pool that this instance belongs to, if any.
|
148
155
|
# Can optionally create an anonymous pool if no pool was found. This anonymous
|
149
156
|
# pool intentionally has a blank sync_configuration implementation.
|
@@ -188,11 +195,17 @@ module Jetpants
|
|
188
195
|
|
189
196
|
private
|
190
197
|
|
191
|
-
# Check if mysqld is running
|
198
|
+
# Check if mysqld is running.
|
199
|
+
# If your Linux distro's implementation of "service" returns output formatted
|
200
|
+
# differently than what we check for here (which matches RHEL and Ubuntu),
|
201
|
+
# you will need to override this method in a plugin! Or if you submit a patch
|
202
|
+
# we'd be happy to merge it upstream to support more distros.
|
192
203
|
def probe_running
|
193
204
|
if @host.available?
|
194
|
-
status = service(:status, 'mysql')
|
195
|
-
|
205
|
+
status = service(:status, 'mysql').downcase
|
206
|
+
# mysql is running if the output of "service mysql status" doesn't include any of these strings
|
207
|
+
not_running_strings = ['not running', 'stop/waiting']
|
208
|
+
@running = not_running_strings.none? {|str| status.include? str}
|
196
209
|
else
|
197
210
|
@running = false
|
198
211
|
end
|
@@ -229,7 +242,8 @@ module Jetpants
|
|
229
242
|
#
|
230
243
|
# Plugins may want to override DB#probe_slaves itself too, if running multiple
|
231
244
|
# MySQL instances per physical machine. In this case you'll want to use
|
232
|
-
# SHOW SLAVE HOSTS, and all slaves must be using the --report-host option
|
245
|
+
# SHOW SLAVE HOSTS, and all slaves must be using the --report-host option if
|
246
|
+
# using MySQL < 5.5.3.
|
233
247
|
def probe_slaves
|
234
248
|
return unless @running # leaves @slaves as nil to indicate unknown state
|
235
249
|
@slaves = []
|
data/lib/jetpants/host.rb
CHANGED
@@ -339,12 +339,16 @@ module Jetpants
|
|
339
339
|
|
340
340
|
###### Misc methods ########################################################
|
341
341
|
|
342
|
-
# Performs the given operation (
|
343
|
-
# service.
|
344
|
-
#
|
345
|
-
#
|
342
|
+
# Performs the given operation (:start, :stop, :restart, :status) for the
|
343
|
+
# specified service (ie "mysql"). Requires that the "service" bin is in
|
344
|
+
# root's PATH.
|
345
|
+
# Please be aware that the output format and exit codes for the service
|
346
|
+
# binary vary between Linux distros! You may find that you need to override
|
347
|
+
# methods that call Host#service with :status operation (such as
|
348
|
+
# DB#probe_running) in a custom plugin, to parse the output properly on
|
349
|
+
# your chosen Linux distro.
|
346
350
|
def service(operation, name)
|
347
|
-
ssh_cmd "
|
351
|
+
ssh_cmd "service #{name} #{operation.to_s}"
|
348
352
|
end
|
349
353
|
|
350
354
|
# Changes the I/O scheduler to name (such as 'deadline', 'noop', 'cfq')
|
data/lib/jetpants/pool.rb
CHANGED
@@ -158,7 +158,8 @@ module Jetpants
|
|
158
158
|
|
159
159
|
alias_text = @aliases.count > 0 ? ' (aliases: ' + @aliases.join(', ') + ')' : ''
|
160
160
|
data_size = @master.running? ? "[#{master.data_set_size(true)}GB]" : ''
|
161
|
-
|
161
|
+
state_text = (respond_to?(:state) && state != :ready ? " (state: #{state})" : '')
|
162
|
+
print "#{name}#{alias_text}#{state_text} #{data_size}\n"
|
162
163
|
|
163
164
|
if extended_info
|
164
165
|
details = {}
|
data/lib/jetpants/shard.rb
CHANGED
@@ -44,8 +44,8 @@ module Jetpants
|
|
44
44
|
# * master: string (IP address) or a Jetpants::DB object
|
45
45
|
# * state: one of the above state symbols
|
46
46
|
def initialize(min_id, max_id, master, state=:ready)
|
47
|
-
@min_id = min_id
|
48
|
-
@max_id = max_id
|
47
|
+
@min_id = min_id.to_i
|
48
|
+
@max_id = (max_id.to_s.upcase == 'INFINITY' ? 'INFINITY' : max_id.to_i)
|
49
49
|
@state = state
|
50
50
|
|
51
51
|
@children = [] # array of shards being initialized by splitting this one
|
@@ -150,6 +150,7 @@ module Jetpants
|
|
150
150
|
|
151
151
|
count.times do |i|
|
152
152
|
spare = Jetpants.topology.claim_spare(role: 'master')
|
153
|
+
spare.disable_read_only! if (spare.running? && spare.read_only?)
|
153
154
|
spare.output "Using ID range of #{id_ranges[i][0]} to #{id_ranges[i][1]} (inclusive)"
|
154
155
|
s = Shard.new(id_ranges[i][0], id_ranges[i][1], spare, :initializing)
|
155
156
|
add_child(s)
|
@@ -195,9 +196,11 @@ module Jetpants
|
|
195
196
|
def clone_to_children!
|
196
197
|
# Figure out which slave(s) we can use for populating the new masters
|
197
198
|
sources = standby_slaves.dup
|
198
|
-
sources.shift
|
199
199
|
raise "Need to have at least 1 slave in order to create additional slaves" if sources.length < 1
|
200
200
|
|
201
|
+
# If we have 2 or more slaves, keep 1 replicating for safety's sake; don't use it for spinning up children
|
202
|
+
sources.shift if sources.length > 1
|
203
|
+
|
201
204
|
# Figure out which machines we need to turn into slaves
|
202
205
|
targets = []
|
203
206
|
@children.each do |child_shard|
|
@@ -254,10 +257,14 @@ module Jetpants
|
|
254
257
|
restart_mysql
|
255
258
|
@state = :replicating
|
256
259
|
sync_configuration
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
260
|
+
if Jetpants.standby_slaves_per_pool > 0
|
261
|
+
my_slaves = Jetpants.topology.claim_spares(Jetpants.standby_slaves_per_pool, role: 'standby_slave')
|
262
|
+
enslave!(my_slaves)
|
263
|
+
my_slaves.each {|slv| slv.resume_replication}
|
264
|
+
[self, my_slaves].flatten.each {|db| db.catch_up_to_master}
|
265
|
+
else
|
266
|
+
catch_up_to_master
|
267
|
+
end
|
261
268
|
else
|
262
269
|
raise "Shard not in a state compatible with calling rebuild! (current state=#{@state})"
|
263
270
|
end
|
@@ -296,7 +303,7 @@ module Jetpants
|
|
296
303
|
end
|
297
304
|
|
298
305
|
# Displays information about the shard
|
299
|
-
def summary(extended_info=false, with_children=
|
306
|
+
def summary(extended_info=false, with_children=false)
|
300
307
|
super(extended_info)
|
301
308
|
if with_children
|
302
309
|
children.each {|c| c.summary}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Additions to the thor command suite
|
2
|
+
# New commands for initially registering shards, pools, and spares
|
3
|
+
|
4
|
+
require 'thor'
|
5
|
+
|
6
|
+
module Jetpants
|
7
|
+
class CommandSuite < Thor
|
8
|
+
desc 'add_pool', 'inform the asset tracker about a pool that was not previously tracked'
|
9
|
+
method_option :name, :desc => 'name of pool'
|
10
|
+
method_option :master, :desc => 'IP address of pool master'
|
11
|
+
def add_pool
|
12
|
+
pool_name = options[:name] || ask('Please enter the name of the pool to add: ')
|
13
|
+
raise "Name is required" unless pool_name && pool_name.length > 0
|
14
|
+
node = ask_node("Please enter the IP of the pool's master: ", options[:master])
|
15
|
+
p = Pool.new(pool_name, node)
|
16
|
+
p.sync_configuration
|
17
|
+
Jetpants.topology.write_config
|
18
|
+
puts 'Be sure to manually register any active read slaves using "jetpants activate_slave"' if p.slaves.count > 0
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'add_shard', 'inform the asset tracker about a shard that was not previously tracked'
|
22
|
+
method_option :min_id, :desc => 'Minimum ID of shard to track'
|
23
|
+
method_option :max_id, :desc => 'Maximum ID of shard to track'
|
24
|
+
method_option :master, :desc => 'IP address of shard master'
|
25
|
+
def add_shard
|
26
|
+
min_id = options[:min_id] || ask('Please enter min ID of the shard: ')
|
27
|
+
max_id = options[:max_id] || ask('Please enter max ID of the shard: ')
|
28
|
+
min_id = min_id.to_i
|
29
|
+
max_id = (max_id.to_s.upcase == 'INFINITY' ? 'INFINITY' : max_id.to_i)
|
30
|
+
node = ask_node("Please enter the IP of the pool's master: ", options[:master])
|
31
|
+
s = Shard.new(min_id, max_id, node)
|
32
|
+
s.sync_configuration
|
33
|
+
Jetpants.topology.write_config
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'add_spare', 'inform the asset tracker about a spare node that was not previously tracked'
|
37
|
+
method_option :node, :desc => 'Clean-state node to register as spare -- should be previously untracked'
|
38
|
+
def add_spare
|
39
|
+
node = ask_node("Please enter the IP of the spare node: ", options[:node])
|
40
|
+
node.start_mysql unless node.running?
|
41
|
+
raise "Spare pool nodes must not be slaving from other nodes!" if node.master
|
42
|
+
raise "Spare pool nodes should not have slaves!" if node.slaves.count > 0
|
43
|
+
raise "Spare pool nodes should not already be in a pool!" if node.pool
|
44
|
+
raise "Spare pool nodes should not have any data yet!" if node.data_set_size > 10000000 # 10 megs
|
45
|
+
table_count = node.query_return_array('show tables').count rescue -1
|
46
|
+
raise "Spare pool nodes should not have any tables yet!" if table_count >= 1
|
47
|
+
raise "Spare pool nodes need to already have users/grants!" if table_count < 0
|
48
|
+
begin
|
49
|
+
node.mysql_root_cmd 'select 1 from dual'
|
50
|
+
rescue
|
51
|
+
raise "Spare pool nodes need to have working MySQL root user!"
|
52
|
+
end
|
53
|
+
Jetpants.topology.tracker.spares.each do |sp|
|
54
|
+
sp = (sp.is_a?(Hash) && sp['node'] ? sp['node'].to_db : sp.to_db)
|
55
|
+
raise "This node is already on the spare list!" if sp == node
|
56
|
+
end
|
57
|
+
Jetpants.topology.tracker.spares << node.to_s
|
58
|
+
Jetpants.topology.update_tracker_data
|
59
|
+
node.output 'Node added to spare pool successfully.'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -8,6 +8,16 @@ module Jetpants
|
|
8
8
|
sync_configuration
|
9
9
|
end
|
10
10
|
|
11
|
+
def after_cleanup!
|
12
|
+
output 'This shard has now been fully split.'
|
13
|
+
nodes.each do |n|
|
14
|
+
n.output 'This node is no longer in use; please recycle or cancel it.'
|
15
|
+
end
|
16
|
+
puts 'If recycling nodes, be sure to completely clean them: wipe binlogs and all'
|
17
|
+
puts 'MySQL data, and put clean data files with proper grants in place, before'
|
18
|
+
puts 'you put the nodes back on the spare list.'
|
19
|
+
end
|
20
|
+
|
11
21
|
|
12
22
|
##### NEW CLASS-LEVEL METHODS ##############################################
|
13
23
|
|
@@ -18,7 +18,7 @@ module Jetpants
|
|
18
18
|
# Array of hashes, each containing info from Shard#to_hash
|
19
19
|
attr_accessor :shards
|
20
20
|
|
21
|
-
# Array of any of the following:
|
21
|
+
# Clean state DB nodes that are ready for use. Array of any of the following:
|
22
22
|
# * hashes each containing key 'node'. could expand to include 'role' or other metadata as well,
|
23
23
|
# but currently not supported.
|
24
24
|
# * objects responding to to_db, such as String or Jetpants::DB
|
@@ -70,4 +70,4 @@ module Jetpants
|
|
70
70
|
end
|
71
71
|
|
72
72
|
# load all the monkeypatches for other Jetpants classes
|
73
|
-
%w(pool shard topology db).each {|mod| require "simple_tracker/#{mod}"}
|
73
|
+
%w(pool shard topology db commandsuite).each {|mod| require "simple_tracker/#{mod}"}
|
@@ -40,7 +40,8 @@ module Jetpants
|
|
40
40
|
def claim_spares(count, options={})
|
41
41
|
raise "Not enough spare machines -- requested #{count}, only have #{@tracker.spares.count}" if @tracker.spares.count < count
|
42
42
|
hashes = @tracker.spares.shift(count)
|
43
|
-
|
43
|
+
update_tracker_data
|
44
|
+
hashes.map {|h| h.is_a?(Hash) && h['node'] ? h['node'].to_db : h.to_db}
|
44
45
|
end
|
45
46
|
|
46
47
|
def count_spares(options={})
|
@@ -57,7 +58,7 @@ module Jetpants
|
|
57
58
|
# only.
|
58
59
|
def update_tracker_data
|
59
60
|
@tracker.global_pools = functional_partitions.map &:to_hash
|
60
|
-
@tracker.shards = shards.map &:to_hash
|
61
|
+
@tracker.shards = shards.reject {|s| s.state == :recycle}.map &:to_hash
|
61
62
|
@tracker.save
|
62
63
|
end
|
63
64
|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: jetpants
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.7.
|
5
|
+
version: 0.7.8
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Evan Elias
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2012-09-
|
14
|
+
date: 2012-09-25 00:00:00 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: mysql2
|
@@ -134,6 +134,7 @@ files:
|
|
134
134
|
- plugins/simple_tracker/simple_tracker.rb
|
135
135
|
- plugins/simple_tracker/db.rb
|
136
136
|
- plugins/simple_tracker/pool.rb
|
137
|
+
- plugins/simple_tracker/commandsuite.rb
|
137
138
|
- etc/jetpants.yaml.sample
|
138
139
|
homepage: https://github.com/tumblr/jetpants/
|
139
140
|
licenses: []
|