高 歌, 胡卉芪
(華東師范大學 數據科學與工程學院, 上海 200062)
人工智能(artificial intelligence, AI)應用普遍存在于日常生活中. 然而, 應用的開發、部署、維護以及AI 數據管理的復雜度為構建現實世界的AI 應用帶來了極大的成本. 通常, 企業為統一化開發流程, 將機器學習任務劃分為一系列有序階段, 主要涵蓋數據收集、數據準備、特征工程、模型訓練、模型驗證、部署和監控等關鍵步驟, 并將配置和工具等基礎設施集成到系統中, 以自動化線下測試與線上生產環境的迭代并改善協作; 由此構建的一套機器學習工作流稱為機器學習管道(machine learning pipeline)[1-4]. 在管道構建和維護階段, 模型的開發和部署只占小部分, 隱藏很多針對機器學習數據管理的“技術債務”[5], 需要耗費大量的數據管理成本; 同時, 隨著管道規模增加, 不同任務間特征數據的組合、演化過程愈加復雜, 管道中缺乏對不斷更新的特征版本的一致性管理. 通過將中心化特征平臺集成于數據管道中: 特征轉換引擎處理特征工程任務, 特征存儲緩存預計算的特征來追蹤特征演化過程;并向下游模型提供特征消費 (feature serving, 與后文的“推送”“檢索”“查詢”為同義) 服務. 實現特征從發布、生產到推送的一致性管理, 改善了機器學習生產流程, 提升機器學習生態系統的擴展型和魯棒性.
現今開發的特征管理平臺是批處理、流計算、緩存和存儲的集成系統. 工業界通常使用開源存儲在機器學習管道之上構建特征存儲: Uber[6]為規范化可擴展模型訓練到生產服務容器部署的統一管理, 采用集成的開源系統和內部開發組件構建了機器學習平臺, 將其用于UberEats 等AI 應用中;Feast[7]采用典型的批流架構的離/在線存儲協同進行特征管理的平臺, 提供注冊、存儲、消費、監控的能力; Feast 不直接管理存儲, 通過增量物化過程將數據從離線存儲加載到在線存儲, 保證訓練和推理階段的特征的一致性. 學術界從特征轉換、可擴展性管理、新鮮度與成本之間的權衡、異構特征管理等不同角度開展了研究: 特征提取是許多在線決策增強(on-line decision augmentation, OLDA)任務中最耗時的工作, 開發基于持久性內存的分布式內存數據庫系統[8], 設計經過PMEM (persistent memory)優化的持久性雙層跳表索引, 能有效減少時間窗口特征抽取時間. 當特征在訓練和推理階段有不同實現方式時, 特征重用引入了額外的同步開銷, 為臨時構建的機器學習管道增加了復雜度,Ormenisan 等[9]討論關于管道可擴展性的問題, 并構建了具有水平可擴展的特征存儲作為機器學習管道的一部分. 除了表格型數據的特征, 在自監督深度學習領域中嵌入向量成為需要管理的對象,Embedded Store[10]總結了管理嵌入訓練數據, 衡量嵌入特征質量和監控使用嵌入的下游模型帶來的挑戰, 討論了以嵌入向量作為管理對象的特征管道構建方法.
特征存儲除了要滿足上述管理訴求以外, 特征新鮮度也成為影響模型性能的主要因素之一. 特征新鮮度, 即版本更新在時間維度上的新舊程度. 特征存儲需要推送滿足新鮮度約束的特征; 尤其在基于在線決策或事件驅動的在線預測等實時機器學習任務中, 模型推理階段需要在線特征存儲推送實時數據來生成更準確的預測, 并使模型適應不斷變化的環境. 例如, 實時個性化推薦服務[11]: 根據用戶的實時行為和反饋, 實時分析用戶興趣和偏好, 并向其推薦相關商品、新聞等內容. 信用欺詐檢測任務: 訓練實時機器學習模型來分析用戶的歷史交易和實時交易行為是否存在異常變化, 以快速識別潛在欺詐交易[8]. 送餐時間估計: 結合歷史、靜態特征 (如商家在該區域送餐的平均時間) 和基于環境變化的實時數據 (路線距離、交通擁堵情況和實時行進速率) 即時反饋到達時間[12]. 綜合上述應用特點,實時預測任務處理的主要特征有: ① 預測是基于事件驅動的, 請求觸發預測同步處理, 并將結果并反饋給用戶. ② 模型推理結果需要滿足低延遲和時效性以確保模型在最新的數據集中的處理決策. 而同步特征計算帶來的延遲是不可忍受的, 通常采用異步計算—頻繁更新的特征流預緩存到在線特征存儲中, 事件到來時查詢最新相關特征到下游任務. ③ 一批特征推送可能同時包含實時特征和靜態(與時間無關) 或歷史特征 (查詢非最新特征), 特征查詢要保證查詢延遲與當前版本數量及查詢新鮮度無關. ④ 特征版本消費頻率隨時間推進減少, 且在線特征訪問具有長尾分布的特點. 大量舊版本和非熱點數據作為冷數據需要被回收. 基于上述特性, 本文考慮了在線特征存儲系統中的主要設計要點:① 多版本特征管理, 支持特征發布和版本控制, 并提供特征版本查詢. ② 支持大規模特征的高吞吐批量更新, 實現特征消費的低延遲和高時效性. ③ 查詢延遲和新鮮度無關, 加速最新特征讀, 同時不影響歷史版本檢索效率. ④ 根據特征訪問的滯后淘汰性和長尾分布特點, 設計冷數據回收機制.
工業界對于在線特征存儲的解決方案通常基于開源存儲系統, 如內存存儲Redis、鍵值存儲CockroachDB 等. 這些開源方案存在一定局限性: 缺乏對版本控制功能的原生支持; 雖然內存存儲如Redis 等可以提供十萬級QPS (queries per second)的讀寫吞吐, 但查詢效率取決于系統實現, 新鮮度約束的查詢可能影響歷史特征的查詢效率, 并難以進行針對性優化; 不支持冷熱數據區分且大量舊版本需要采用手動回收策略. 典型的在線存儲優化方案有: Doordash 基于Redis 設計特征存儲并應用于送餐時間預測任務[12]; 主要從字符串哈希、protobuf 序列化復合特征類型、值壓縮等方法優化內存占用, 緩解Redis 擴展成本高的問題. Ralf 將模型的準確性作為評估特征質量的重要指標[13-14], 提供了特征消費新鮮度和消費成本間的最佳權衡. 本文研究聚焦于大規模在線特征的版本管理和消費, 設計并開發了基于內存的在線特征存儲. 本文的主要貢獻有:
(1) 基于上述總結的系統設計要點, 開發了基于內存的多版本特征存儲引擎FeaDB, 提供一套特征版本管理原語. 滿足特征從生產到消費的在線管理需求, 包括特征的注冊發布, 多源特征攝入, 多版本管理與在線消費.
(2) 采用索引技術優化特征檢索過程. 提出了加速單版本查詢的內嵌版本索引列表的擴展哈希表索引和支持版本集查詢的Crosshint 索引, 確保查詢效率受新鮮度差異影響小. 實驗證明兩者在讀延遲方面都有不同程度的優化, 且Crosshint 相對哈希索引查詢性能更穩定.
(3) 提出了基于時間戳的特征回收方法. 緩解了由大量舊版本特征擠壓內存空間及降低版本查詢效率的問題.
FeaDB 系統架構見圖1. 特征在接入前需要被注冊發布到特征存儲中; 其上游系統接入批、流處理系統, 數據流或批量歷史數據經預處理、計算轉換為特征, 被加載到存儲中; 在模型推理階段, 當有事件請求時, 模型服務系統檢索當前時間約束的實時版本及其他特征用于預測任務, 同時可能有多個服務系統接入消費模塊. FeaDB 包括特征注冊、存儲模塊、特征訪問 (消費) 層、回收數據塊的后臺線程和分配器. 工程師編寫的自定義特征, 如果沒有共享和管理, 會導致重復工作和缺乏定義的一致性,難以應對規模化特征管道并維護特征的可組合性和可擴展性. 特征存儲允許特征在特征注冊模塊發布特征—用戶在特征庫中編寫特征定義元數據, 包括特征轉換、版本、所有者等; 特征注冊模塊間接跨用戶和服務提供了共享渠道. 預計算的轉換結果被寫入存儲, 向下游模型提供服務以滿足不同的查詢延遲要求, 需要一套版本管理原語以向消費層提供版本檢索功能. 存儲內部由版本集、索引、用于塊回收和分配的空間管理器組成. 特征消費層向下游推送最新版本特征.

圖1 FeaDB 設計架構圖Fig. 1 FeaDB architecture
1.2.1 特 征
特征演進是由數據集更迭以及模型更新引起的, 特征版本為數據源在某一時刻從上游生成/觀察到的帶有時間戳的特征. 采用時間序列數據表征特征動態變化, 特征的最小更迭單位—特征版本,可表示為 [Uid, Time, Value] . Uid 唯一標識一個特征記錄; Time 表示時間戳, (時序)特征時間或系統分配的時間戳字段標識一個版本; Value 為該特征版本的特征數據. 特征版本的時間字段在時間點連接(point-in-time-join)中使用, 以確保最新的特征值被推送到模型.
1.2.2 特征集與特征快照
用戶也可針對模型推理任務創建一組特征集, 在特征集上建立版本快照. 特征集(feature set)是一組特征的只讀集合, 用戶可為每個模型版本注冊一個或多個特征集, 用于在推理階段向模型推送滿足新鮮度約束的在線特征, 通常以快照為邏輯單位向下游任務推送數據, 其中快照定義為特征集在某時間范圍的一組特征值. 特征集在創建時需要指定Uid 列表、TTL (time-to-live)、存留策略. TTL 表示快照內的特征時間范圍, 存留策略為特征集中每個快照的存留期限, 當TTL 超過存留期限范圍時自動刪除版本快照. FeaDB 支持特征集創建、快照讀/刪除操作.
當特征集被創建后, 不再原地更新值或插入特征, 只進行整體快照版本的更迭, 根據設定的TTL 定期觸發一組版本快照的生成. 用戶也可顯式執行特征集快照刪除操作, 只刪除快照, 保留特征值, 快照機制的實現見1.5.1 節. 特征集的概念適用于管理一組模式較為固定的特征, 其中特征可以被多個特征集共享, 當所有包含該特征的特征集都被刪除時, 才會刪除該特征. 通過特征集快照,FeaDB 能夠重現任意特征集在過去某個特定時間段的特征狀態. 在線推理階段通過快照機制生成時間點正確的特征集以避免數據泄露, 確保未來的特征值不會被泄漏給模型.
FeaDB 通過提供特征注冊、存儲、歷史檢索, 在線消費等來創建自定義特征管理工作流, 實現特征從生產到消費的數據管道. FeaDB 提供特征版本插入, 時間點正確的版本查詢 (單版本與范圍查詢)以及面向特征集的操作, 包括特征集創建、快照檢索、快照刪除等. 用戶可通過快照查詢來檢索在線特征, 并推送到模型服務系統. 用戶注冊特征集作為某個模型版本的數據集, 設置快照創建的TTL 及存留策略. 通過快照讀方法或范圍查找方法檢索時間點之前的最新快照版本 (子集). FeaDB 不支持對單特征的刪除操作, 用戶需要建立待刪除特征集, 對特征集執行統一刪除. 表1 列出了主要操作.

表1 FeaDB 提供的特征管理原語Tab. 1 Feature management operation in FeaDB
重新設計存儲引擎來滿足預期性能目標, 將從索引管理、存儲布局、空間回收策略三方面介紹FeaDB 的主體設計. 這三個重要模塊都會對特征的讀寫性能造成影響, 同時彼此相互制約, 因此涉及的一些技術難點將在1.4、1.5 節中分別討論.
1.4.1 優化版本查詢的索引設計
在信息檢索領域中, 加速檢索過程的技術手段有索引、緩存等. 考慮到為快速適應不同任務間特征檢索的新鮮度差異, 傳統基于頻率的緩存替換算法不符合要求, 需要提出量化版本分布的指標并設計精確的自適應替換策略即時響應分布變化, 以提高命中率和查詢性能穩定性, 除了淘汰策略, 緩存大小等變量也會影響緩存命中率, 同時緩存無法解決冷啟動問題. 綜合上述考慮, 本文選擇采用索引來加速版本查詢過程.
索引設計要求: ① 支持版本點查與范圍查詢. ② 空間利用率高. 批量更新操作和舊版本索引條目回收對索引結構影響小. 即減少頻繁的索引更新及其帶來的昂貴的分裂、合并操作. ③ 并發度高, 讀寫阻塞率低. ④ 查詢延遲與新鮮度無關. 基于4 點要求, 最終選擇空間利用率和并發友好的動態可擴展哈希作為基本索引結構, 相比樹類索引和靜態哈希索引, 其節點 (桶) 分裂 (重哈希) 代價更低. 然而版本粒度的索引構建會引入頻繁的桶分裂代價, 且重哈希過程會鎖住整個索引導致并發讀降低; 再者, 可擴展哈希本質上可理解為物理聚集的版本鏈, 新鮮度差異影響查詢效率. 在此基礎上提出索引優化方法, 索引頁粒度而非版本粒度來減少桶分裂頻率, 查詢路徑被分散到不同桶中增加并發度; 同時, 將版本鏈取代為頁粒度的版本索引列表, 大大縮減版本搜索路徑, 提升查詢穩定性.
考慮到每次對只讀特征集快照的版本查詢都要在哈希索引中重復同樣的檢索過程, 查詢效率低.提出了特征集快照索引Crosshint, 即時返回快照版本, 由于索引對象只讀, 也不會改變Crosshint 結構. 下面詳述兩種索引原理, 在2.1 節中闡述采用兩種索引的特征檢索過程.
動態可擴展哈希表是一種動態哈希技術[15]. 相比于靜態哈希表, 動態哈希表隨著插入key 數量的增加支持動態的桶增長 (分裂)、桶聚合操作; 空間靈活且內存利用率更高. 可擴展哈希表由以下幾部份組成.
(1) 哈希目錄: 為一個指向數據塊 (桶) 的指針數組. 哈希目錄倍增擴張, 因此, 其長度總是2 的冪.
(2) 哈希桶: 存儲哈希鍵值. 目錄指向桶, 當局部深度小于全局深度時, 一個桶可能由多個指針指向它. 圖2 右側為示例的4 個桶.

圖2 內嵌版本索引列表的動態可擴展哈希表Fig. 2 Extendible Hashtable with embedded version index array
(3) 全局深度 (global depth): 跟目錄相關聯, 表示通過哈希值的前“全局深度”個比特位作為哈希目錄的索引. 圖2 中哈希目錄條目為8, 全局深度為3.
(4) 局部深度 (local depth): 每個桶關聯一個局部深度值, 表示該桶內所有的哈希鍵的前“局部深度”個比特位是相同的. 圖2 的每個目錄索引的標紅的比特數表示其局部深度, 若局部深度小于全局深度, 表示可以有多個條目共享同一個桶.
當桶溢出時, 表示現有的局部深度不足以區分“桶大小”的哈希鍵, 因此, 局部深度遞增, 桶分裂,桶內的數據被重新分配到兩個桶中. 桶分裂后, 當該桶的局部深度大于全局深度時, 全局深度遞增, 且哈希目錄倍增. 在哈希查找中, 在哈希目錄中通過哈希鍵查找哈希桶, 匹配桶內數據.
FeaDB 中可擴展哈希表的索引值為塊粒度的版本索引列表, 即若該特征的某個版本在某塊中出現, 則將首次插入的特征值地址插入哈希表. 在哈希表中, 每個Uid 的桶槽 (即桶的每個條目) 記錄該特征在塊中的首個版本地址. 圖2 顯示了Uid 為001 的特征版本鏈, 其在3 個數據塊中首次出現的版本分別為t1,t3,t5. Uid 的哈希桶條目中記錄這3 個版本及其元組地址. 當查找該Uid 的某個版本時,通過哈希索引取得其版本索引列表, 在列表上進行二分搜索定位所在塊的首個版本地址, 通過元組的next 字段遍歷版本鏈直到塊尾即可. 該索引可避免過長的版本鏈遍歷 (由指針追蹤造成的性能降低),從而將搜索范圍減小到塊內的短鏈搜索. 考慮到Hash 條目不宜占過大的空間, 每個特征限制其版本索引列表長度為8. 當查詢版本未落入該特征“索引窗口”時, 選擇替換掉最舊頁的索引元組; 同時, 塊中嵌入的輕量min-max 索引也能夠實現結果集剪枝, 減少無效的塊遍歷, 具體見1.4.2 節.
Crosshint 索引基于特征集快照檢索設計. FeaDB 用兩個哈希表分別存儲特征集的Uid 信息和Crosshint 索引, 見圖3. Crosshint 索引特征集在某個版本的快照數據. 具體實現中, Crosshint 的索引鍵為“特征集名, 版本”, 如圖3 中“F1,t1”的哈希值為特征集F1在版本t1上的Crosshint 索引. Crosshint版本索引使用一個指針數組實現, 每個指針節點定位到具體的特征版本. 當查詢特征集快照時, 根據哈希索引讀取Crosshint 指向的特征集版本. 當快照被移除時, 只刪除Crosshint 索引而無需改動任何數據.

圖3 特征集和Crosshint 哈希索引Fig. 3 FeatureSet and Crosshint Hashtable index
1.4.2 存儲布局
特征版本存儲結構可采用版本鏈或順序組織, 基于對版本回收效率和內存利用率的考量, 采用順序組織的方式、更新的版本以追加模式寫入內存塊中, 塊寫滿則分配新塊, 以塊為單位回收舊版本. 采用這種方式的優點是版本回收效率高且幾乎無內存碎片, 減少指針追蹤 (pointer chasing) 隱患. 弊端是版本分散到不同塊中, 需要依賴索引設計優化版本讀, 以緩解版本隨機讀帶來的查詢延遲損耗, 見1.4.1 節的索引設計. 圖4.1 展示了包含數據塊和索引塊在內的存儲布局.

圖4 FeaDB 內存存儲布局Fig. 4 FeaDB in-memory storage layout
特征數據以4 kB 大小內存塊為單位進行管理, 由塊內元組數據和其他元數據構成. 其中每個元組結構分為數據長度、元組、下一個元組版本地址 (next 字段) 3 部分. 通過4 個字段(Uid, time,value, refCount)表示一個元組結構, 其中refCount 字段表示該特征所屬特征集數量, 當特征集刪除時該字段遞減, 當字段減為0 時value 字段更新為刪除標記. 當value 為變長字符且其長度超過塊大小的1/3 時, 為該元組value 單獨分配一個塊, 采用間接方式訪問, 此時value 為指向該塊的指針變量.下一個版本地址由 (塊號, 塊內偏移量) 組成, 通過該字段形成Uid 的一個版本鏈.
塊內元信息包含塊號、塊內有效負載、塊內元組數量及min-max 索引. min-max 索引為塊內第一個元組和最后被插入元組的時間戳(默認數據被有序插入, 該字段也可作為塊創建和塊滿時間). minmax 索引的好處有: ① max 值可標記為該塊時間戳, 作為塊回收基準; ② 塊粒度索引列表的索引范圍有限 (8 塊), 當查詢范圍超出其索引范圍時, 可通過min-max 索引快速過濾結果塊集.
存儲索引的數據塊可分為哈希目錄塊和桶塊兩部分. 哈希目錄為指針條目數組, 每個條目索引到相應的桶. 當現有全局深度不能區分Uid 時, 桶分裂的同時目錄以倍數擴增. 其哈希目錄結構如圖4所示. 塊內字段包含: 塊號, 全局深度, 哈希值的前“全局深度”比特位索引哈希鍵; 桶數量和每個桶的局部深度; 哈希目錄大小和索引到桶的指針數組.
每個桶存儲哈希鍵值: 哈希鍵為Uid, 值為相應特征鏈中在各個塊中首次出現的版本地址以及最新版本地址. 每個鍵值占據一個槽, 在一個4 kB 的索引塊中為每個桶分配32 個槽, 桶滿時觸發一次分裂操作, 桶中索引將被重新分配. 桶內的每個槽包含鍵值信息: Uid, 最新版本地址和塊首個版本地址列表(版本索引列表). 桶內元信息除了塊號、有效槽負載、已分配槽長度等塊信息外, 其他定義的字段包含該桶的局部深度、桶中哈希值 (該字段標識索引到該桶的哈希值)、可讀性位圖 (該Uid 是否有效)、占用位圖 (該Uid 是否被刪除) 等.
1.5.1 空間約束的內存管理機制
索引溢出或數據塊剩余空間不足時, 塊管理器向分配器申請4 kB 大小的塊, 分配器取出空閑鏈表頭的空閑塊; 當塊被回收時, 分配器將其插回空閑塊中. 分配器為空閑鏈表的大小設置上下限, 當小于下限時分配器向內存申請空間, 大于上限時將多余的塊釋放交給操作系統管理. 這樣可避免剩余空閑塊過少影響塊分配效率, 同時減少因空閑塊過多造成的空間利用率低的問題.
1.5.2 基于時間戳的版本回收策略
特征集的版本回收包含3 種方式: ① 移除某個版本的快照只需刪除對應的Crosshint 索引. ② 當特征集版本的數據用戶在創建特征集時設置存留策略 (即特征集快照有效期) , 版本回收線程將定期掃描特征集中是否包含過期快照, 并根據用戶設置進行版本數據刪除或持久化. 對過期特征版本值更新為刪除標記, 真正的刪除延遲在塊回收步驟實施. ③ 用戶顯式調用特征集刪除, 該特征集上所有數據及其快照都會被刪除.
塊回收. 由于舊版本的訪問頻率更低, 可將不再推送的過期特征刪除, 避免擠壓內存空間. 釋放舊版本引發塊空間回收時, 需要盡可能少的修改元數據, 且數據塊的回收也會引入索引結構可能的合并代價. 后臺回收線程定期掃描, 若舊塊時間戳 (min-max 索引的max 字段標記) 超過存留時間期限, 則將該塊回收, 由分配器清空該塊并插入空閑鏈表中. 由于舊塊中的所有數據都可看作是“過期”的 (由TTL 標記和存留期限共同決定), 因此基于時間戳的塊回收機制可確保是安全的, 即回收查詢操作不存在交集. 塊回收的優勢除了增加內存利用率, 實驗證明還可以增加查詢效率, 減小消費延遲, 見3.3 節性能評估部分.
2.1.1 單特征歷史版本檢索
查詢步驟: ① 哈希索引查找Uid, 得到版本索引列表(即該Uid 每個版本所在頁的首個記錄地址(塊號, 塊內偏移)), 對列表進行二分查找, 得到滿足檢索條件的頁中首次出現的版本地址. ② 對于索引得到的每個結果由next 字段進行頁內的鏈表遍歷, 查找符合檢索條件的元組; 由于哈希索引的頁面范圍是有限的, 若該特征索引的最后一頁符合檢索條件, 則后續頁還需要進行頁遍歷, 通過minmax 索引判斷該塊內是否有符合檢索條件的結果集.
2.1.2 時間點正確的特征集快照檢索
查找Crosshint 在 (特征集, 版本) 的快照. 其中快照建立的過程滿足2.1.1 節中的過程. 查找Crosshint 哈希時, 若已創建符合時間范圍內的快照, 則返回Crosshint 指向的版本值; 若未找到對應的快照, 則搜索時先查詢已有快照中屬于時間戳上下限的快照版本Crosshint(fs, tj), 其中ti≤TTL≤tj,遍歷ti到tj之間的版本鏈定位到所屬時間范圍的特征集. 通過該方法可確保查找到最近的時間范圍不超過檢索條件的版本集.
特征插入大致分為寫入、插入版本鏈、更新索引3 個部分. 插入步驟為:
(1) 根據塊管理器得到上次插入結束的位置, 寫入新元組并更新塊管理器維護的寫入位置變量.
(2) 若該塊剩余空間不夠, 則更新滿塊的min-max 索引; 塊管理器向分配器申請新塊, 寫入新元組并更新該塊的min-max 索引.
(3) 和上個版本的元組建立鏈接. 哈希索引查找Uid, 根據最新版本索引得到上一個版本地址(塊號, 塊內偏移量), 更新上個版本元組的next 指針為新元組地址.
(4) 更新索引. 若新元組地址的塊和上個版本的元組所在塊不同, 則更新版本索引列表, 作為新塊首個出現的版本; 同時, 更新索引對應條目的最新版本地址字段. 詳細的特征版本插入流程見圖5.

圖5 特征版本插入流程Fig. 5 Feature version insertion procedure
2.3.1 刪除快照
刪除快照時只需移除Crosshint 哈希索引的對應條目, 其他結構不做變化.
2.3.2 刪除特征集
FeaDB 只支持以特征集為粒度的刪除. 若需要刪除單個特征, 則需要建立包含單個特征的臨時特征集, 再執行特征集刪除操作. 在刪除特征集時, 首先移除相關的Crosshint 索引以刪除在該特征集的特征快照. 對特征集中相關特征的引用數減1, 當引用數為0 時對該特征value 字段標記為刪除, 待塊時間戳超過存留時間時該塊中所有數據被回收.
實驗在Macos Ventura 13.0.1 操作系統上進行. 處理器型號為Intel Core i5 @2 GHz 4 核, 內存為16 GB.
實驗特征數據選取時間范圍為2023 年5 月1 日9∶00—5 月3 日9∶00 (共48 h), 10 000 條特征,其中每個特征的版本數在100 ~ 1 000 之間, 使用32 byte 整形類型作為特征值, 平均每個特征在此時間范圍內生成大約300 個版本. 總數據量約44 MB. 在3 組負載下分別評估讀線程數的單個操作的讀延遲, 實驗結果見圖6.

圖6 不同負載下版本查詢延遲Fig. 6 Read latency per lookup with different payloads
結果符合推測, 寫會影響讀性能. 即使并發度增加會降低單個操作的延遲, 但隨著寫負載量增加,并發帶來的優勢降低. 讀線程數量越多, 寫對讀的影響越大. 原因是寫特征版本要更新索引的最新版本地址和新塊Uid 出現的首個版本地址, 因此會出現鎖競爭, 當讀線程增多時, 競爭的索引桶數增加,進一步影響讀延遲.
系統內部實現范圍查詢采取兩種途徑: 基于可擴展哈希的時間范圍查詢以及在預定義的特征集上基于Crosshint 的快照版本讀. 設置特征集將特征分組, 所有查詢特征鍵 (Uid) 均屬于某批特征集數據, 設置TTL 為2 h, 存留策略為前12 h, 則每個快照版本內數據時間范圍為2 h, 同時只保留前12 h的數據. 比較特征在48 h 內的使用范圍查詢和快照查詢間的性能差異. 以30 min 為窗口, 對隨機的批量特征做100 次范圍查詢后進行一次延遲統計, 實驗結果見圖7.

圖7 哈希范圍查詢與快照讀的性能差異Fig. 7 Hash range lookup performance vs. snapshot read performance
圖7 結果表明, Crosshint 索引的讀顯著優于哈希表索引讀, 快照讀性能更穩定, 不受查詢時間戳影響, 而基于哈希的范圍查詢受時間影響較大. 這是由于隨著查詢時間由舊到新 (在實際情況中, 等價于寫入數據量的增加), 當查詢的特征版本所在塊超過哈希表中版本索引列表的索引范圍時, 即超過默認設置的8 個塊時, 需要遍歷后續塊的min-max 索引來定位結果集, 增加了讀延遲. 觀察到圖7 哈希索引查詢延遲的間斷性驟減, 是因為階段性的塊回收使得塊數量減小, 查詢時間窗口退回到索引范圍內, 避免了塊間遍歷, Crosshint 也有相應的性能提升, 但顯著性不如哈希查詢. 間接性表明了舊版本特征回收對查詢性能提升的重要性.
機器學習模型性能很大程度依賴于其特征質量; 同時, 在AI 輔助的在線決策應用中, 決策結果需要在線特征即時輸入以反映真實世界數據分布的變化. 因此, 在數據管理層面, 對特征的實時攝取和消費提出了更高的要求. 為響應這一挑戰, 特征存儲需要為特征版本管理和實時更新提供保證, 以協同上游的數據攝取管道, 為模型服務系統提供數據動力. 本篇工作提出了FeaDB—基于內存的在線特征存儲引擎, 核心是為上游模型推斷任務提供低延遲的特征消費. 設計了兩種優化版本查找的可擴展哈希版本索引和Crosshint 特征集索引, 實驗證明Crosshint 查找快照可以獲得更好且更穩定的讀性能; 提出了只讀快照機制, 相對于哈希檢索能夠實現更高效的批量特征推送; 分配器采用基于時間戳舊塊回收策略, 提升了塊回收效率, 同時減少內存空間消耗.