資安新創 Artemis 用查詢合併術,把偵測規則執行時間從 173 秒壓到 2.5 秒
ClickHouse Blog · 2026-07-01
資安新創 Artemis Security 在 ClickHouse 官方部落格的文章中,揭露了他們如何把單一批次的偵測規則查詢從 173 秒壓到 2.5 秒。Artemis 是一家 2026 年 4 月低調上線、募得 7000 萬美元的 AI 原生威脅偵測平台,靠 AI agent 持續對客戶的日誌資料下查詢找可疑行為。這篇文章記錄的是他們在 ClickHouse Cloud 上做的架構調整,其中「查詢合併」讓偵測規則的執行速度提升了 69x。
原本的問題
Artemis 每天要處理數量以「兆位元組壓縮日誌、數百億列」計的資料,整體規模是對數兆列資料跑數百萬次查詢。最大的效能瓶頸來自每個客戶要跑一百多條獨立的偵測規則,每一條規則各自是一個 SQL 查詢,卻掃描完全相同的時間窗口資料,造成重複掃描的乘數效應。
另一個問題出在資料本身:多租戶的 SharedMergeTree 資料表裡存了大量 JSON 欄位,這些欄位沒有針對巢狀欄位建立高效索引,任何查詢都得在讀取當下即時解析 JSON,來自 CloudTrail、身分、網路、端點等數十種異質日誌來源的半結構化 schema 還會持續變動,讓解析成本雪上加霜。
採用的方法
Artemis 的核心作法是查詢合併(query coalescing):把同一客戶底下多條規則的判斷條件,用 ClickHouse 原生函式合併進單一查詢裡執行,符合條件的資料列會被標記上所有觸發它的規則 ID,而不是分別對每條規則各跑一次全表掃描。實作上有幾個關鍵調整:
- 自動排除約 15% 無法合併的規則,包括用到 CTE、JOIN、GROUP BY、LIMIT、子查詢,或是在
ARRAY JOIN裡用到 lambda 的規則 - 把
max_query_size上限從 256 KB 調高到 1 MB,讓合併後動輒巨大的單一查詢語句塞得下 - 加入二分切割(binary-split)的降級批次機制,查詢超過大小限制時自動拆成更小批次重試
另外針對 JSON 欄位的查詢,Artemis 把常被查詢的 JSON 子欄位額外抽出,建立成型別原生(非 JSON)的平行物化視圖(materialized view),查詢時先在抽取出來的表上做過濾,縮小時間範圍後再回頭查完整的 JSON 表,形成「兩段式」查詢模式。
實際效果
| 優化項目 | 優化前 | 優化後 | 倍數 |
|---|---|---|---|
| 單批查詢合併 150+ 條規則 | 173 秒 | 2.5 秒 | 69x |
| CPU 使用量(同一批查詢) | 基準值 | 降低 | 46x |
| 記憶體 I/O(同一批查詢) | 基準值 | 降低 | 100x |
| 狀態/事件代碼過濾(物化視圖) | 12 秒 | 0.2 秒 | 60x |
| 依來源 IP 分組(物化視圖) | 45 秒 | 1.5 秒 | 30x |
生產環境中,100 條以上規則組成的批次現在可以在一秒內執行完畢,而依查詢型態不同,物化視圖帶來的 CPU 用量降幅落在 60x 到 400x 之間。文章也提到 Artemis 另外用一套整合 ClickHouse 系統表、CloudWatch、Datadog 的 Claude Code 技能(內部代號 /poke)做效能除錯,一次針對突發實例效能下降的調查在 90 秒內就定位出 8 GiB 以上的記憶體用量與 42 TiB 的資料讀取量。
原始來源:How Artemis Security runs 69x faster detection queries with ClickHouse Cloud
ClickHouse Managed Postgres 用一整組 PgBouncer 打破連線池的單執行緒天花板
ClickHouse Blog · 2026-07-01
ClickHouse Postgres 工程主管 Kaushik Iska 在 ClickHouse 官方部落格撰文,說明 ClickHouse Managed Postgres(ClickHouse Cloud 底下的託管 PostgreSQL 服務)如何解決連線池軟體 PgBouncer 的單執行緒瓶頸。做法是在同一台機器上跑一整組 PgBouncer 行程,並用 Linux 的 SO_REUSEPORT 讓它們共用同一個對外連接埠。在 16 vCPU 的 c7i.4xlarge 測試機型上,這套架構把尖峰吞吐量從約 8.7 萬 TPS 拉高到 33.6 萬 TPS。
原本的問題
PgBouncer 的根本限制是單一行程只能吃滿一顆 CPU 核心,不管機器實際有幾顆核心都一樣。在 16-vCPU 的執行個體上,這代表連線池行程會先於 PostgreSQL 本身觸頂:PgBouncer 這顆核心被打到接近滿載,其餘 15 顆核心卻是閒置的,整體吞吐量還沒摸到資料庫的極限就先被連線池卡死。
對於 Managed Postgres 這種要同時服務大量租戶連線的場景,客戶端連線數一旦往上衝,單一 PgBouncer 行程就會變成無法繞過的天花板,即便後端 Postgres 叢集還有餘裕也無濟於事。
採用的方法
ClickHouse 的解法是把單一 PgBouncer 行程換成一支「艦隊(fleet)」,讓多個行程平行分攤負載:
- 依可用核心數部署對應數量的 PgBouncer 行程,測試機型上是 16 個行程
- 所有行程用
SO_REUSEPORTsocket 選項綁定同一個連接埠,由核心(kernel)在行程之間自動負載平衡連入的連線 - 客戶端只需連到單一個對外端點,完全不用知道背後其實有多個連線池行程
- 沿用 transaction 模式做連線池化:一筆交易一提交,伺服器端連線就立刻歸還連線池
- 行程之間彼此「認得」對方(peering),這樣當取消查詢的請求被核心誤導到錯誤的行程,該行程能把取消請求轉發給實際握有該連線的行程
- 把連線預算(
max_client_conn、max_db_connections)依行程數量平均切分
概念上等同於讓多個 PgBouncer 監聽同一個埠:
# 每個行程各自的設定,靠 SO_REUSEPORT 共享同一個埠
so_reuseport = 1
listen_port = 6432
pool_mode = transaction
max_client_conn = 分配後的配額
max_db_connections = 分配後的配額實際效果
ClickHouse 在同款 c7i.4xlarge(16 vCPU)機器上,分別對單一 PgBouncer 行程與 16 行程艦隊做壓測:
| 並發客戶端數 | 單一行程 TPS | 艦隊 TPS | 單一行程 CPU | 艦隊 CPU |
|---|---|---|---|---|
| 8 | 8,910 | 6,450 | 0.8% | 2.9% |
| 64 | 86,570 | 219,439 | 8.3% | 31.9% |
| 256 | 76,893 | 336,469 | 7.7% | 48.9% |
在 256 個並發客戶端時,單一行程的吞吐量因為卡在一顆核心而先降後平,CPU 使用率顯示它已經被釘死在接近滿載的一整顆核心上;艦隊架構則把負載攤開到約 8 顆核心,尖峰吞吐量達到 336,469 TPS,相較單一行程的 76,893 TPS 是約 4x 的提升。值得注意的是在並發數僅 8 的低負載情境下,艦隊架構的 TPS(6,450)反而略低於單一行程(8,910),顯示多行程協調本身在低競爭場景下有額外開銷。
Redis 把 AI Agent 記錯事實的問題定名為「語意過載」,開出混合檢索與圖譜記憶的藥方
Redis Blog · 2026-07-02
Redis 官方部落格在 一篇文章中,把 AI agent 從檢索資料裡撈到過時或矛盾事實的現象定名為語意過載(semantic overload)。作者 Jim Allen Wallace 舉例:agent 自信地告訴使用者育嬰假是 12 週,但公司政策一年前已經改成 16 週,舊版 HR 手冊、新版手冊、和 Slack 公告全部都還在檢索索引裡,向量搜尋只是撈到了餘弦相似度最高的那份過時 PDF。
什麼是語意過載
語意過載指的是把過多、過雜、或彼此矛盾的語意內容塞給 agent,反而讓表現變差的一群失效模式。Transformer 架構讓 n 個 token 之間產生 n 平方量級的兩兩關聯,每個 token 都在跟其他 token 搶注意力,資料塞得越多,模型不見得答得更準,反而可能更慢、更容易答錯。
業界已經歸納出五種常見的情境失效模式:context poisoning(脈絡遭污染)、distraction(分心)、confusion(混淆)、clash(衝突)、以及 context rot(脈絡腐化)。這五種模式各自描述內容過多或過雜如何拖垮推理,合起來就是語意過載在實務上的具體樣貌。
向量檢索抓不到的關聯
向量檢索能找出語意相近的內容,卻無法推理事實彼此之間怎麼連結。文章用一個多跳(multi-hop)問題示範這個限制:文件 A 說「幫浦 X」接到「閥門 Y」,文件 B 說「閥門 Y」容易觸發「壓力警告 Z」,若問「幫浦 X 有什麼風險」,一般向量搜尋大概率會失敗,因為「幫浦 X」跟「壓力警告 Z」從沒出現在同一個文字區塊裡,向量資料庫把它們索引成向量空間裡互不相干的兩個鄰域,沒有明確的關係邊可走。
語意相近也不等於事實正確。餘弦相似度在處理否定語意時尤其吃力:像「happy」跟「not happy」這類詞語的向量嵌入往往彼此靠得很近,即使意思完全相反,一個查詢只要卡在一個「不」字,照樣可能撈回原本想排除的內容。另外向量搜尋按語意排序而非時間排序,本身沒有「新舊」的概念,2023 年點名某家供應商的舊報告,可能跟 2024 年已經替換掉它的新公告並列出現,相似度分數完全沒有標示哪一份才可信——這正是文章開頭育嬰假案例的成因。
記憶體裡的關聯缺口
同樣的問題也出現在 agent 跨會話(session)記憶上。目前常見的 agent 記憶多半是線性、非結構化,或簡單的鍵值儲存:固定長度的 token 序列、向量資料庫、或是流水帳式的日誌緩衝區,這些方式能把事實存進去也能撈出來,卻沒有記錄事實彼此的關係——哪個取代了哪個、哪個導致了哪個、哪些該被歸在一起。這段缺失的關係層,就是所謂的關聯缺口(relational gap),具體會表現成四種生產環境常見狀況:
- 矛盾:扁平儲存把互相衝突的事實並列存放,沒有任何標記說明哪一筆才是最新的
- 時效:例如「Alice 曾在新創公司工作,現在在大型科技公司」,若沒有記錄各筆事實成立的時間點,兩者會被同等看待
- 來源:儲存的事實沒有記錄出處,答案難以驗證或追溯
- 規模:每次互動都往記憶裡塞,久了有用的事實會被雜訊淹沒,拖慢檢索速度
四種緩解做法與 Redis Iris
文章提出四種能各自解決部分問題的做法,沒有單一技術能包辦所有語意過載的失效模式:
- 混合搜尋(hybrid search):把密集向量檢索跟稀疏的關鍵字(lexical)檢索混用,再疊上 metadata 過濾,用關鍵字比對抓住向量搜尋容易模糊掉的精確用詞
- 交叉編碼器重排(re-ranking with cross-encoders):把查詢跟每份候選文件一起編碼再重新打分,找出第一輪向量搜尋排名排得太低的相關結果,代價是延遲變高
- 知識圖譜與 GraphRAG:檢索的對象換成互相連結的實體與關係,而不是各自獨立的文字區塊,能走通前面幫浦 X 到閥門 Y 到壓力警告 Z 的推理鏈,但建圖本身容易出錯,對開放式問題的幫助也有限
- 結構化的圖式記憶:讓 agent 能查到像「使用者 A 在某日購買了產品 X」這種明確關係,而不是把一大塊文字丟進 prompt 賭模型自己挑出重點
Redis 在文章最後把這套思路對應到自家產品 Redis Iris:一個介於 agent 與資料之間的「情境引擎」,把檢索、記憶、資料新鮮度三層包成 Redis Cloud 上的全託管服務,包括做語意快取的 LangCache、處理跨會話記憶的 Agent Memory、面向結構化商業資料的 Context Retriever,以及負責資料同步的 Data Integration,向量、全文、混合搜尋則統一跑在 Redis Search 之上。
原始來源:Semantic overload: why AI agents get facts wrong、Knowledge graphs & GraphRAG for AI agent memory