Postgresql

100k DDL 語句上的 Postgres 事務 OOM

  • October 15, 2018

我們在 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

包括創建模式語句,大約有 94304 條 DDL 語句將被執行。

根據PostgreSQL 中的事務性 DDL

像它的幾個商業競爭對手一樣,PostgreSQL 更高級的特性之一是它能夠通過其預寫日誌設計執行事務 DDL。這種設計甚至支持撤銷對 DDL 的較大更改,例如表創建。您無法從數據庫或表空間上的添加/刪除中恢復,但所有其他目錄操作都是可逆的。

我們甚至在單個事務中將大約 35GB 的數據導入 PostgreSQL 沒有任何問題,但是為什麼在單個事務中執行數千個 DDL 語句時 Postgres 連接需要巨大的記憶體?

我們可以通過增加 RAM 或分配交換來臨時解決它,但我們可以說在單個事務中創建模式的數量可以增加到 50 - 60(大約 1M DDL 語句),這需要 100+ Gigs 的 RAM 或交換這現在不可行。

PostgreSQL 版本:9.6.10

為什麼執行大量 DDL 語句需要更多記憶體而 dml 語句不需要?兩者都不是通過寫入底層 WAL 來處理事務嗎?那麼為什麼,對於 DLL 它是不同的呢?

單筆交易的原因

我們將整個客戶數據庫從客戶前提 (SQL Server) 同步到雲 (PostgreSQL)。所有客戶都有不同的數據庫。過程是,整個數據將從 SQL Server 生成為 CSV,並使用 Temp Tables、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 郵件列表中詢問核心問題。

一個更好的主意是使用SQL Server FDW,它實​​際上具有將 Microsoft SQL Server 拉入 PostgreSQL 格式的邏輯(例如,Bit映射到Bool)。從此

然後每三分鐘,

  • 您將外部架構導入last_fetch_schema
  • 如果last_fetch_schema不同於local_schema
    • 你重新同步模式
  • 您使用 複製所有數據INSERT INTO ... SELECT ON CONFLICT DO UPDATE,並且您只能選擇最新數據。
  • 你放棄了外部模式last_fetch_schema

你有什麼收穫?

  • 首次載入時,您可以簡單地使用CREATE TABLE local.foo ( LIKE foreign.foo)
  • 您可以輕鬆比較元數據差異
  • CSV 會失去類型並讓您推斷事物,FDW 可以讀取元數據目錄。
  • 如果行是版本化的/您不必再發送整個數據庫,則僅獲取最新的東西非常簡單。

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);

我真的不明白,因為這個可能有指針的“人”是誰?這是私有記憶體,不是共享記憶體。無論如何,它似乎確實可以解釋膨脹,因為同一事務中的每個“更改表”語句都會為該表留下另一個 TupDesc 副本。顯然,即使您在一個中使用多個操作alter table,每個單獨的操作也會留下一個副本。但不管有什麼優點,這確實解釋了記憶體使用的很大一部分。

有關更多討論,請參閱pg hackers郵件列表。

引用自:https://dba.stackexchange.com/questions/220053