在研究一个奇怪的缓存错误时,我受到启发去重新看一下Actual如何在网络上本地存储数据。我需要解释一些历史。几年前,Actual只会成为桌面应用程序。这意味着所有数据都存储在本地。没有服务器。
然后,我意识到了移动技术的重要性,并且大多数人不想担心将计算机丢到海里而丢失数据。同步引擎诞生了,从那时起,台式机和移动应用程序就很高兴地同步了其数据。副本保存在服务器上,因此用户可以登录并轻松查看其数据,如果担心隐私,可以启用端到端加密。
去年,我开始嫉妒Web应用程序。查看他们可以多么容易地进行部署……他们可以多么迅速地将用户直接带入应用程序。无需安装。在这里,我要求用户下载80MB的文件只是为了运行该应用程序。该下载绝对会降低会话率,并使登录流程,支持,A / B测试以及所有其他方面变得更加困难。
我喜欢台式机应用程序,因为您可以使用更好的技术(例如本地sqlite3),该应用程序非常快(无需网络调用),并且用户拥有自己的数据。但是,我不能忽视网络的优势使这些优势相形见.。
我开始考虑实际的网络版本。经过一番黑客攻击后,我无需更改体系结构就可以正常工作。这意味着您的所有数据仍存储在浏览器本地,并且没有网络调用。这是完全100%本地的浏览器[0]中的应用程序。
我尚未对网络版本进行过多的营销,因为它尚未经过足够的测试,并且需要进行诸如延迟加载代码之类的改进以使其更快地加载。我最担心的是数据存储层。由于所有数据都是本地数据,因此如果出现问题,则可能会丢失数据。而且,我们通过将所有内容存储在其中来真正强调浏览器的持久性数据库。
需要明确的是:我们不会弃用台式机版本。但是,将来,Web版本将成为主要平台,如果需要,可以选择下载桌面版本。
实际使用sqlite3。这是一个硬性要求。该应用程序运行大量复杂的SQL查询以汇总财务数据,并且非常擅长于此。查询很容易表达,而且运行速度非常快。
在台式机和移动设备上,使用本机sqlite3。该网络不支持sqlite3。为了解决这个问题,Actual使用了sqlite3的wasm版本并创建了一个内存数据库。
显而易见的问题是持久性。进行更改时,我们需要将其保留在某个位置,以便用户重新加载时不会丢失数据。幸运的是,我们使用的是基于状态的CRDT,所有更新都以“消息”列表的形式出现。如果您在线,这些消息将同步到我们的服务器,因此当您重新加载时,所有数据都应该同步。
不过,每次打开应用程序时都要求进行大的同步并不是理想的选择。另外,如果您处于离线状态,则不会丢失任何数据。为了解决这个问题,Actual将每条消息都保留在IndexedDB中。当应用程序打开时,它将应用来自本地IndexedDB的所有消息以获取最新信息。
要求在负载上应用任何消息仍然不是理想的选择。它无法扩展-如果您使用该应用程序数月,将累积成千上万条消息。 IndexedDB将无限期增长,并且加载应用程序会变慢。为了解决这个问题,当存储的消息超过阈值时,它将整个sqlite3 db刷新到IndexedDB并清除所有消息。
这意味着sqlite3 db的二进制表示形式和消息列表都保留在IndexedDB中。在加载时,该应用程序从快照创建内存中的sqlite3 db,并应用IDB中剩余的所有消息。
我担心IndexedDB的可靠性。阅读文档似乎浏览器可能会根据需要删除数据库,但实际上这似乎没有发生[1]。在内存不足的移动设备上,这可能是一个更大的问题,但我不会弄乱移动网络(请改用本机应用程序)。我还担心会达到IDB存储的限制,但是正如接下来说明的那样,这并不是问题。
这项技术起初只是一项实验,但效果令人惊讶。我有5年的实际数据值,而sqlite3 db的大小为9.7MB。消息表的阈值约为50KB,因此对于一个已经使用了5年的用户,我总共在IndexedDB中存储了约10MB的内存。它甚至还没有达到IndexedDB的最大存储限制,目前这些限制至少为500MB。
到目前为止,它仍然有效,但我希望对此方法100%充满信心。我一直在深入研究每种浏览器如何在磁盘上存储IndexedDB数据,并发现了我可以做的一些改进。我将在这篇文章中写关于它们的文章,但最终我写了更多关于整体方法的文章。在下一篇文章中,我将深入研究IndexedDB在各种浏览器中的工作方式。
[0]虽然我在这篇文章中没有提到,但这也意味着整个应用程序都在浏览器中运行。后端在后台工作线程中运行,并且一切都在本地发生。
[1]如果本地数据确实以某种方式被破坏或删除,那不是什么大不了的事情。所有更改仍将发送并存储在服务器上(这是所有其他设备同步的方式)。如果出现问题,该应用程序可以从服务器重新下载您的数据。唯一会丢失数据的情况是您脱机并丢失了本地数据,这是可以预料的。