PolarDB-X 数据分布解读 :Hash vs Range

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: Hash与Range对于数据库,到底意味着什么?作为应用,又该如何选择?PolarDB-X的Hash分区与其它数据库的Hash分区有什么区别?

PolarDB-X同时支持Hash与Range的分区算法,但是默认情况下,我们选择了使用Hash算法。

像YugabyteDB默认也是Hash的分区算法,而TiDB、CockroachDB是Range分区算法。

那么,Hash与Range对于数据库,到底意味着什么?作为应用,又该如何选择?PolarDB-X的Hash分区与其它数据库的Hash分区有什么区别?

先直接抛结论:

  1. Hash适合预分片,系统会更具确定性;Range更依赖调度系统
  2. Range对范围查询很友好,Hash范围查询基本等价于全表扫描
  3. Range对前缀查询很友好,实际上属于一种范围查询;PolarDB-X的Hash分区也能很好的支持前缀查询;其他数据库的Hash分区需要依赖非模板化的subpartition来支持部分的前缀查询
  4. Range分区与PolarDB-X的Hash分区都能很好的解决数据分布热点问题
  5. Range在自增序列、时间等字段的写入上容易导致热点,但可以通过使用不自增的序列等方法规避
  6. PolarDB-X从设计理念上更关注稳定性与确定性

一致性Hash与取模

首先我们需要再次明确的是,PolarDB-X中的Hash指的是一致性Hash的。其实一致性Hash与Range是非常类似的,底层都是按范围区间划分分区,像PolarDB-X、YugabyteDB的Hash都是这样实现的一致性Hash。

可以简单的理解为,这里的一致性Hash就是对Hash值做Range。例如,对于同一个值value:

  • 一致性Hash:Range(Hash(value))
  • Range:Range(value)

一致性Hash示意:

undefined

因此一致性Hash支持很多对Range的操作,例如对某个Partition做分裂、合并某些Partition等。所以一致性Hash在增减分片数上,可以做到局部性。

与之对应的,是一些采用取模算法实现的Hash,例如大部分数据库中间件。

取模算法实现简单,但有一个很大的问题是,你无法对一个单独的分片做分裂等操作,做不到局部性。

例如,当前是4个分片,其取模算法就是ID % 4,当你要增加到5个分片的时候,取模算法就是ID %5,这时你会发现,超过80%的数据都要重新移动,其代价和完全重建一遍数据没有什么太大差异,如图:

undefined

所以取模实现的Hash在分布式数据库中是非常糟糕的一种分布策略。

我们这里比较的是一致性Hash与Range。

预分区

Hash值的分布一般是比较均匀的,因此Hash可以非常便捷的做预分区。例如提前分为64个分区,那只需要在整个哈希值的范围区间上等分为64份就可以了。

但是对于Range分区,数据本身的分布是跟业务特征强相关的,没有办法提前预测到分布的情况,很难做预分区。常见的Range的分区方法有两类:

  1. 用户提前定义,常见于时间类型分区里
  2. 依赖调度器,随着数据的写入,逐渐的将Range进行分裂

第一种方式适用场景有限。第二种依赖调度器的方式,需要业务进行长时间的运行,才能达到一个比较稳定的状态,存在一些不确定性。

对于稳定性比较高的OLTP业务,一般会希望数据库在性能和稳定性方面是比较确定性的,因此会更倾向于使用预分区的方式。

范围查询

Hash与Range显而易见的区别,在于范围查询上。

Range由于具备顺序性,能够做到相邻的值在同一个或者相邻的分区上,在面对范围查询时,可很好的做到分区裁剪。

但是对于Hash,其Hash值具备顺序性,但其值是不具备顺序性的。因此一般情况下,一个范围查询涉及到几个值,大概率就会涉及到几个分区。

查一个分区的代价显然比查多个分区要小。

范围查询常见于几个场景:

  1. 时间类型上的范围查询。时间是天然跟顺序相关的数据了,例如查询最近一个月的日志等,都是典型的范围查询
  2. 另外常见的是一个benchmark,典型的如sysbench中的oltp_read_only脚本,会测试一个范围查询。这个其实很容易理解,sysbench诞生的时候单机数据库还是主流,测测范围查询是很正常的,实际上并无明显的业务意义,这个让很多使用Hash的分布式数据库吃亏不少。

对于大多数业务来说,查一个范围的用户id,查一个范围的商品id,这些绝对不会是主流用法。从业务实际意义上来看,范围查询主要还是针对时间类型。

对于范围查询,Range一定是有优势的,所以我们也推荐用户在一些时间类型的Global Index上,使用Range分区。

另外还有另外一种不那么明显的范围查询,用处是非常广泛的。

前缀查询

索引的前缀匹配查询其实是一种范围查询。

组合索引是很常用的索引类型,它的特点是包含多个索引字段。一般情况下,我们期望在满足前缀匹配的情况下,即使没有办法提供所有的索引字段的值,也能将数据的扫描范围缩小。

例如组合索引(a,b),如果a有一定的区分度,那么对条件a=1也有过滤效果。

Range分区对前缀查询的处理

Range对前缀匹配查询的处理是很自然的。上文提到,索引的前缀匹配查询其实是一种范围查询,例如对a=1的查询,实际上可以表示成:

 (1,b.MIN) <= key <= (1,b.MAX) 

这样的范围查询对于Range分区,能够很好的做分区裁剪。直观的理解是,对于a=1所有记录,都在同一个或者相邻的分区里。

因此,当组合索引使用Range分区时,并不需要做多级分区,只需一级分区即可。

Hash分区对前缀查询的处理

对于组合索引(a,b),使用Hash分区的话,分区键应该如何设计?

方案1:使用a作为分区键。 这里会出现的问题是,a的区分度如果很低,例如,a代表的省份,那就会出现热点数据。这是YugabyteDB使用的方案。

方案2:使用(a,b)同时作为分区键。 也即Hash(a,b)=Hash(concat(a,b))。

如图示意:

undefined

这种方案下,只要(a,b)整体的区分度比极高,就不会出现热点。这个和使用单机数据库的理念是一样的。

但是,给定的查询条件只有a时,则无法计算出hash值,不能做任何的分区裁剪。

这个和单机数据库二级索引一般情况下都支持前缀查询是不一样的。

注意,大多数提供类似partition by hash(a,b)语法的数据库,无论是单机的还是分布式的,都是这样无法做前缀裁剪的实现。

方案3:使用二级分区,a、b分别作为一级分区和二级分区的分区键。 如图示意:

undefined

这样在匹配到前缀的情况下,依然能做一定的分区裁剪。

但是,这样做,需要支持无限层级的子分区(索引支持多少列,就能建多少级的子分区。),用法上会比较奇怪,目前也没有数据库实现无限级的子分区。

事实上,这是大多数支持Hash分区的分布式数据库你能使用到的方法,例如OceanBase等,你只能用它来解决不超过两个列的前缀查询问题。

方案4:使用a的hash值与b的hash值作为range分区键,也即定义Hash(a,b)=Range(Hash(a),Hash(b))。

这样在匹配到前缀的情况下,依然能做一定的分区裁剪。同时,只有一级分区,并不需要多级分区。

例如对a=1的查询,实际上可以表示成:

 (hash(1),b.HASH_MIN) <= key <= (hash(1),b.HASH_MAX) 

如图示意:

undefined

通过上图可以发现,a=1会包含p45与p46两个相邻的分区,a=99仅包含p47一个分区。

PolarDB-X中的partition by hash(a,b)使用的是方案4,可以很好的支持前缀查询。

热点值

这是非常有趣的一个点。

这里的热点值指的是某个key的某个值的行数或者写入量非常多。例如,一个典型的电商系统,存储订单表:

create table orders (
   id bigint, 
   buyer_id varchar(128) comment '买家', 
   seller_id varchar(128) comment '卖家',
   primary key(id),
   index sdx(seller_id),
   index bdx(buyer_id)
)

数据如下:

undefined

我们关注下sdx这个Global Index。

这个字段表示的是卖家ID,我们可以想象到,电商系统里一般会存在一些大卖家,例如天猫超市,就是淘宝系统里的一个大卖家。这种大卖家会产生非常多的订单,如上图,sdx这个Global Index,seller_id=88的数据行会非常的多,远高于普通的卖家。

这种数据就是我们这里要讨论的热点值。

注意: 热点很容易被理解为数据量的热点。但实际的生产系统中,数据量本身往往并不会构成瓶颈(本例中,单一的卖家数据再多,超过T的量级也是很罕见的)。更常见的是写入量成为热点,例如单一的卖家产生了远大于单DN能力的写入TPS(双十一零点的订单写入量就是典型的此类场景)。

我们来看下,这种热点数据,PolarDB-X的Range和Hash分别是如何处理的。

Range分区对热点值的处理

按照常规做法,使用Range对索引进行分区,我们会使用索引Key与主键一起作为分区键,对于本例,也即partition by range(seller_id,id)。我们稍后来看为什么。

本例中的数据按照(seller_id, id)有序,分布如下:

undefined

当发现p1存在热点时,我们将p1分区进行分裂即可:

undefined

seller_id=88的记录分布在了两个partition内。好处是,数据被打散了,两个partition可以被调度到不同的DN上,共同承载写入的负载与存储空间;但是,针对seller_id的查询,也需要同时去查两个分片。这种方式,拓展了写的能力与存储空间的上限,但是读的代价是变大了的。

对于88这种大卖家,这个代价是可以接受的,但是对于普通的小卖家,我们还是希望尽可能让其数据分布在一个partition内。Range的特点很好的保证了这一点。

这里可以发现Range处理热点的特点:

  1. 由于Range能很好的支持前缀查询,直接对热点分区进行分裂即可
  2. 对于非热点数据,Range能做到将它们放在最多相邻的两个分区里,其查询不会有放大的问题;对于热点数据,Range又能通过分裂将他们放在多个分区里

可以看出,我们似乎什么都没做,Range的特性(范围查询,前缀匹配,分裂)自然而然的很好的消除了数据的热点。

也正因如此,我们会把主键一起作为索引分区键的一部分拼在最后(对于TiDB、CockroachDB等基于分布式KV做的数据来说,这么做还有一个原因是用来实现非唯一键)。

PolarDB-X Hash分区对热点值的处理

同Range分区的索引类似,使用Hash分区的索引,我们也会使用索引Key与主键一起作为分区键,对于本例,也即partition by hash(seller_id,id)。

本例中的数据按照(hash(seller_id), hash(id))有序,分布如下:

undefined

例子中,seller_id=88的数据集中在分区p1,这个分区相对于其他分区就是明显的热点。

与range分区类似,当发现p1存在热点时,我们将p1分区进行分裂即可:

undefined

可以看到,PolarDB-X的Hash分区对热点数据的处理的结果与Range分区类似,也能做到只打散大卖家的数据而不影响小卖家的数据。

其他数据库Hash分区对热点值的处理

如上文所述,对于非PolarDB-X的数据库,大多数多列的Hash分区不支持前缀查询,因此无法像PolarDB-X一样简单的对分区进行分裂即可打散热点。此类数据库一般通过子分区来部分解决此类问题。

与PolarDB-X使用(seller_id, id)做分区键不同,由于不支持前缀查询,所以分区键只能选择seller_id,也即partition by hash(seller_id),本例中的数据分布如图所示:

undefined

例子中,seller_id=88的数据集中在分区p1,这个分区相对于其他分区就是明显的热点。

在其它数据库中,典型的处理此类问题的方法是对该分区按其他字段(一般主键字段即可)做子分区,如图:

undefined

这样p1变成了p1_0与p1_1两个分区,每个分区的数据量重新达到了均衡的状态,没有了热点。

这里值得注意的是:为了保证上文提到的不对小卖家造成读放大的问题,我们必须对 特定的热点分区做subpartition,但不能对所有分区做subpartition。 这个一般称之为非模板化的subpartition。

所以如果一个数据库的Hash分区不支持前缀查询,那它能否解决部分热点问题,需要看其是否具备非模板化的subpartition能力。

连续写入

这是Range分区特有的一类也是很常见的问题。

例如当我们生成的主键具备一定的单调性,当发生连续INSERT的时候,就会将写请求集中在最后的分片上。

这个例子大家已经很常见了,常见的做法是对生成的主键做一下打散,例如TiDB中的SHARD_ROW_ID_BITS就是将生成的序列值的前几位随机的进行反转,实际上就是完全放弃了AUTO_INCREMENT的单调性。

另一个有趣的例子是Date与Datetime。

例如我们有一张使用Range进行分区的日志表:

CREATE TABLE log(
    id int primary key,
    c1 datetime,
    c2 date,
    index idx1(c1),
    index idx2(c2)
)

当我们往这个表里不停的写记录时,我们会发现idx1上会出现写入的热点,而idx2不会,为什么?

因为c2的精度只到日期,前后写入的数据的c2的值是一样的,因此它并不具备单调性,此时c2="2021-08-11"的记录,会成为一个热点,但随着数据的不断写入,按我们上一个章节的描述,c2="2021-08-11"所在的分区会进行分裂,等于按照id做了打散,热点会逐渐消失。

但是c1的精度到毫秒(甚至更高),前后写入的数据的c1值是不一样的,并且是增长的,因此c1上会出现热点,并且这种热点是不会消失的,无论如何分裂,最后一个分区都会是热点。

我们为什么选择默认使用Hash

Range分区在对范围查询上相对于Hash分区有着很强的优势。但是,Range要能很好的做到这样的效果,无法做到预分区,强依赖“随着数据变化,Range不停的进行分裂、合并”这样的自动调度机制。你的数据库系统内,会随时发生Range的分裂、合并等操作,是一个动态的过程,具有比较强的不确定性。

例如在双十一的0点,一个分片触发分裂带来的抖动可能会是致命的。

调度依赖的变量非常多,例如数据量、读写量、IO、CPU、网络等等。双十一前我们都会做压测,但大促真正到来时,这些变量很难和压测的时候是一致的。你很难去对调度器的行为做很多的预设,你只能相信它是正确的。但作为DBA或者应用开发者,肯定希望在这样的关键时刻,数据库的运行状态和压测时的状态一致。

而Hash能够做预分区,具备更强的确定性,可以确保在一些重稳定性的系统上,数据分布处于一种相对静态的状态。同时,PolarDB-X的Hash分区同样能够很好的处理热点等问题。

PolarDB-X源于淘宝的电商、交易等核心场景,对稳定性、确定性的要求放在了第一位,因此选择了使用Hash作为默认的分区方式。对于包含大量范围查询的场景,我们也推荐使用Range分区

相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
目录
相关文章
|
1月前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
在9月20日2024云栖大会上,阿里云智能集团副总裁,数据库产品事业部负责人,ACM、CCF、IEEE会士(Fellow)李飞飞发表《从数据到智能:Data+AI驱动的云原生数据库》主题演讲。他表示,数据是生成式AI的核心资产,大模型时代的数据管理系统需具备多模处理和实时分析能力。阿里云瑶池将数据+AI全面融合,构建一站式多模数据管理平台,以数据驱动决策与创新,为用户提供像“搭积木”一样易用、好用、高可用的使用体验。
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
|
1月前
|
人工智能 关系型数据库 分布式数据库
拥抱Data+AI|“全球第一”雅迪如何实现智能营销?DMS+PolarDB注入数据新活力
针对雅迪“云销通App”的需求与痛点,本文将介绍阿里云瑶池数据库DMS+PolarDB for AI提供的一站式Data+AI解决方案,助力销售人员高效用数,全面提升销售管理效率。
|
2月前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
阿里云瑶池在2024云栖大会上重磅发布由Data+AI驱动的多模数据管理平台DMS:OneMeta+OneOps,通过统一、开放、多模的元数据服务实现跨环境、跨引擎、跨实例的统一治理,可支持高达40+种数据源,实现自建、他云数据源的无缝对接,助力业务决策效率提升10倍。
|
3月前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
阿里云数据库重磅升级!元数据服务OneMeta + OneOps统一管理多模态数据
|
4月前
|
存储 SQL Cloud Native
揭秘!PolarDB-X存储引擎如何玩转“时间魔术”?Lizard多级闪回技术让你秒回数据“黄金时代”!
【8月更文挑战第25天】PolarDB-X是一款由阿里巴巴自主研发的云原生分布式数据库,以其高性能、高可用性和出色的可扩展性著称。其核心竞争力之一是Lizard存储引擎的多级闪回技术,能够提供高效的数据恢复与问题诊断能力。本文通过一个电商公司的案例展示了一级与二级闪回技术如何帮助快速恢复误删的大量订单数据,确保业务连续性不受影响。一级闪回通过维护最近时间段内历史数据版本链,支持任意时间点查询;而二级闪回则通过扩展数据保留时间并采用成本更低的存储方式,进一步增强了数据保护能力。多级闪回技术的应用显著提高了数据库的可靠性和灵活性,为企业数据安全保驾护航。
53 1
|
4月前
|
数据库 Windows
超详细步骤解析:从零开始,手把手教你使用 Visual Studio 打造你的第一个 Windows Forms 应用程序,菜鸟也能轻松上手的编程入门指南来了!
【8月更文挑战第31天】创建你的第一个Windows Forms (WinForms) 应用程序是一个激动人心的过程,尤其适合编程新手。本指南将带你逐步完成一个简单WinForms 应用的开发。首先,在Visual Studio 中创建一个“Windows Forms App (.NET)”项目,命名为“我的第一个WinForms 应用”。接着,在空白窗体中添加一个按钮和一个标签控件,并设置按钮文本为“点击我”。然后,为按钮添加点击事件处理程序`button1_Click`,实现点击按钮后更新标签文本为“你好,你刚刚点击了按钮!”。
348 0
|
2月前
|
关系型数据库 MySQL 分布式数据库
零基础教你用云数据库PolarDB搭建企业网站,完成就送桌面收纳桶!
零基础教你用云数据库PolarDB搭建企业网站,完成就送桌面收纳桶,邀请好友完成更有机会获得​小米Watch S3、小米体重称​等诸多好礼!
零基础教你用云数据库PolarDB搭建企业网站,完成就送桌面收纳桶!
|
3月前
|
关系型数据库 MySQL Serverless
探索PolarDB MySQL版:Serverless数据库的灵活性与性能
本文介绍了个人开发者对阿里云PolarDB MySQL版,特别是其Serverless特性的详细评测体验。评测涵盖了产品初体验、性能观测、Serverless特性深度评测及成本效益分析等方面。尽管试用过程中遇到一些小问题,但总体而言,PolarDB MySQL版表现出色,提供了高性能、高可用性和灵活的资源管理,是个人开发者和企业用户的优秀选择。
|
16天前
|
Cloud Native 关系型数据库 分布式数据库
PolarDB 分布式版 V2.0,安全可靠的集中分布式一体化数据库管理软件
阿里云PolarDB数据库管理软件(分布式版)V2.0 ,安全可靠的集中分布式一体化数据库管理软件。
|
1天前
|
SQL 关系型数据库 分布式数据库
夺冠在即 | PolarDB数据库创新设计赛(天池杯)决赛答辩通知
2024年全国大学生计算机系统能力大赛PolarDB数据库创新设计赛(天池杯)于8月21日启动,吸引了200多所高校近千支队伍参赛。经过激烈角逐,60支队伍晋级决赛第一阶段,36支队伍脱颖而出进入现场答辩,将于12月29日在武汉大学争夺最终奖项。决赛要求选手基于PolarDB-PG开源代码部署集群并优化TPCH查询性能。完赛率超90%,成绩表现出明显梯度,前20名均在500秒内完成。评委来自学术界和工业界,确保评选公正。预祝选手们取得优异成绩!