將一個純本地應(yīng)用移植到 Web 端
點擊上方“逆鋒起筆”,公眾號回復(fù)?pdf 領(lǐng)取大佬們推薦的學(xué)習(xí)資料
作者 | James Long
譯者 | 王強(qiáng)
策劃 | 李俊辰
在研究一個奇怪的緩存錯誤(https://actualbudget.com/blog/cursed-caching-curious)時我得到了啟發(fā),于是去重新看了一下 Actual 是如何在 Web 端本地存儲數(shù)據(jù)的。這里我需要解釋一些歷史背景:多年前,Actual 原本是一個單純的桌面應(yīng)用程序來著。這意味著我們的所有數(shù)據(jù)都會存儲在本地,沒有服務(wù)器,自然也不會在網(wǎng)絡(luò)上存儲任何內(nèi)容。
然后我意識到了移動平臺的重要性,并且發(fā)覺大多數(shù)用戶不想為這樣的事情而擔(dān)驚受怕:某天設(shè)備失手扔進(jìn)了大海,然后數(shù)據(jù)也一并煙消云散。正因如此,同步引擎誕生了。從那時起,桌面和移動應(yīng)用程序就可以愉快地同步它們的數(shù)據(jù)了。一份數(shù)據(jù)副本被保存在服務(wù)器上,這樣用戶就可以在登錄后輕松查看他們的數(shù)據(jù)。如果擔(dān)心隱私安全問題,應(yīng)用程序可以啟用端到端加密。
去年,我開始嫉妒 Web 應(yīng)用。看看那些應(yīng)用吧,部署起來那么輕松方便……它們可以那么直截了當(dāng)?shù)貙⒂脩魩霊?yīng)用,用不著麻煩的安裝過程。可是在桌面端,我得先要求用戶下載 80MB 大小的文件,然后他們才能開始運行應(yīng)用。這種下載需求當(dāng)然會嚴(yán)重影響用戶轉(zhuǎn)化率,還會讓登錄流程、支持、A/B 測試以及所有事務(wù)做起來都麻煩許多。
我很喜歡桌面應(yīng)用,因為你可以在桌面端用上好得多的技術(shù)(例如原生的 sqlite3);桌面應(yīng)用的速度也非常快(無需網(wǎng)絡(luò)調(diào)用),并且用戶可以完全掌控自己的數(shù)據(jù)。但我也不得不承認(rèn),Web 帶來的種種優(yōu)勢讓桌面端的這些好處相形見絀。
https://www.kalzumeus.com/2009/09/05/desktop-aps-versus-web-apps/
我開始考慮開發(fā) Actual 的 Web 版本。經(jīng)過一番研究,做了點技術(shù)活兒后,我沒有改動整個架構(gòu)就移植到了 Web 端。
https://app.actualbudget.com/
這意味著你的所有數(shù)據(jù)仍會存儲在瀏覽器本地,并且沒有網(wǎng)絡(luò)調(diào)用。它是在瀏覽器 [注 0] 中運行的完全 100%的“本地”應(yīng)用。
我還沒有對這個 Web 版本大肆宣傳,因為它還沒有經(jīng)過足夠的測試,并且有不少內(nèi)容需要改進(jìn),例如采用代碼延遲加載技術(shù)來加快加載速度。我最擔(dān)心的是數(shù)據(jù)存儲層。由于 所有數(shù)據(jù)都在本地存儲,因此如果本地環(huán)境出現(xiàn)了什么問題,用戶就可能會丟失數(shù)據(jù)。而且因為我們要把所有內(nèi)容都存儲在本地,這給瀏覽器的持久數(shù)據(jù)庫也帶來了巨大壓力。關(guān)注公眾號 逆鋒起筆,回復(fù) pdf,下載你需要的各種學(xué)習(xí)資料。
需要明確的是:我們不會棄用桌面版本。但將來,Web 版本將成為 Actual 的主要平臺,如果用戶需要則可以選擇下載桌面版本。
它的工作機(jī)制不太常見。下面我從高級層面做一概述:
Actual 使用的是 sqlite3。這是一個硬性要求。這款應(yīng)用會運行大量復(fù)雜的 SQL 查詢以匯總財務(wù)數(shù)據(jù),這是它的專長所在。查詢都很容易表達(dá),而且運行速度非常快。
在桌面和移動端,我們使用的是原生 sqlite3,但 Web 端不支持 sqlite3。為了解決這個問題,Actual 使用了 sqlite3 的一個 wasm 版本并創(chuàng)建了一個內(nèi)存內(nèi)數(shù)據(jù)庫。
顯而易見的問題是持久性。進(jìn)行更改時,我們需要將其保留在某個位置,以便在用戶重新加載時避免丟失數(shù)據(jù)。所幸我們使用的是基于狀態(tài)的 CRDT,所有更新都以一個“消息”列表的形式發(fā)布。如果用戶在線,這些消息將同步到我們的服務(wù)器,這樣當(dāng)用戶重新加載時,所有數(shù)據(jù)都應(yīng)該同步。
不過,每次打開應(yīng)用時都要求進(jìn)行大量同步操作并不是理想的選擇。另外,如果你處于離線狀態(tài),應(yīng)用就無法承受任何數(shù)據(jù)丟失的風(fēng)險。為了解決這個問題,Actual 將每條消息都保留在 IndexedDB 中。當(dāng)應(yīng)用程序打開時,它將應(yīng)用來自本地 IndexedDB 的所有消息以獲取最新信息。
要求在加載時應(yīng)用所有消息也不是理想的選項。這種方法無法擴(kuò)展——如果用戶使用 Actual 已經(jīng)有好幾個月,就會累積成千上萬條消息。IndexedDB 會無限增長下去,并且應(yīng)用加載速度會變得越來越慢。為了解決這個問題,當(dāng)存儲的消息超過閾值時,它會將整個 sqlite3 db 刷新到 IndexedDB 并清除所有消息。
這意味著 sqlite3 db 的一個二進(jìn)制表示形式和消息列表都保存在 IndexedDB 中。在加載時,應(yīng)用會從快照創(chuàng)建內(nèi)存內(nèi)的 sqlite3 db,并應(yīng)用 IDB 中剩余的所有消息。
其實,這種方法和預(yù)寫日志的工作機(jī)制很像。
我之前比較擔(dān)心 IndexedDB 的可靠性。從它的文檔來看,似乎瀏覽器可能會根據(jù)需要刪除數(shù)據(jù)庫,但實際操作中這種情況似乎沒有發(fā)生 [注 1]。在存儲空間不足的移動設(shè)備上這個問題可能會更突出,但我并沒有趟移動 Web 這潭渾水(而是用了原生應(yīng)用)。我還擔(dān)心應(yīng)用會到達(dá) IDB 存儲的上限,但正如接下來所解釋的那樣,這并不是個問題。
這項技術(shù)起初只是一項實驗,但它的效果很驚艷。我在自己的 Actual 應(yīng)用里有積累 5 年的數(shù)據(jù),而它們在 sqlite3 db 中的大小是 9.7MB。消息表的閾值約為 50KB,因此對于一位已經(jīng)使用 Actual 長達(dá) 5 年的用戶,我也不過是在 IndexedDB 中存儲總共約 10MB 的數(shù)據(jù)而已。這離 IndexedDB 的最大存儲限制還差得遠(yuǎn),目前它的上限至少為 500MB 之多。
到目前為止這個辦法效果還不錯,但是我希望對它建立 100%的信心。我一直在深入研究各種瀏覽器是如何在磁盤上存儲 IndexedDB 數(shù)據(jù)的,并發(fā)現(xiàn)了我可以做出的一些改進(jìn)策略。我本想在這篇文章中詳細(xì)介紹一番,但最后我還是把主題放在了整體概述上。在下一篇文章中,我將深入研究 IndexedDB 是如何在瀏覽器中工作的。
?注釋
[0] 雖然我在這篇文章中沒有談?wù)撨@個話題,不過它意味著整個應(yīng)用都在瀏覽器中運行。“后端”運行在一個后臺 Worker 線程中,并且一切都是在本地運行的。
[1] 如果本地數(shù)據(jù)真的被用某種方式破壞或刪除掉了,那也不是什么大問題。所有更改仍將發(fā)送并存儲在服務(wù)器上(這也是其他設(shè)備同步的方式)。如果出現(xiàn)問題,應(yīng)用可以從服務(wù)器重新下載用戶的所有數(shù)據(jù)。唯一會丟失數(shù)據(jù)的情況是用戶在離線狀態(tài)下丟掉了本地數(shù)據(jù),這也是理所當(dāng)然的。
https://actualbudget.com/blog/porting-local-app-web
據(jù)說程序員等電梯的時候都想過調(diào)度算法,網(wǎng)友:還真是
點個『在看』支持下?

