堪比Web SQL:開源項目讓IDB速度原地提升數十倍

IndexedDB 的優點是通用性能良好,缺點是速度太慢了。

資料庫結構化查詢語言 SQL,是絕大多數程式設計師都會經常用到的工具。不過這個東西入門容易精通難,有很多技巧和特性是一直被人忽略的。

最近,程式設計師 James Long 提出了 absurd-sql,它是 Web 上 SQLite 的持久化後端,不會將整個資料庫載入到記憶體中,並且可以持續寫入。

James Long 撰寫了一篇部落格來講解 Web 儲存 API(主要是 IndexedDB)的缺點,並介紹了 SQLite 在 absurd-sql 的加持下如何提供 10 倍的性能改進及其技巧。

項目地址:https://github.com/jlongster/absurd-sql

IndexedDB 並不是完美的

如果要編寫一個 web 應用程序,你可能會選擇使用 IndexedDB 來儲存資料,這是能在所有瀏覽器上運行資料庫的唯一選項。

當試圖構建一個本地應用時,你會發現圍繞這個資料庫構建整個應用並不理想,當然,其中的少量功能還是很有用的。但如果想構建更好的網路應用程序,我們需要一種更強大的方式來處理資料。

IndexedDB 有一個缺點是速度緩慢。一般來說,資料庫的簡單操作大約需要 10ms 的時間,profile SQLite 通常需要 0.01 ms 的時間,這些時間長短和具體編寫什麼應用程序有關。

在 IndexedDB 中查詢資料,需要使用者手動操作。IndexedDB 唯一提供的函數是 count,其餘的 API 只返回一系列 item。使用者必須以特定的方式連接索引和構造資料,才能構建查詢函數。

甚至添加一個新的對象儲存都很困難,如果使用者想打開資料庫添加對象儲存,需要強制終止其他所有 tab 與資料庫的連接。

IDB 不是完美甚至是有些低級的,因此作者提出了 absurd-sql。

absurd-sql

SQL 是構建應用程序的好方法。尤其是小型本地網路應用程序。鍵 / 值儲存可能在大型分散式系統中佔有一席之地,但是如果我們可以在 Web 上使用 SQLite 會不會很棒?

absurd-sql 使這成為了可能。absurd-sql 是 sql.js 的檔案系統後端,它允許 SQLite 從 IndexedDB 讀 / 寫一小部分資料,就像磁碟一樣。

值得注意的是,absurd-sql 的項目作者受到了 SQLite 的啟發。

sql.js 是 SQLite 到 Webassembly 的一個埠,通過使用 Emscripten 編譯 SQLite C 程式碼,它是 SQLite 在 Web 上應用的一個良好例子。它使用儲存在記憶體中的虛擬資料庫檔案,因此不會保留對資料庫所做的更改。sql.js 將 SQLite 編譯為 WebAssembly,並允許讀取資料庫和運行查詢。這裡就存在一個重要的問題——使用者不能持續進行任何寫操作。它將整個資料庫載入到記憶體中,並且只更改記憶體中的資料。刷新頁面後,所有更改都將丟失。

雖然記憶體資料庫有其用途,但這會讓 SQLite 的用途變得很受限。開發者要使用它構建任何類型的應用程序,都需要具備資料持久化的能力。

absurd-sql 解決了這個問題,它通過攔截來自 SQLite 的讀 / 寫請求,將它們獲取並持久化到 IndexedDB(或任何其他持久後端)來工作。項目作者編寫了一個完整的檔案系統層,該檔案系統層知道 SQLite 如何讀取和寫入資料塊,並能夠有效地正確執行操作。這意味著它永遠不會將資料庫載入到記憶體中,而是隻載入 SQLite 要求的任何內容,並且寫入始終保持不變。

項目作者表示,這裡使用 sql.js 是因為它已經擁有一個龐大的社區,並且是迄今為止在 Web 上使用 SQL 的最常見方式。

安裝 absurd-sql 的程式碼如下:

    import initSqlJs from '@jlongster/sql.js';import { SQLiteFS } from 'absurd-sql';import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';SQL = await initSqlJs();sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend());// This is temporary for nowSQL.register_for_idb(sqlFS);SQL.FS.mkdir('/sql');SQL.FS.mount(sqlFS, {}, '/sql');let db =new  SQL.Database('/sql/db.sqlite', { filename: true });

    資料庫 db 的使用也與常規使用方法沒有什麼區別,並且還做到了有效地讀取和持久寫入 IndexedDB:

      let stmt = db.prepare('INSERT INTO kv (key, value) VALUES (?, ?)');stmt.run(['item-id-00001', 35725.29]);let stmt = db.prepare('SELECT SUM(value) FROM kv');stmt.step();console.log(stmt.getAsObject());

      這樣一操作,Web SQL 彷彿回來了!項目作者表示:「雖然目前還不推薦將 absurd-sql 用於生產,但我移植了我的應用程序 Actual 來使用 absurd-sql,結果表明還能夠良好工作。」

      速度提升

      IndexedDB 是當前唯一適用於所有瀏覽器的持久儲存,它將資料庫和持久儲存兩種需求合二為一。

      作者使用 sql.js + absurd-sql 抽象出儲存層。並討論了 absurd-sql 能讓 IndexedDB 變快多少。不過速度仍然比原生 SQLite 慢 50-100 倍,這是因為 IndexedDB 的寫入速度很慢,無法進行批量讀寫。

      IndexedDB 只是 absurd-sql 的一個後端,作者還嘗試過使用 webkitFileSystem 做後端,結果發現其無法匹敵 IDB 後端的性能。

      實際上,SQLite 在各項性能指標上都能輕鬆擊敗 IndexedDB。該項目作者編寫了一個基準測試應用程序來測試不同的場景,運行幾個不同的查詢,並允許使用者以多種方式配置 SQLite 和基於原始 IndexedDB 實現運行測試。

      測試結果:https://docs.google.com/spreadsheets/d/1Cpb9r3cZlbZgp1RoSTmh22wOPCRMqINzMUelUTIDo8Y/edit#gid=0

      基準測試程式碼:https://github.com/jlongster/absurd-sql/tree/master/src/examples/bench

      以寫入為例,IDB 的速度比 SQLite 慢了數十倍:

      批處理為 SQLite 提供瞭如此巨大的性能優勢,任何 CPU 處理都無法彌補差距。

      而 absurd-sql 通過避免 IndexedDB 讀 / 寫節省了大量時間,以至於 CPU 命中的影響可以忽略不計。

      實驗表明,absurd-sql 能夠很好地處理 1000000 item 的情況。從速度上看,absurd-sql 寫入需要 4-6s,讀取需要 2-3 秒;而 IndexedDB 完成這一過程,寫入大約是 2 或 3 分鐘,讀取大概在 1 分鐘左右。

      參考內容:

      https://jlongster.com/future-sql-web

      相關文章

      世界算力簡史(中)

      世界算力簡史(中)

      接上集:世界算力簡史(上) 在上一篇裡,小棗君提到了ENIAC的誕生。 其實,在1945年-1948年,也就是中國還處於內戰時期時,除了EN...