分布式数据库(七)

HTAP 是不是赢者通吃的游戏

OLTP 是面向交易的处理过程,单笔交易的数据量很小,但是要在很短的时间内给出结果;而 OLAP 场景通常是基于大数据集的运算。

OLTP-OLAP

OLAP 和 OLTP 通过 ETL 进行衔接。为了提升 OLAP 的性能,需要在 ETL 过程中进行大量的预计算,包括数据结构的调整和业务逻辑处理。这样的好处是可以控制 OLAP 的访问延迟,提升用户体验。但是,因为要避免抽取数据对 OLTP 系统造成影响,所以必须在日终的交易低谷期才能启动 ETL 过程。这样一来, OLAP 与 OLTP 的数据延迟通常就在一天左右,习惯上大家把这种时效性表述为 T+1。其中,T 日就是指 OLTP 系统产生数据的日期,T+1 日是 OLAP 中数据可用的日期,两者间隔为 1 天。

这个体系的主要问题就是 OLAP 系统的数据时效性,T+1 太慢了。进入大数据时代后,商业决策更加注重数据的支撑,而且数据分析也不断向一线操作渗透,这都要求 OLAP 系统更快速地反映业务的变化。

解决思路主要有两种:重建 OLAP 体系、新建 HTAP 系统。

两种解决思路

重建 OLAP 体系

重建 OLAP 体系,重视数据加工的时效性,正是近年来大数据技术的主要发展方向。Kappa 架构就是新体系的代表,它最早由 LinkedIn 的 Jay Kreps 在 2014 年的 一篇文章 中提出。

重建 OLAP 体系

在 Kappa 架构中,原来的批量文件传输方式完全被 Kafka 替代,通过流计算系统完成数据的快速加工,数据最终落地到 Serving DB 中提供查询服务。这里的 Serving DB 泛指各种类型的存储,可以是 HBase、Redis 或者 MySQL。

要注意的是,Kappa 架构还没有完全实现,因为在实践中流计算仍然无法替代批量计算,Serving DB 也无法满足各种类型的分析查询需求。未来,Kappa 架构需要在两方面继续完善:

  1. 流计算能力的增强,这需要用到 Kafka 和 Flink 等软件。
  2. Serving DB 即时计算能力的增强,这就寄希望于 OLAP 数据库的突破,就像 ClickHouse 已经做的那样。

总的来说,新的 OLAP 体系试图提升即时运算能力,去除批量 ETL,降低数据延迟。这个新体系是流计算的机遇,也是 OLAP 数据库的自我救赎。


新建 HTAP 系统

HTAP(Hybrid Transaction/Analytical Processing)就是混合事务分析处理,它最早出现在 2014 年 Gartner 的一份报告中,很巧和 Kappa 架构是同一年。Gartner 用 HTAP 来描述一种新型数据库,它打破了 OLTP 和 OLAP 之间的隔阂,在一个数据库系统中同时支持事务型数据库场景和分析型数据库场景。这个构想非常美妙,HTAP 可以省去繁琐的 ETL 操作,避免批量处理造成的滞后,更快地对最新数据进行分析。

这个构想很快表现出它侵略性的一面,由于数据产生的源头在 OLTP 系统,所以 HTAP 概念很快成为 OLTP 数据库,尤其是 NewSQL 风格的分布式数据库,向 OLAP 领域进军的一面旗帜。

那么,NewSQL 在初步解决 OLTP 场景的高并发、强一致性等问题后,能不能兼顾 OLAP 场景,形成赢者通吃的局面呢?

其实还很难讲,因为从技术实践看,重建 OLAP 路线的相关技术似乎发展得更快,参与厂商也更加广泛,在实际生产环境的落地效果也不断改善。

相比之下,HTAP 的进展比较缓慢,鲜有生产级的工业实践,但仍有不少厂商将其作为产品的演进方向。目前,厂商官宣的 HTAP 至少包括 TiDB 和 TBase, 而 OceanBase 也宣布在近期版本中推出 OLAP 场景的特性。基于商业策略的考虑,我相信未来还会有更多分布式数据库竖起 HTAP 的大旗。那么接下来,我们分析下 HTAP 面临的挑战,让你更好地识别什么是 HTAP。


HTAP 的两种存储设计

这要先说回 OLTP 和 OLAP,在架构上,它们的差异在于计算和存储两方面。

计算是指计算引擎的差异,目标都是调度多节点的计算资源,做到最大程度地并行处理。因为 OLAP 是海量数据要追求高吞吐量,而 OLTP 是少量数据更重视低延迟,所以它们计算引擎的侧重点不同。

存储是指数据在磁盘上的组织方式不同,而组织方式直接决定了数据的访问效率。OLTP 和 OLAP 的存储格式分别为行式存储和列式存储。

分布式数据库的主流设计理念是计算与存储分离,那么计算就比较容易实现无状态化,所以在一个 HTAP 系统内构建多个计算引擎显然不是太困难的事情,而真的要将 HTAP 概念落地为可运行系统,根本性的挑战就是存储。面对这个挑战,业界有两个不同的解决思路:

  1. Spanner 使用的融合性存储 PAX(Partition Attributes Across),试图同时兼容两类场景。
  2. TiDB4.0 版本中的设计,在原有行式存储的基础上,新增列式存储,并通过创新性的设计,保证两者的一致性。

Spanner:存储合一

首先,先看看 Spanner 的方案。Spanner2017 论文“Spanner: Becoming a SQL System”中介绍了它的新一代存储 Ressi,其中使用了类似 PAX 的方式。这个 PAX 并不是 Spanner 的创新,早在 VLDB2002 的论文 "Data Page Layouts for Relational Databases on Deep Memory Hierarchies" 中就被提出了。论文从 CPU 缓存友好性的角度,对不同的存储方式进行了探讨,涉及 NSM、DSM、PAX 三种存储格式。

NSM(行式存储)

NSM(N-ary Storage Model)就是行式存储,也是 OLTP 数据库默认的存储方式,始终伴随着关系型数据库的发展。常用的 OLTP 数据库,比如 MySQL(InnoDB)、PostgreSQL、Oracle 和 SQL Server 等等都使用了行式存储。

顾名思义,行式存储的特点是将一条数据记录集中存在一起,这种方式更加贴近于关系模型。写入的效率较高,在读取时也可以快速获得一个完整数据记录,这种特点称为记录内的局部性(Intra-Record Spatial Locality)。

NSM

但是,行式存储对于 OLAP 分析查询并不友好。OLAP 系统的数据往往是从多个 OLTP 系统中汇合而来,单表可能就有上百个字段。而用户一次查询通常只访问其中的少量字段,如果以行为单位读取数据,查询出的多数字段其实是无用的,也就是说大量 I / O 操作都是无效的。同时,大量无效数据的读取,又会造成 CPU 缓存的失效,进一步降低了系统的性能。

CPU CACHE

图中显示 CPU 缓存的处理情况,可以看到很多无效数据被填充到缓存中,挤掉了那些原本有机会复用的数据。


DSM(列式存储)

DSM(Decomposition Storage Model)就是列式存储,它的出现要晚于行式存储。典型代表系统是 C-Store,它是迈克尔 · 斯通布雷克(Micheal Stonebraker)主导的开源项目,后来的商业化产品就是 Vertica。

列式存储就是将所有列集中存储,不仅更加适应 OLAP 的访问特点,对 CACHE 也更友好。这种特点称为记录间的局部性(Inter-Record Spatial Locality)。列式存储能够大幅提升查询性能,以速度快著称的 ClickHouse 就采用了列式存储。

列式存储的问题是写入开销更大,这是因为根据关系模型,在逻辑上数据的组织单元仍然是行,改为列式存储后,同样的数据量会被写入到更多的数据页(page)中,而数据页直接对应着物理扇区,那么磁盘 I / O 的开销自然增大了。

DSM

列式存储的第二个问题,就是很难将不同列高效地关联起来。毕竟在多数应用场景中,不只是使用单列或单表数据,数据分散后,关联的成本会更高。


PAX

PAX

PAX 增加了 minipage 这个概念,是原有的数据页下的二级单位,这样一行数据记录在数据页上的基本分布不会被破坏,而相同列的数据又被集中地存储在一起。PAX 本质上还是更接近于行式存储,但它也在努力平衡记录内局部性和记录间局部性,提升了 OLAP 的性能。

理论上,PAX 提供了一种兼容性更好的存储方式,可让人有些信心不足的是其早在 2002 年提出,但在 Spanner 之前却少有落地实现。

与这个思路类似的设计还有 HyPer 的 DataBlock(SIGMOD2016),DataBlock 构造了一种独有的数据结构,同时面向 OLTP 和 OLAP 场景。


TiFlash:存储分离

如果底层存储是一份数据,那么天然就可以保证 OLTP 和 OLAP 的数据一致性,这是 PAX 的最大优势,但是由于访问模式不同,性能的相互影响似乎也是无法避免,只能尽力选择一个平衡点。TiDB 展现了一种不同的思路,介于 PAX 和传统 OLAP 体系之间,那就是 OLTP 和 OLAP 采用不同的存储方式,物理上是分离的,然后通过创新性的复制策略,保证两者的数据一致性。

TiDB 是在较早的版本中就提出了 HTAP 这个目标,并增加了 TiSpark 作为 OLAP 的计算引擎,但仍然共享 OLTP 的数据存储 TiKV,所以两种任务之间的资源竞争依旧不可避免。直到近期的 4.0 版本中,TiDB 正式推出了 TiFlash 作为 OLAP 的专用存储。

TiFlash

关注点集中在 TiFlash 与 TiKV 之间的同步机制上。其实,这个同步机制仍然是基于 Raft 协议的。TiDB 在 Raft 协议原有的 Leader 和 Follower 上增加了一个角色 Learner。这个 Learner 和 Paxos 协议中的同名角色,有类似的职责,就是负责学习已经达成一致的状态,但不参与投票。这就是说,Raft Group 在写入过程中统计多数节点时,并没有包含 Learner,这样的好处是 Learner 不会拖慢写操作,但带来的问题是 Learner 的数据更新必然会落后于 Leader。

Raft 协议能够实现数据一致性,是因为限制了只有主节点提供服务,否则别说是 Learner 就是 Follower 直接对外服务,都不能满足数据一致性。所以,这里还有另外一个设计。

Learner 每次接到请求后,首先要确认本地的数据是否足够新,而后才会执行查询操作。怎么确认足够新呢? Learner 会拿着读事务的时间戳向 Leader 发起一次请求,获得 Leader 最新的 Commit Index,就是已提交日志的顺序编号。然后,就等待本地日志继续 Apply,直到本地的日志编号等于 Commit Index 后,数据就足够新了。而在本地 Region 副本完成同步前,请求会一直等待直到超时。

这种同步机制有效运转的前提是 TiFlash 不能落后太多,否则每次请求都会带来数据同步操作,大量请求就会超时,也就没法实际使用了。但是,TiFlash 是一个列式存储,列式存储的写入性能通常不好,TiFlash 怎么能够保持与 TiKV 接近的写入速度呢?

这就要说到 TiFlash 的存储引擎 Delta Tree,它参考了 B+ Tree 和 LSM-Tree 的设计,分为 Delta Layer 和 Stable Layer 两层,其中 Delta Layer 保证了写入具有较高的性能。

当然,TiFlash 毕竟是 OLAP 系统,首要目标是保证读性能,因此写入无论多么重要,都要让位于读优化。作为分布式系统,还有最后一招可用,那就是通过扩容降低单点写入的压力。


小结

  1. OLTP 通过 ETL 与 OLAP 衔接,所以 OLAP 的数据时效性通常是 T+1,不能及时反映业务的变化。这个问题有两种解决思路,一种是重建 OLAP 体系,通过流计算方式替代批量数据处理,缩短 OLAP 的数据延迟,典型代表是 Kappa 架构。第二种思路是 Gartner 提出的 HTAP。
  2. HTAP 的设计要点在计算引擎和存储引擎,其中存储引擎是基础。对于存储引擎也两种不同的方案,一种是以 PAX 为代表,用一份物理存储融合行式和列式的特点,Spanner 采用了这种方式。另一种是 TiDB 的 TiFlash,为 OLTP 和 OLAP 分别设置行式存储和列式存储,通过创新性的同步机制保证数据一致。
  3. TiDB 的同步机制仍然是基于 Raft 协议的,通过增加 Learner 角色实现异步复制。异步复制必然带来数据的延迟,Learner 在响应请求前,通过与 Leader 同步增量日志的方式,满足数据一致性,但这会带来通讯上的额外开销。
  4. TiFlash 作为列存,首先要保证读取性能,但因为要保证数据一致性,所以也要求具备较高的写入性能,TiFlash 通过 Delta Tree 的设计来平衡读写性能。这个问题我们没有展开,将在 22 讲继续讨论。

加餐:每次 TiFlash 接到请求后,都会向 TiKV Leader 请求最新的日志增量,本地 replay 日志后再继续处理请求。这种模式虽然能够保证数据一致性,但会增加一次网络通讯。这个模式还能优化吗?

可以利用 Raft 协议的特性进行优化。Raft 在同步数据时是不允许出现“日志空洞”的,这意味着如果 Follower 节点收到时间戳为 300 的日志,则代表一定已经收到了小于这个时间戳的所有日志。所以,在 TiFlash 接收到查询请求时,如果查询时间戳小于对应分片的最后写入时间戳,那么本地分片的数据一定是足够新的,不用再与 TiKV 的 Leader 节点通讯。


查询性能优化

分布式数据库的主体架构是朝着计算和存储分离的方向发展的,这一点在 NewSQL 架构中体现得尤其明显。但是计算和存储是一个完整的过程,架构上的分离会带来一个问题:是应该将数据传输到计算节点 (Data Shipping),还是应该将计算逻辑传输到数据节点 (Code Shipping)?

从直觉上说,肯定要选择 Code Shipping,因为 Code 的体量远小于 Data,因此它能传输得更快,让系统的整体性能表现更好。

这个将 code 推送到存储节点的策略被称为『计算下推』,是计算存储分离架构下普遍采用的优化方案。

计算下推

将计算节点的逻辑推送到存储节点执行,避免了大量的数据传输,也达到了计算并行执行的效果。这个思路还是很好理解的,用一个例子来具体说明下。

假如有一张数据库表 test,目前有四条记录。

计算下推数据库表

在客户端执行下面这条查询 SQL。

select value from test where cond=’C1’;

计算节点接到这条 SQL 后,会将过滤条件『cond=‘C1’』下推给所有存储节点。

下推

存储节点 S1 有符合条件的记录,则返回计算节点,其他存储节点没有符合的记录,返回空。计算节点直接将 S1 的结果集返回给客户端。

这个过程因为采用了下推方式,网络上没有无效的数据传输,否则,就要把四个存储节点的数据都送到计算节点来过滤。

这个例子是计算下推中比较典型的『谓词下推』(Predicate Pushdown),很直观地说明了下推的作用。这里的谓词下推,就是把查询相关的条件下推到数据源进行提前的过滤操作,表现形式主要是 Where 子句。但场景更复杂时,比如事务包含了写入操作时,对于某些分布式数据库来说,就没这么简单了。


TiDB 的挑战

下面的例子就是关于 TiDB 如何处理下推的,首先来看这组 SQL:

begin;
insert into test (id, value, cond) values(‘5’,’V5’,’C4’);
select * from test where cond=’C4’;

SQL 的逻辑很简单,先插入一条记录后,再查询符合条件的所有记录。结合上一个例子中 test 表的数据存储情况,得到的查询结果应该是两条记录,一条是原有 ID 等于 4 的记录,另一条是刚插入的 ID 等于 5 的记录。这对单体数据库来说,是很平常的操作,但是对于 TiDB 来说,就是一个有挑战的事情了。

TiDB 采用了『缓存写提交』技术,就是将所有的写 SQL 缓存起来,直到事务 commit 时,再一起发送给存储节点。这意味着执行事务中的 select 语句时,insert 的数据还没有写入存储节点,而是缓存在计算节点上的,那么 select 语句下推后,查询结果将只有 ID 为 4 的记录,没有 ID 等于 5 的记录。

缓存写提交

这个结果显然是错误的。为了解决这个问题,TiDB 开始的设计策略是,当计算节点没有缓存数据时,就执行下推,否则就不执行下推。

这种策略限制了下推的使用,对性能的影响很大。所以,之后 TiDB 又做了改进,将缓存数据也按照存储节点的方式组织成 Row 格式,再将缓存和存储节点返回结果进行 Merge,就得到了最后的结果。这样,缓存数据就不会阻碍读请求的下推了。

不阻碍请求下推


分区键与 Join 下推

除了谓词下推,还有一个对下推来讲很重要的关联设计,那就是分区键。分区键是沿用单体数据库的说法,这里的分区实质是指分片,也就是在定义建表语句时,显式指定的分片对应的键值。

不同的分片机制与架构风格直接相关。通常,在 PGXC 架构中是显式指定分片的,所以会出现分区键;而 NewSQL 主要采用 Range 分片,用户感受不到分片的存在,所以往往无法利用这个特性。

只要 SQL 的谓词条件中包含分区键,那么很多时候是可以下推到各个存储节点执行的。就算是面对多表关联的情况,只要这些表使用了相同的分区键,也是可以下推的,类似的方式在 PolarDB 中被称为『Join 下推』,在 Greenplum 中被称为本地连接(Local Joins)。Join 下推可以保证数据移动最少,减少网络开销。

但是,多表使用相同的分区键并不是一个通用的方法,很多时候会在性能的均衡上面临挑战。例如,对用户表和交易表同样使用『机构』来做分区键,这时在每个分片内用户数量和交易数量往往不成正比。这是因为少量用户贡献了多数的交易,同时这些少量用户可能又会集中在几个节点上,就会出现局部资源紧张。

最后,也不是所有计算都能下推的。比如,排序操作,业务需求往往不只是在一个分区内进行排序;还有关联查询(Join),即使关联的多张表都使用了分区键,但如果查询条件中没有包含分区键,也是很难处理。


索引分布

分布式数据库执行计算下推的目的就是为了加速查询。那单体数据库的查询优化手段是什么呢?

索引是数据库加速查询的重要手段。索引优化的基本逻辑是:索引实质是数据库表的子表,它的数据量更少,所以查询索引比查询数据表更高效。那么,先通过索引确定记录的主键后再『回表』查询,也就比直接查询数据表的速度更快。当然,在有些情况下,索引包含的数据项已经能够满足查询的需要,可以免去『回表』这个步骤,性能表现会更好。

索引优化对于分布式数据库来说仍然是重要的优化手段,并且和前面介绍的计算下推有密切的关系。

对于单体数据库,索引和数据表必然在同一节点上;而在分布式架构下,索引和数据既可能是同节点的,也可能是跨节点的,而这对于读写性能有很大影响。按照索引的分布情况和作用范围,可以分为全局索引和分区索引两种类型。在很多分布式数据库中都有对应实现,支持情况稍有差异。


分区索引

分区索引就是索引与数据在同一分区,这个分区实际就是我们之前说的分片。因为分片是最小调度单位,那就意味着在分区索引下,索引和数据是确保存储在同一物理节点。我们把索引和数据在同一个物理节点的情况称为同分布(co_located)。

分区索引的优点很明显,那就是性能好,因为所有走索引的查询都可以下推到每个存储节点,每个节点只把有效查询结果返回给计算节点,避免了大量无效的数据传输。分区索引的实现难点在于如何保证索引与数据始终同分布。

索引与数据同分布又和分片的基本策略有关。动态分片的分拆和调度,都会影响同分布。Spanner2017 论文中简短地介绍了父子表模式(parent-child)的同分布策略,原理就是利用键值存储系统左前缀匹配 Key 区间的特性,通过设置子表记录与父表记录保持相同的前缀,来实现两者的同分布。索引作为数据的子表,也采用了类似的设计理念。

NewSQL 分布式数据库的底层就是分布式键值存储系统,所以下面用 BigTable 的开源实现 HBase 来介绍具体实现原理。

在 HBase 下,每个分片都有一个不重叠的 Key 区间,这个区间左闭右开。当新增一个键值对(Key / Value)时,系统会先判断这个 Key 与哪个分片的区间匹配,而后就分配到那个匹配的分片中保存,匹配算法一般采用左前缀匹配方式。

分区索引

这个场景中,要操作的是一张用户信息表 T_USER,它有四个字段,分别是主键 PID、客户名称(Name)、城市(City)和年龄(Age)。T_USER 映射到 HBase 这样的键值系统后,主键 PID 作为 Key,其他数据项构成 Value。事实上,HBase 的存储格式还要更复杂些,这里做了简化。

存储格式

在『Ctiy』字段上建立索引,索引与数据行是一对一的关系。索引存储也是 KV 形式,Key 是索引自身的主键 ID,Value 是反序列化信息用于解析主键内容。索引主键由三部分构成,分别是分片区间起始值、索引值和所指向数据行的主键(PID)。因为 PID 是唯一的,索引主键在它的基础上增加了前缀,所以也必然是唯一的。

索引存储

整个查询的流程是这样的:

  1. 客户端发起查询 SQL。
  2. 计算节点将 SQL 下推到各个存储节点。
  3. 存储节点在每个 Region 上执行下推计算,取 Region 的起始值加上查询条件中的索引值,拼接在一起作为左前缀,扫描索引数据行。
  4. 根据索引扫描结果中的 PID,回表查询。
  5. 存储节点将 Region 查询结果,反馈给计算节点。
  6. 计算节点汇总结果,反馈给客户端。

实现分区索引的难点在于如何始终保持索引与数据的同分布,尤其是发生分片分裂时,这是很多索引方案没有完美解决的问题。有些方案是在分裂后重建索引,这样开销太大,而且有短暂的不一致。其实,设计思想并不复杂,那就是把同分布的索引和数据装入一个更小的组织单元 (Bucket),而在分片分裂时要保持 Bucket 的完整性。这样一来,因为 Bucket 的粒度足够小,就不会影响分片分裂本身的目标,也就是平衡分片的数据量和访问压力,又能维持索引数据同分布。


全局索引

当然,分区索引也是有缺陷的。如果期望的是一个唯一索引,那么分区索引就无法实现。因为唯一值的判定显然是一个全局性的约束,而所有全局性的约束,都无法在一个分片内完成。

唯一索引对应的方案就是全局索引。全局索引并不保持索引与数据同分布,于是就带来两个问题:

  1. 读操作的通讯成本更高,计算节点要与存储节点做两轮通讯,第一次查询索引信息,第二次回表查询数据。
  2. 写操作的延迟更长,因为任何情况下索引应该与数据保持一致,如果同分布,那么数据变更时可以通过本地事务保证,但在全局索引下就变成了一个分布式事务,代价当然更高了。

所以,在使用分布式数据库时,是否有必要建立全局索引,是一个非常谨慎的决定。

回到产品层面,并不是所有分布式数据库都支持了分区索引和全局索引供用户选择,比如 TiDB 的二级索引只支持全局索引。


小结

  1. 计算与存储分离是分布式数据库的指导思想,但容易造成数据的无效传输,降低整体性能。所以将计算逻辑推送到存储节点,是一个主要的优化方向,这个策略被称为『计算下推』。
  2. 计算下推的逻辑非常简单,但并不是每个产品都能轻松实现的。比如,在 TiDB 中因为缓存了写操作,需要使用更复杂的机制来对冲。
  3. 索引是数据优化的重要手段,在分布式数据库下具体实现包括分区索引和全局索引。分区索引可以兼容计算下推的要求,将负载分散到各个存储节点。
  4. 分区索引的难点在于分片分裂时如何保持索引与数据的同分布,可以通过引入更小的数据组织单元解决这个问题。
  5. 分区索引无法实现唯一索引,只能用全局索引支持。但是全局索引会带来两个问题,一是查询索引与数据是两轮通讯,延迟更长,二是索引必须与数据同步更新,这就增加了一个分布式事务,造成数据更新的代价更大。

其实,计算与存储分离架构自诞生起,就伴随着对其性能的质疑,这也推动了各种分布式数据库进行计算下推的优化,在存储节点支持更多的计算函数。但是计算下推终归要受到运算逻辑的限制,并不是所有计算都可以无冗余地下推。分区索引是计算下推的一种特殊形式,但很多分布式数据库并没支持这个特性,而是用实现起来更简单的全局索引代替,也因此增加了读、写两个方面的性能开销。


加餐:将『单表排序』操作进行下推,该如何设计一种有冗余的下推算法?

排序是一个全局性的处理,任何全局性的控制对分布式架构来说都是挑战。这个设计的关键是有冗余。假如执行下面这一条 SQL,查询账户余额最多的 1,000 条记录。

select * from balance_info order by balance_num limit 1000;

一个比较简单的思路是计算节点将这个 SQL 直接推送给所有数据节点,每个数据节点返回 top 1,000,再由计算节点二次排序选择前 1,000 条记录。

不过,这个方式有点太笨拙。因为当集群规模比较大时,比如有 50 个节点,计算节点会收到 50,000 条记录,其中 49,000 都是无效的,如果 limit 数量再增加,那无效的数据会更多。这种方式在网络传输上不太经济,有一点像读放大情况。

可以基于集群节点的数量适当缩小下推的规模,比如只取 top 500,这样能够降低传输成本。但相应地要增加判断逻辑,因为也许数据分布很不均衡,top 1,000 账户都集中在某个节点上,那么就要进行二次通讯。这个方式如果要再做优化,就是在计算节点保留数据统计信息,让数据量的分配符合各节点的情况,这就涉及到 CBO 的概念了。


学习资料

Anastassia Ailamaki et al.: Data Page Layouts for Relational Databases on Deep Memory Hierarchies

Harald Lang et al: Data Blocks: Hybrid OLTP and OLAP on Compressed Storage using both Vectorization and Compilation

Jay Kreps: Questioning the Lambda Architecture

Nigel Rayner et al.: Hybrid Transaction/Analytical Processing Will Foster Opportunities for Dramatic Business Innovation

David F. Bacon et al.: Spanner: Becoming a SQL System


更新时间:2021-05-15 20:57:45

本文由 caroly 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载 / 出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://caroly.fun/archives/分布式数据库七
最后更新:2021-05-15 20:57:45

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×