jetpants 0.7.6 → 0.7.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.pools.concurrent_each &:probe
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.map(&:nodes).flatten.each {|n| counts[n.role] += 1}
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 'rebuild_slave', 'export and re-import data set on a standby slave'
256
- method_option :node, :desc => 'IP of standby slave to rebuild'
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 rebuild non-shard slaves from command suite; use jetpants console instead" unless node.pool.is_a?(Shard)
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
- new_last_shard_slaves = Jetpants.topology.claim_spares(Jetpants.standby_slaves_per_pool, role: 'standby_slave')
425
- new_last_shard_slaves.each do |x|
426
- x.change_master_to new_last_shard_master
427
- x.resume_replication
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
@@ -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 rebuild_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.
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
 
@@ -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
- Some alternatives to the range-based approach include:
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
 
@@ -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. 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. Either way, some examples of how to add things include:
22
-
23
- # Create a global pool (functional partition)
24
- p = Pool.new('some-name', '10.45.67.220')
25
-
26
- # The slaves of the master will be detected automatically,
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
 
@@ -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
- * a RHEL/CentOS distribution of Linux.
15
- * It should be easy to write a plugin supporting another distribution. The main change might be overriding Jetpants::Host#service, if your distribution doesn't have <tt>/sbin/service</tt>.
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. Once there are too many users on this shard, it becomes necessary to truncate its range such that it handles IDs X through Y, where Y is an ID "in the future" (ie, sufficiently higher than your current max ID such that you won't hit ID Y for another few days). You must then add a new last shard handles IDs (Y+1) to infinity. We call this process "shard cutover", and it is a supported operation in the Jetpants command suite.
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.
@@ -1,3 +1,7 @@
1
+ # This is the Jetpants module entrypoint. It loads all base Jetpants files,
2
+ # configuration, and plugins. It then initializes the object model / database
3
+ # topology.
4
+
1
5
  require 'sequel'
2
6
  require 'net/ssh'
3
7
  require 'yaml'
@@ -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, database=false, password=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
- output mysql_root_cmd "STOP SLAVE; CHANGE MASTER TO master_host=''; RESET SLAVE"
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
@@ -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
- @running = !(status.downcase.include?('not running'))
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 = []
@@ -339,12 +339,16 @@ module Jetpants
339
339
 
340
340
  ###### Misc methods ########################################################
341
341
 
342
- # Performs the given operation ('start', 'stop', 'restart') on the specified
343
- # service. Default implementation assumes RedHat/CentOS style /sbin/service.
344
- # If you're using a distibution or OS that does not support /sbin/service,
345
- # override this method with a plugin.
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 "/sbin/service #{name} #{operation.to_s}"
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')
@@ -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
- print "#{name}#{alias_text} #{data_size}\n"
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 = {}
@@ -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
- my_slaves = Jetpants.topology.claim_spares(Jetpants.standby_slaves_per_pool, role: 'standby_slave')
258
- enslave!(my_slaves)
259
- my_slaves.each {|slv| slv.resume_replication}
260
- [self, my_slaves].flatten.each {|db| db.catch_up_to_master}
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=true)
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
- hashes.map {|h| h['node'] ? h['node'].to_db : h.to_db}
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.6
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-18 00:00:00 Z
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: []