8 个版本
0.3.0 | 2020年7月10日 |
---|---|
0.2.3 | 2020年7月10日 |
0.2.2 | 2020年6月26日 |
0.1.4 | 2020年6月22日 |
#1302 在 开发工具 中
每月 32 次下载
140KB
3.5K SLoC
squawk
Postgres 迁移代码检查工具
为什么?
防止由数据库迁移引起的意外停机。
这看起来是一个很好的项目,可以更多地使用 Rust。
安装
注意:由于 squawk
依赖于 libpg_query
,squawk
只支持 Linux 和 macOS。
npm install -g squawk-cli
cargo install squawk
# or install binaries directly via the releases page
https://github.com/sbdchd/squawk/releases
用法
❯ squawk example.sql
example.sql:2:1: warning: prefer-text-field
2 | --
3 | -- Create model Bar
4 | --
5 | CREATE TABLE "core_bar" (
6 | "id" serial NOT NULL PRIMARY KEY,
7 | "alpha" varchar(100) NOT NULL
8 | );
note: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock.
help: Use a text field with a check constraint.
example.sql:9:2: warning: require-concurrent-index-creation
9 |
10 | CREATE INDEX "field_name_idx" ON "table_name" ("field_name");
note: Creating an index blocks writes.
note: Create the index CONCURRENTLY.
example.sql:11:2: warning: disallowed-unique-constraint
11 |
12 | ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name);
note: Adding a UNIQUE constraint requires an ACCESS EXCLUSIVE lock which blocks reads.
help: Create an index CONCURRENTLY and create the constraint using the index.
example.sql:13:2: warning: adding-field-with-default
13 |
14 | ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10;
note: In Postgres versions <11 adding a field with a DEFAULT requires a table rewrite with an ACCESS EXCLUSIVE lock.
help: Add the field as nullable, then set a default, backfill, and remove nullabilty.
squawk--help
squawk
Find problems in your SQL
USAGE:
squawk [FLAGS] [OPTIONS] [paths]... [SUBCOMMAND]
FLAGS:
-h, --help
Prints help information
--list-rules
List all available rules
-V, --version
Prints version information
OPTIONS:
--dump-ast <dump-ast>
Output AST in JSON [possible values: Raw, Parsed]
-e, --exclude <exclude>...
Exclude specific warnings
For example: --exclude=require-concurrent-index-creation,ban-drop-database
--explain <explain>
Provide documentation on the given rule
--reporter <reporter>
Style of error reporting [possible values: Tty, Gcc, Json]
ARGS:
<paths>...
Paths to search
SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
upload-to-github Comment on a PR with Squawk's results
规则
可以通过 --exclude
标志禁用单个规则
squawk --exclude=adding-field-with-default,disallowed-unique-constraint example.sql
require-concurrent-index-creation
确保所有索引创建都使用 CONCURRENTLY
选项。
此规则忽略同一事务中创建的表添加的索引。
在正常索引创建期间会阻止更新。 CONCURRENTLY
避免了阻止的问题。
https://postgresql.ac.cn/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY
constraint-missing-not-valid
检查所有新约束是否具有 NOT VALID
。
默认情况下,新约束需要扫描表并阻止对表的写入。使用 NOT VALID
与后续的 VALIDATE CONSTRAINT
调用一起使用可以防止表扫描,并且验证步骤只需要 SHARE UPDATE EXCLUSIVE
锁。
https://postgresql.ac.cn/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
adding-field-with-default
在 PostgreSQL 版本小于 11 的情况下,添加具有 DEFAULT
的字段需要使用具有 ACCESS EXCLUSIVE
锁的表重写。
https://postgresql.ac.cn/docs/10/sql-altertable.html#SQL-ALTERTABLE-NOTES
changing-column-type
更改列类型需要获取表上的 ACCESS EXCLUSIVE
锁定,这将阻止读取操作。
更改列类型可能会破坏正在从表中读取的其他客户端。
https://postgresql.ac.cn/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
adding-not-nullable-field
添加 NOT NULL
约束需要表扫描,并且执行 ALTER TABLE
操作需要 ACCESS EXCLUSIVE
锁定。
通常,这会与一个 DEFAULT
配合使用,但它在低于 \11 的版本中存在问题。请参阅 adding-field-with-default
规则。
renaming-column
重命名列可能会破坏现有的客户端。
renaming-table
重命名表可能会破坏现有的客户端。
disallowed-unique-constraint
添加 UNIQUE
约束需要 ACCESS EXCLUSIVE
锁定,这将阻止读取操作。
相反,可以创建一个 CONCURRENTLY
索引,并使用索引创建约束。
https://postgresql.ac.cn/docs/current/sql-altertable.html
ban-drop-database
删除数据库可能会破坏现有的客户端。
prefer-text-field
更改 varchar
字段的长度需要 ACCESS EXCLUSIVE
锁定。
使用带有 CHECK CONSTRAINT
的文本字段可以更容易地更改最大长度。请参阅 constraint-missing-not-valid
规则。
prefer-robust-stmts
本规则的目的是使迁移在部分失败时更加健壮。
例如,可能有一个包含两个步骤的迁移。首先,迁移向表中添加一个字段,然后它创建一个并发索引。
由于第二部分是并发的,它不能在事务中运行,因此迁移的第一部分可以成功,而第二部分可能会失败,这意味着第一部分不会被回滚。
然后当再次运行迁移时,它将在添加字段时失败,因为它已经存在。
为了满足此规则,可以使用如 IF NOT EXISTS
的守卫,或将所有语句包裹在事务中。
Bot 设置
Squawk 作为 CLI 工具使用,但也可以使用 upload-to-github
子命令在 GitHub Pull Requests 上创建注释。
以下是 squawk
使用存储库中的 example.sql
创建的示例注释
https://github.com/sbdchd/squawk/pull/14#issuecomment-647009446
创建一个新的应用
Squawk 需要一个相应的 GitHub App,以便它可以与 GitHub 通信。
-
创建应用
-
添加应用名称和主页 URL
-
取消选中 Webhook 下的
active
复选框 -
添加权限
名称 类型 原因 拉取请求 写入 在 PR 上发表评论 点击创建并复制 "关于" 部分下的
App ID
-
转到页面底部的 "私钥" 部分,并点击 "生成私钥"
几秒钟后,私钥将自动下载。请保存此密钥,我们稍后会用到。
现在我们有了
App ID
和Private Key
,现在我们需要安装应用 -
安装应用程序并获取安装ID
前往 https://github.com/settings/apps/$YOUR_APP_NAME/installations 并点击 "安装"。
GitHub 应该会将您重定向到 https://github.com/settings/installations/$INSTALL_ID 页面,其中
$INSTALL_ID
是一个数字。保存此ID以备后用。
现在我们有我们的
SQUAWK_GITHUB_APP_ID
、SQUAWK_GITHUB_PRIVATE_KEY
、SQUAWK_GITHUB_INSTALL_ID
。Squawk 需要与拉取请求相关的值:
SQUAWK_GITHUB_REPO_NAME
、SQUAWK_GITHUB_REPO_OWNER
、和SQUAWK_GITHUB_PR_NUMBER
。这些值的查找方式取决于您如何运行 Squawk,但下一步我假设您正在以 CircleCI 作业的形式运行 Squawk。
-
查找拉取请求变量
CircleCI
https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables
CIRCLE_PULL_REQUEST
包含我们需要的值示例:
https://github.com/recipeyak/recipeyak/pull/567
现在我们需要分割这个链接以获取仓库名称、仓库所有者和拉取请求 ID。
借助一些帮助
echo "https://github.com/recipeyak/recipeyak/pull/567" | awk -F/ '{print $4 " " $5 " " $7}' recipeyak recipeyak 567
SQUAWK_GITHUB_REPO_OWNER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $4}') SQUAWK_GITHUB_REPO_NAME=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $5}') SQUAWK_GITHUB_PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $7}')
-
结论
汇总所有信息,我们应该有以下环境变量
SQUAWK_GITHUB_APP_ID= # fill in with id found in step 5 SQUAWK_GITHUB_INSTALL_ID= # fill in with id found in step 7 # downloaded via step 6, your key will have a different name SQUAWK_GITHUB_PRIVATE_KEY=$(cat ./cool-bot-name.private-key.pem) # can also use the SQUAWK_GITHUB_PRIVATE_KEY_BASE64 instead ^ SQUAWK_GITHUB_REPO_OWNER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $4}') SQUAWK_GITHUB_REPO_NAME=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $5}') SQUAWK_GITHUB_PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $7}')
我们可以在运行 Squawk 之前将这些变量传递到环境中,或者将它们转换为命令行标志。您觉得哪种方式更方便。
示例运行如下(假设已设置环境变量)
squawk upload-to-github example.sql
这会创建如下评论
https://github.com/sbdchd/squawk/pull/14#issuecomment-647009446
现有技术
相关工具
- https://github.com/yandex/zero-downtime-migrations
- https://github.com/tbicr/django-pg-zero-downtime-migrations
- https://github.com/3YOURMIND/django-migration-linter
相关博客文章/SE 文章/PG 文档
- https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/
- https://gocardless.com/blog/zero-downtime-postgres-migrations-the-hard-parts/
- https://www.citusdata.com/blog/2018/02/22/seven-tips-for-dealing-with-postgres-locks/
- https://realpython.com/create-django-index-without-downtime/#non-atomic-migrations
- https://dba.stackexchange.com/questions/158499/postgres-how-is-set-not-null-more-efficient-than-check-constraint
- https://postgresql.ac.cn/docs/10/sql-altertable.html#SQL-ALTERTABLE-NOTES
- https://postgresql.ac.cn/docs/current/explicit-locking.html
- https://benchling.engineering/move-fast-and-migrate-things-how-we-automated-migrations-in-postgres-d60aba0fc3d4
开发
cargo install
cargo run
./s/test
./s/lint
./s/fmt
发布新版本
- 更新 CHANGELOG.md
- 在所有依赖项的
Cargo.toml
以及 CLI 的Cargo.toml
中提升版本 - 在 GitHub 上创建新的发布 - CI 会自动附加二进制文件
- 在
package.json
中提升版本并遵循npm
步骤 - 以 DAG 方式将每个 crate 发布到 cargo
工作原理
Squawk 将对 libpg_query-sys 的调用封装在安全接口中,并将 JSON 解析为更容易处理的结构。libpg_query-sys 然后 使用 bindgen 绑定到 libpg_query,它本身使用一些 C 代码将 Postgres 的 SQL 解析器封装起来,并将解析后的抽象语法树 (AST) 输出为 JSON 字符串。
Squawk 然后在解析后的 AST 上运行规则函数,收集并格式化输出规则违规。
依赖关系
~60MB
~1M SLoC