去年,科技界巨头马斯克和“下云”理念的坚定追随者DHH通过将更多工作负载迁移到本地基础设施,成功节省了巨额云成本,这一举措在行业内掀起了巨大波澜。继这一趋势,前不久,Hivekit公司,一家专注于提供自动化工作协调平台的企业,也因为不堪重负的云成本开支,踏上了自建数据库的征程。
Hivekit平台的主要应用场景包括采矿和建筑行业等,通过地理空间应用程序跟踪人员和车辆,执行基于空间事件的逻辑。空间规则引擎是其特色之一,允许用户创建规则和逻辑,实现操作全自动化。
Hivekit的程序员团队近日在博客中透露,他们成功自建了一个专门为地理空间数据构建的数据库,用以取代原先使用的数据库和PostGIS。并节省了98%的云成本。这一消息在论坛上引发了热烈的讨论。
决心打破编程的首要规则
编程的第一条规则是什么?也许是“不要重复编写代码”或“如果能跑起来,就不要碰它”?又或者是“不要自建数据库”!
自建数据库是一场噩梦,从原子性、一致性、隔离性和持久性的复杂要求,再到数据分片、故障恢复、运维管理等操作来看, 这简直令人难以想象。
幸运的是,市面上有一些经过几十年打磨令人惊叹的数据库可以免费使用。那么我们究竟为什么会愚蠢地从头开始编写一个呢?
我们为什么要自建数据库?
我们正在运行的云平台,可以同时跟踪数万计的人和车辆。每次位置更新都会被存储并可以通过历史API进行搜索。随着时间的推移,同时连接的车辆数量及其位置更新的频率变化随之变大,平台大约有13000个同时连接,每个连接每秒发送大约一个更新。
我们的客户以不同的方式使用这些数据。有些用例非常粗略,例如,当汽车租赁公司想要显示客户当天所走路线的轮廓时。这种要求可以通过一小时行程的30-100个位置点来处理,并且允许我们在存储位置数据之前对其进行大量聚合和压缩。
但也有些客户还有其他的需求。例如,配送公司希望能够回溯事故发生前的精准时刻;矿井需要精确的现场位置跟踪,希望生成有关哪个工人踏入哪个禁区精确度高达半米的报告。
因此,鉴于我们事先不知道每个客户需要什么级别的粒度,我们会存储每个位置更新。每月有13000辆车更新35亿次 ,而且这个数字还会继续增长。到目前为止,我们一直在使用云原生数据库和PostGIS扩展来进行地理空间数据存储。但仅数据库一项每月的成本就已超过10000美元 ,而且将来只会变得更加昂贵。
但这不仅仅是数据库定价的问题。虽然数据库在负载下表现得很好,但我们的许多客户都在使用我们的本地版本。在那里,他们必须运行自己的数据库集群,这些集群很容易被如此大量的更新淹没。
为什么不用专门为地理空间数据构建的数据库?
不幸的是,没有这样的数据库。(如果有,但我们在研究中以某种方式忽略了它,请告诉我)。H2 、 Redis等许多数据库都支持点和区域等空间数据类型。还有一些空间数据库只是现有数据库之上的扩展,建立在PostgreSQL之上的PostGIS可能是最著名的一个,还有像比如Geomesa,在其他存储引擎之上提供了强大的地理空间查询功能。
不幸的是,这不是我们需要的。我们的需求概况如下:
1、极高的写入性能
我们希望每个节点每秒能够处理多达3万个位置更新。它们可以在写入之前进行缓冲,从而大幅降低IOPS。
2、无限并行
多个节点需要能够同时写入数据,没有上限
3、磁盘空间小
考虑到数据量,我们需要确保它占用的磁盘空间尽可能少
这意味着,我们必须接受一些权衡。这是我们可以接受的:
1、对磁盘读取性能要求不高
我们的服务器是基于内存的架构。针对内存中的数据运行实时流的查询和过滤,速度非常快。
仅当新服务器上线、客户端使用历史记录API或用户在我们的数字孪生界面上回溯时间时,才会从磁盘读取数据。这些磁盘读取需要足够快才能获得良好的用户体验,但它们相对不频繁且容量较低。
2、低一致性保证
我们可以接受丢失一些数据。在写入磁盘之前,我们会缓冲大约一秒钟的更新。在极少数情况下,当一台服务器宕机而另一台服务器接管时,我们可以接受当前缓冲区中丢失一秒种的位置更新。
我们需要存储什么类型的数据?
我们需要存储的主要数据类型是对象,包括车辆、人员、传感器和机器。对象具有ID标签、位置(经度、纬度、速度、航向、高度)和任意键/值数据,(例如燃油水平或当前乘客ID)。在更新时,可能只需要更改这些字段的子集。
此外,我们还需要存储区域、任务(对象必须执行的操作)和指令(hivekit服务器根据传入数据执行的微小空间逻辑)。
我们建造了什么
我们专门创建了一个进程内存储引擎,它与核心服务器相同,是可执行文件的一部分。它以一种最小化的、基于增量的二进制格式进行写入。单个条目如下所示:
每个块代表一个字节。标有“flags”的两个字节是指定“有纬度”、“有经度”、“有数据”等的“是/否”开关标识位,告诉解析器在条目的剩余字节中查找什么。
我们每200次写入都会存储一个对象的完整状态。在此之间,我们只存储增量数据。这样一来,一个位置更新(包含时间、 ID、经度、纬度)仅需要34个字节。这意味着,我们可以将大约3000万个位置更新塞进1GB的磁盘空间中。
我们还维护一个单独的索引文件,将每个条目的静态字符串ID及其类型(对象、区域等)转换为唯一的4字节标识符。由于我们知道这个固定大小的标识符始终位于每个条目的6-9地址段,因此检索特定对象的历史记录非常快。
结果:云成本降低了 98%,速度更快
这个存储引擎是服务器二进制文件的一部分,因此运行它的成本没有改变。但变化的是,我们将每月需花费10000美元的数据库替换为每月200美元的EBS卷。我们使用具有3000 IOPS的预配置IOPS SSD(io2),并且批量更新为每个节点和领域每秒进行一次写入。
EBS内置了自动备份和恢复功能,并具备高可用性保证,因此我们并不认为它比数据库差。我们目前每月产生大约100GB的数据。但是,由于客户很少查询10天前的数据条目,因此我们已开始将30GB以上的所有内容迁移,从而进一步降低了我们的EBS成本。
但这不仅仅是成本。通过文件系统写入本地EBS比写入数据库要快得多,开销也更低。查询速度也快了很多。由于查询并不完全相似,因此很难量化,但例如,在领域历史记录中重新创建特定时间点的时间从大约两秒减少到了约13毫秒。
当然,这是一个不公平的比较,毕竟Postgres是一个具有表达性查询语言的通用数据库,而我们构建的只是一个用于流式传输二进制文件的游标,功能非常有限。但话又说回来,它具备我们需要的功能,同时,我们也并没有失去任何功能。