pg_online_schema_change 0.9.5 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4b544005274743fc18f3147204997861e3ac55e7c619a6859849049eff42324
4
- data.tar.gz: a4123844149ed79c7e8781588ca055f49954afa245a7a63c50886e076be9562e
3
+ metadata.gz: e1838409c592e1497790a6e02f883b857851450181854baf660b9b7ee3664c6e
4
+ data.tar.gz: 4f7e58eeafb2413ec774988b3190391033e819a444a25da3b1ff0fcbf6bf64e4
5
5
  SHA512:
6
- metadata.gz: d5b08b1f26c9d3f3c19d819cf65d0787bbbd6221845ce44bcf2aaf13a0389f8824139d3034507cb8118751b2e09713ec4116322e8b86b8988276d6ab8f4a2390
7
- data.tar.gz: e5e589f58797e28fbe15fd355367a61c2673f6d90ccadb67c9b43427ea0a5b86afba9da4ec5199dd4656397f4c7a5e8b27a3805876915d7eddb8366a19892a4b
6
+ metadata.gz: c769f18ab1815fa543246fd18bb3157add074894f15371abb88e1af61e9381eade6850e4fba3a7d6d3701711594ed10a9018c7fe1c45a5ffdaf501891bf7e548
7
+ data.tar.gz: 84667ac77dab1d97478d51d9635dfcacd38e23ffe59ef6171863ca52d69294af19672bd89dba49a6d325b1c64c4e76a5dc8c57bf3dfee1120e2bd4bec1ca9e21
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [0.9.6] - 2023-11-04
2
+
3
+ - Fix and add links to caveats section in #130
4
+ - Refresh views across all schemas post swap in #134
5
+
6
+ ## [0.9.5] - 2023-10-15
7
+
8
+ - Validate one constraint at a time in #124
9
+ - Introduce --skip-foreign-key-validation in #125
10
+
1
11
  ## [0.9.4] - 2023-09-17
2
12
 
3
13
  - Resolving gem push and sync glitch in 0.9.3
@@ -13,6 +23,7 @@
13
23
 
14
24
  - Dependency updates
15
25
  - Create shadow and audit with auatovacuum default turned off. Should avoid lock queues when disabling vacuum on audit table. #97
26
+
16
27
  ## [0.9.1] - 2023-06-24
17
28
 
18
29
  - Dependency updates and refresh docker release process with multi-platform build
data/Gemfile.lock CHANGED
@@ -1,21 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pg_online_schema_change (0.9.5)
4
+ pg_online_schema_change (0.9.7)
5
5
  ougai (~> 2.0.0)
6
6
  pg (>= 1.3.2, < 1.6.0)
7
7
  pg_query (>= 2.1.3, < 4.3.0)
8
- thor (~> 1.2.1)
8
+ thor (>= 1.2.1, < 1.4.0)
9
9
 
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
13
  ast (2.4.2)
14
- base64 (0.1.1)
14
+ bigdecimal (3.1.5)
15
15
  coderay (1.1.3)
16
16
  diff-lcs (1.5.0)
17
- google-protobuf (3.24.4-arm64-darwin)
18
- google-protobuf (3.24.4-x86_64-linux)
17
+ google-protobuf (3.25.2-arm64-darwin)
18
+ google-protobuf (3.25.2-x86_64-linux)
19
19
  haml (6.1.1)
20
20
  temple (>= 0.8.2)
21
21
  thor
@@ -23,11 +23,12 @@ GEM
23
23
  json (2.6.3)
24
24
  language_server-protocol (3.17.0.3)
25
25
  method_source (1.0.0)
26
- oj (3.16.1)
26
+ oj (3.16.3)
27
+ bigdecimal (>= 3.0)
27
28
  ougai (2.0.0)
28
29
  oj (~> 3.10)
29
30
  parallel (1.23.0)
30
- parser (3.2.2.3)
31
+ parser (3.2.2.4)
31
32
  ast (~> 2.4.1)
32
33
  racc
33
34
  pg (1.5.4)
@@ -39,9 +40,9 @@ GEM
39
40
  method_source (~> 1.0)
40
41
  racc (1.7.1)
41
42
  rainbow (3.1.1)
42
- rake (13.0.6)
43
+ rake (13.1.0)
43
44
  rbs (3.1.0)
44
- regexp_parser (2.8.1)
45
+ regexp_parser (2.8.2)
45
46
  rexml (3.2.6)
46
47
  rspec (3.12.0)
47
48
  rspec-core (~> 3.12.0)
@@ -56,19 +57,18 @@ GEM
56
57
  diff-lcs (>= 1.2.0, < 2.0)
57
58
  rspec-support (~> 3.12.0)
58
59
  rspec-support (3.12.0)
59
- rubocop (1.56.4)
60
- base64 (~> 0.1.1)
60
+ rubocop (1.57.2)
61
61
  json (~> 2.3)
62
62
  language_server-protocol (>= 3.17.0)
63
63
  parallel (~> 1.10)
64
- parser (>= 3.2.2.3)
64
+ parser (>= 3.2.2.4)
65
65
  rainbow (>= 2.2.2, < 4.0)
66
66
  regexp_parser (>= 1.8, < 3.0)
67
67
  rexml (>= 3.2.5, < 4.0)
68
68
  rubocop-ast (>= 1.28.1, < 2.0)
69
69
  ruby-progressbar (~> 1.7)
70
70
  unicode-display_width (>= 2.4.0, < 3.0)
71
- rubocop-ast (1.29.0)
71
+ rubocop-ast (1.30.0)
72
72
  parser (>= 3.2.1.0)
73
73
  rubocop-capybara (2.18.0)
74
74
  rubocop (~> 1.41)
@@ -97,7 +97,7 @@ GEM
97
97
  rbs
98
98
  syntax_tree (>= 2.0.1)
99
99
  temple (0.10.0)
100
- thor (1.2.2)
100
+ thor (1.3.0)
101
101
  tilt (2.1.0)
102
102
  unicode-display_width (2.5.0)
103
103
 
data/README.md CHANGED
@@ -9,10 +9,11 @@ pg-online-schema-change (`pg-osc`) is a tool for making schema changes (any `ALT
9
9
 
10
10
  `pg-osc` uses the concept of shadow table to perform schema changes. At a high level, it creates a shadow table that looks structurally the same as the primary table, performs the schema change on the shadow table, copies contents from the primary table to the shadow table and swaps the table names in the end while preserving all changes to the primary table using triggers (via audit table).
11
11
 
12
- `pg-osc` is inspired by the design and workings of tools like `pg_repack` and `pt-online-schema-change` (MySQL). Read more below on [how does it work](#how-does-it-work), [prominent features](#prominent-features), the [caveats](#caveats) and [examples](#examples)
12
+ `pg-osc` is inspired by the design and workings of tools like `pg_repack` and `pt-online-schema-change` (MySQL). Read more below on [how does it work](#how-does-it-work), [prominent features](#prominent-features), the [caveats](#few-things-to-keep-in-mind) and [examples](#examples)
13
13
 
14
14
  ## Table of Contents
15
15
 
16
+ - [Table of Contents](#table-of-contents)
16
17
  - [Installation](#installation)
17
18
  - [Requirements](#requirements)
18
19
  - [Usage](#usage)
@@ -22,15 +23,15 @@ pg-online-schema-change (`pg-osc`) is a tool for making schema changes (any `ALT
22
23
  - [Renaming a column](#renaming-a-column)
23
24
  - [Multiple ALTER statements](#multiple-alter-statements)
24
25
  - [Kill other backends after 5s](#kill-other-backends-after-5s)
26
+ - [Replaying larger workloads](#replaying-larger-workloads)
25
27
  - [Backfill data](#backfill-data)
26
28
  - [Running using Docker](#running-using-docker)
27
- - [Caveats](#caveats)
29
+ - [Few things to keep in mind](#few-things-to-keep-in-mind)
28
30
  - [How does it work](#how-does-it-work)
29
31
  - [Development](#development)
32
+ - [Local testing](#local-testing)
30
33
  - [Releasing](#releasing)
31
34
  - [Contributing](#contributing)
32
- - [License](#license)
33
- - [Code of Conduct](#code-of-conduct)
34
35
 
35
36
  ## Installation
36
37
 
@@ -103,12 +104,12 @@ print the version
103
104
  ## Prominent features
104
105
 
105
106
  - `pg-osc` supports when a column is being added, dropped or renamed with no data loss.
106
- - `pg-osc` acquires minimal locks throughout the process (read more below on the caveats).
107
+ - `pg-osc` acquires minimal locks throughout the process (read more below on the [caveats](#few-things-to-keep-in-mind)).
107
108
  - Copies over indexes and Foreign keys.
108
109
  - Optionally drop or retain old tables in the end.
110
+ - Reduce bloat (since pg-osc creates a new table and drops the old one post swap).
109
111
  - Tune how slow or fast should replays be from the audit/log table ([Replaying larger workloads](#replaying-larger-workloads)).
110
112
  - Backfill old/new columns as data is copied from primary table to shadow table, and then perform the swap. [Example](#backfill-data)
111
- - **TBD**: Ability to reverse the change with no data loss. [tracking issue](https://github.com/shayonj/pg-osc/issues/14)
112
113
 
113
114
  ## Load test
114
115
 
@@ -216,7 +217,7 @@ docker run --network host -it --rm shayonj/pg-osc:latest \
216
217
  --drop
217
218
  ```
218
219
 
219
- ## Caveats
220
+ ## Few things to keep in mind
220
221
 
221
222
  - Partitioned tables are not supported as of yet. Pull requests and ideas welcome.
222
223
  - A primary key should exist on the table; without it, `pg-osc` will raise an exception
@@ -229,8 +230,6 @@ docker run --network host -it --rm shayonj/pg-osc:latest \
229
230
  - Due to the nature of duplicating a table, there needs to be enough space on the disk to support the operation.
230
231
  - Index, constraints and sequence names will be altered and lose their original naming.
231
232
  - Can be fixed in future releases. Feel free to open a feature req.
232
- - Triggers are not carried over.
233
- - Can be fixed in future releases. Feel free to open a feature req.
234
233
  - Foreign keys are dropped & re-added to referencing tables with a `NOT VALID`. A follow on `VALIDATE CONSTRAINT` is run.
235
234
  - Ensures that integrity is maintained and re-introducing FKs doesn't acquire additional locks, hence the `NOT VALID`.
236
235
 
@@ -297,11 +296,3 @@ bundle exec bin/pg-online-schema-change perform -a 'ALTER TABLE pgbench_accounts
297
296
  ## Contributing
298
297
 
299
298
  Bug reports and pull requests are welcome on GitHub at https://github.com/shayonj/pg-osc.
300
-
301
- ## License
302
-
303
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
304
-
305
- ## Code of Conduct
306
-
307
- Everyone interacting in the PgOnlineSchemaChange project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/shayonj/pg-osc/blob/main/CODE_OF_CONDUCT.md).
@@ -67,5 +67,9 @@ module PgOnlineSchemaChange
67
67
 
68
68
  @copy_statement = File.binread(file_path)
69
69
  end
70
+
71
+ def checkout_connection
72
+ PG.connect(dbname: dbname, host: host, user: username, password: password, port: port)
73
+ end
70
74
  end
71
75
  end
@@ -5,6 +5,7 @@ require "securerandom"
5
5
  module PgOnlineSchemaChange
6
6
  class Orchestrate
7
7
  SWAP_STATEMENT_TIMEOUT = "5s"
8
+ TRACK_PROGRESS_INTERVAL = 60 # seconds
8
9
 
9
10
  extend Helper
10
11
 
@@ -58,6 +59,10 @@ module PgOnlineSchemaChange
58
59
 
59
60
  raise Error, "Parent table has no primary key, exiting..." if primary_key.nil?
60
61
 
62
+ logger.info("Performing some house keeping....")
63
+ run_analyze!
64
+ run_vacuum!
65
+
61
66
  setup_audit_table!
62
67
 
63
68
  setup_trigger!
@@ -159,6 +164,12 @@ module PgOnlineSchemaChange
159
164
  end
160
165
 
161
166
  def setup_shadow_table!
167
+ logger.info("Setting up shadow table", { shadow_table: shadow_table })
168
+ Query.run(
169
+ client.connection,
170
+ "SELECT create_table_all('#{client.table_name}', '#{shadow_table}');",
171
+ )
172
+
162
173
  # re-uses transaction with serializable
163
174
  # This ensures that all queries from here till copy_data run with serializable.
164
175
  # This is to to ensure that once the trigger is added to the primay table
@@ -168,13 +179,6 @@ module PgOnlineSchemaChange
168
179
  # adding the trigger, till the copy ends, since they all happen in the
169
180
  # same serializable transaction.
170
181
  Query.run(client.connection, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", true)
171
- logger.info("Setting up shadow table", { shadow_table: shadow_table })
172
-
173
- Query.run(
174
- client.connection,
175
- "SELECT create_table_all('#{client.table_name}', '#{shadow_table}');",
176
- true,
177
- )
178
182
 
179
183
  # update serials
180
184
  Query.run(
@@ -208,19 +212,44 @@ module PgOnlineSchemaChange
208
212
  )
209
213
  Query.run(client.connection, "DELETE FROM #{audit_table}", true)
210
214
 
211
- logger.info(
212
- "Copying contents..",
213
- { shadow_table: shadow_table, parent_table: client.table_name },
214
- )
215
215
  if client.copy_statement
216
216
  query = format(client.copy_statement, shadow_table: shadow_table)
217
217
  return Query.run(client.connection, query, true)
218
218
  end
219
219
 
220
+ logger.info(
221
+ "Copying contents..",
222
+ { shadow_table: shadow_table, parent_table: client.table_name },
223
+ )
224
+
225
+ @copy_finished = false
226
+ log_progress
227
+
220
228
  sql = Query.copy_data_statement(client, shadow_table, true)
221
229
  Query.run(client.connection, sql, true)
222
230
  ensure
223
231
  Query.run(client.connection, "COMMIT;") # commit the serializable transaction
232
+ @copy_finished = true
233
+ end
234
+
235
+ def log_progress
236
+ new_connection = client.checkout_connection
237
+ source_table_size = Query.get_table_size(new_connection, client.schema, client.table_name)
238
+
239
+ Thread.new do
240
+ loop do
241
+ break if @copy_finished
242
+
243
+ shadow_table_size = Query.get_table_size(new_connection, client.schema, shadow_table)
244
+ progress = (shadow_table_size.to_f / source_table_size) * 100
245
+ logger.info("Estimated copy progress: #{progress.round(2)}% complete")
246
+
247
+ break if @copy_finished || progress >= 100
248
+ sleep(TRACK_PROGRESS_INTERVAL) unless ENV["CI"]
249
+ rescue StandardError => e
250
+ logger.info("Reporting progress failed: #{e.message}")
251
+ end
252
+ end
224
253
  end
225
254
 
226
255
  def replay_and_swap!
@@ -272,7 +301,13 @@ module PgOnlineSchemaChange
272
301
  def run_analyze!
273
302
  logger.info("Performing ANALYZE!")
274
303
 
275
- Query.run(client.connection, "ANALYZE VERBOSE #{client.table_name};")
304
+ client.connection.async_exec("ANALYZE VERBOSE #{client.schema}.#{client.table_name};")
305
+ end
306
+
307
+ def run_vacuum!
308
+ logger.info("Performing VACUUM!")
309
+
310
+ client.connection.async_exec("VACUUM VERBOSE #{client.schema}.#{client.table_name};")
276
311
  end
277
312
 
278
313
  def validate_constraints!
@@ -311,20 +311,25 @@ module PgOnlineSchemaChange
311
311
 
312
312
  def view_definitions_for(client, table)
313
313
  query = <<~SQL
314
- SELECT DISTINCT dependent_view.relname as view_name, pg_get_viewdef(format('%I.%I', '#{client.schema}', dependent_view.relname)::regclass) as view_definition
315
- FROM pg_depend
316
- JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
317
- JOIN pg_class as dependent_view ON pg_rewrite.ev_class = dependent_view.oid
318
- JOIN pg_class as source_table ON pg_depend.refobjid = source_table.oid
319
- JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
320
- WHERE
321
- source_ns.nspname = '#{client.schema}'
322
- AND source_table.relname = '#{table}'
314
+ SELECT DISTINCT
315
+ dependent_view.relname AS view_name,
316
+ pg_get_viewdef(dependent_view.oid) AS view_definition,
317
+ view_ns.nspname AS schema_name
318
+ FROM pg_class AS source_table
319
+ JOIN pg_depend ON pg_depend.refobjid = source_table.oid
320
+ JOIN pg_rewrite ON pg_rewrite.oid = pg_depend.objid
321
+ JOIN pg_class AS dependent_view ON dependent_view.oid = pg_rewrite.ev_class
322
+ JOIN pg_namespace AS view_ns ON dependent_view.relnamespace = view_ns.oid
323
+ AND dependent_view.relkind = 'v'
324
+ AND source_table.relname = '#{table}';
323
325
  SQL
324
326
 
325
327
  definitions = []
326
328
  run(client.connection, query) do |result|
327
- definitions = result.map { |row| { row["view_name"] => row["view_definition"].strip } }
329
+ definitions =
330
+ result.map do |row|
331
+ { "#{row["schema_name"]}.#{row["view_name"]}" => row["view_definition"].strip }
332
+ end
328
333
  end
329
334
 
330
335
  definitions
@@ -423,6 +428,15 @@ module PgOnlineSchemaChange
423
428
  SELECT setval((select pg_get_serial_sequence('#{shadow_table}', '#{primary_key}')), (SELECT max(#{primary_key}) FROM #{table}));
424
429
  SQL
425
430
  end
431
+
432
+ def get_table_size(connection, schema, table_name)
433
+ size_query = "SELECT pg_table_size('#{schema}.#{table_name}');"
434
+ result = run(connection, size_query).first
435
+ result["pg_table_size"].to_i
436
+ rescue StandardError => e
437
+ logger.error("Error getting table size: #{e.message}")
438
+ 0
439
+ end
426
440
  end
427
441
  end
428
442
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgOnlineSchemaChange
4
- VERSION = "0.9.5"
4
+ VERSION = "0.9.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_online_schema_change
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shayon Mukherjee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-15 00:00:00.000000000 Z
11
+ date: 2024-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ougai
@@ -68,16 +68,22 @@ dependencies:
68
68
  name: thor
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - "~>"
71
+ - - ">="
72
72
  - !ruby/object:Gem::Version
73
73
  version: 1.2.1
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: 1.4.0
74
77
  type: :runtime
75
78
  prerelease: false
76
79
  version_requirements: !ruby/object:Gem::Requirement
77
80
  requirements:
78
- - - "~>"
81
+ - - ">="
79
82
  - !ruby/object:Gem::Version
80
83
  version: 1.2.1
84
+ - - "<"
85
+ - !ruby/object:Gem::Version
86
+ version: 1.4.0
81
87
  - !ruby/object:Gem::Dependency
82
88
  name: prettier_print
83
89
  requirement: !ruby/object:Gem::Requirement
@@ -305,7 +311,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
305
311
  requirements:
306
312
  - - ">="
307
313
  - !ruby/object:Gem::Version
308
- version: 2.7.0
314
+ version: 3.0.0
309
315
  required_rubygems_version: !ruby/object:Gem::Requirement
310
316
  requirements:
311
317
  - - ">="