数据库迁移期间 PostgreSQL 共享内存不足

问题描述

由外部 PostgreSQL 支持的新创建 GitLab 实例始终无法就绪。迁移 Job 失败并进入 CrashLoopBackOff,而 sidekiq 和 webservice 一直停留在 Init,因为它们在等待该 Job:

NAME                                  READY   STATUS
<RELEASE>-migrations-xxxxxxx-xxxxx    0/1     CrashLoopBackOff
<RELEASE>-sidekiq-all-in-1-v2-xxxxx   0/1     Init:CrashLoopBackOff
<RELEASE>-webservice-default-xxxxx    0/2     Init:CrashLoopBackOff

迁移日志显示,架构加载在中途失败:

Running db:schema:load rake task
psql:/srv/gitlab/db/structure.sql:52247: ERROR:  out of shared memory
HINT:  You might need to increase max_locks_per_transaction.
rake aborted!
failed to execute:
psql --set ON_ERROR_STOP=1 --quiet --no-psqlrc --output /dev/null \
  --file /srv/gitlab/db/structure.sql --single-transaction <DATABASE>

HINT 行指出了需要更改的参数。如果日志在此之前就被截断,则 ERROR: out of shared memory 行中的 structure.sql 也是同一个问题。

请注意,数据库连接本身正常——同一日志中更早的行确认 GitLab 已连接到 PostgreSQL 和 Redis:

Checking: main
Database Schema - main (<DATABASE>)
NOTICE: Database has not been initialized yet.
NOTICE: There are NNNN pending migrations.

根本原因

这里的 out of shared memory 不是 shared_buffers 或主机内存耗尽的问题,而是 PostgreSQL 的锁表已被占满。

GitLab 使用 psql --single-transaction 加载架构,因此整个 db/structure.sql——数千个表、索引、约束和序列——都会在一个事务中创建,并且它涉及的每个对象都会占用一个锁。PostgreSQL 在启动时根据 max_locks_per_transaction 确定共享锁表的大小,所需锁数量超过该值的事务会因 out of shared memory 而失败。

默认值为 64,远低于 GitLab 架构加载所需的值。任何仍使用默认值的 PostgreSQL 都会以这种方式失败,因此该现象会出现在全新实例上,而不是在负载下才出现。

故障排除

读取迁移 Job 日志,并确认失败原因为 out of shared memory,且发生在 structure.sql 行,而不是连接或身份验证错误:

kubectl -n <NAMESPACE> logs job/<RELEASE>-migrations-xxxxxxx

然后在 PostgreSQL 端检查该设置:

SHOW max_locks_per_transaction;
SHOW max_connections;

如果值为 64(内置默认值),并且同时出现上述错误,即可确认原因。

解决方案

在 PostgreSQL 实例上增大 max_locks_per_transaction 并重启实例,然后删除失败的 GitLab 实例的迁移 Job,使其重新运行(或重新创建该实例)。

256 是一个安全的起始值。该参数不是动态参数:PostgreSQL 会在启动时分配锁表,因此需要重启。

具体设置方式取决于 PostgreSQL 的管理方式。

对于平台的数据服务 PostgreSQL,该参数通过实例的参数配置进行设置——请参阅 Creating a PostgreSQL Instance。若要从命令行在现有实例上更改该参数,请修补 CR 并让 operator 推出更改:

kubectl -n <NAMESPACE> patch postgresql <PG_INSTANCE> --type merge -p \
  '{"spec":{"postgresql":{"parameters":{"max_locks_per_transaction":"256"}}}}'
WARNING

spec.postgresql.parameters 会替换整个映射,因此请包含所有已设置的参数——先读取当前值并手动合并,否则这些参数会在不知不觉中丢失。

等待推出完成,然后在数据库中确认新值——CR 再次报告 Running 仅表示 operator 已完成操作,并不表示参数已生效:

# 1. the operator has finished rolling the instance
kubectl -n <NAMESPACE> get postgresql <PG_INSTANCE> -o jsonpath='{.status.PostgresClusterStatus}'

# 2. the value is actually live
kubectl -n <NAMESPACE> exec <PG_POD> -c postgres -- \
  psql -U postgres -tAc 'SHOW max_locks_per_transaction'
WARNING

在多副本实例上,请将任何写入操作发送到主节点。副本为只读,并会因 cannot execute ... in a read-only transaction 拒绝 DDL,这很容易被误认为是权限问题。(SHOW 为只读操作,可在任意节点上运行。)通过其标签查找主节点:

kubectl -n <NAMESPACE> get pods -l application=spilo,spilo-role=master

GitLab 是 GitLab Inc. 的商标。

Alauda 是独立供应商。本产品与 GitLab Inc. 无关联,也未获得其认可或赞助。所有商标均归其各自所有者所有,此处仅用于标识目的。