Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blockio #144

Open
wanghenshui opened this issue Oct 18, 2024 · 17 comments
Open

blockio #144

wanghenshui opened this issue Oct 18, 2024 · 17 comments

Comments

@wanghenshui
Copy link
Owner

https://smalldatum.blogspot.com/2024/07/searching-for-regressions-in-rocksdb.html

@wanghenshui
Copy link
Owner Author

github.com/facebook/rocksdb/pull/12937/files

@wanghenshui
Copy link
Owner Author

https://ronekins.com/2024/01/16/how-to-reduce-linux-block-storage-io-sizes/

max_hw_sectors_kb (read-only) > max_sectors_kb (read/write)

注意max_sectors_kb大小

一个sector 512B
4MB (8192*512B) avgrq-sz * sector-sz

https://kernel.dk/when-2mb-turns-into-512k.pdf

IO调度,是存在组合的

bio 256 个page 4k

多个bio 组合成batch

@wanghenshui
Copy link
Owner Author

https://fee-mendes.gitbook.io/scylladb-mc-compare#tests-and-results

seastar简单场景是没优势的。

停止对技术名字的神圣化

@wanghenshui
Copy link
Owner Author

wanghenshui commented Oct 25, 2024

https://www.scylladb.com/2017/07/31/database-caches-not-good/

多一跳
成本
可用性
应用场景复杂化

外部缓存破坏后段数据缓存。这个点可以,需要重放 热key 点查,防止冷冲击

外部缓存不安全 存疑

外部缓存没有更好的和数据库后段数据联动,这个和前面的 有点冲突

防止冷启动/proxy预热是比较重要的点

@wanghenshui
Copy link
Owner Author

@wanghenshui
Copy link
Owner Author

@wanghenshui
Copy link
Owner Author

@wanghenshui

This comment was marked as resolved.

@wanghenshui
Copy link
Owner Author

wanghenshui commented Oct 29, 2024

@wanghenshui
Copy link
Owner Author

Vortex 的主要特点:

一个可扩展的、最先进的列式文件格式
在磁盘上、内存中和网络传输中共享相同的数据布局,实现零拷贝和(几乎)零分配读取
可以对压缩数据直接进行计算的机制
基于 BtrBlocks 的级联轻量级压缩方案
可扩展的压缩编解码器集合,包括最先进的实现:
FastLanes(整数位打包)
ALP(浮点数)
FSST(字符串)
可配置的文件布局(是否使用行组等),允许写入器针对不同场景进行优化:
快速写入
快速读取
小文件
少量列
大量列
超大列等
精心设计的前向兼容性,Vortex 可能是你需要的最后一个文件格式 🙈
早期基准测试表明:
与 Parquet(使用 zstd)相比有相似的压缩率
1-2 倍的写入吞吐量
2-3 倍的扫描速度
200 倍的随机访问速度

parquet 定制太多,跟进麻烦,使用复杂

但新写一个能解决这个问题吗?

@wanghenshui
Copy link
Owner Author

How Rockset Separates Compute and Storage Using RocksDB https://rockset.com/blog/separate-compute-storage-rocksdb/
Rockset 的云原生架构简介

Rockset 将计算与存储分离。虚拟实例(VIs)是计算和内存资源的分配,负责数据摄取、转换和查询。Rockset 的热存储层由许多附加 SSD 的存储节点组成,以提高性能。

在底层,Rockset 使用 RocksDB 作为其嵌入式存储引擎,该引擎设计用于可变性。Rockset 在 RocksDB 之上创建了 RocksDB-Cloud 库,以利用新的云架构。RocksDB-Cloud 通过与 Amazon S3 等云服务集成,即使在机器故障时也能提供数据持久性。这使 Rockset 可以拥有分层存储架构,其中一份热数据存储在 SSD 上以提高性能,副本存储在 S3 中以增强持久性。这个分层存储架构为 Rockset 客户带来了更好的性价比。

在设计热存储层时,我们牢记以下设计原则:

类似于紧耦合计算存储架构的查询性能
在部署或扩展时不降低性能
容错能力
我们在 Rockset 使用 RocksDB 的方式

RocksDB 是一种流行的日志结构合并 (LSM) 树存储引擎,设计用于处理高写入速率。在 LSM 树架构中,新写入会写入内存表 (memtable),并在内存表填满后将其刷入不可变的排序字符串表 (SST) 文件。Rockset 执行 RocksDB memtable 的细粒度复制,以确保实时更新的延迟不依赖于 SST 文件的创建和分发过程。

SST 文件被压缩成统一的存储块以提高存储效率。当数据发生变化时,RocksDB 删除旧的 SST 文件并创建一个包含更新数据的新文件。这种压缩过程类似于语言运行时中的垃圾收集,定期运行以删除过时的数据版本,防止数据库膨胀。

新的 SST 文件会上传到 S3 以确保持久性。然后热存储层从 S3 提取文件以提高性能。这些文件是不可变的,这简化了热存储层的角色:它只需要发现和存储新创建的 SST 文件,并逐出旧的 SST 文件。

在执行查询时,RocksDB 从热存储层请求数据块,每个块由文件中的偏移量和大小表示。RocksDB 还会在计算节点缓存最近访问的块,以便快速检索。

除了数据文件外,RocksDB 还将元数据信息存储在 MANIFEST 文件中,这些文件跟踪表示当前数据库版本的数据文件。这些元数据文件每个数据库实例固定数量且体积较小。元数据文件是可变的,在创建新 SST 文件时会更新,但查询执行时很少读取。

与 SST 文件不同,元数据文件存储在计算节点和 S3 上以确保持久性,但不存储在热存储层上。由于元数据文件体积小且很少从 S3 读取,将它们存储在计算节点上不会影响可扩展性或性能。此外,这简化了存储层,因为它只需要支持不可变文件。

Rockset 将数据写入 S3 并从 SSD 中读取以实现快速查询性能。

数据在热存储层中的放置

从高层次来说,Rockset 的热存储层是一个 S3 缓存。一旦文件被写入 S3 就认为是持久的,并且会根据需求从 S3 下载到热存储层。然而,Rockset 的热存储层使用了广泛的技术来实现 99.9999% 的缓存命中率。

在热存储层分配 RocksDB 数据

每个 Rockset 集合(或关系世界中的表)被分成多个切片,每个切片包含一组 SST 文件。切片由属于这些 SST 文件的所有块组成。热存储层在切片粒度上做出数据放置决策。

Rendezvous 哈希用于将切片映射到其对应的存储节点,一个主要节点和一个次要节点。哈希值还被计算节点用于识别从哪个存储节点获取数据。Rendezvous 哈希算法的工作原理如下:

为每个集合切片和存储节点分配一个 ID。这些 ID 是静态的,永远不会改变。
对于每个存储节点,哈希切片 ID 和存储节点 ID 的连接。
将结果哈希值排序。
从有序的 Rendezvous 哈希列表中选出前两个存储节点,作为切片的主要和次要所有者。
我们选择 Rendezvous 哈希进行数据分配是因为它具有以下几个有趣的属性:

当存储节点数量变化时,它会产生最小的移位。添加或移除热存储层中的节点时,更改所有者的集合切片数量将与节点数量 (N) 成正比,即 (1/N)。这使热存储层实现快速扩展。
该算法有助于在节点故障时更快恢复热存储层,因为恢复责任分散在所有剩余节点中并行进行。
添加新的存储节点时导致切片所有者发生变化,可以轻松计算前一个所有者是哪一个。排序的 Rendezvous 哈希列表将仅移位一个元素。这样,计算节点可以从前一个所有者获取块,同时新的存储节点进行预热。
系统的每个组件可以单独确定文件的位置而无需直接通信。只需要极少的元数据:切片 ID 和可用存储节点的 ID。当创建新文件时,这尤其有用,因为集中式放置算法会增加延迟并降低可用性。
虽然存储节点在集合切片和 SST 文件粒度上工作,总是下载它们负责的切片的整个 SST 文件,但计算节点仅检索它们每个查询所需的块。因此,存储节点只需要了解数据库的物理布局,以知道哪个 SST 文件属于切片,并依赖计算节点在其 RPC 请求中指定块边界。

设计可靠性、性能和存储效率

所有关键分布式系统的重要目标是始终可用并有良好性能。构建在 Rockset 上的实时分析具有严苛的可靠性和延迟目标,这直接转化为对热存储层的严格要求。因为我们始终可以从 S3 读取数据,所以我们将热存储层的可靠性视为我们能够以磁盘级延迟提供读取服务的能力。

计算-存储分离性能的维护

最小化通过网络请求数据块的开销

为了确保 Rockset 的计算-存储分离具有良好性能,其架构设计尽量减少了网络调用和数据从磁盘提取所需的时间。因为通过网络请求块可能比本地磁盘读取更慢。许多实时系统的计算节点将数据集保留在附加存储中,以避免这种负面性能影响。Rockset 采用了缓存、预读取和并行化技术来限制网络调用的影响。

Rockset 通过增加一个额外的缓存层,即以 SSD 为支撑的持久性次级缓存 (PSC),来扩展计算节点上可用的缓存空间,以支持大型工作数据集。计算节点包含内存块缓存和 PSC。PSC 在计算节点上有固定的存储空间,用于存储从内存块缓存中最近逐出的 RocksDB 块。与内存块缓存不同,PSC 中的数据在处理重启之间是持久的,从而确保可预测的性能并减少从热存储层请求缓存数据的需求。

避免查询时从 S3 读取数据

从热存储层检索的块比从 S3 的读取未命中快 100 倍,时间差异从小于 1 毫秒到 100 毫秒。因此,避免在查询路径中下载 S3 数据对于像 Rockset 这样的实时系统至关重要。

如果某个计算节点请求一个热存储层中不存在的文件块,则存储节点必须在将请求块发送回计算节点之前从 S3 下载 SST 文件。为了满足客户的延迟要求,我们必须确保查询时需要的所有块在计算节点请求之前都在热存储层中可用。热存储层通过三种机制实现这一点:

每当创建新 SST 文件时,计算节点都会向热存储层发送同步预取请求。这发生在 memtable 刷新和压缩过程中。RocksDB 在热存储层下载文件后提交 memtable 刷新或压缩操作,确保计算节点在请求块之前文件可用。
当存储节点发现一个新的切片时,由于计算节点发送预取请求或读取块请求,它会主动扫描 S3 下载该切片的剩余文件。所有属于同一切片的文件在 S3 中共享相同的前缀,简化了这一过程。
存储节点定期扫描 S3,保持其拥有的切片同步。任何本地缺失的文件将被下载,而本地可用的过时文件将被删除。
可靠性的副本

为了确保可靠性,Rockset 在热存储层的不同存储节点上存储了最多两份文件副本。Rendezvous 哈希用于确定数据切片的主要和次要所有者存储节点。主要所有者通过计算节点发出的预取 RPC 和扫描 S3,积极下载每个切片的文件。次要所有者仅在计算节点读取文件后才下载该文件。为了在扩展事件中保持可靠性,前一个所有者会在新所有者下载数据之前保留一个副本。在此期间,计算节点将前一个所有者用作块请求的备用目标。

在设计热存储层时,我们意识到可以通过只存储部分副本来节省存储成本,同时实现弹性。我们使用 LRU 数据结构来确保即使丢失其中一个副本,查询所需的数据仍然可用。我们在热存储层分配固定数量的磁盘空间作为次级副本文件的 LRU 缓存。从生产测试中我们发现,存储大约 30-40% 的数据副本,加上计算节点上的内存块缓存和 PSC,就足以避免从 S3 检索数据,即使在存储节点崩溃的情况下。

pod本身无状态,有secondary cache

pvc共享一个盘,然后后端接入S3 PVC等于S3缓存

利用备用缓冲容量提高可靠性

Rockset 通过动态调整 LRU 尺寸来进一步减少磁盘容量需求。在其他数据系统中,缓冲容量被保留用于将新数据引入存储层并下载数据。我们使热存储层在本地磁盘使用上更加高效,通过填充动态调整大小的 LRU 来利用缓冲容量。LRU 的动态性质意味着当对引入和下载数据的需求增加时,我们可以缩小用于次级副本的空间。通过这种存储设计,Rockset 充分利用存储节点的磁盘容量,使用备用缓冲容量来存储数据。

我们还选择在摄取速度快于存储速度的情况下,将主副本存储在 LRU 中。在理论上,所有虚拟实例的累积摄取速率可能超过热存储层扩展容量的速率,此时 Rockset 将耗尽磁盘空间并停止摄取数据。如果没有 LRU,这将导致摄取停止。通过将主副本存储在 LRU 中,Rockset 可以逐出最近未访问的主副本数据,以便为新数据腾出空间,从而继续摄取和服务查询。

通过减少我们存储的数据量以及利用更多可用磁盘空间,我们显著降低了运行热存储层的成本。

单副本世界中的安全代码部署

所有文件的 LRU 顺序持久在磁盘上,以便在部署和进程重启后保持。但是,我们还需要确保在没有第二个完整数据集副本的情况下进行安全部署或扩展集群。

典型的滚动代码部署包括关闭运行旧版本的进程,然后启动运行新版本的进程。这样,在旧进程已排空和新进程已准备好之间会有一段时间的停机,使我们不得不在两个不理想的选项之间进行选择:

接受在此期间存储在存储节点上的文件将不可用。在这种情况下,查询性能可能会受到影响,因为其他存储节点需要按需下载计算节点请求的 SST 文件,直到存储节点重新上线。
在排空进程时,将存储节点负责的数据传输到其他存储节点。这将保持热存储层在部署期间的性能,但会导致大量数据移动,使部署时间更长。这也会增加我们的 S3 成本,因为需要进行大量的 GetObject 操作。
这些权衡表明,为无状态系统创建的部署方法不适用于像热存储层这样的有状态系统。因此,我们实施了一种部署过程,避免了数据移动,同时也保证了所有数据的可用性,我们称之为零停机部署(Zero Downtime Deploys)。其工作原理如下:

在每个存储节点上运行旧版本代码的进程时,同时启动运行新版本代码的第二个进程。由于这个新进程运行在相同的硬件上,因此它也可以访问已存储在该节点上的所有 SST 文件。
新进程接替运行前一个版本二进制文件的进程,并开始处理来自计算节点的块请求。
一旦新进程完全接管所有责任,旧进程可以被排空。
运行在同一个存储节点上的每个进程在 Rendezvous 哈希有序列表中的位置相同。这使我们可以在没有任何数据移动的情况下将进程数翻一番。全局配置参数(“Active version”)让系统知道哪个进程是该存储节点的有效所有者。计算节点使用此信息来决定将请求发送到哪个进程。

除了保证零不可用性之外,这个过程还带来了巨大的操作好处。启动新版本服务和新版本开始处理请求的时间点是截然不同的步骤。这意味着我们可以启动新进程,逐渐增加它们的流量,并在不启动新进程、节点或任何数据移动的情况下立即回滚到旧版本。立即回滚意味着减少问题发生的机会。

热存储层的缩放操作以提高存储效率

增加存储节点以增加容量

热存储层确保有足够的容量来存储每个文件的副本。当系统接近容量时,会自动向集群添加更多节点。现有节点会在新节点获取数据切片后,即刻放弃这些数据切片,使其他文件有空间存放。

搜索协议确保计算节点仍然能够找到数据块,即使数据切片的所有者已更改。如果我们同时添加 N 个存储节点,切片的前一个所有者将在 Rendezvous 哈希算法中的最多第 (N+1) 位。 因此,如果块在热存储层中可用,计算节点可以通过并行联系列表中的第 2、第 3、...(第 N+1)个服务器来始终找到一个块。

减少存储节点以减少容量

如果热存储层检测到资源过度配置,它将减少节点数量以降低成本。简单地缩减一个节点会导致从 S3 的读取未命中,而剩余的存储节点需要下载被移除节点之前拥有的数据。为了避免这种情况,被移除的节点进入“预排空”状态:

被指定删除的存储节点将数据切片发送到下一个存储节点。下一个存储节点由 Rendezvous 哈希确定。
一旦所有切片都已复制到下一个存储节点,被指定删除的存储节点将从 Rendezvous 哈希列表中移除。这确保了在缩减存储节点期间数据始终可用用于查询。
这项设计使 Rockset 能够在其热存储层中提供 99.9999% 的缓存命中率,而无需额外的数据副本。此外,这使得 Rockset 能够更快地扩展和缩减系统。

计算和存储节点之间的通信协议

为了避免在查询时访问 S3,计算节点希望从最有可能在本地磁盘上拥有数据的存储节点请求数据块。计算节点通过乐观搜索协议实现这一目标:

计算节点通过 TryReadBlock RPC 向主要所有者发送仅磁盘数据块请求。如果块不在该存储节点的本地磁盘上,该 RPC 将返回空结果。与此同时,计算节点向次要所有者发送 Existence 检查,通过 BlockExists RPC 返回一个布尔标志,指示该块是否在次要所有者处可用。
如果主要所有者在 TryReadBlock 响应中返回了请求的块,则读取已完成。同样,如果主要所有者没有数据但次要所有者有(如 BlockExists 响应所示),则计算节点向次要所有者发出 ReadBlock RPC,从而完成读取。
大多数情况下,Rockset 在查询时通过乐观搜索协议避免访问 S3。此时,主要所有者持有请求的文件并返回数据块。

如果两个所有者都不能立即提供数据,计算节点向数据切片的指定备份目的地发送 BlockExists RPC。该目的地是根据 Rendezvous 哈希确定的下一个存储节点。如果备份指示该块在本地可用,计算节点就从那里读取数据。
主要和次要所有者没有数据,因此它从备份位置检索数据。

如果这些存储节点之一本地拥有文件,则读取可以快速完成(小于 1 毫秒)。在完全缓存未命中的极少数情况下,ReadBlock RPC 同步从 S3 下载数据,耗时 50-100 毫秒。这种情况下查询可用性得到保证,但查询延迟增加。
尽管罕见,但在文件不在热存储层中的情况下,它从 S3 中检索。

该协议的目标:

避免同步 S3 下载的需求,如果请求的块在热存储层中的任何地方存在。上文 (3) 中计算节点联系的故障恢复存储节点数量可以大于一个,以增加找到数据块的可能性。
减少存储节点的负载。磁盘 I/O 带宽是存储节点上的宝贵资源。处理请求的存储节点是唯一需要从本地磁盘读取数据的节点。BlockExists 是一个非常轻量级的操作,不需要磁盘访问。
最小化网络流量。为避免使用不必要的网络 I/O 带宽,只有一个存储节点返回数据。在某些情况下(即如果主要所有者没有数据但次要所有者有),向主要和次要所有者发送两个 TryReadBlock 请求,可以节省一次往返时间。然而,这会使每个块读取的数据量翻倍。主要所有者在绝大多数情况下返回请求的块,因此发送重复数据不会是一个可接受的权衡。
确保主要和次要所有者与 S3 同步。如果基础文件本地不可用,TryReadBlock 和 BlockExists RPC 将触发从 S3 异步下载。这样,基础文件将在未来请求时可用。

@wanghenshui
Copy link
Owner Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant