safe-pg-migrations 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -5
- data/lib/safe-pg-migrations/base.rb +3 -0
- data/lib/safe-pg-migrations/configuration.rb +0 -6
- data/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb +14 -18
- data/lib/safe-pg-migrations/plugins/idem_potent_statements.rb +12 -5
- data/lib/safe-pg-migrations/plugins/statement_insurer.rb +8 -8
- data/lib/safe-pg-migrations/plugins/statement_retrier.rb +1 -2
- data/lib/safe-pg-migrations/plugins/useless_statements_logger.rb +33 -0
- data/lib/safe-pg-migrations/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6c0ad1795c94252929d907f94e3cf65617ca9a353414d031fb74a852ee294ea
|
4
|
+
data.tar.gz: f2d1045f55ff1896ef045edb4fe2868b7f26bc4e1b7cc542907eaab08da91075
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b32973e6920a5db9d7d48440cc5ac09fbdd511539dcf3ec436584584d6a9b501a1027a212d991a27e47ae9a20e471cabc6d705e42f6324eb79eb1b529186f1f
|
7
|
+
data.tar.gz: 0c006975e25f1c318caa3375a05c1aa43a45c50813ef6b03a4d4d7d842b5c5a2188243744a8f4e769196c39ef6ca162c693a22b48dec1c38f13aa4b1d61bfc80
|
data/README.md
CHANGED
@@ -122,7 +122,7 @@ Note: the addition of the not null constraint may timeout. In that case, you may
|
|
122
122
|
|
123
123
|
</details>
|
124
124
|
|
125
|
-
<details><summary>Safe <code>add_index</code> and <code>remove_index</code></summary>
|
125
|
+
<details><summary id="safe_add_remove_index">Safe <code>add_index</code> and <code>remove_index</code></summary>
|
126
126
|
|
127
127
|
Creating an index requires a `SHARE` lock on the target table which blocks all write on the table while the index is created (which can take some time on a large table). This is usually not practical in a live environment. Thus, **Safe PG Migrations** ensures indexes are created concurrently.
|
128
128
|
|
@@ -135,7 +135,7 @@ If you still get lock timeout while adding / removing indexes, it might be for o
|
|
135
135
|
|
136
136
|
</details>
|
137
137
|
|
138
|
-
<details><summary>safe <code>add_foreign_key</code> (and <code>add_reference</code>)</summary>
|
138
|
+
<details><summary id="safe_add_foreign_key">safe <code>add_foreign_key</code> (and <code>add_reference</code>)</summary>
|
139
139
|
|
140
140
|
Adding a foreign key requires a `SHARE ROW EXCLUSIVE` lock, which **prevent writing in the tables** while the migration is running.
|
141
141
|
|
@@ -206,9 +206,7 @@ So you can actually check that the `CREATE INDEX` statement will be performed co
|
|
206
206
|
```ruby
|
207
207
|
SafePgMigrations.config.safe_timeout = 5.seconds # Lock and statement timeout used for all DDL operations except from CREATE / DROP INDEX
|
208
208
|
|
209
|
-
SafePgMigrations.config.
|
210
|
-
|
211
|
-
SafePgMigrations.config.blocking_activity_logger_margin = 1.second # Delay to output blocking queries before timeout. Must be smaller than safe_timeout and index_lock_timeout
|
209
|
+
SafePgMigrations.config.blocking_activity_logger_margin = 1.second # Delay to output blocking queries before timeout. Must be shorter than safe_timeout
|
212
210
|
|
213
211
|
SafePgMigrations.config.batch_size = 1000 # Size of the batches used for backfilling when adding a column with a default value pre-PG11
|
214
212
|
|
@@ -6,6 +6,7 @@ require 'safe-pg-migrations/plugins/blocking_activity_logger'
|
|
6
6
|
require 'safe-pg-migrations/plugins/statement_insurer'
|
7
7
|
require 'safe-pg-migrations/plugins/statement_retrier'
|
8
8
|
require 'safe-pg-migrations/plugins/idem_potent_statements'
|
9
|
+
require 'safe-pg-migrations/plugins/useless_statements_logger'
|
9
10
|
|
10
11
|
module SafePgMigrations
|
11
12
|
# Order matters: the bottom-most plugin will have precedence
|
@@ -14,6 +15,7 @@ module SafePgMigrations
|
|
14
15
|
IdemPotentStatements,
|
15
16
|
StatementRetrier,
|
16
17
|
StatementInsurer,
|
18
|
+
UselessStatementsLogger,
|
17
19
|
].freeze
|
18
20
|
|
19
21
|
class << self
|
@@ -78,6 +80,7 @@ module SafePgMigrations
|
|
78
80
|
end
|
79
81
|
|
80
82
|
def disable_ddl_transaction
|
83
|
+
UselessStatementsLogger.warn_useless '`disable_ddl_transaction`' if super
|
81
84
|
true
|
82
85
|
end
|
83
86
|
|
@@ -5,7 +5,6 @@ require 'active_support/core_ext/numeric/time'
|
|
5
5
|
module SafePgMigrations
|
6
6
|
class Configuration
|
7
7
|
attr_accessor :safe_timeout
|
8
|
-
attr_accessor :index_lock_timeout
|
9
8
|
attr_accessor :blocking_activity_logger_margin
|
10
9
|
attr_accessor :batch_size
|
11
10
|
attr_accessor :retry_delay
|
@@ -13,7 +12,6 @@ module SafePgMigrations
|
|
13
12
|
|
14
13
|
def initialize
|
15
14
|
self.safe_timeout = 5.seconds
|
16
|
-
self.index_lock_timeout = 30.seconds
|
17
15
|
self.blocking_activity_logger_margin = 1.second
|
18
16
|
self.batch_size = 1000
|
19
17
|
self.retry_delay = 1.minute
|
@@ -24,10 +22,6 @@ module SafePgMigrations
|
|
24
22
|
pg_duration(safe_timeout)
|
25
23
|
end
|
26
24
|
|
27
|
-
def pg_index_lock_timeout
|
28
|
-
pg_duration(index_lock_timeout)
|
29
|
-
end
|
30
|
-
|
31
25
|
def pg_duration(duration)
|
32
26
|
value, unit = duration.integer? ? [duration, 's'] : [(duration * 1000).to_i, 'ms']
|
33
27
|
"#{value}#{unit}"
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module SafePgMigrations
|
4
4
|
module BlockingActivityLogger
|
5
5
|
SELECT_BLOCKING_QUERIES_SQL = <<~SQL.squish
|
6
|
-
SELECT blocking_activity.query
|
6
|
+
SELECT blocking_activity.query, blocked_activity.xact_start as start
|
7
7
|
FROM pg_catalog.pg_locks blocked_locks
|
8
8
|
JOIN pg_catalog.pg_stat_activity blocked_activity
|
9
9
|
ON blocked_activity.pid = blocked_locks.pid
|
@@ -25,32 +25,23 @@ module SafePgMigrations
|
|
25
25
|
SQL
|
26
26
|
|
27
27
|
%i[
|
28
|
-
add_column remove_column add_foreign_key remove_foreign_key change_column_default
|
29
|
-
change_column_null create_table add_index remove_index
|
28
|
+
add_column remove_column add_foreign_key remove_foreign_key change_column_default change_column_null create_table
|
30
29
|
].each do |method|
|
31
30
|
define_method method do |*args, &block|
|
32
|
-
log_blocking_queries
|
31
|
+
log_blocking_queries { super(*args, &block) }
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
35
|
private
|
37
36
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
SafePgMigrations.config.index_lock_timeout
|
42
|
-
else
|
43
|
-
SafePgMigrations.config.safe_timeout
|
44
|
-
end
|
45
|
-
|
46
|
-
timeout_delay - SafePgMigrations.config.blocking_activity_logger_margin
|
47
|
-
end
|
37
|
+
def log_blocking_queries
|
38
|
+
delay_before_logging =
|
39
|
+
SafePgMigrations.config.safe_timeout - SafePgMigrations.config.blocking_activity_logger_margin
|
48
40
|
|
49
|
-
def log_blocking_queries(method)
|
50
41
|
blocking_queries_retriever_thread =
|
51
42
|
Thread.new do
|
52
|
-
sleep delay_before_logging
|
53
|
-
SafePgMigrations.alternate_connection.
|
43
|
+
sleep delay_before_logging
|
44
|
+
SafePgMigrations.alternate_connection.query(SELECT_BLOCKING_QUERIES_SQL % raw_connection.backend_pid)
|
54
45
|
end
|
55
46
|
|
56
47
|
yield
|
@@ -75,7 +66,7 @@ module SafePgMigrations
|
|
75
66
|
"Statement was being blocked by the following #{'query'.pluralize(queries.size)}:", true
|
76
67
|
)
|
77
68
|
SafePgMigrations.say '', true
|
78
|
-
queries.each { |query| SafePgMigrations.say " #{query}", true }
|
69
|
+
queries.each { |query, start_time| SafePgMigrations.say "#{format_start_time start_time}: #{query}", true }
|
79
70
|
SafePgMigrations.say(
|
80
71
|
'Beware, some of those queries might run in a transaction. In this case the locking query might be '\
|
81
72
|
'located elsewhere in the transaction',
|
@@ -86,5 +77,10 @@ module SafePgMigrations
|
|
86
77
|
|
87
78
|
raise
|
88
79
|
end
|
80
|
+
|
81
|
+
def format_start_time(start_time, reference_time = Time.now)
|
82
|
+
duration = (reference_time - start_time).round
|
83
|
+
"transaction started #{duration} #{'second'.pluralize(duration)} ago"
|
84
|
+
end
|
89
85
|
end
|
90
86
|
end
|
@@ -43,12 +43,19 @@ module SafePgMigrations
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def create_table(table_name, comment: nil, **options)
|
46
|
-
return super
|
46
|
+
return super if options[:force] || !table_exists?(table_name)
|
47
47
|
|
48
|
-
SafePgMigrations.say
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
SafePgMigrations.say "/!\\ Table '#{table_name}' already exists.", true
|
49
|
+
|
50
|
+
td = create_table_definition(table_name, **options)
|
51
|
+
|
52
|
+
yield td if block_given?
|
53
|
+
|
54
|
+
SafePgMigrations.say(td.indexes.empty? ? '-- Skipping statement' : '-- Creating indexes', true)
|
55
|
+
|
56
|
+
td.indexes.each do |column_name, index_options|
|
57
|
+
add_index(table_name, column_name, index_options)
|
58
|
+
end
|
52
59
|
end
|
53
60
|
|
54
61
|
private
|
@@ -50,7 +50,7 @@ module SafePgMigrations
|
|
50
50
|
options[:algorithm] = :concurrently
|
51
51
|
SafePgMigrations.say_method_call(:add_index, table_name, column_name, options)
|
52
52
|
|
53
|
-
|
53
|
+
without_timeout { super }
|
54
54
|
end
|
55
55
|
|
56
56
|
def remove_index(table_name, options = {})
|
@@ -58,7 +58,7 @@ module SafePgMigrations
|
|
58
58
|
options[:algorithm] = :concurrently
|
59
59
|
SafePgMigrations.say_method_call(:remove_index, table_name, options)
|
60
60
|
|
61
|
-
|
61
|
+
without_timeout { super }
|
62
62
|
end
|
63
63
|
|
64
64
|
def backfill_column_default(table_name, column_name)
|
@@ -99,12 +99,12 @@ module SafePgMigrations
|
|
99
99
|
with_setting(:statement_timeout, 0) { yield }
|
100
100
|
end
|
101
101
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
102
|
+
def without_lock_timeout
|
103
|
+
with_setting(:lock_timeout, 0) { yield }
|
104
|
+
end
|
105
|
+
|
106
|
+
def without_timeout
|
107
|
+
without_statement_timeout { without_lock_timeout { yield } }
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
@@ -3,8 +3,7 @@
|
|
3
3
|
module SafePgMigrations
|
4
4
|
module StatementRetrier
|
5
5
|
RETRIABLE_SCHEMA_STATEMENTS = %i[
|
6
|
-
add_column add_foreign_key remove_foreign_key change_column_default
|
7
|
-
change_column_null add_index remove_index remove_column
|
6
|
+
add_column add_foreign_key remove_foreign_key change_column_default change_column_null remove_column
|
8
7
|
].freeze
|
9
8
|
|
10
9
|
RETRIABLE_SCHEMA_STATEMENTS.each do |method|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SafePgMigrations
|
4
|
+
module UselessStatementsLogger
|
5
|
+
def self.warn_useless(action, link = nil, *args)
|
6
|
+
SafePgMigrations.say "/!\\ No need to explicitly use #{action}, safe-pg-migrations does it for you", *args
|
7
|
+
SafePgMigrations.say "\t see #{link} for more details", *args if link
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_index(*, **options)
|
11
|
+
warn_for_index(**options)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def remove_index(table_name, options = {})
|
16
|
+
warn_for_index(options) if options.is_a? Hash
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_foreign_key(*, **options)
|
21
|
+
if options[:validate] == false
|
22
|
+
UselessStatementsLogger.warn_useless '`validate: :false`', 'https://github.com/doctolib/safe-pg-migrations#safe_add_foreign_key'
|
23
|
+
end
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def warn_for_index(**options)
|
28
|
+
return unless options[:algorithm] == :concurrently
|
29
|
+
|
30
|
+
UselessStatementsLogger.warn_useless '`algorithm: :concurrently`', 'https://github.com/doctolib/safe-pg-migrations#safe_add_remove_index'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe-pg-migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthieu Prat
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-11-
|
12
|
+
date: 2020-11-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -166,6 +166,7 @@ files:
|
|
166
166
|
- lib/safe-pg-migrations/plugins/idem_potent_statements.rb
|
167
167
|
- lib/safe-pg-migrations/plugins/statement_insurer.rb
|
168
168
|
- lib/safe-pg-migrations/plugins/statement_retrier.rb
|
169
|
+
- lib/safe-pg-migrations/plugins/useless_statements_logger.rb
|
169
170
|
- lib/safe-pg-migrations/plugins/verbose_sql_logger.rb
|
170
171
|
- lib/safe-pg-migrations/railtie.rb
|
171
172
|
- lib/safe-pg-migrations/version.rb
|