失控查詢壓測:RDS 存活率 0%,ClickHouse Postgres 撐住
ClickHouse Blog · 2026-09-28
在一場刻意製造記憶體耗盡的壓力測試中,Amazon RDS 在 23 個併發連線下只有 7% 的查詢跑完、叢集存活率掉到 0%;同一測試條件下,ClickHouse Managed Postgres 的查詢完成率是 32%,叢集存活率卻維持 100%。這項測試由 ClickHouse 工程團隊於 2026 年 9 月 28 日發布,比較了四家代管 Postgres 服務在同一支失控查詢下的行為差異。
原本的問題
Postgres 本身沒有內建的「每條查詢記憶體上限」。work_mem 只限制單一 sort 或 hash 節點,平行查詢時每個 worker 又各自吃一份,聚合、排序疊加起來很容易遠超過設定值。文章舉例:預設 work_mem 是 4MB、hash_mem_multiplier 預設 2.0,一段簡單查詢的三個 process 加總用了約 33MB,已經是 work_mem 的 8 倍以上。
把 work_mem 調到 8MB 雖然消除了溢出到磁碟的排序,代價是實際用量變成約 68MB,是原本的 8.5 倍。也就是說,查詢一旦複雜或併發連線一多,記憶體用量會快速疊加,而 Postgres 沒有機制主動擋下單一查詢把整台主機的記憶體榨乾——這道防線幾乎全靠代管服務商自己加。ClickHouse 這次測試想驗證的,就是各家防護層在真正被壓爆時到底做了什麼。
測試方法
測試在四個代管 Postgres 服務上跑同一支失控查詢:ClickHouse Managed Postgres、Google Cloud SQL、PlanetScale Postgres、Amazon RDS,全部統一在 Postgres 18.6、同等級機型(r8gd.large / db-c4a-highmem-2 / db.r8g.large)。查詢本體是一段遞迴 CTE,用 UNION(非 UNION ALL)的去重雜湊表製造出約 1260 萬個節點的圖,光是這張去重雜湊表就吃掉約 1 GiB 記憶體:
WITH RECURSIVE walk(n) AS (
SELECT 0
UNION
SELECT (walk.n + step.s) % 12600000
FROM walk
CROSS JOIN (VALUES (1), (251)) AS step(s)
)
SELECT n FROM walk;併發連線數從 7 一路加到 23,每個連線數重跑 10 次、跑完冷卻 60 秒,並設定 120 秒的 statement_timeout。失敗分三類記錄:查詢報錯、連線被砍斷、整個叢集當機。
結果與影響
| 服務 | 防護機制 | 觀察結果(23 連線) | 失敗時的訊息 |
|---|---|---|---|
| ClickHouse Managed Postgres | 停用 kernel 記憶體 overcommit,對已提交記憶體設硬上限 | 查詢完成率 32%,叢集存活率 100% | SQLSTATE 53200(out_of_memory),交易回滾、連線不中斷 |
| Amazon RDS | 無主動防護,仰賴 Linux OOM killer 事後補救 | 查詢完成率 7%,叢集存活率 0% | OOM killer 對行程送出 SIGKILL,Postgres 視為共享記憶體損毀,整叢集重啟進入 crash recovery |
| Google Cloud SQL | Supervisor 搶在 OOM 前主動砍連線 | 低到中負載就已出現不穩定 | FATAL: terminating connection due to administrator command |
| PlanetScale Postgres | Supervisor 搶在 OOM 前主動砍連線 | 11 個連線以上多數執行直接崩潰 | FATAL: terminating connection due to administrator command |
三種失敗模式的差別在於誰先出手、出手後系統是否還完整。ClickHouse 自己在應用層擋下超額記憶體,回報明確的 SQLSTATE 錯誤,交易回滾但連線和叢集都還在;Cloud SQL、PlanetScale 的 supervisor 搶在真正 OOM 前砍連線,但只丟出一句「因管理指令終止連線」,不解釋原因;RDS 沒有中間防線,直接讓 Linux OOM killer 動手,而 Postgres 把行程被 SIGKILL 這件事解讀成共享記憶體損毀,觸發的是整個叢集重啟,不是單一查詢失敗。
ClickHouse 表示自己是四家中唯一同時做到「關閉 kernel overcommit」與「對已提交記憶體設硬上限」的服務:記憶體用量會在約 57% 的主機 RAM 就封頂(16 GiB 機型約 9 GiB),而不是像 RDS 那樣讓 backend 幾乎吃光全部可用 RAM;shared_buffers 預設就先佔掉主機 25%(4GB)。對正在管理受管 Postgres、或會寫分析型查詢與遞迴 CTE 的工程師來說,這代表評估供應商時不能只看「有沒有 statement_timeout」,還要問清楚記憶體被壓爆時,系統擋下的是單一連線,還是整台機器。
原始來源:Can your Postgres survive a bad query?(ClickHouse Blog)