jetpants 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.rdoc +4 -9
- data/bin/jetpants +7 -6
- data/doc/capacity_plan.rdoc +77 -0
- data/doc/commands.rdoc +1 -1
- data/doc/jetpants_collins.rdoc +2 -1
- data/doc/online_schema_change.rdoc +45 -0
- data/doc/plugins.rdoc +7 -1
- data/doc/requirements.rdoc +1 -1
- data/doc/upgrade_helper.rdoc +68 -0
- data/lib/jetpants/db/client.rb +2 -1
- data/lib/jetpants/db/import_export.rb +12 -3
- data/lib/jetpants/db/replication.rb +6 -2
- data/lib/jetpants/db/schema.rb +40 -0
- data/lib/jetpants/db/server.rb +2 -2
- data/lib/jetpants/host.rb +12 -1
- data/lib/jetpants/pool.rb +41 -0
- data/lib/jetpants/shard.rb +201 -124
- data/lib/jetpants/table.rb +80 -10
- data/plugins/capacity_plan/capacity_plan.rb +353 -0
- data/plugins/capacity_plan/commandsuite.rb +19 -0
- data/plugins/capacity_plan/monkeypatch.rb +20 -0
- data/plugins/jetpants_collins/db.rb +45 -6
- data/plugins/jetpants_collins/jetpants_collins.rb +32 -21
- data/plugins/jetpants_collins/pool.rb +22 -1
- data/plugins/jetpants_collins/shard.rb +9 -2
- data/plugins/jetpants_collins/topology.rb +8 -9
- data/plugins/online_schema_change/commandsuite.rb +56 -0
- data/plugins/online_schema_change/db.rb +33 -0
- data/plugins/online_schema_change/online_schema_change.rb +5 -0
- data/plugins/online_schema_change/pool.rb +105 -0
- data/plugins/online_schema_change/topology.rb +56 -0
- data/plugins/simple_tracker/shard.rb +1 -1
- data/plugins/upgrade_helper/commandsuite.rb +212 -0
- data/plugins/upgrade_helper/db.rb +78 -0
- data/plugins/upgrade_helper/host.rb +22 -0
- data/plugins/upgrade_helper/pool.rb +259 -0
- data/plugins/upgrade_helper/shard.rb +61 -0
- data/plugins/upgrade_helper/upgrade_helper.rb +21 -0
- data/scripts/global_rowcount.rb +75 -0
- metadata +28 -15
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Jetpants
|
4
|
+
class CommandSuite < Thor
|
5
|
+
|
6
|
+
desc 'capacity_snapshot', 'create a snapshot of the current useage'
|
7
|
+
def capacity_snapshot
|
8
|
+
Plugin::Capacity.new().snapshot
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'capacity_plan', 'capacity plan'
|
12
|
+
method_option :email, :email => 'email address to send capacity plan report to'
|
13
|
+
def capacity_plan
|
14
|
+
email = options[:email] || false
|
15
|
+
Plugin::Capacity.new().plan(email)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Enumerable
|
2
|
+
def sum
|
3
|
+
self.inject(0){|accum, i| accum + i }
|
4
|
+
end
|
5
|
+
|
6
|
+
def mean
|
7
|
+
self.sum/self.length.to_f
|
8
|
+
end
|
9
|
+
|
10
|
+
def sample_variance
|
11
|
+
m = self.mean
|
12
|
+
sum = self.inject(0){|accum, i| accum +(i-m)**2 }
|
13
|
+
sum/(self.length - 1).to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
def standard_deviation
|
17
|
+
return Math.sqrt(self.sample_variance)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -66,6 +66,11 @@ module Jetpants
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
# After changing the status of a node, clear its list of spare-node-related
|
70
|
+
# validation errors, so that we will re-probe when necessary
|
71
|
+
def after_collins_status=(value)
|
72
|
+
@spare_validation_errors = nil
|
73
|
+
end
|
69
74
|
|
70
75
|
##### NEW METHODS ##########################################################
|
71
76
|
|
@@ -76,14 +81,48 @@ module Jetpants
|
|
76
81
|
end
|
77
82
|
|
78
83
|
# Returns true if this database is a spare node and looks ready for use, false otherwise.
|
79
|
-
#
|
80
|
-
#
|
81
|
-
# in a sane state. (The caller of this method already checks that the node is SSHable,
|
82
|
-
# and that MySQL is running, and the node isn't already in a pool -- so no need to
|
83
|
-
# check any of those here.)
|
84
|
+
# Normally no need for plugins to override this (as of Jetpants 0.8.1), they should
|
85
|
+
# override DB#validate_spare instead.
|
84
86
|
def usable_spare?
|
85
|
-
|
87
|
+
if @spare_validation_errors.nil?
|
88
|
+
@spare_validation_errors = []
|
89
|
+
|
90
|
+
# The order of checks is important -- if the node isn't even reachable by SSH,
|
91
|
+
# don't run any of the other checks, for example.
|
92
|
+
# Note that we probe concurrently in Topology#query_spare_assets, ahead of time
|
93
|
+
if !probed?
|
94
|
+
@spare_validation_errors << 'Attempt to probe node failed'
|
95
|
+
elsif !available?
|
96
|
+
@spare_validation_errors << 'Node is not reachable via SSH'
|
97
|
+
elsif !running?
|
98
|
+
@spare_validation_errors << 'MySQL is not running'
|
99
|
+
elsif pool
|
100
|
+
@spare_validation_errors << 'Node already has a pool'
|
101
|
+
else
|
102
|
+
validate_spare
|
103
|
+
end
|
104
|
+
|
105
|
+
unless @spare_validation_errors.empty?
|
106
|
+
error_text = @spare_validation_errors.join '; '
|
107
|
+
output "Removed from spare pool for failing checks: #{error_text}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
@spare_validation_errors.empty?
|
86
111
|
end
|
87
112
|
|
113
|
+
# Performs validation checks on this node to see whether it is a usable spare.
|
114
|
+
# The default implementation just ensures a collins status of Allocated and state
|
115
|
+
# of SPARE.
|
116
|
+
# Downstream plugins may override this to do additional checks to ensure the node is
|
117
|
+
# in a sane condition.
|
118
|
+
# No need to check whether the node is SSHable, MySQL is running, or not already in
|
119
|
+
# a pool -- DB#usable_spare? already does that automatically.
|
120
|
+
def validate_spare
|
121
|
+
# Confirm node is in Allocated:SPARE status:state. (Because Collins find API hits a
|
122
|
+
# search index which isn't synchronously updated with all writes, there's potential
|
123
|
+
# for a find call to return assets that just transitioned to a different status or state.)
|
124
|
+
status_state = collins_status_state
|
125
|
+
@spare_validation_errors << "Unexpected status:state value: #{status_state}" unless status_state == 'allocated:spare'
|
126
|
+
end
|
88
127
|
end
|
89
128
|
end
|
@@ -156,7 +156,7 @@ module Jetpants
|
|
156
156
|
|
157
157
|
# Pass in a hash mapping field name symbols to values to set
|
158
158
|
# Symbol => String -- optionally set any Collins attribute
|
159
|
-
# :status => String -- optionally set the status value for the asset
|
159
|
+
# :status => String -- optionally set the status value for the asset. Can optionally be a "status:state" string too.
|
160
160
|
# :asset => Collins::Asset -- optionally pass this in to avoid an extra Collins API lookup, if asset already obtained
|
161
161
|
#
|
162
162
|
# Alternatively, pass in 2 strings (field_name, value) to set just a single Collins attribute (or status)
|
@@ -180,26 +180,30 @@ module Jetpants
|
|
180
180
|
output "WARNING: unable to set Collins status to #{val}"
|
181
181
|
next
|
182
182
|
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
success = Jetpants::Plugin::JetCollins.set_status!(asset, val)
|
200
|
-
raise "#{self}: Unable to set Collins status to #{val}" unless success
|
201
|
-
output "Collins status changed from #{previous_value} to #{val}"
|
183
|
+
state_val = attrs[:state]
|
184
|
+
previous_status = asset.status.capitalize
|
185
|
+
# Allow setting status:state at once via foo.collins_status = 'allocated:running'
|
186
|
+
if val.include? ':'
|
187
|
+
raise "Attempting to set state in two places" if state_val
|
188
|
+
vals = val.split(':', 2)
|
189
|
+
val = vals.first.capitalize
|
190
|
+
state_val = vals.last.upcase
|
191
|
+
end
|
192
|
+
if state_val
|
193
|
+
previous_state = asset.state.name.upcase
|
194
|
+
next unless previous_state != state_val.to_s.upcase || previous_status != val.to_s.capitalize
|
195
|
+
success = Jetpants::Plugin::JetCollins.set_status!(asset, val, 'changed through jetpants', state_val)
|
196
|
+
unless success
|
197
|
+
# If we failed to set to this state, try creating the state as new
|
198
|
+
Jetpants::Plugin::JetCollins.state_create!(state_val, state_val, state_val, val)
|
199
|
+
success = Jetpants::Plugin::JetCollins.set_status!(asset, val, 'changed through jetpants', state_val)
|
202
200
|
end
|
201
|
+
raise "#{self}: Unable to set Collins state to #{state_val} and Unable to set Collins status to #{val}" unless success
|
202
|
+
output "Collins status:state changed from #{previous_status}:#{previous_state} to #{val.capitalize}:#{state_val.upcase}"
|
203
|
+
elsif previous_status != val.to_s.capitalize
|
204
|
+
success = Jetpants::Plugin::JetCollins.set_status!(asset, val)
|
205
|
+
raise "#{self}: Unable to set Collins status to #{val}" unless success
|
206
|
+
output "Collins status changed from #{previous_status} to #{val}"
|
203
207
|
end
|
204
208
|
when :state
|
205
209
|
unless asset && asset.status && attrs[:status]
|
@@ -231,9 +235,16 @@ module Jetpants
|
|
231
235
|
|
232
236
|
end
|
233
237
|
|
238
|
+
# Returns a single downcased "status:state" string, useful when trying to compare both fields
|
239
|
+
# at once.
|
240
|
+
def collins_status_state
|
241
|
+
values = collins_get :status, :state
|
242
|
+
"#{values[:status]}:#{values[:state]}".downcase
|
243
|
+
end
|
244
|
+
|
234
245
|
end # module JetCollins
|
235
246
|
end # module Plugin
|
236
|
-
end
|
247
|
+
end # module Jetpants
|
237
248
|
|
238
249
|
|
239
250
|
# load all the monkeypatches for other Jetpants classes
|
@@ -70,7 +70,7 @@ module Jetpants
|
|
70
70
|
master_read_weight: master_read_weight
|
71
71
|
[@master, slaves].flatten.each do |db|
|
72
72
|
current_status = (db.collins_status || '').downcase
|
73
|
-
db.collins_status = 'Allocated' unless current_status == 'maintenance'
|
73
|
+
db.collins_status = 'Allocated:RUNNING' unless current_status == 'maintenance'
|
74
74
|
db.collins_pool = @name
|
75
75
|
end
|
76
76
|
@master.collins_secondary_role = 'MASTER'
|
@@ -128,11 +128,32 @@ module Jetpants
|
|
128
128
|
end
|
129
129
|
end
|
130
130
|
end
|
131
|
+
|
132
|
+
# Clean up any slaves that are no longer slaving (again only looking at current datacenter)
|
133
|
+
assets = Jetpants.topology.server_node_assets(@name, :slave)
|
134
|
+
assets.reject! {|a| a.location && a.location.upcase != Plugin::JetCollins.datacenter}
|
135
|
+
assets.map(&:to_db).each do |db|
|
136
|
+
if !db.running? || db.pool != self
|
137
|
+
db.output "Not replicating from new master, removing from pool #{self}"
|
138
|
+
db.collins_pool = ''
|
139
|
+
db.collins_secondary_role = ''
|
140
|
+
db.collins_status = 'Unallocated'
|
141
|
+
end
|
142
|
+
end
|
131
143
|
end
|
132
144
|
|
133
145
|
|
134
146
|
##### NEW METHODS ##########################################################
|
135
147
|
|
148
|
+
# Returns the pool's creation time (as a unix timestamp) according to Collins.
|
149
|
+
# (note: may be off by a few hours until https://github.com/tumblr/collins/issues/80
|
150
|
+
# is resolved)
|
151
|
+
# Not called from anything in jetpants_collins, but available to your own
|
152
|
+
# custom automation if useful
|
153
|
+
def collins_creation_timestamp
|
154
|
+
collins_asset.created.to_time.to_i
|
155
|
+
end
|
156
|
+
|
136
157
|
# Called from DB#after_probe_master and DB#after_probe_slave for machines
|
137
158
|
# that are unreachable via SSH, or reachable but MySQL isn't running.
|
138
159
|
def slaves_according_to_collins
|
@@ -77,13 +77,13 @@ module Jetpants
|
|
77
77
|
db.collins_secondary_role = ''
|
78
78
|
db.collins_status = 'Unallocated'
|
79
79
|
end
|
80
|
-
|
80
|
+
elsif @state != :initializing
|
81
81
|
# Note that we don't call Pool#slaves here to get all 3 types in one shot,
|
82
82
|
# because that potentially includes child shards, and we don't want to overwrite
|
83
83
|
# their pool setting...
|
84
84
|
[@master, active_slaves, standby_slaves, backup_slaves].flatten.each do |db|
|
85
85
|
current_status = (db.collins_status || '').downcase
|
86
|
-
db.collins_status = 'Allocated' unless current_status == 'maintenance'
|
86
|
+
db.collins_status = 'Allocated:RUNNING' unless current_status == 'maintenance'
|
87
87
|
db.collins_pool = @name
|
88
88
|
end
|
89
89
|
@master.collins_secondary_role = 'MASTER'
|
@@ -91,6 +91,13 @@ module Jetpants
|
|
91
91
|
standby_slaves.each {|db| db.collins_secondary_role = 'STANDBY_SLAVE'}
|
92
92
|
backup_slaves.each {|db| db.collins_secondary_role = 'BACKUP_SLAVE'}
|
93
93
|
end
|
94
|
+
|
95
|
+
# handle lockless master migration situations
|
96
|
+
if @state == :child && master.master && !@parent
|
97
|
+
to_be_ejected_master = master.master
|
98
|
+
to_be_ejected_master.collins_secondary_role = :standby_slave # not accurate, but no better option for now
|
99
|
+
end
|
100
|
+
|
94
101
|
true
|
95
102
|
end
|
96
103
|
|
@@ -73,7 +73,7 @@ module Jetpants
|
|
73
73
|
end
|
74
74
|
|
75
75
|
|
76
|
-
# Returns (count) DB objects. Pulls from machines in the
|
76
|
+
# Returns (count) DB objects. Pulls from machines in the spare state
|
77
77
|
# and converts them to the Allocated status.
|
78
78
|
# You can pass in :role to request spares with a particular secondary_role
|
79
79
|
def claim_spares(count, options={})
|
@@ -84,7 +84,7 @@ module Jetpants
|
|
84
84
|
db.collins_pool = ''
|
85
85
|
db.collins_secondary_role = ''
|
86
86
|
db.collins_slave_weight = ''
|
87
|
-
db.collins_status = 'Allocated'
|
87
|
+
db.collins_status = 'Allocated:CLAIMED'
|
88
88
|
db
|
89
89
|
end
|
90
90
|
end
|
@@ -181,6 +181,7 @@ module Jetpants
|
|
181
181
|
|
182
182
|
if primary_roles.count == 1
|
183
183
|
selector[:type] = '^CONFIGURATION$'
|
184
|
+
selector[:primary_role] = primary_roles.first
|
184
185
|
else
|
185
186
|
values = primary_roles.map {|r| "primary_role = ^#{r}$"}
|
186
187
|
selector[:query] = 'type = ^CONFIGURATION$ AND (' + values.join(' OR ') + ')'
|
@@ -239,7 +240,7 @@ module Jetpants
|
|
239
240
|
|
240
241
|
private
|
241
242
|
|
242
|
-
# Helper method to query Collins for spare
|
243
|
+
# Helper method to query Collins for spare DBs.
|
243
244
|
def query_spare_assets(count, options={})
|
244
245
|
# Intentionally no remoteLookup=true here. We only want to grab spare nodes
|
245
246
|
# from the datacenter that Jetpants is running in.
|
@@ -247,7 +248,8 @@ module Jetpants
|
|
247
248
|
operation: 'and',
|
248
249
|
details: true,
|
249
250
|
type: 'SERVER_NODE',
|
250
|
-
status: '
|
251
|
+
status: 'Allocated',
|
252
|
+
state: 'SPARE',
|
251
253
|
primary_role: 'DATABASE',
|
252
254
|
size: 100,
|
253
255
|
}
|
@@ -257,15 +259,12 @@ module Jetpants
|
|
257
259
|
keep_nodes = []
|
258
260
|
|
259
261
|
# Probe concurrently for speed reasons
|
260
|
-
nodes.map(&:to_db).concurrent_each
|
262
|
+
nodes.map(&:to_db).concurrent_each {|db| db.probe rescue nil}
|
261
263
|
|
262
264
|
# Now iterate in a single-threaded way for simplicity
|
263
265
|
nodes.each do |node|
|
264
266
|
db = node.to_db
|
265
|
-
db.
|
266
|
-
if node.pool || !db.available? || !db.running? || !db.usable_spare?
|
267
|
-
db.output "Removed from potential spare pool for failing checks"
|
268
|
-
else
|
267
|
+
if db.usable_spare?
|
269
268
|
keep_nodes << node
|
270
269
|
break if keep_nodes.size >= count
|
271
270
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Jetpants
|
4
|
+
class CommandSuite < Thor
|
5
|
+
|
6
|
+
desc 'alter_table', 'perform an alter table'
|
7
|
+
method_option :pool, :desc => 'Name of pool to run the alter table on'
|
8
|
+
method_option :dry_run, :desc => 'Dry run of the alter table', :type => :boolean
|
9
|
+
method_option :alter, :desc => 'The alter statment (eg ADD COLUMN c1 INT)'
|
10
|
+
method_option :database, :desc => 'Database to run the alter table on'
|
11
|
+
method_option :table, :desc => 'Table to run the alter table on'
|
12
|
+
method_option :all_shards, :desc => 'To run on all the shards', :type => :boolean
|
13
|
+
def alter_table
|
14
|
+
unless options[:all_shards]
|
15
|
+
pool_name = options[:pool] || ask('Please enter a name of a pool: ')
|
16
|
+
pool = Jetpants.topology.pool(pool_name)
|
17
|
+
raise "#{pool_name} is not a pool name" unless pool
|
18
|
+
end
|
19
|
+
|
20
|
+
database = options[:database] || false
|
21
|
+
table = options[:table] || ask('Please enter a name of a table: ')
|
22
|
+
alter = options[:alter] || ask('Please enter a alter table statment (eg ADD COLUMN c1 INT): ')
|
23
|
+
|
24
|
+
if options[:all_shards]
|
25
|
+
Jetpants.topology.alter_table_shards(database, table, alter, options[:dry_run])
|
26
|
+
else
|
27
|
+
unless pool.alter_table(database, table, alter, options[:dry_run])
|
28
|
+
print "check for errors during online schema change\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'alter_table_drop', 'drop the old table after the alter table is complete'
|
34
|
+
method_option :pool, :desc => 'Name of pool that your ran the alter table on'
|
35
|
+
method_option :table, :desc => 'Table you ran the alter table on'
|
36
|
+
method_option :database, :desc => 'Database you ran the alter table on'
|
37
|
+
method_option :all_shards, :desc => 'To run on all the shards', :type => :boolean
|
38
|
+
def alter_table_drop
|
39
|
+
unless options[:all_shards]
|
40
|
+
pool_name = options[:pool] || ask('Please enter a name of a pool: ')
|
41
|
+
pool = Jetpants.topology.pool(pool_name)
|
42
|
+
raise "#{pool_name} is not a pool name" unless pool
|
43
|
+
end
|
44
|
+
|
45
|
+
database = options[:database] || false
|
46
|
+
table = options[:table] || ask('Please enter a name of a table: ')
|
47
|
+
|
48
|
+
if options[:all_shards]
|
49
|
+
Jetpants.topology.drop_old_alter_table_shards(database, table)
|
50
|
+
else
|
51
|
+
pool.drop_old_alter_table(database, table)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Jetpants
|
2
|
+
class DB
|
3
|
+
|
4
|
+
# Creates a temporary user for use of pt-table-checksum and pt-upgrade,
|
5
|
+
# yields to the supplied block, and then drops the user.
|
6
|
+
# The user will have a randomly-generated 50-character password, and will
|
7
|
+
# have elevated permissions (ALL PRIVILEGES on the application schema, and
|
8
|
+
# a few global privs as well) since these are necessary to run the tools.
|
9
|
+
# The block will be passed the randomly-generated password.
|
10
|
+
def with_online_schema_change_user(username, database)
|
11
|
+
password = DB.random_password
|
12
|
+
create_user username, password
|
13
|
+
grant_privileges username, '*', 'PROCESS', 'REPLICATION CLIENT', 'REPLICATION SLAVE', 'SUPER'
|
14
|
+
grant_privileges username, database, 'ALL PRIVILEGES'
|
15
|
+
begin
|
16
|
+
yield password
|
17
|
+
rescue StandardError, Interrupt, IOError
|
18
|
+
drop_user username
|
19
|
+
raise
|
20
|
+
end
|
21
|
+
drop_user username
|
22
|
+
end
|
23
|
+
|
24
|
+
# make sure there is enough space to do an online schema change
|
25
|
+
def has_space_for_alter?(table_name, database_name=nil)
|
26
|
+
database_name ||= app_schema
|
27
|
+
table_size = dir_size("#{mysql_directory}/#{database_name}/#{table_name}.ibd")
|
28
|
+
|
29
|
+
table_size < mount_stats['available']
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# JetCollins monkeypatches to add Collins integration
|
2
|
+
|
3
|
+
module Jetpants
|
4
|
+
class Pool
|
5
|
+
collins_attr_accessor :online_schema_change
|
6
|
+
|
7
|
+
def alter_table(database, table, alter, dry_run=true, force=false)
|
8
|
+
database ||= app_schema
|
9
|
+
error = false
|
10
|
+
|
11
|
+
raise "not enough space to run alter table on #{table}" unless master.has_space_for_alter?(table, database)
|
12
|
+
|
13
|
+
unless(check_collins_for_alter)
|
14
|
+
raise "alter table already running on #{@name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
max_threads = max_threads_running(30,1)
|
18
|
+
max_threads = 50 unless max_threads > 50
|
19
|
+
|
20
|
+
critical_threads_running = 2 * max_threads > 500 ? 2 * max_threads : 500
|
21
|
+
|
22
|
+
update_collins_for_alter(database, table, alter)
|
23
|
+
|
24
|
+
master.with_online_schema_change_user('pt-osc', database) do |password|
|
25
|
+
|
26
|
+
command = "pt-online-schema-change --nocheck-replication-filters --max-load='Threads_running:#{max_threads}' --critical-load='Threads_running:#{critical_threads_running}' --nodrop-old-table --retries=10 --set-vars='wait_timeout=100000' --dry-run --print --alter '#{alter}' D=#{database},t=#{table},h=#{master.ip},u=#{'pt-osc'},p=#{password}"
|
27
|
+
|
28
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]---------------------------------------------------------------------------------------\n"
|
29
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{command}\n"
|
30
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]---------------------------------------------------------------------------------------\n"
|
31
|
+
|
32
|
+
IO.popen command do |io|
|
33
|
+
io.each do |line|
|
34
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{line.gsub("\n","")}\n"
|
35
|
+
end
|
36
|
+
error = true if $?.to_i > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
if !(dry_run || error)
|
40
|
+
continue = 'no'
|
41
|
+
unless force
|
42
|
+
continue = ask('Dry run complete would you like to continue?: (YES/no)')
|
43
|
+
end
|
44
|
+
|
45
|
+
if force || continue == 'YES'
|
46
|
+
command = "pt-online-schema-change --nocheck-replication-filters --max-load='Threads_running:#{max_threads}' --critical-load='Threads_running:#{critical_threads_running}' --nodrop-old-table --retries=10 --set-vars='wait_timeout=100000' --execute --print --alter '#{alter}' D=#{database},t=#{table},h=#{master.ip},u=#{'pt-osc'},p=#{password}"
|
47
|
+
|
48
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]---------------------------------------------------------------------------------------\n\n\n"
|
49
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{command}\n"
|
50
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]\n\n---------------------------------------------------------------------------------------\n\n\n"
|
51
|
+
|
52
|
+
IO.popen command do |io|
|
53
|
+
io.each do |line|
|
54
|
+
print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{line.gsub("\n","")}\n"
|
55
|
+
end
|
56
|
+
error = true if $?.to_i > 0
|
57
|
+
end #end execute
|
58
|
+
|
59
|
+
end #end continue
|
60
|
+
|
61
|
+
end #end if ! dry run
|
62
|
+
|
63
|
+
end #end user grant block
|
64
|
+
|
65
|
+
clean_up_collins_for_alter
|
66
|
+
|
67
|
+
! error
|
68
|
+
end
|
69
|
+
|
70
|
+
# update collins for tracking alters, so there is only one running at a time
|
71
|
+
def update_collins_for_alter(database, table, alter)
|
72
|
+
self.collins_online_schema_change = JSON.pretty_generate({
|
73
|
+
'running' => true,
|
74
|
+
'started' => Time.now.to_i,
|
75
|
+
'database' => database,
|
76
|
+
'table' => table,
|
77
|
+
'alter' => alter
|
78
|
+
})
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
# check if a alter is already running
|
83
|
+
def check_collins_for_alter()
|
84
|
+
return true if self.collins_online_schema_change.empty?
|
85
|
+
meta = JSON.parse(self.collins_online_schema_change)
|
86
|
+
if(meta['running'])
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
|
92
|
+
# clean up collins after alter
|
93
|
+
def clean_up_collins_for_alter()
|
94
|
+
self.collins_online_schema_change = ''
|
95
|
+
end
|
96
|
+
|
97
|
+
# drop old table after an alter, this is because
|
98
|
+
# we do not drop the table after an alter
|
99
|
+
def drop_old_alter_table(database, table)
|
100
|
+
database ||= app_schema
|
101
|
+
master.mysql_root_cmd("USE #{database}; DROP TABLE _#{table}_old")
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|