Redis 陣列型別:漫長開發過程中的小故事
原文由 Salvatore Sanfilippo 于 發布,訂閱此部落格
我在一月初就開始為 Redis 開發新的 Array 資料型別。PR 直到現在才合併進儲存庫,所以這段程式碼醞釀了四個月。我算是兼職做這個實作(說是兼職,是因為有好幾週其實是全職投入,有時候要讓自己離開鍵盤真的很難),就算在還沒有 LLM 的時代,我大概也能在四個月內完成這個實作。不同的是,在同樣的時間裡,我現在能做得更多。這就是這段期間發生的小故事。
第一個月我只在寫規格文件。包含新資料型別的設計理念、C 語言的結構、採用的稀疏表示法,以及環形緩衝區與 ARINSERT 的陣列游標的精確語意。我一開始花了好幾天手寫了一份很長的規格,接著先跟 Opus 一起協作,然後 GPT 5.3 推出後,我就把所有的設計和開發都轉到 Codex 上。從那之後,系統程式設計相關的工作我都只用 GPT 5.x。多虧了 AI,規格透過來回的回饋、思辯,歷經了大幅度的演進,不斷挑戰什麼才是最好的設計、什麼才是最恰當的取捨、什麼是過度設計、什麼又不是。
從第二個月開始,我改用自動化程式設計(你也可以說是 auto coding)來實作,並不斷審視產出的程式碼。接著我發現自己選擇的間接層級是錯的。我真的很希望使用者能夠執行 ARSET myarray 293842948324 foo 這樣的指令,而不需要巨大的記憶體配置就能正常運作。我原本設計的 directory + slices(稀疏與稠密)兩層結構並不夠。因為有 AI 幫忙,我決定不做任何妥協,要做就做到最好。當達到特定條件時,資料結構會在內部改變型態,變成一個由切片過的稠密目錄所組成的超級目錄,再指向實際的陣列切片(預設每個切片 4096 個元素)。這個設計依然保留了我想要的那種內部「本質上就是陣列」的表示方式,也符合我追求的記憶體特性,同時還能讓 ARSCAN 和 ARPOP 在掃描現有陣列時,花費的時間與實際存在的元素數量成正比,而不是與索引範圍的大小成正比。
接著,就是逐行閱讀所有程式碼的時候了。雖然一切都能運作,而且這個型別本身也有大量測試——這同樣要感謝 AI——但表面上能跑,不代表就是最佳的。我發現了許多我不想要的小缺點或設計錯誤,於是開始對許多模組進行手動與 AI 輔助的重寫。完成這個階段後,到了第三個月,我開始用各種不同的方式對這個實作進行壓力測試。我開始確信它真的夠穩固、實用,而且設計得很好。
然後……就發生了。在模擬各種使用情境、看看這個資料結構用起來是否順手時,我開始把 markdown 檔案放進 Redis 陣列裡。因為檔案跟它實在太搭了。在這個時候,正好我也在為了其他目標跟 agent 一起工作,我意識到我可以擁有我所需要的、集中存放 skills markdown 檔案的知識庫,於是出於自身的需求,我決定實作 ARGREP。但我還想要正規表示式。該選哪個函式庫呢?
最後我選了 TRE(感謝 Ville Laurikari!),因為當你在 Redis 裡使用正規表示式時,你會希望確保不會有在時間或空間上出現病態模式的情況。但 TRE 在一個非常實用且特定的情況——也就是匹配 foo|bar|zap 這類樣式時——效率非常差。所以在 GPT 的協助下,我對它進行了最佳化,修掉了一些潛在的安全性問題,並擴充了測試。一切就都到位了。
你知道這一切最大的體悟是什麼嗎?要做出高品質的系統程式設計,你仍然必須全心投入,但我卻敢於挑戰一個若沒有 AI、我本來會跳過的複雜度。AI 提供了兩方面的安全網:一是那些非常耗費心力的龐大任務(像是後來才加入並完成測試的 32 位元支援),二是確保複雜演算法中沒有明顯錯誤所需要的虛擬人力。而撰寫最初那份龐大的規格文件,正是後續所有工作的關鍵,就像逐行審視 sparsearray.c 和 t_array.c、並把不合適的地方全部修改掉一樣關鍵。
關於使用情境我就不再多說了,因為我已經在 PR 本身的說明訊息中詳細記錄了:
https://github.com/redis/redis/pull/15162
所以在這裡重複就沒什麼意義了。只要說,我真心認為 Redis 早就該有一個將數值索引作為語意一部分的資料型別了。
我希望 Array 的 PR 能盡快被接受,讓我們都能受惠於它所開啟的新使用情境。當然,也歡迎大家提供回饋。謝謝。
隨機一篇部落格
留言
登入後參與討論