我開發 Stoolap 已有一段時間,這是一個以純 Rust 編寫的嵌入式 SQL 資料庫。它最初是一個 Go 專案,後來發展得越來越龐大,最近我認為:好吧,這個東西很快,但人們在 Rust 之外要如何實際使用它呢?
對許多開發者來說,答案是 Node.js。所以我建置了 @stoolap/node,一個由 NAPI-RS 驅動的原生驅動程式,讓你能從 JavaScript 和 TypeScript 直接存取 Stoolap。
中間沒有 HTTP 伺服器。沒有序列化開銷。你的 Node.js 處理程序透過原生綁定直接與資料庫引擎對話。
看看,SQLite 很棒。我自己也用它。它經過實戰考驗,文件齊全,無處不在。但有些事情它做得不好,或者根本不做。
Stoolap 擁有 MVCC 交易、成本導向查詢優化器、平行執行、語意查詢快取,以及帶有 AS OF 的時間查詢。這些不是可有可無的功能;它們在真實的工作負載中確實能發揮作用。
但我一直被問到的問題是:它真的比較快嗎?
這是個好問題。所以我跑了基準測試。
我撰寫了一套全面的基準測試套件,針對 @stoolap/node 和 better-sqlite3(Node.js 中 SQLite 的黃金標準)執行了 53 個相同的 SQL 操作。相同的資料,相同的查詢,相同的機器。
設定:10,000 筆資料,混合了點查詢、連接查詢、聚合查詢、子查詢和分析操作。為了公平起見,所有操作都在記憶體中執行。
老實說,我沒預料到這個比例。讓我來分析一下最大的差距在哪裡。
這些差異並不大。其中一些數字讓我感到驚訝:
COUNT DISTINCT 的結果快了 138 倍,這可能是最驚人的。Stoolap 維護內部資料結構,使得計算不同計數幾乎是免費的,而 SQLite 每次都必須掃描並去重。
子查詢效能(EXISTS, NOT EXISTS, IN, NOT IN)來自 Stoolap 的半連接優化。它從子查詢結果建立一個 HashSet 並探查它,而不是逐行執行相關子查詢。
讓我們誠實地談談 SQLite 較快的地方:
這些都是小幅度的差距,大多在 1.0x 到 1.6x 的範圍內。SQLite 的單行操作受益於數十年來在該特定路徑上的優化。B 樹頁快取對於點查詢的調校非常出色。
但請注意模式:SQLite 的優勢在於簡單的單行操作,這兩種資料庫都已經能以毫秒級完成。Stoolap 的優勢在於分析性和複雜查詢,其差異在 10 倍到 100 倍以上。
無鎖 MVCC。Stoolap 使用多版本並行控制,這意味著讀取器永遠不會阻塞寫入器。在 Node.js 驅動程式中,這表示你的非同步查詢不會因為待處理的寫入而停滯。
成本導向優化器。Stoolap 不會總是執行順序掃描或總是使用索引,而是估計不同執行策略的成本並選擇最便宜的。對於具有多個條件或連接的查詢,這會產生巨大差異。
平行執行。對大型資料集執行的查詢會自動使用 Rayon 的工作竊取排程器進行平行化。篩選、雜湊連接、排序和不同計數操作都可以跨核心擴展。
如果你使用過 better-sqlite3 或任何其他嵌入式資料庫驅動程式,這個 API 應該會讓你感到熟悉:
同時提供非同步和同步 API。非同步 API 在 libuv 的執行緒集區上運行,因此不會阻塞你的事件迴圈。如果你在允許阻塞的環境(腳本、CLI 工具、測試)中,同步 API 對於簡單操作來說速度稍快。
對於熱門路徑,預備語句可以完全跳過解析:
交易的運作方式符合你的預期:
記憶體資料庫對於基準測試來說很棒,但實際應用需要持久性。Stoolap 使用 WAL(Write-Ahead Logging)並具有可設定的持久性:
資料會在處理程序重新啟動後保留。快照會定期在背景執行,因此 WAL 不會無限增長。
提供預先建置的二進位檔,支援 macOS (x64, ARM64)、Linux (x64, ARM64) 和 Windows (x64)。不需要 Rust 工具鏈。
完整的 API 文件可在驅動程式文件 中找到。
Node.js 驅動程式目前是 v0.3.1 版本。它涵蓋了完整的 Stoolap API:資料庫、交易、預備語句、批次操作以及所有查詢方法。
我計劃在未來的版本中加入連線集區助手和串流查詢支援。如果你遇到問題或有功能請求,請在 GitHub 上開啟一個 issue。
試用看看,並告訴我你的想法。