天天看點

将一個純本地應用移植到 Web 端

點選上方“逆鋒起筆”,公衆号回複 pdf領取大佬們推薦的學習資料

作者 | James Long

如果小明有一個全部資料都存儲在本地的應用,而這部裝置被他失手扔進了大海,然後資料也一并煙消雲散了。為了避免這種情況發生,我們可以嘗試将一個純本地應用移植到 Web 端。

在研究一個奇怪的緩存錯誤(https://actualbudget.com/blog/cursed-caching-curious)時我得到了啟發,于是去重新看了一下 Actual 是如何在 Web 端本地存儲資料的。這裡我需要解釋一些曆史背景:多年前,Actual 原本是一個單純的桌面應用程式來着。這意味着我們的所有資料都會存儲在本地,沒有伺服器,自然也不會在網絡上存儲任何内容。

然後我意識到了移動平台的重要性,并且發覺大多數使用者不想為這樣的事情而擔驚受怕:某天裝置失手扔進了大海,然後資料也一并煙消雲散。正因如此,同步引擎誕生了。從那時起,桌面和移動應用程式就可以愉快地同步它們的資料了。一份資料副本被儲存在伺服器上,這樣使用者就可以在登入後輕松檢視他們的資料。如果擔心隐私安全問題,應用程式可以啟用端到端加密。

去年,我開始嫉妒 Web 應用。看看那些應用吧,部署起來那麼輕松友善……它們可以那麼直截了當地将使用者帶入應用,用不着麻煩的安裝過程。可是在桌面端,我得先要求使用者下載下傳 80MB 大小的檔案,然後他們才能開始運作應用。這種下載下傳需求當然會嚴重影響使用者轉化率,還會讓登入流程、支援、A/B 測試以及所有事務做起來都麻煩許多。

我很喜歡桌面應用,因為你可以在桌面端用上好得多的技術(例如原生的 sqlite3);桌面應用的速度也非常快(無需網絡調用),并且使用者可以完全掌控自己的資料。但我也不得不承認,Web 帶來的種種優勢讓桌面端的這些好處相形見绌。

https://www.kalzumeus.com/2009/09/05/desktop-aps-versus-web-apps/

我開始考慮開發 Actual 的 Web 版本。經過一番研究,做了點技術活兒後,我沒有改動整個架構就移植到了 Web 端。

https://app.actualbudget.com/

這意味着你的所有資料仍會存儲在浏覽器本地,并且沒有網絡調用。它是在浏覽器 [注 0] 中運作的完全 100%的“本地”應用。

我還沒有對這個 Web 版本大肆宣傳,因為它還沒有經過足夠的測試,并且有不少内容需要改進,例如采用代碼延遲加載技術來加快加載速度。我最擔心的是資料存儲層。由于 所有資料都在本地存儲,是以如果本地環境出現了什麼問題,使用者就可能會丢失資料。而且因為我們要把所有内容都存儲在本地,這給浏覽器的持久資料庫也帶來了巨大壓力。關注公衆号 逆鋒起筆,回複 pdf,下載下傳你需要的各種學習資料。

需要明确的是:我們不會棄用桌面版本。但将來,Web 版本将成為 Actual 的主要平台,如果使用者需要則可以選擇下載下傳桌面版本。

它的工作機制不太常見。下面我從進階層面做一概述:

  • Actual 使用的是 sqlite3。這是一個硬性要求。這款應用會運作大量複雜的 SQL 查詢以彙總财務資料,這是它的專長所在。查詢都很容易表達,而且運作速度非常快。
  • 在桌面和移動端,我們使用的是原生 sqlite3,但 Web 端不支援 sqlite3。為了解決這個問題,Actual 使用了 sqlite3 的一個 wasm 版本并建立了一個記憶體内資料庫。
  • 顯而易見的問題是持久性。進行更改時,我們需要将其保留在某個位置,以便在使用者重新加載時避免丢失資料。所幸我們使用的是基于狀态的 CRDT,所有更新都以一個“消息”清單的形式釋出。如果使用者線上,這些消息将同步到我們的伺服器,這樣當使用者重新加載時,所有資料都應該同步。
  • 不過,每次打開應用時都要求進行大量同步操作并不是理想的選擇。另外,如果你處于離線狀态,應用就無法承受任何資料丢失的風險。為了解決這個問題,Actual 将每條消息都保留在 IndexedDB 中。當應用程式打開時,它将應用來自本地 IndexedDB 的所有消息以擷取最新資訊。
  • 要求在加載時應用所有消息也不是理想的選項。這種方法無法擴充——如果使用者使用 Actual 已經有好幾個月,就會累積成千上萬條消息。IndexedDB 會無限增長下去,并且應用加載速度會變得越來越慢。為了解決這個問題,當存儲的消息超過門檻值時,它會将整個 sqlite3 db 重新整理到 IndexedDB 并清除所有消息。
  • 這意味着 sqlite3 db 的一個二進制表示形式和消息清單都儲存在 IndexedDB 中。在加載時,應用會從快照建立記憶體内的 sqlite3 db,并應用 IDB 中剩餘的所有消息。

其實,這種方法和預寫日志的工作機制很像。

我之前比較擔心 IndexedDB 的可靠性。從它的文檔來看,似乎浏覽器可能會根據需要删除資料庫,但實際操作中這種情況似乎沒有發生 [注 1]。在存儲空間不足的移動裝置上這個問題可能會更突出,但我并沒有趟移動 Web 這潭渾水(而是用了原生應用)。我還擔心應用會到達 IDB 存儲的上限,但正如接下來所解釋的那樣,這并不是個問題。

這項技術起初隻是一項實驗,但它的效果很驚豔。我在自己的 Actual 應用裡有積累 5 年的資料,而它們在 sqlite3 db 中的大小是 9.7MB。消息表的門檻值約為 50KB,是以對于一位已經使用 Actual 長達 5 年的使用者,我也不過是在 IndexedDB 中存儲總共約 10MB 的資料而已。這離 IndexedDB 的最大存儲限制還差得遠,目前它的上限至少為 500MB 之多。

到目前為止這個辦法效果還不錯,但是我希望對它建立 100%的信心。我一直在深入研究各種浏覽器是如何在磁盤上存儲 IndexedDB 資料的,并發現了我可以做出的一些改進政策。我本想在這篇文章中詳細介紹一番,但最後我還是把主題放在了整體概述上。在下一篇文章中,我将深入研究 IndexedDB 是如何在浏覽器中工作的。

 注釋

繼續閱讀