每次快速写入的操作在别处完成
每个存储引擎都必须决定在告知客户端写入成功之前必须完成什么。最快的答案是在将字节复制到内存后返回。本地持久写入在数据库主机上的SSD上等待fdatasync()。在该主机消失后保持写入意味着要等待网络卷、对象存储或多个数据库服务器保存自己的副本。这些选择将延迟和持久性结合在一起,因为在内存后返回很快,但机器崩溃可能会丢失写入。等待本地SSD可以抵御进程或内核崩溃,但无法抵御设备或主机的丢失,而远程存储或多个数据库服务器可以通过将网络和复制时间分配到每个写入中来承受更多的故障。一些系统的fdatasync()等待附加到数据库主机的1个SSD,而另一些则通过相同的NVMe接口暴露一个网络卷并等待远程存储服务。系统调用名称是相同的,但延迟和数据能够生存的故障却不同。围绕对象存储构建的较新存储设计使这一选择对我特别有趣,因为许多存储不可修改的有序文件到对象存储,使用本地主机的NVMe SSD作为写前日志或缓存,并使用日志结构合并树(LSM)布局来组织数据。对象存储可以成为构建的绝佳原语,因为存储服务可以拥有持久副本,而计算则随之进出,写入新不可修改的文件可以避免对共享磁盘页面的小随机更新。在成功的对象PUT之后,存储服务拥有满足其对于这些字节的广告持久性的责任。数据库仍然必须决定哪个版本是当前的,记录该决定以便其他机器可以看到,快速读取,删除旧版本,并在崩溃后恢复。本地写前日志可以使写入速度更快,但随后数据库必须决定是否可以接受丢失该本地副本,或者在成功之前是否必须存在另一副本。我将客户端PUT用于发送到数据库的键值请求,将对象PUT用于发送到对象存储的HTTP请求。一旦我将这些写入分开,就更容易跟踪1个客户端PUT通过内存、一个本地SSD、远程存储和大多数持久化的数据库服务器。因此,当我看到某个操作的速度非常快时,我想知道哪个操作为此付出了代价,成功后还有什么可以丢失的,以及系统可以容忍多少未完成的清理。我发现从我希望的仅附加的键值存储的成本开始是有用的。操作成功前的工作GET O(key bytes + returned bytes),1次索引查找和1次读取值PUT O(key bytes + payload bytes),1次通过有效载荷的传递进行哈希,1次WAL附加,和1次与其他写入共享的fdatasync() DELETE O(key bytes),附加1个删除记录这些成本看起来很有吸引力,因为正常请求从未扫描每个保留的对象或遍历完整的写入历史,更大的值也以一种不令人惊讶的方式比小的值花费更多。这些小请求成本之所以可能,是因为索引已经将每个键映射到其最新值以供GET使用,多个PUT请求可以共享设备刷新,DELETE记录某个值已失效而无需立即删除旧字节。PUT是看到工作转移的最简单位置,所以我从那里开始。客户端PUT如何到达本地SSD这里我指的是本地主机的NVMe SSD,它与数据库在同一主机上,而不是通过远程存储服务访问。一旦写入被同步,它可以抵御进程或内核崩溃,但丢失SSD或主机仍然会丢失写入。NVMe命名了用于向设备发送命令的接口,但它没有告诉我们存储位置或数据能够生存的故障。远程块卷也可以出现在作为NVMe设备,尽管每个写入穿越网络,存储服务保留了副本。没有其确认点,NVMe延迟数字是不完整的。定时器是在将字节复制到内存后停止,还是在同步1个本地SSD后停止,或者在远程卷确认写入后停止?本地WAL是诱人的,因为延迟差距可能很大。AWS将S3 Express One Zone描述为具有一致的单数毫秒读取和写入请求延迟,并且比S3 Standard快10倍,而Turso测量了4 KB PUT的平均时间为6.4 ms,p99的时间为7 ms。测量为1 ms的本地fdatasync()将使本地路径快约6倍,而0.1 ms将使其快约64倍。50倍的差异可以是一个真实的基准结果,但这不是每个NVMe设备和对象存储的属性,而且这两条路径仍然能够承受不同的故障。本地写入将字节复制到内存与使其持久化分开。写前日志(WAL)是数据库在崩溃后可以重放的变更的有序记录。在一个缓冲实现中,客户端PUT通过内核和设备如下所示。---
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡