ClickHouse Managed Postgres 如何用 cgroup v2 隔離「背景服務」搶資源的問題
ClickHouse Blog · 2026-08-21
ClickHouse 於 2026 年 8 月 21 日在官方部落格發表文章,說明其 Managed Postgres 服務如何防止「陪跑」的背景程序拖垮資料庫本體。一個 Postgres 執行個體實際上從來不是只有 postgres 一個行程在跑,還包含 PgBouncer、WAL-G 備份代理、Prometheus、postgres_exporter、node_exporter 等四個 Go 服務與監控元件。這篇文章描述的是這些「supporting processes」在記憶體、CPU、磁碟壓力上升時如何被系統性地限制,避免其中任何一個因為記憶體洩漏或流量突增而觸發核心 OOM killer,連帶殺死 Postgres 主行程。
核心改動
整體防線分成三層。第一層是執行期記憶體預算:每個 Go 寫成的背景服務都設定了 GOMEMLIMIT,讓 Go runtime 在逼近設定值前就主動觸發垃圾回收、收縮 heap,這是在核心介入之前的「軟性」自我調節,比等到 cgroup 硬限制或 OOM killer 出手更早、更平滑。
第二層是 cgroup v2 的強制隔離:所有背景服務被放進同一個獨立的 cgroup slice,透過 systemd 的 MemoryHigh/MemoryMax 屬性對應到 cgroup 的 memory.high(超過會觸發回收與節流)與 memory.max(硬上限,超過觸發僅限該 cgroup 範圍的 OOM)。Postgres 本身則被放在另一個獨立的 cgroup,兩者互不干擾——背景服務吃滿記憶體時,核殺的是它們自己那個 cgroup 裡的行程,而不會波及資料庫。文章也提到 Postgres 的 shared_buffers 是以 huge pages 型式在開機階段就配置好、受保護的記憶體區塊,不受這層 cgroup 記憶體壓力影響。
第三層是排程與磁碟滿載時的應變:CPU 資源以 cgroup 的 CPU weight 比例分配,備份與 WAL-G 相關工作被壓在較低的權重下,確保前景查詢優先取得 CPU;而磁碟即將寫滿時,看門狗(watchdog)機制會終止非關鍵連線階段(session),但明確排除 replication 與 monitoring 兩類使用者的連線,讓維運人員在緊急狀況下仍能連進去查看複寫延遲與系統指標。
影響範圍
這篇文章沒有揭露具體的 memory.high/memory.max 數值或 CPU weight 百分比,走的是架構原則說明而非可直接複製貼上的調校範本。但其設計邏輯值得注意的地方在於:把「資料庫本體」與「維運周邊」在資源隔離層面徹底分開,是託管資料庫服務商用來對外承諾可用性 SLA 的常見手法——同一套模式也出現在該公司先前談 huge pages 與 strict overcommit 記憶體策略的文章中,屬於其 Managed Postgres 一系列可靠性工程文章的延伸。
原始來源:ClickHouse Blog: Protect Postgres from supporting processes
ClickHouse 26.7 為文字索引加入詞位資訊,片語搜尋加速 40 倍
ClickHouse Blog(26.7 版本發布說明) · 2026-08-06
從 ClickHouse 8 月電子報中挑出最具工程份量的項目:26.7 版本發布說明裡的文字索引(text index)升級。這個版本讓文字索引除了記錄「哪些 row 含有這個 token」之外,還額外儲存每個 token 在文本中的位置,這是實作片語搜尋(phrase search)在索引層面加速所必需的資訊——沒有位置資訊,索引只能告訴你兩個詞「都出現在同一列」,無法判斷它們是否相鄰、順序是否正確,也就無法跳過不符合片語的候選列。
規格細節
這是標記為實驗性的功能,需要先開啟 allow_experimental_text_index_phrase_search 設定才能使用。建立索引時在 text 類型索引上加上 support_phrase_search = 1 參數:
INDEX idx_text text TYPE text(
tokenizer = 'splitByNonAlpha',
support_phrase_search = 1)查詢端則透過 hasPhrase(text, 'Google web search') 函式來做片語比對。官方在 HackerNews 資料集上做的基準測試顯示,同一條片語查詢從 1.343 秒降到 0.033 秒,約 40 倍加速——差距主要來自索引可以直接靠 token 位置排除不相鄰的候選列,不需要在符合「同列出現」條件的所有列上做逐字元的字串掃描。
影響範圍
同一份 26.7 發布說明裡還包含幾項 JOIN 相關的優化,其中最大的一項是build-side 建索引後反向剪枝 probe-side granule,在 TPC-H scale 100 的基準測試中達到 6.2× 提速、讀取資料量減少 9.4×、峰值記憶體降低 8.8×;此外新增了 query_plan_optimize_join_order_algorithm = 'dpsub' 的多表 JOIN 動態規劃排序演算法,以及可以印出實際平行度與各階段耗時的 EXPLAIN ANALYZE 子句。文字索引的片語搜尋因為目前仍是實驗性開關,尚未預設開啟,需要應用端自行評估開啟後的索引建置成本與空間開銷再上線。
DuckDB Java Driver 新增分塊讀取 API,繞過 JDBC 逐列取值的效能損耗
DuckDB Blog · 2026-08-21
DuckDB 官方部落格於 2026 年 8 月 21 日發文,介紹 Java driver 1.5.3.0 版本新增的分塊查詢結果 API。DuckDB 的執行引擎內部是以 2,048 列為單位的欄式資料塊(chunk)在運算,但傳統 JDBC 的 ResultSet 介面規定必須逐列(row-by-row)走訪,每一次 rs.next()/rs.getLong() 呼叫都要把欄式資料重新包裝成列式的物件存取,這一層轉換在大結果集下會變成明顯的 CPU 開銷。這個新 API 讓呼叫端可以直接拿到引擎原生產生的資料塊,依欄(vector)為單位批次讀取。
核心改動
這個功能來自 PR duckdb-java#682「Support fetching query results in chunks」,作者 staticlibs,於 2026-05-15 合併進 main 分支(merge commit 23448c1),改動涵蓋 22 個檔案、新增 682 行、刪除 52 行。它把 DuckDB C API 既有的 duckdb_fetch_chunk 能力包裝暴露給 Java 層,新增三個類別:
DuckDBChunkedResult:管理惰性抓取(lazy)的資料塊序列,以nextChunk()逐塊前進DuckDBDataChunkReader:讀取單一資料塊內容,與既有 Java UDF 使用的介面共用同一套 APIDuckDBReadableVector:對單一欄(column vector)做型別特化的存取,例如getLong()、getInt()、getString()
使用方式上,這條路徑只能透過 DuckDBPreparedStatement 的 query() 方法取得,索引一律採零基(zero-based),與 DuckDB C API 的慣例一致:
try (DuckDBPreparedStatement ps = conn.prepare(
"SELECT l_orderkey, l_linenumber, l_shipmode "
+ "FROM 's3://my-bucket-name/lineitems.parquet'")) {
try (DuckDBChunkedResult res = ps.query()) {
while (res.nextChunk()) {
DuckDBDataChunkReader chunk = res.chunk();
// 依 vector 逐欄讀取
}
}
}影響範圍
目前這條路徑只支援基本型別,LIST、STRUCT 等複合型別的讀取還沒實作;也只開放給 prepared statement,沒有對應 query(String) 的便利多載,也就是說既有直接用字串下查詢再用標準 Statement 執行的程式碼無法直接改用這條路徑,需要先改寫成 prepared statement 形式。官方也表示 reader 的介面覆蓋面目前有限,UTF-8 byte[] 的直接讀取等能力仍在規劃中,尚未落地。