介绍
pg-online-schema-change (pg-osc) 是一个工具,用于以最小的锁在 PostgreSQL 表中进行模式更改(任何ALTER语句),以帮助实现在生产环境工作负载下进行零停机的模式更改。
pg-osc使用了影子表的概念来执行模式更改。在较高级别上,它会创建一个在结构上与主表相同的影子表,对影子表执行模式更改,将内容从主表复制到影子表,并在最后交换表名称,同时使用触发器(通过审计表)保留对主表的所有更改。
pg-osc受到了pt-online-schema-change (MySQL) 和pg_repack等工具的设计和工作原理的启发。可在下面阅读更多内容,关于它是如何工作的、亮点特性、注意事项和示例。
用法
pg-online-schema-change help perform
Usage:
pg-online-schema-change perform -a,--alter-statement=ALTER_STATEMENT -d,--dbname=DBNAME -h,--host=HOST -p,--port=N -s,--schema=SCHEMA -u,--username=USERNAME
Options:
-a,--alter-statement=ALTER_STATEMENT # The ALTER statement to perform the schema change
-s,--schema=SCHEMA # The schema in which the table is. Default: public
-d,--dbname=DBNAME # Name of the database
-h,--host=HOST # Server host where the Database is located
-u,--username=USERNAME # Username for the Database
-p,--port=N # Port for the Database. Default: 5432
-w,[--password=PASSWORD] # DEPRECATED: Password for the Database. Please pass PGPASSWORD environment variable instead.
-v,[--verbose],[--no-verbose] # Emit logs in debug mode
-f,[--drop],[--no-drop] # Drop the original table in the end after the swap
-k,[--kill-backends],[--no-kill-backends] # Kill other competing queries/backends when trying to acquire lock for the shadow table creation and swap. It will wait for --wait-time-for-lock duration before killing backends and try upto 3 times.
-w,[--wait-time-for-lock=N] # Time to wait before killing backends to acquire lock and/or retrying upto 3 times. Default: 10. It will kill backends if --kill-backends is true, otherwise try upto 3 times and exit if it cannot acquire a lock.
-c,[--copy-statement=COPY_STATEMENT] # Takes a .sql file location where you can provide a custom query to be played (ex: backfills) when pgosc copies data from the primary to the shadow table. More examples in README.
-b,[--pull-batch-count=N] # Number of rows to be replayed on each iteration after copy. Default: 1000. This can be tuned for faster catch up and swap. Best used with delta-count.
-e,[--delta-count=N] # Indicates how many rows should be remaining before a swap should be performed. Default: 20. This can be tuned for faster catch up and swap, especially on highly volume tables. Best used with pull-batch-count.
-o,[--skip-foreign-key-validation],[--no-skip-foreign-key-validation] # Skip foreign key validation after swap. You shouldn't need this unless you have a very specific use case, like manually validating foreign key constraints after swap.
Usage:
pg-online-schema-change --version, -v
print the version
亮点特性
- • pg-osc支持在添加、删除或重命名列时运行,而不会丢失数据。
- • pg-osc在整个过程中只获取最少的锁(阅读下面的警告)。
- • 复制索引和外键。
- • 可以在最后删除或保留旧表(可选)。
- • 减少膨胀(因为 pg-osc 会创建一个新表,并在交换后删除旧表)。
- • 调整审计/日志表中重放速度的快慢(重放更大的工作负载)。
- • 在将数据从主表复制到影子表时,回填旧列/新列,然后执行交换。示例。
示例
重命名列
export PGPASSWORD=""
pg-online-schema-change perform \
--alter-statement 'ALTER TABLE books RENAME COLUMN email TO new_email' \
--dbname "postgres" \
--host "localhost" \
--username "jamesbond"
多个 ALTER 语句
export PGPASSWORD=""
pg-online-schema-change perform \
--alter-statement 'ALTER TABLE books ADD COLUMN "purchased" BOOLEAN DEFAULT FALSE; ALTER TABLE books RENAME COLUMN email TO new_email;' \
--dbname "postgres" \
--host "localhost" \
--username "jamesbond" \
--drop
5 秒后杀死其他后端
如果操作是在一个繁忙的表上执行的,则可以使用pg-osc的kill-backend功能,来终止可能与pg-osc操作竞争的其他后端,以便在短时间内获取锁。pg-osc获得的ACCESS EXCLUSIVE锁只持有一小段时间,然后就释放了。您可以调整在杀死其他后端之前pg-osc应该等待多长时间(或者pg-osc是否应该在一开始就杀死后端)。
export PGPASSWORD=""
pg-online-schema-change perform \
--alter-statement 'ALTER TABLE books ADD COLUMN "purchased" BOOLEAN DEFAULT FALSE;' \
--dbname "postgres" \
--host "localhost" \
--username "jamesbond" \
--wait-time-for-lock5 \
--kill-backends \
--drop
重放更大的工作负载
如果表的写入吞吐量较高,则默认的重放速度可能不够用。也就是说,您可能会看到pg-osc从审计表中一次性重放 1000 行(pull-batch-count)。pg-osc还会等到审计表中的剩余行数(delta-count)为 20 后再进行交换。您可以将这些值调整为更高的值,以便更快地赶上此类工作负载。
export PGPASSWORD=""
pg-online-schema-change perform \
--alter-statement 'ALTER TABLE books ADD COLUMN "purchased" BOOLEAN DEFAULT FALSE;' \
--dbname "postgres" \
--host "localhost" \
--username "jamesbond" \
--pull-batch-count 2000
--delta-count 500
--wait-time-for-lock5 \
--kill-backends \
--drop
回填数据
在将数据插入到影子表时,您可以传入自定义 sql 文件,来执行复制和任何其他工作,而不仅仅是从主表复制所有列和行。例如:回填某些列。通过提供copy-statement,pg-osc将改为运行查询以执行复制操作。
重要提示:
• 可能会意外违反约束或不复制数据,因此请谨慎操作。
• 在自定义 SQL 中连接时,必须使用 OUTER JOIN,否则会丢失与连接表不匹配的行。
• ALTER语句可能会更改表的结构,因此请谨慎操作。
• 保留%{shadow_table},因为它会替换为影子表的目标。
• 强烈建议用户在生产环境上使用之前,先测试和验证结果!
-- file: /src/query.sql
INSERT INTO %{shadow_table}(foo, bar, baz, rental_id, tenant_id)
SELECT a.foo,a.bar,a.baz,a.rental_id,r.tenant_id AS tenant_id
FROM ONLY examples a
LEFT OUTER JOIN rentals r
ON a.rental_id = r.id
pg-online-schema-change perform \
--alter-statement 'ALTER TABLE books ADD COLUMN "tenant_id" VARCHAR;' \
--dbname "postgres" \
--host "localhost" \
--username "jamesbond" \
--copy-statement "/src/query.sql" \
--drop
使用 Docker 运行
docker run --network host -it --rm shayonj/pg-osc:latest \
pg-online-schema-change perform \
--alter-statement 'ALTER TABLE books ADD COLUMN "purchased" BOOLEAN DEFAULT FALSE; ALTER TABLE books RENAME COLUMN email TO new_email;' \
--dbname "postgres" \
--host "localhost" \
--username "jamesbond" \
--drop
注意事项
• 目前尚不支持分区表。欢迎提供 PR 请求和想法。
• 表上应存在主键;没有的话,pg-osc会引发异常
• 这是因为,目前没有其他方法可以在重放期间唯一标识行。
• pg-osc会在操作期间两次获取父表上的ACCESS EXCLUSIVE锁。
• 第一次,在设置触发器和影子表时。
• 下一次,在执行交换和更新外键引用时。
• 注意:如果指定了kill-backends,它会尝试终止在这两个时间内的任何竞争操作。
• 根据设计,pg-osc不会终止正在执行的任何其他 DDL。在操作过程中,最好不要对父表运行任何 DDL。
• 由于复制表的性质,磁盘上需要有足够的空间来支持该操作。
• 索引、约束和序列名称会被更改,并失去其原始命名。
• 可以在将来的版本中修复。如有需要,可创建特性请求。
• 外键会被删除,并重新以NOT VALID的方式添加到引用表。接着运行VALIDATE CONSTRAINT操作。
• 要确保保持完整性,并在重新引入外键时不用获取额外的锁,因此才会使用NOT VALID。
它是如何工作的
此工具中有 3 种类型的表:
- • 主表:可能要对其运行模式更改的表
- • 影子表:现有主表的副本表
- • 审计表:用于存储主表上的任何更新/插入/删除的表
how-it-works
1. 创建一个审计表,以记录对父表所做的更改。
2. 获取一个简短的ACCESS EXCLUSIVE锁,以在父表上添加触发器,记录插入、更新、删除操作到审计表。
3. 创建一个新的影子表,并在影子表上运行 ALTER 或迁移。
4. 复制旧表中的所有行。
5. 在新表上构建索引。
6. 将审计表中累积的所有更改重放到影子表中。
• 在审计审计表中的行时,删除这些行。
7. 一旦差量行数(剩余行数)到 ~20,则在事务中获取父表上的ACCESS EXCLUSIVE锁,然后:
• 交换表名称(影子表 <> 父表)。
• 删除外键,并重新以NOT VALID的方式创建,以更新其他表中的外键引用。
8. 在新表上运行ANALYZE。
9. 验证所有添加的NOT VALID的外键。
10. 删除父表(现在是旧表)(可选)。