資料與儲存 2026 年 7 月 15 日

2026-07-15 — DeFi 交易所改用 ClickHouse Cloud 即時分析、RowBinary 函式庫讓 AI agent 生成專屬剖析器、Managed Postgres 用 Huge Pages 削減連線記憶體稅

primary=https://clickhouse.com/blog/bullet-real-time-analytics primary=https://clickhouse.com/blog/clickhouse-rowbinary-library-parser-compiler primary=https://clickhouse.com/blog/huge-pages-clickhouse-managed-postgres

DeFi 交易所 Bullet 如何把分析延遲從 90 分鐘壓到 5 秒內

clickhouse.com/blog · 2026-07-15

Bullet 是建構在 Solana 上的一個應用專屬 rollup,以 Rust 撰寫,提供衍生性商品交易、現貨交易與借貸服務,交易結算延遲可達 p50 500 微秒、p99 650 微秒,使用者可自行託管資產而不必依賴中心化交易所。該團隊近日在 ClickHouse 官方部落格分享,他們把分析管線從 Databricks 批次系統換成 ClickHouse Cloud,將排行榜等關鍵指標的更新延遲從原本 1 到 2 小時壓到 5 秒以內。

原本的問題

Bullet 原本用 Databricks 每 60 分鐘跑一次批次分析,光是排行榜計算本身就要 20 到 30 分鐘執行完畢,使用者看到的數字最多可能是 90 分鐘前的狀態。資料新鮮度不足之外,Databricks 的查詢延遲落在 10 到 15 秒,前端根本無法直接呼叫。

為了繞開這個限制,團隊被迫額外維護一套 DynamoDB 服務層,把 Databricks 算出來的結果每小時批次寫入快取再供前端讀取。這種雙系統架構本身就帶來額外的維運負擔:兩套管線要保持同步,任何 schema 變動都要改兩個地方。

採用的方法

新架構的資料流是:Bullet 的 rollup 定序器產生事件,經 Rust 撰寫的 WebSocket 索引器送進 Kafka(AWS MSK),再由 Redpanda Connect 做分類與路由,最終寫入 ClickHouse Cloud 的資料表,同時並行備份到 S3 Glacier。整條管線經過壓力測試可承受每秒 300 萬筆交易且零遺失,事件從定序器送出到進入 ClickHouse 通常在一秒內完成。

每一筆交易都帶有唯一遞增的事件編號與交易雜湊,寫入 ReplacingMergeTree 表後,ClickHouse 會以時間戳為判斷依據自動保留最新版本,讓整條管線天生具備冪等性,重送或亂序到達的事件都不會造成重複計算。排行榜則改用 refreshable materialized view 每 10 分鐘重算一次,內部以六個 CTE 搭配 window function 跨多張表 join,並在計算損益排名時刻意排除已入金的本金部位。

資料表的設計也從單一雜亂資料庫,改成依事件類型分表,每種事件各自的排序鍵與索引都能對應到最常見的查詢型態,而不是為了通用性犧牲效能。

實際效果

指標Databricks(舊)ClickHouse Cloud(新)
查詢延遲10-15 秒毫秒等級
資料新鮮度1-2 小時5 秒內
排行榜更新頻率60 分鐘一次,計算耗時 20-30 分鐘每 10 分鐘,可視需求即時觸發

團隊表示三個環境合計處理的資料量成長了約千倍,月費用卻維持相近水準。這套管線目前支撐三類場景:交易紀錄、損益圖表與稽核紀錄等使用者可見功能;24 小時交易量、排行榜、推薦獎勵等即時分析;以及每分鐘一次的系統快照送進 Grafana 儀表板,搭配 Slack 與 PagerDuty 告警。

原始來源:ClickHouse Blog


@clickhouse/rowbinary:把函式庫本身變成一座剖析器編譯器

clickhouse.com/blog · 2026-07-14

ClickHouse 團隊在部落格發表了 @clickhouse/rowbinary,一套供 Node.js 讀寫 ClickHouse RowBinaryRowBinaryWithNamesRowBinaryWithNamesAndTypes 格式的函式庫。這套函式庫的特別之處不在於效能本身,而在於它同時把自己包裝成一份可以教會 coding agent「生成專屬剖析器」的 Agent Skill。

設計動機

RowBinary 是一種小端序、以 LEB128 變長整數編碼、逐列無額外開銷的二進位格式,但每種葉節點型別都有各自的讀取方式,NullableArrayMapTupleLowCardinality 這類容器型別又可以任意巢狀組合。多數應用因此退而求其次改用 JSON,但 JSON 會讓超過 Number.MAX_SAFE_INTEGERUInt64 數值悄悄失真;若寫成通用的型別分派剖析器,雖然好維護,執行期的動態分派又拖慢效能。

核心機制

ClickHouse 沒有選擇傳統的 codegen 編譯器,而是把「產生程式碼」這件事拆成三種 agent 可以直接讀取、重組的素材:第一是大量加註解的最小讀取原語,每種葉節點型別各自一個可單型化的小函式;第二是 SKILL.md,描述如何把這些原語組裝成針對特定 query 的專屬剖析器;第三則是在推論當下由 agent 即時生成程式碼,而非預先編譯。

以一個含 UInt8UUIDDecimal64(2)Enum8 欄位的訂單 schema 為例,通用寫法要逐欄呼叫對應的 reader:

export const readOrderRow: Reader<OrderRow> = (s) => ({
  id: readUInt8(s),
  uid: formatUUID(readUUID(s)),
  price: readDecimal64(2)(s),
  status: readInt8(s),
});

agent 生成的專屬版本則把整列的欄位長度算成常數 offset,只做一次邊界檢查,並用查表法直接格式化 UUID:

export const readOrderRowFast: Reader<OrderRow> = (s) => {
  const { buf, view } = s;
  const o = advance(s, 26);
  const id = buf[o]!;
  const uid = formatUUIDTable(buf.subarray(o + 1, o + 17));
  const price: DecimalValue = [view.getBigInt64(o + 17, true), 2];
  const status = view.getInt8(o + 25);
  return { id, uid, price, status };
};

註解裡明確標出哪些軸線可以安全修改,例如 buffer 所有權、要用 BigInt 還是 numberDate 的映射方式、Decimal 精度處理與 Array 的具現化策略,讓 agent 在生成程式碼時有明確邊界可循。

效益

官方測出 agent 生成的專屬剖析器,相較於一般組合式 reader 快 1.5 到 3.4 倍:寬整數的財務帳本場景快 1.55 倍、IoT 遙測(浮點/整數混合)快 2.46 倍、固定寬度的訂單 schema 快 3.41 倍。整體而言,RowBinary 又比 JSON 快 2.5 到 3.3 倍,主因是指標運算取代了容易分支預測失誤的 tokenizing。

生成一支專屬剖析器的成本約為每次 0.20 美元(啟用 prompt caching 後),未快取時上限約 0.72 美元,輸入約 23 萬 token(多數命中快取)、輸出約 2100 token。函式庫本身提供 parseRowBinary(...) 供一般情境使用,並依賴 @clickhouse/datatype-parser 處理型別解析;安裝與註冊方式為:

  • npm i @clickhouse/rowbinary
  • npx skills add ClickHouse/clickhouse-js --skill clickhouse-js-node-rowbinary

原始來源:ClickHouse Blogclickhouse-js SKILL.md


ClickHouse Managed Postgres 如何用 Huge Pages 削減每條連線的記憶體稅

clickhouse.com/blog · 2026-07-14

ClickHouse Cloud 團隊在其 Managed Postgres 服務中,分享了他們如何透過設定 Huge Pages 大幅壓低 PostgreSQL 在多連線情境下的記憶體開銷。Huge Pages(2MB 或 1GB)取代作業系統預設的 4KB 分頁,讓一筆分頁表項目就能對應到多達 262,144 倍的記憶體,藉此降低 TLB(Translation Lookaside Buffer)壓力,避免頻繁的分頁表查找造成延遲。

原本的問題

PostgreSQL 的 shared_buffers 快取,是由每個後端連線各自的分頁表個別映射的。以 4KB 分頁計算,一個 100GB 的共享快取,每個後端連線大約要多耗 200MB 的分頁表項目儲存空間;100 條連線就會吃掉 20GB RAM 純粹用於記憶體管理的簿記,而不是實際快取資料。這個「每連線稅」在連線數上升時會急遽惡化。

採用的方法

團隊把設定拆成三個步驟。第一步是在 Postgres 啟動前預留 huge pages,通常抓系統總 RAM 的 25%,對應 shared_buffers = 25% of RAM 的常見設定慣例:

echo 'vm.nr_hugepages = <N>' > /etc/sysctl.d/10-hugepages.conf
sync
echo 3 > /proc/sys/vm/drop_caches
echo 1 > /proc/sys/vm/compact_memory
sysctl --system

第二步是強制啟用,把 huge_pages = 'on' 寫進設定,讓 Postgres 在拿不到 huge pages 時直接啟動失敗並丟出明確錯誤,而不是悄悄退回 4KB 分頁:

huge_pages = 'on'
huge_page_size = 0        # 系統預設,即 2MB
shared_buffers = <計算後的 kB 值>

第三步是精確反推 shared_buffers 大小,用 postgres -D <data_directory> -C shared_memory_size 量出緩衝區描述子、WAL buffer、鎖定表等實際 segment 開銷,讓最終設定值剛好塞進預留的 huge pages 池中,不多也不少。

實際效果

團隊在 r7i.4xlarge(16 vCPU、128GB RAM)機型上,用 Postgres 16 搭配 32GB shared_buffers 做實測:

連線數4KB 分頁2MB Huge Pages
048 MB5 MB
25789 MB18 MB
1003.06 GB58 MB
2006.12 GB111 MB

換算下來,4KB 分頁每增加一條連線平均多耗 31.1MB,2MB huge pages 下每條連線只多耗約 0.5MB。100 個並發客戶端的吞吐量也從 373,083 TPS 提升到 418,087 TPS,約成長 12%。

要確認 huge pages 是否真的生效,而非悄悄退回 4KB 分頁,可以直接檢查執行期指標:

grep HugetlbPages /proc/<pid>/status

若該欄位顯示接近零的數值,代表設定沒有生效,系統仍在用一般分頁運作。

原始來源:ClickHouse Blog


End of article
0
Would love your thoughts, please comment.x
()
x