100k DDL 語句上的 Postgres 事務 OOM
我們在 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。這種設計甚至支持撤銷對 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郵件列表。