safe-pg-migrations 1.1.0 → 1.2.0
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 +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
|