jetpants 0.8.0 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|