将纯本地应用程序移植到网络
作者|詹姆斯龙
翻译|王强
规划|李俊辰
如果小明有一个应用,所有的数据都存储在本地,这个设备误投大海,那么数据就消失了。为了避免这种情况,我们可以尝试将一个纯本地应用程序移植到Web上。
当我研究一个奇怪的缓存错误时(https://actual budget.com/blog/诅咒-缓存-好奇),我受到了启发,所以我重新研究了actual如何在网络上本地存储数据。这里我需要说明一些历史背景:很多年前,Actual本来就是一个简单的桌面应用。这意味着我们所有的数据都将存储在本地,没有服务器,自然网络上也不会存储任何东西。
然后我意识到移动平台的重要性,发现大部分用户都不愿意害怕这样的事情:有一天,设备误投大海,然后数据就没了。正因为如此,同步引擎诞生了。从那时起,桌面和移动应用程序可以愉快地同步它们的数据。数据的副本保存在服务器上,以便用户登录后可以轻松查看他们的数据。如果您担心隐私问题,应用程序可以启用端到端加密。
去年,我开始羡慕Web应用。看看这些应用程序,它们部署起来非常简单方便.他们可以直接将用户带入应用程序,而不需要麻烦的安装过程。然而,在桌面端,我必须要求用户在开始运行应用程序之前下载80MB的文件。这个下载要求当然会严重影响用户的转化率,让登录过程、支持、A/B测试以及所有交易都麻烦得多。
我很喜欢桌面应用,因为你可以在桌面端使用好得多的技术(比如原生SQLite 3);桌面应用也很快(没有网络调用),用户可以完全控制自己的数据。但是我不得不承认,网络带来的优势让桌面相形见绌。
https://www . kalzumeus.com/2009/09/05/desktop-APS-vs-web-apps/
我开始考虑开发一个实际的网络版本。经过一些研究和技术工作,我把它移植到了Web上,没有改变整个架构。
https://app.actualbudget.com/
这意味着您的所有数据仍将存储在浏览器的本地,并且没有网络调用。它是一个完全“本地”的应用程序,运行在浏览器中。
这个Web版本我没有做过广告,因为测试不够,还有很多内容需要改进,比如采用代码延迟加载技术加快加载。最让我担心的是数据存储层。因为所有数据都在本地存储,如果当地环境有问题,用户可能会丢失数据。而且因为我们要把所有的内容都存储在本地,给浏览器的持久数据库带来了很大的压力。
需要明确的是:我们不会弃用桌面版本.不过,未来Web版会成为Actual的主要平台,用户需要的话可以选择下载桌面版。
它的工作机制不是很常见。让我从一个高层次来概述一下:
实际使用sqlite3。这是一个硬性要求。这个应用会运行大量复杂的SQL查询来汇总财务数据,这是它的特长。查询易于表达,运行速度非常快。
在桌面和移动端,我们使用原生sqlite3,但是Web端不支持sqlite3。为了解决这个问题,实际使用了sqlite3的wasm版本,并创建了一个内存数据库。
明显的问题是坚持。在进行更改时,我们需要将它们保存在某个地方,以避免用户重新加载时丢失数据。幸运的是,我们使用基于州的CRDT,所有更新都以“消息”列表的形式发布。如果用户在线,这些消息将同步到我们的服务器,这样当用户重新加载时,所有数据都应该同步。
但是,每次打开应用程序时都需要大量的同步操作,这不是一个理想的选择。此外,如果您离线,应用程序不能承担任何数据丢失的风险。要解决这个问题,行动吧
ual 将每条消息都保留在 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 是如何在浏览器中工作的。
注释
[0] 虽然我在这篇文章中没有谈论这个话题,不过它意味着整个应用都在浏览器中运行。“后端”运行在一个后台 Worker 线程中,并且一切都是在本地运行的。
[1] 如果本地数据真的被用某种方式破坏或删除掉了,那也不是什么大问题。所有更改仍将发送并存储在服务器上(这也是其他设备同步的方式)。如果出现问题,应用可以从服务器重新下载用户的所有数据。唯一会丢失数据的情况是用户在离线状态下丢掉了本地数据,这也是理所当然的。
