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