100k DDL 语句上的 Postgres 事务 OOM

The*_*der 7 postgresql performance transaction ddl postgresql-9.6 postgresql-performance

我们在 PostgreSQL 的单个事务中执行大约 100k DDL 语句。在执行过程中,各个 Postgres 连接的内存使用量逐渐增加,一旦它无法获得更多内存(在 3GB 内存上从 10MB 使用量增加到 2.2GB),OOM 杀手用 9 命中它,导致 Postgres 进入恢复模式.

BEGIN;

CREATE SCHEMA schema_1;
-- create table stmts - 714
-- alter table add pkey stmts - 714
-- alter table add constraint fkey stmts - 34
-- alter table add unique constraint stmts - 2
-- alter table alter column set default stmts - 9161
-- alter table alter column set not null stmts - 2405
-- alter table add check constraint stmts - 4
-- create unique index stmts - 224
-- create index stmts - 213

CREATE SCHEMA schema_2;
-- same ddl statements as schema_1 upto schema_7
-- ...
-- ...
-- ...
CREATE SCHEMA schema_7;

COMMIT
Run Code Online (Sandbox Code Playgroud)

包括 create schema 语句,大约有 94304 条 DDL 语句将被执行。

根据PostgreSQL 中的事务 DDL

与它的几个商业竞争对手一样,PostgreSQL 更高级的特性之一是它能够通过它的预写日志设计执行事务性 DDL。这种设计支持回退对 DDL 的甚至较大的更改,例如表创建。您无法从数据库或表空间的添加/删除中恢复,但所有其他目录操作都是可逆的。

我们甚至在一个事务中将大约 35GB 的数据导入 PostgreSQL 没有任何问题,但是为什么在单个事务中执行数千条 DDL 语句时,Postgres 连接需要大量内存?

我们可以通过增加 RAM 或分配交换来临时解决它,但我们可以说单个事务中的模式创建数量最多可以增加到 50 - 60(大约 1M DDL 语句),这将需要 100+ Gig 的 RAM 或交换这现在不可行。

PostgreSQL 版本:9.6.10

为什么执行大量 DDL 语句需要更多内存而 dml 语句不需要?不是都通过写入底层 WAL 来处理事务吗?那么为什么,对于 DLL,它是不同的?

单笔交易原因

我们将整个客户数据库从客户场所 (SQL Server) 同步到云 (PostgreSQL)。所有客户都有不同的数据库。过程是,整个数据将从 SQL Server 生成为 CSV,并使用临时表、COPY 和 ON CONFLICT DO UPDATE 导入到 PostgreSQL。在此过程中,我们将每个客户视为 PG 中的单个数据库,将客户 SQL Server 中的单个 DB 视为客户 PG DB 中的架构。

因此,基于 CSV 数据,我们将动态创建模式并将数据导入其中。根据我们的应用程序设计,PG 中的数据在任何时间点都应该严格一致,不应该有任何部分的模式/表/数据。因此,我们必须在单个事务中实现这一目标。此外,我们每 3 分钟从客户增量同步到云数据库。所以模式创建可以在第一次同步或增量同步中发生。但是在第一次同步中创建这么多模式的可能性非常高。

更新 1

注释ALTER TABLE ALTER COLUMN语句大大减少了内存使用量,因为现在最多只需要 300MB。必须将它们合并到CREATE TABLE语句本身中。

将在 PG Hackers 邮件列表中询问核心问题。

jja*_*nes 3

src/backend/utils/cache/relcache.c 中的这段注释似乎相关:

    * If we Rebuilt a relcache entry during a transaction then its
    * possible we did that because the TupDesc changed as the result
    * of an ALTER TABLE that ran at less than AccessExclusiveLock.
    * It's possible someone copied that TupDesc, in which case the
    * copy would point to free'd memory. So if we rebuild an entry
    * we keep the TupDesc around until end of transaction, to be safe.
    */
    if (remember_tupdesc)
        RememberToFreeTupleDescAtEOX(relation->rd_att);
Run Code Online (Sandbox Code Playgroud)

我不太明白,这个可能有指针的“人”是谁?这是私有内存,而不是共享内存。不管怎样,它似乎确实解释了膨胀,因为同一事务中的每个“alter table”语句都会留下该表的 TupDesc 的另一个副本。显然,即使您在一个操作中使用多个操作alter table,每个单独的操作也会留下一个副本。但无论优点如何,这确实解释了内存使用的很大一部分。

请参阅pg hackers邮件列表以获取更多讨论。