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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d806334c473708774d2180dbe91d2483b49f0d27b0a223a2973bc95d986d4030
4
- data.tar.gz: 9b5c2d329d5cc1bd12c80944420e20eb5454e5b0a0f7fda2d6ec3fc3c3a5e042
3
+ metadata.gz: e6c0ad1795c94252929d907f94e3cf65617ca9a353414d031fb74a852ee294ea
4
+ data.tar.gz: f2d1045f55ff1896ef045edb4fe2868b7f26bc4e1b7cc542907eaab08da91075
5
5
  SHA512:
6
- metadata.gz: 6786e68cce9dbd91a4633c80526a1fd63f6fbfa335d6e2b53d29b71ba0b566f9bb32169745bb13f89a059b82b0c0b21585a9bffdb9ebc751aa0e4a467f0f7934
7
- data.tar.gz: 299a69f1d9413279fd61908a7d8f6eb65bd4c9912abcc1101ca8acbfee39c40d33086efa28a9f2133c0679c1ad9cdbbfe3ce84338f2175ad882b7d1ace707654
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.index_lock_timeout = 30.seconds # Lock timeout used for CREATE / DROP INDEX
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(method) { super(*args, &block) }
31
+ log_blocking_queries { super(*args, &block) }
33
32
  end
34
33
  end
35
34
 
36
35
  private
37
36
 
38
- def delay_before_logging(method)
39
- timeout_delay =
40
- if %i[add_index remove_index].include?(method)
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(method)
53
- SafePgMigrations.alternate_connection.query_values(SELECT_BLOCKING_QUERIES_SQL % raw_connection.backend_pid)
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 unless table_exists?(table_name)
46
+ return super if options[:force] || !table_exists?(table_name)
47
47
 
48
- SafePgMigrations.say(
49
- "/!\\ Table '#{table_name}' already exists. Skipping statement.",
50
- true
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
- with_index_timeouts { super }
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
- with_index_timeouts { super }
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 with_index_timeouts
103
- without_statement_timeout do
104
- with_setting(:lock_timeout, SafePgMigrations.config.pg_index_lock_timeout) do
105
- yield
106
- end
107
- end
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SafePgMigrations
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  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.1.0
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-03 00:00:00.000000000 Z
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