我有点同意他的话,因为这已经发生在我身上。我渴望拥有一组特定值的数据库,而几年来,我只是在等待有人最终编写它。看完他的演讲后,布莱恩对我说:“该停止等待了,自己开始写吧”。
因此,让我尝试对这种数据库进行概述,并回顾一下它的价值。
总览我想要一个允许我创建可以同步数据的去中心化客户端应用程序的数据库。
我现在可以给出的最好的单行描述是:
有点像PouchDB,Git,Datomic,SQLite和Mentat。
更具描述性的版本可能是:
嵌入式,不可变,可同步的关系数据库。
让我们一一讲解我在每个方面的意思。
嵌入式的我认为服务器端数据库格局是多样且成熟的,足以满足我的需求(即使我大多数时候最终选择SQLite),而我所追求的是将数据库嵌入到客户端应用程序本身中,它是台式机,浏览器,移动设备等。
这种数据库的目的不是在失去连接的情况下保留一些本地数据缓存:我们已经为此找到了很好的解决方案。它应该充当真理的来源,并允许应用程序在真理之上运行。
SQLite就是一个很好的例子:它是一个非常强大的关系数据库,几乎可以在任何地方运行。我从中错过了SQLite没有提供的功能,它可以在浏览器上运行它:即使可以将其编译为WebAssembly,它也假定必须模拟POSIX文件系统。
PouchDB是另一个很好的例子:它是针对JavaScript环境(主要是浏览器和Node.js)的 CouchDB的完全重新实现 。但是,我想要一个可以部署在任何地方的工具,而不是将其应用程序限制在已经具有JavaScript运行时环境的地方,或者强制开发人员将JavaScript运行时环境与其应用程序捆绑在一起。GTK +应用程序,命令行程序,Android应用程序等都是如此。
Mentat是一个有趣的项目,但是由于它依赖于SQLite,因此它继承了SQLite本身的大部分缺点(也有其优点)。
具有这样的要求会强制采用不同的存储方式:我们必须将有关存储复杂性的知识与存储本身的使用脱钩,以便模块(例如查询处理)可以通过API访问存储而无需了解其实施。这允许数据库以POSIX文件系统存储API和IndexedDB存储API为目标,并使其余代码与存储无关。PouchDB具有这种机制(称为适配器),而Datomic也具有这种机制(称为 存储服务)。
这将使数据库能够适应其嵌入位置:当针对浏览器时,IndexedDB存储API将提供数据库所需的持久层,同样,当针对POSIX系统(如台式机)时,POSIX文件系统存储API将提供持久层。 ,移动等)。
但是,嵌入还带来了一个额外的限制:它需要提供和可嵌入的工件,很可能是一个公开库兼容的FFI的二进制库对象,类似于 SQLite那样。捆绑完整的运行时环境是可能的,但是并不能使其成为嵌入的引人注目的解决方案。这排除了大多数语言,并为我们提供了C,Rust,Zig和可用于POSIX系统和WebAssembly的类似选项。
一成不变的不变是意味着仅添加新信息,就不会发生就地更新,也不会删除任何内容。
拥有不可变的数据库会在持久数据结构中给我们带来类似的权衡,例如读取时缺乏协调性,缓存始终保持一致性以及更多地使用空间。
Datomic是该示例的转到数据库的示例:它只会添加信息(datom),并允许您以多种方式查询它们。斯图尔特·哈洛韦(Stuart Halloway)称其为“仅累计”而不是“仅追加” 2:
它仅是累积的,不是仅附加的。因此,只有大多数人在说自己暗示发生了什么时才暗示某些事实。
此外,数据库也可以是仅追加的,并可以通过清理“陈旧”数据来用新信息覆盖现有信息。我更喜欢采用“仅累计”的命名方式。
Git就是另一个例子:新的提交总是被添加到先前的数据之上,并且通过添加提交而不是替换现有的提交而增长。
Git存储库只能增加大小,这不仅是可以接受的条件,也是使用它的原因之一。
所有这些都意味着不会在数据上进行就地更新,并且数据库将更加关注其存储数据的紧凑性和效率,而不是其写入磁盘的速度。被嵌入时,存储限制是a)设备拥有多少存储空间,或b)设计用于应用程序使用的存储空间。因此,即使理论上数据库可以使用数百TB的数据,浏览器页面或移动应用程序也无法访问此数量的存储。SQLite甚至说它确实支持大约280 TB的数据,但是这些限制未经测试。
保留所有内容的好处是,您可以拥有数据的历史视图,这是非常强大的。这也意味着应用程序在不相关时应将其关闭3。
可同步在谈论脱机优先解决方案时,这是一个常见的话题。在构建应用程序时:
可以完全脱机工作,存储数据,将该数据传播到其他应用程序实例,那么您将需要一个冲突解决策略来处理不同应用程序实例不同意的所有情况。这些应用程序实例可以是同一应用程序的桌面和浏览器版本,也可以是不同设备中的同一移动应用程序。
三向合并似乎是最好的方法,在此之上您可以添加应用程序特定的冲突解决功能,例如:
选择带有较高时间戳的更改;如果一个更改是删除,则选择它;在屏幕上显示差异,并允许用户合并它们。一些数据库尝试通过为您选择一种策略来简化此过程,但我发现不同的应用程序需要不同的冲突解决策略。取而代之的是,数据库应由用户自行决定,并为他们提供工具。
版本控制中的三向合并是最好的示例,在可能的情况下执行自动合并,并在出现冲突时要求用户解决冲突。
版本控制系统的冲突单位是一行文本。数据库等效项可能是单个属性,而不是完整实体或完整行。
将所有冲突解决逻辑都设置为本地,应允许数据库具有加密的远程服务器,类似于git-remote-gcrypt 向Git添加此功能的方式。这将使用户能够使用不受信任的中介在设备之间同步应用程序数据。
关系型我想要客户端应用程序上的关系查询的功能。
反对传统的面向表的关系数据库的大多数论点都与写入性能有关,但是这些不适用于此处。客户端应用程序的瓶颈通常不是写入吞吐量。当您的总大小限制为500 MB时,没人希望区分1 MB / s或10 MB / s。
数据库的关系模型既可以是基于SQL和表喜欢SQLite的,也许数据记录和datoms像Datomic。
从方面到价值观现在,让我们根据Bryan Cantrill的建议,将以上各方面转化为价值。
可移植性能够针对许多不同平台是一个大胆的目标,而数据库的嵌入式特性要求可移植性是其核心价值。
廉洁当本地数据库成为应用程序真相的来源时,它必须提供一致性保证,使应用程序能够依赖它。
表现力数据库应授权应用程序以其想要的任何方式对数据进行切片和切块。
下一步由于找不到符合这些要求的数据库,因此我终于自己决定要这样做了。
我可能需要花费几年的时间来完成它,使其在POSIX和IndexedDB之间可移植将是最大的挑战。我为自己准备了一些有关数据库的书籍。
我想知道我是否能够完成这项工作。
最后,在时间29:49。在与朋友谈论本文的草稿时,他指出Bryan O’Sullivan(与另一个Bryan)在4:15的演讲“ 在Haskell上经营一家初创公司 ”中说了类似的话。 ↩
有关Datomic信息模型的视频“ Datomic第2部分 ”,时间为12:28。 ↩
类似于 Datomic的:db/noHistory。 ↩