EPISODE · Jun 4, 2026 · 8 MIN
@posthog:Karpathy 的 Autoresearch 發現了我們查詢引擎中一個隱藏 3 年的 Bug(並提升了 11% 的效能) 幾週前,我們在里斯本舉辦了一場…
from EasyVibeCoding Podcast · host PostHog
Karpathy 的 Autoresearch 發現了我們查詢引擎中一個隱藏 3 年的 Bug(並提升了 11% 的效能) 幾週前,我們在里斯本舉辦了一場團隊外地辦公(offsite),當時我們將一個 AI Agent 指向我們的查詢引擎,餵給它來自正式環境的慢查詢,並讓它徹夜執行。 隔天早上,它發現了一個令人尷尬的事實:將近三年來,每一個帶有時間戳記(timestamp)篩選器的查詢,都沒有正確使用 ClickHouse 的主鍵。這個修正讓 ClickHouse 在基準測試查詢中需要掃描的顆粒(granules)數量減少了 62%,並使查詢本身的速度顯著提升。 這篇文章將介紹我們使用的設定、該 Bug 本身,以及我們目前正在開發的系統,以便讓這類分析能夠自動化執行。 什麼是 autoresearch? 這個概念並非我們首創。Andrej Karpathy 在 2026 年 3 月將其封裝並命名為「autoresearch」:給 AI Agent 一個小型但真實的系統、一個基準測試和預算,然後讓它循環執行;提出修改建議、執行基準測試、保留有用的變更、捨棄無效的變更。 Karpathy 針對一個深度為 12 的 nanochat 訓練執行了兩天的測試,發現了約 20 個能改善驗證損失(validation loss)的變更,其中一些甚至能遷移到更大的模型中。這種模式並不新鮮(DeepMind 的 FunSearch (2023) 和 Sakana 的 AI Scientist (2024) 都是更早期的例子),但 Karpathy 的儲存庫足夠小且具體,足以激勵你在一個下午內建立自己的版本。 對我們來說,最有趣的是二階效應:Agent 不會帶有長期在程式庫中工作所產生的偏見。對我們而言,toTimeZone() 的包裝一直都在那裡,屬於那種你看久了就會視而不見的程式碼。但 Agent 沒有先入為主的觀念。它會執行每一項診斷,閱讀周圍的 ClickHouse 和 PostHog 原始碼以獲取上下文,並以同樣的懷疑態度審視一個存在三年的表達式,就像審視你昨天寫的那一行程式碼一樣。 在黑客松中為 ClickHouse 設定 autoresearch 我們每年都會在公司外地辦公時舉辦黑客松。現在 PostHog 的許多功能(如 session replay、資料倉儲、日誌等)都是這樣開始的。在里斯本針對 Analytics Platform 和 Query Performance 團隊舉辦的小型聯合外地辦公中,我們的黑客松專案就是要做 Karpathy 那一套,但目標是 ClickHouse 的查詢效能。 我們使用的技術堆疊: pi:由 Mario Zechner 建立的一個小型終端程式開發 Agent。它能與你指定的任何 LLM 對話,提供一個小型 SDK,且體積小到你可以讀完整個程式庫。 pi-autoresearch:由 davebcn87 開發的社群擴充功能,將 Karpathy 的循環機制整合進 pi。你給它一個目標、基準線、基準測試指令和目標指標。它會進行迭代、提交每個候選變更、執行基準測試,並保留日誌,確保執行過程在上下文重置後依然存在。 我們在 pi-autoresearch 之上編寫的 Campaign 編排合約。當單一 ClickHouse 查詢有數百種合理的改寫方式時,基本的「嘗試、測量、保留或捨棄」循環顯得太過鬆散。如果沒有結構,Agent 可能會對查詢的某個角落反覆修改直到放棄;有了結構,你就能得到更接近人類進行調查的方式。我們將每次調查分為四個部分: 一個包含一個慢查詢和一個 git 分支的 Campaign。 這被拆分為多個「通道」(lanes),即針對懷疑瓶頸的優化方向:謂詞排序、JSON 解析、時區處理、主鍵使用等。當通道不再產生訊號時可以暫停,當發現包含兩個不同想法時可以拆分,或者當不同通道的優化結果可以合併時進行合併。 每個通道內的一個具體、可測試的假設。 每個假設內的一個實驗,包含一次執行、基準測試和判定。Agent 必須在每次實驗後進行明確的反思(reflection pass),而不是讓循環只會盲目地爬坡(hill-climb)。 一個拋棄式的 ClickHouse 測試叢集:這保持了迭代的高速度,並使基準測試數據可預測。它擁有與正式環境相同的資料結構,但經過匿名化處理,並運行在專門分配給 Agent 的較便宜硬體上。在開發者的筆記型電腦上運行會太慢,無法進行有效的內循環;而在正式環境運行則意味著要與其他任務爭搶資源,並冒著干擾客戶查詢的風險。 範圍縮小(Range-narrowing)也有幫助。當目標查詢逾時時,Agent 會將範圍減半(30 天、14、7、3、1),直到它能在 1 到 10 秒內完成,然後針對該縮小版本進行優化。這個視窗短到足以進行快速迭代,但又長到足以讓索引和分區效應發揮作用。目前的最佳候選者會定期在完整範圍內重新測試;一旦在那裡完成,該 Campaign 就會「畢業」並從原始查詢繼續進行。 在黑客松期間,我們手動餵給它過去我們抱怨過的慢查詢,以及我們在 system.query_log 中手動發現的查詢(我們現在正在將此過程自動化)。 發現我們默默損壞的主鍵 ClickHouse 之所以快,是因為它能跳過不必要的運算。我們的事件資料表是 PARTITION BY toYYYYMM(timestamp),主鍵則是 (team_id, toDate(timestamp), event, …)。一個格式正確且帶有時間戳記邊界的查詢,應該能讓 ClickHouse 直接捨棄整個月的資料,然後直接跳轉到它需要查看的月份中的正確週次。 但實際情況並非如此。 當我們在 2023 年 4 月為 HogQL 加入各團隊時區支援時,我們做了自認為合理的事:將每個對 timestamp 的引用都包裝成 toTimeZone(timestamp, team_tz),以確保顯示的日期正確。但我們沒意識到的是,ClickHouse 查詢規劃器無法看穿 toTimeZone()。這意味著它無法推導出以下邊界: 無法從 toTimeZone(timestamp, tz) >= '2024-03-01' 推導出 toYYYYMM(timestamp),因此分區修剪(partition pruning)失效了。 無法推導出 toDate(timestamp),所以主鍵的使用僅限於 team_id 和 event,之後就停止了。 之所以這件事沒能觸發我們的警報,是因為 ClickHouse 在 timestamp 上還有一個 MinMax 跳過索引。MinMax 索引會儲存每個「顆粒」(預設為 8,192 行)中欄位的最小值和最大值。當你將 toTimeZone(timestamp, tz) 與常數進行比較時,ClickHouse 仍然可以針對每個顆粒的 min/max 進行評估,並跳過範圍不重疊的顆粒。這比分區修剪弱得多,但它確實有效,所以查詢並不是災難性地慢,只是比應有的速度慢了一些。 這就是那種會永遠隱藏起來的 Bug。它很慢,但還沒慢到需要「呼叫值班人員」的程度。每個查詢都受到影響,所以沒人能與「良好」版本進行 A/B 比較。而且這個關鍵證據隱藏在 EXPLAIN PLAN indexes=1, json=1 的輸出中,除非已經懷疑有問題,否則沒人會去執行它。 在其中一個通道中,autoresearch 循環執行了 EXPLAIN。它注意到 Partition: Condition='true'(即沒有修剪),所以嘗試了兩件事: 加入帶有原始時間戳記邊界的 indexHint()。 改寫比較方式,讓欄位側保持原始狀態,並將時區標註在常數側。 第二種方法大獲全勝,這也是我們最終發佈的方案: `sql -- 修改前:規劃器無法看穿 toTimeZone toTimeZone(timestamp, 'US/Pacific') >= '2024-03-01' -- 修改後:左側為原始欄位,右側為帶有時區標註的常數 timestamp >= toDateTime64('2024-03-01', 6, 'US/Pacific') ` 語意是完全相同的,因為 toTimeZone() 只會改變顯示的元數據:底層的 epoch 並未改變。規劃器現在能看到原始的時間戳記,並能執行它的工作。 針對正式環境中某個團隊的 7 天漏斗分析(loadbalancing='inorder',確保每個變體都命中相同的分片,各執行五次,取中間三次的截尾平均值): 速度提升在短日期範圍的查詢中最為顯著,因為這正是分區修剪最關鍵的地方。在 7 天的範圍內,如果規劃器配合,你可以捨棄大部分的分區。較寬的範圍無論如何都必須查看更多分區…
Embed this episode
Ready to play
@posthog:Karpathy 的 Autoresearch 發現了我們查詢引擎中一個隱藏 3 年的 Bug(並提升了 11% 的效能) 幾週前,我們在里斯本舉辦了一場…
No transcript for this episode yet
Similar Episodes
No similar episodes found.
Similar Podcasts
No similar podcasts found.