背景

在新分布式的架构中,SG 是用户建模时的逻辑概念(类似于 database),DataRegion 是可以不让用户感知到的物理概念(拥有一套 TSM)。

我们希望能够做到将逻辑概念与物理概念解耦,即任何 sg 都能够拥有利用集群所有存算资源的能力。

在当前的分区设计中,不同的 sg 拥有不同的设备组,每个设备组的不同时间分区均会按照一定的策略被分配到对应的 DataRegion 共识组中去。

从 DataRegion 共识组的角度来看,其会管理若干设备组在不同时间分区的数据。那么,一个 DataRegion 共识组是否可以管理不同 sg 的设备组呢?如果可以,整个集群的 DataRegion 共识组数便不会随着 sg 的增加而增加,这会进而影响分布式架构在负载均衡,资源管理等方面的设计。

由于只存在一个 sg 时讨论该问题没有意义,因而以下部分会假设用户存在多个 sg。

本文将就在多 sg 场景下,一个 DataRegion 共识组只能管理一个 sg 的设备组的优劣势进行讨论并给出一些其他系统的选择。


对比

本小节将简单列举一个 DataRegion 共识组只能管理一个 sg 数据的优劣势。

优势

  • 写入理论延时更低:原因在于逻辑分片的粒度更细。
    • 对于类似于 Ratis/Sofa-jraft 的共识算法库,其默认用一个异步 apply 线程去往状态机按序 apply 日志,然而在单机引擎内不同 sg 的写入原本可以并行。
    • 如果不做并行 apply 的优化,可以预见写入延时会很高;
    • 如果做并行 apply 的优化,则需要在 raft 库的 apply 线程中再进行一次异步调度,同时还需要支持 apply 返回异步 future 的 raft 库才方便做这样的优化,集成成本和维护成本会更高。
  • 查询理论延时更低:原因在于逻辑分片的粒度更细。
    • 对于类似于 Ratis/Sofa-jraft 的共识算法库,其默认的读接口是线性一致性读,即需要等到 applyIndex 达到 leader 的 commitIndex 之后再去状态机读,这样的机制主要是为了防止出现脏读。当不同 sg 的数据存在于一个 raft 组中时,即使某一个 sg 没有写入,在查询该 sg 的数据时可能也要等待该共识组其他 sg 数据的并发写入被 apply 之后才可以去状态机读,这会进而增大查询延时。
  • 做时间分区,多级存储和 TTL 时开发成本更低,效率更高:原因在于只有拥有相同属性的设备会被存在同一个 tsfile 中,便于以文件粒度进行处理。
    • 对于时间分区和多级存储,当一个 DataRegion 共识组只能管理一个 sg 的数据时,其整体的流程基本都可以按照文件的粒度去生成和迁移,开发成本较低。
    • 对于 TTL,当一个 DataRegion 共识组只能管理一个 sg 的数据时, 一个 tsfile 所有设备的 ttl 均相同,过期删除实现方便,查询也不需要做额外的过滤。
  • 做数据备份的开发成本更低,性能更好:原因在于只有拥有相同属性的设备会被存在同一个 tsfile 中,便于以文件粒度进行处理。
    • 对于做单 sg 的数据备份,显然此方案开发成本更低,性能(往往只需要硬链接,不需要重新去 build)都会更好。
  • 调度灵活性更强:原因在于逻辑分片的个数更多。
    • 对于多 sg 场景,由于集群的总共识组个数相比另一种方案更多,对共识组的副本组做调度即成员变更时的成本会更低。
  • 更容易做到资源隔离:
    • 对于多 sg 场景,此方案更容易支持不同 sg 的资源隔离(挂载不同磁盘,分配不同数量的线程资源),另一个方案写入一套 TSM 后就很难再去做资源隔离了。
  • 与现有逻辑保持一致:
    • 在当前单机的 DataRegion 实现中,一个 tsfile 只会拥有单个 sg 的数据。目前还不确定在 tsfile 写入不同 sg 的数据时读写流程是否还需要改动。

劣势

  • 当用户创建大量的 sg 且写入少量数据时,可能导致产生大量的 raft 组,对集群的 CPU 资源产生浪费,这对存储引擎的线程模型提出了一定的挑战。
    • 可能的解决方案(可以都做):
      • 方案 1:目前单机版推荐用户的存储组数 * DataRegion 个数 = 总核数,在新分布式架构中,可以为 sg 指定 DataRegion 的个数,默认为集群总核数/副本数。这样一方面方便单 sg 用户去充分利用集群所有的存算资源,也能给为多 sg 用户做细粒度的资源分配提出方案。
      • 方案 2:在存储上,此方案更便于做到 IO 的隔离。在计算上,参照 TiDB,CockroachDB 等数据库去做 multi-raft 的优化,包括但不限于心跳合并,线程池合并等。不论是单机还是分布式都应该尽可能将数据结构(LSM,Raft)和计算(线程资源)进行解耦。可以抽象出一些复用的线程池并对计算做一些管理。
      • 其实就跟操作系统对不同进程提供的保证一样,在我看来,没必要去限制进程个数不多于核数。尽管当进程过多时进程切换会带来一定的性能损耗,但其扩展性一定是更强的,而且聪明的程序员一定能从自己的应用场景寻找到避免频繁进程切换的解决方案。

调研

TDengine

tdengine 允许通过配置参数的方式去限制最大 vnode 的个数,其一个 vnode 只能管理一个 database 的数据。其对于该问题的解决方案本质上与一个 DataRegion 共识组不被 sg 复用类似


TiDB

在 TiDB 的架构中,TiKV 可以被理解为一个无限大的有序 map,不同 database 的表会按照对应的编码规则组成 key 写入到 TiKV 中去。其每个一个 region 是一个 raft 组,其 region 个数既不与 database 个数有关,也不是静态不变(总核数 / 副本数)的,而是与数据的总大小有关,默认 128M 会组成一个 raft 组。

尽管 TiDB 不同 database 的数据在编码后理论上可能存在于一个 region 中,但大部分情况下同一个 region 的数据均是同一个 database 同一张表的数据。

总结

本文简单介绍了一个 DataRegion 共识组只能管理一个 sg 的设备组的优劣势并分别就原因和解决方案进行了讨论。由于作者水平有限,以上观点不一定绝对正确,欢迎大家补充修正。


  • No labels

2 Comments

  1. 感谢作者的总结和细致分析。这里分享一下我个人的想法,不一定正确哈:

    1) 只要允许客户创建 多个 SG,实际情况中,很多客户一定会创建多个 SG 来使用,而且这个数量不太好控制。这势必导致大量的 raft 组instance。感觉仅这一点就很麻烦。看到文中的解决方案,但是效果不好说。

    2) 关于 数据插入时延的问题, 即使每个SG 独享一套 VSG共识组,感觉其时延收益也是有限的。因为 Raft 协议做数据apply 时,是可以1个消息里带 批量 Index msg的。

           如果将来传输真出现瓶颈,直接设法提高传输能力,是不更直接? 如果仅为此点,就整出 N 套 共识组,即N套 raft 组instance,是不是太重了?

    3)文中的讨论似乎有一个假设,就是在一个机器上一个VSG共识组对应一套 TSfile。所以如果一个VSG共识组仅属于1个 SSG,这样 TTL、数据备份等更方便。

         这里是否可以将 VSG共识组 与 TSfileProcessor 解耦?  即 一个 VSG共识组 里的数据 根据 SG 的不同可以分散到不同 TSfileProcessor。

         这样即使 不同 SG共用 共识组,  TTL、数据备份等问题也自然被解决了。

    1. 之前调研部分不知道为什么被删了,目前已经改回来了。您也可以参考下其他系统的方案,现对问题一一进行回复:

      1. 我的理解是这样的设计确实会带来一些问题,但至少可以通过调参调优来解决,而且一些线程模型的优化也能提高 baseline。另一种方案的话有些问题就很难解决了,额外带来的开发成本和复杂度很大,可以认真想想 7 个问题的解决方案,我目前没感觉到太多省时高效的方案,欢迎讨论。
      2. 这个问题的答案上面已经写了,即使 raft 的一个 entry 里面可以带批量 msg,默认也是串行去做的,要想提升性能就得做对一个 entry 的 批量 msg 做并行 apply 的优化(目前的分布式版本就是这么干的,然而这样的设计在 snapshot 正确性和 raftlog 与 wal 的合并上都带了一定的挑战,目前还没完全解决完),其实写入倒不是技术上的问题,是开发成本上的考量,但读的话影响确实实实在在的。
        1. 如果不做并行 apply 的优化,可以预见写入延时会很高;
        2. 如果做并行 apply 的优化,则需要在 raft 库的 apply 线程中再进行一次异步调度,同时还需要支持 apply 返回异步 future 的 raft 库才方便做这样的优化,集成成本和维护成本会更高。
      3. 以上其实把所有问题都列了,如果将 DataRegion 与 TSFileProcessor 解耦,咱们再列列剩余的优势吧:
        1. 写入理论延时更低:否则需要付出较大的开发成本去保证并行 apply 的正确性和性能调优。
        2. 查询理论延时更低:否则查询需要做更多的调优。
        3. 调度灵活性更强:因为单共识组数据更少。
        4. 与现有逻辑保持一致: