0
2
4
0
专栏/.../

TiDB Placement Rule实战总结

 dba-kit  发表于  2023-12-24

一、背景

Placement Rule in SQL从v6.1开始正式GA,但是还是有很多小伙伴对其并不熟悉。具体原理和隔离效果,本篇文章就不做过多介绍了,TUG社区中已经有很多类似的文章介绍,我重点介绍一下在实践中的实战方案,给大家参考一下,让大家少走一些弯路。 这里先列一下在实际中遇到的一些问题:

1. 什么是Placement Rule in SQL

想知道什么是Placement Rule in SQL,首先要了解什么是Placement RulePlacement Rule是TiDB 4.0就有的一个特性,它是一套副本规则系统,用于指导 PD 针对不同类型的数据生成对应的调度。通过组合不同的调度规则,用户可以精细地控制任何一段连续数据的副本数量、存放位置、主机类型、是否参与 Raft 投票、是否可以担任 Raft leader 等属性(偷懒摘自官方文档)。 但是Placement Rule必须先通过tidb-ctl获取对应表的StartKey和EndKey,再通过pd-ctl来设置具体的规则,很不方便,当对表执行DDL时StartKey和EndKey还可能会发生变化,需要手动调整规则很容易踩坑,所以使用的人很少。而Placement Rule in SQL则是可以通过SQL来设置Placement Rule,更便捷灵活,适应性更强。

2. 什么场景下才需要使用Placement Rule in SQL

这个问题,其实在官方Placement Rule in SQL文档里有列举:

  • 合并多个不同业务的数据库,大幅减少数据库常规运维管理的成本
  • 增加重要数据的副本数,提高业务可用性和数据可靠性
  • 将最新数据存入 NVMe,历史数据存入 SSD,降低归档数据存储成本
  • 把热点数据的 leader 放到高性能的 TiKV 实例上
  • 将冷数据分离到不同的存储中以提高可用性
  • 支持物理隔离不同用户之间的计算资源,满足实例内部不同用户的隔离需求,以及不同混合负载 CPU、I/O、内存等资源隔离的需求

对我而言,主要功能有两点:

  1. 隔离TP和AP的数据:线上TiDB集群是个混合集群,既汇总了多个MySQL产生的数据,又存在数仓团队的分析数据,数仓团队在计算时候,经常会导致整个TiDB集群负载很高。
  2. 冷热分离:有多个大表的历史数据基本不会被查询,但是占用了大量空间,每个月增量数据有1T左右,必须经常扩容TiKV节点,集群成本很高。

而这两种需求都可以通过Placement Rule in SQL来实现。

二、配置指南

1. TiKV设置合适的Labels值

不管是资源隔离、还是冷热分离,都是要将TiKV节点分成不同组,所以必须要提前规划好。有两种方法可以修改TiKV的Label:

  1. 通过TiKV的配置参数:在初始化时候就设置好server.labels
  2. 通过pd-ctl来设置:初始化后,可以通过pd-ctl store label命令来修改

注意: store 的 label 更新方法使用的是合并策略。如果修改了 TiKV 配置文件中的 store label,进程重启之后,PD 会将自身存储的 store label 与其进行合并更新,并持久化合并后的结果。

2. PD设置location-labels

具体可以参考: 设置 PD 的 location-labels 配置,这个参数的值要和TiKV设置的Label对应,其次需要注意的是,TiDB会按照设置的数组的层级来尽可能平均的分布数据,也就是说要尽可能保证机器在每个层都是平均分布的,如果机器分布不平均,就可能会有这个帖子一样的疑问,发现不同区的TiKV磁盘使用率不一样。

3. TiDB执行Placement Rule in SQL制定合适的规则

现在假定集群有9台TiKV节点(6台高性能NVMe-SSD分为TP、AP两组,3台大容量HDD存冷数据),配置设置如下:

  • location-labels: "zone,dc,host,disk"
  • TP组的TiKV节点的server.labels中增加: "disk": "tp-ssd"
  • AP组的TiKV节点的server.labels中增加: "disk": "ap-ssd"
  • 冷数据TiKV节点的server.labels中增加: "disk": "hdd" 之后,就可以在通过Placement Rule in SQL创建对应的三个规则:
CREATE PLACEMENT POLICY `on_tp_ssd` CONSTRAINTS="[+disk=tp-ssd]";
CREATE PLACEMENT POLICY `on_ap_ssd` CONSTRAINTS="[+disk=ap-ssd]";
CREATE PLACEMENT POLICY `on_hdd` CONSTRAINTS="[+disk=hdd]";

之后就可以对分区、表、库级别来设置相应的规则,将对应的region限制到特定TiKV节点,例子如下:

ALTER DATABASE test PLACEMENT POLICY=`on_tp_ssd`;
ALTER TABLE test.t1 PLACEMENT POLICY=`on_tp_ssd`;
ALTER TABLE test.t1 PARTITION `pbefore` PLACEMENT POLICY=`on_hdd`;

-- 取消PLACEMENT POLICY
ALTER TABLE test.t1 PLACEMENT POLICY=`default`;

这里需要注意的是PLACEMENT POLICYCHARSET比较类似,只有建表时没有指定放置策略的时候,表才会从数据库继承放置策略,一旦表创建之后再改变数据库的PLACEMENT POLICY,只会对新表有效,对旧表是无效的。同样分区表也和CHARSET类似,改变表的放置策略,也会让分区应用新的放置策略。

三、实战

3.1 如何保证只有冷数据才会放到HDD节点中?

在冷热分离这个场景下,在引入HDD节点之前,必须要考虑一个问题:如何保证不影响正常数据的写入?也就是如何保证只有冷数据才会放到HDD节点中?举个例子:

现在有一套TiDB汇总了线上20套MySQL的表,并通过白名单方式对所有database都执行了ALTER DATABASE test PLACEMENT POLICY=`on_tp_ssd`,保证现有database的数据只放到TP SSD节点上。但是如果后续有新业务上线,在某个MySQL上创建了一个新的database,而TiDB上因为疏忽,并没有修改PLACEMENT POLICY=`on_tp_ssd` 会发生什么事情呢?这个database下面所有的表,数据都均匀分布在所有节点中,既可能是AP SSD节点、也可能是HDD节点,完全没办法达到资源隔离的牡蛎,肯定会影响TP数据的写入效率的。

可能细心的同学在上面就已经发现,在TiDB是可以通过将PLACEMENT POLICY设置为default的方式,让表使用默认的规则,那么我们可以修改名为defaultPLACEMENT POLICY么?

答案是不可以的,不过可以通过其他方式实现类似的需求,以下是我梳理的操作步骤:

注意:此处为了突出黑名单功能,对场景做了简化,假定disk这个label只包含["hdd", "ssd"]两个值,我们只想保证热数据不放入HDD。我们线上其实是通过白名单方式,让默认数据值放在disk=on_tp_ssd的磁盘上,配置文件会略有区别,不过原理不变。

7.5之前:通pd-ctl设置

7.5之前default的placement-rule没办法通过Placement rule in SQL的方式来直接修改,只能通过原始的pd-ctl来修改。原理可以参考下面官方文章。详细的执行步骤如下:

  1. 查看当前的default规则
tiup ctl:v6.5.3 pd config placement-rules show --group=pd --id=default >rules.json

正常来说,rules.json里的内容应该是

{
  "group_id": "pd",
  "id": "default",
  "start_key": "",
  "end_key": "",
  "role": "voter",
  "is_witness": false,
  "count": 3,
  "location_labels": [
    "zone",
    "dc",
    "host",
    "disk"
  ]
}

  1. 只需要手动修改rules.json,在json中增加一个基于lable的label_constraints,排除掉HDD节点的label(这里假定每个HDD节点,lables里都有disk: hdd这个参数),这种情下配置如下:
{
  "group_id": "pd",
  "id": "default",
  "start_key": "",
  "end_key": "",
  "role": "voter",
  "is_witness": false,
  "count": 3,
  "label_constraints": [
    {
      "key": "disk",
      "op": "notIn",
      "values": [
        "hdd"
      ]
    },
    {
      "key": "engine",
      "op": "notIn",
      "values": [
        "tiflash"
      ]
    }
  ],
  "location_labels": [
    "zone",
    "dc",
    "host"
  ]
}
  1. 将配置应用到pd中
tiup ctl:v6.5.3 pd config placement save --in=rules.json

7.5之后:通过SQL来设置

7.5之后,TiDB新增了一个ALTER RANGE的语法,现在 ALTER RANGE 能起作用的有 global 和 meta 两个参数:

  • global:表示集群内全域数据的范围;
  • meta:表示 TiDB 内部存储的元信息的数据范围。

操作步骤如下:

  1. 创建放置规则,并将其设置为global级别
MySQL [(none)]> CREATE PLACEMENT POLICY on_ssd CONSTRAINTS="[-disk=hdd]";
Query OK, 0 rows affected (0.01 sec)

MySQL [(none)]> ALTER RANGE global PLACEMENT POLICY = on_ssd;
Query OK, 0 rows affected (0.01 sec)
  1. 其实原理和我们手动设置的类似,也是设置了group_id=TiDB_GLOBAL一个全局的放置规则,具体内容如下:
[tidb@prod-tiup-f01 ~]$ tiup ctl:v7.5.0 pd config placement-rules show --group=TiDB_GLOBAL
Starting component `ctl`: /home/tidb/.tiup/components/ctl/v7.5.0/ctl pd config placement-rules show --group=TiDB_GLOBAL
[
  {
    "group_id": "TiDB_GLOBAL",
    "id": "on_ssd_rule_0",
    "start_key": "",
    "end_key": "",
    "role": "voter",
    "is_witness": false,
    "count": 3,
    "label_constraints": [
      {
        "key": "disk",
        "op": "notIn",
        "values": [
          "hdd"
        ]
      },
      {
        "key": "engine",
        "op": "notIn",
        "values": [
          "tiflash"
        ]
      }
    ],
    "create_timestamp": 1703414881
  }
]

3.2 在使用冷热后,如何兼顾查询历史数据的效率?

如果某个大表十分重要,有很多业务都会使用到,在某些场景下还需要使用历史数据进行分析,在预算充足的情况下,这种情况处理起来也简单,申请资源扩容 TiKV节点就可以了。不过当你申请资源时候,大多数情况下老板的回答是,既要想办法降低存储成本,又要不影响分析查询的性能,降本和增效一个都不能少。不过这也是为数不多,我们DBA能刷存在感的时机了,能提出合理的解决方案,才能体现我们不是吃干饭的。 常见的历史数据查询场景是,大多数业务只会查询最近几个月的数据,只有少数的报表才需要查询历史数据,根据这种场景:

  • 最简单处理方式就是只把历史分区放置在HDD节点中,最近几个月数据放在SSD节点中,不过这种架构会导致一些分析类查询变得非常慢。如果老板希望看最近几年的业务增长曲线,结果在Tableau页面点击查询后,半天没有响应甚至查询失败后,显然会置疑TiDB的能力,不是说TiDB是HTAP全能数据库么,怎么查一年的业务增量需要时间这么长,比MySQL还慢。这必然会影响你在老板心中解决问题的能力。
  • 更好的解决方案需要利用Tiflash列式存储引擎,将历史数据放到HDD节点中减少SSD储存数据量,对整表增加Tiflash副本(单副本/多副本均可,副本个数的提升对查询性能影响不大更多是可用性的提升)。由于TiFlash对高并发支持并不友好,这种方案的前提是只有少量AP类查询会查询历史数据,不会有高并发查询历史数据的场景。

PS:

  1. 记得要开启分区动态裁剪功能,这个可以让分区表的查询变快。(6.5之后,默认开启)
  2. 由于7.1之前TiFlash并不会将WHERE条件下推,导致即便只需要查询部分数据(例如某一年/月数据), TiFlash也会从磁盘扫描某几列全部数据到内存中,在内存中完成过滤,所以即便是API类查询,性能也可能比TiKV还慢。 7.1之后TiFlash延迟物化功能GA,默认会开启tidb_opt_enable_late_materialization,上述场景的查询会有较大的性能提升,TiFlash适应的场景会更广泛。延迟物化是指:TiFlash 支持下推部分过滤条件到 TableScan 算子,即先扫描过滤条件相关的列数据,过滤得到符合条件的行后,再扫描这些行的其他列数据,继续后续计算,从而减少 IO 扫描和数据处理的计算量。

PPS: 7.4.0之后TiFlash存储计算资源分离和 S3 共享存储 (GA),TiFlash支持存储和计算资源分离,提升 HTAP 资源的弹性能力,可以将全量数据基于 S3 的存储引擎,以更低的成本提供共享存储,给所有表都增加TiFlash副本的成本变的很低很低。 PPPS: 现在7.5.0 LTS也已经在2023-12-01发布了,强烈建议大家在测试环境验证验证新版本,在观望一段时候后,将重要性没那么强的集群先升级到7.5.0。当然关键的正式集群,最好至少等到7.5.2之后再考虑升级,别做小白鼠。

3.3 如何快速将非分区表改造为分区表?

线上有一套TiDB集群汇总了所有MySQL表,由于不是所有业务在上线前,都会根据表的预期数据增量来考虑是否需要对表进行分区,导致往往是排查空间问题时,才发现是某个上游MySQL上出现新的数据量特别大的表,且没有提前周知给我们。在这种情况下,如何高效的将非分区表改造为分区表呢?

6.1之后7.5之前:通过EXCHANGE PARTITION间接改造

由于TiDB 6.1之后支持了EXCHANGE PARTITION操作,可以基于EXCHANGE PARTITION,来将快速的将非分区表改造为分区表。我总结出来的操作步骤是:

  1. 基于当前表创建对应的分区表。(第一个分区的上界要大于表的当前最大值)
mysql> show create table t1\G
*************************** 1. row ***************************
     Table: t1
Create Table: CREATE TABLE `t1` (
`date` date NOT NULL,
`name` varchar(10) NOT NULL,
PRIMARY KEY(`date`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1 row in set (0.00 sec)

mysql> select * from t1;
+------------+------+
| date       | name |
+------------+------+
| 2023-07-02 | 0000 |
| 2023-07-03 | 0000 |
| 2023-07-04 | 0000 |
| 2023-07-05 | 0000 |
+------------+------+
4 rows in set (0.01 sec)

mysql> CREATE TABLE `t1_partition` (
  ->   `date` date NOT NULL,
  ->   `name` varchar(10) NOT NULL,
  ->   PRIMARY KEY(`date`,`name`)
  -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
  -> PARTITION BY RANGE COLUMNS(`date`)
  -> (PARTITION `pbefore` VALUES LESS THAN ('2023-08-01'),
  ->  PARTITION `p202308` VALUES LESS THAN ('2023-09-01'),
  ->  PARTITION `p202309` VALUES LESS THAN ('2023-10-01'),
  ->  PARTITION `p202310` VALUES LESS THAN ('2023-11-01'),
  ->  PARTITION `p202311` VALUES LESS THAN ('2023-12-01'),
  ->  PARTITION `p202312` VALUES LESS THAN ('2024-01-01'),
  ->  PARTITION `pfuture` VALUES LESS THAN (MAXVALUE));
Query OK, 0 rows affected (0.17 sec)
  1. 使用EXCHANGE PARTITION操作,将非分区表数据交换到分区表第一个分区中
mysql> alter table t1_partition exchange partition pbefore with table t1;
Query OK, 0 rows affected, 1 warning (0.23 sec)

mysql> show warnings;
+---------+------+---------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                               |
+---------+------+---------------------------------------------------------------------------------------+
| Warning | 1105 | after the exchange, please analyze related table of the exchange to update statistics |
+---------+------+---------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
  1. 交换非分区表和分区表的表名
mysql> rename table t1 to t1_bak, t1_partition to t1;
Query OK, 0 rows affected (0.28 sec)
  1. 最后,别忘记第二步还有个warning,经过EXCANGE之后,需要对新表进行analyze table操作,才会生成全局的动态裁剪统计信息。
mysql> analyze table t1;
Query OK, 0 rows affected, 14 warnings (2.24 sec)

需要注意的是:

  1. 这种场景下,在EXCHANGE PARTITION后,RENAME TABLE之前,这期间可能会有几秒中业务查询t1表会是个空表,需要提前跟业务沟通好,找个业务低峰期处理
  2. TiDB在开启聚簇索引后是不支持修改主键的,而开启冷热分离一般来说是将某个时间字段加到主键中,根据时间字段来分区,也就是需要更改表的主键,所以强烈建议汇总类的TiDB集群的tidb_enable_clustered_index参数最好设置为OFF,这样MySQL上新增的表默认才会是NONCLUSTERED的,可以方便的修改大表的主键。(6.5之前默认值是INT_ONLY,6.5之后默认值是ON,在默认情况下,基本上90%的表是不能直接进行分区化改造的)

7.5之后:官方支持非分区表和分区表之间的相互转换

详细内容可以看:官方文档,直接一条SQL就能更改。

ALTER TABLE <table_name> PARTITION BY <new partition type and definitions>

不过,别忘记官方文档里说的:该语句在执行时,将根据新的分区定义复制表中的所有行,并在线重新创建索引。这是一个非常非常重的操作,如果表已经非常大了,还是建议按照手动操作步骤来变更,可以减少行的复制操作。

总结

本篇文章,总结了一下通过Placement Rule完成业务隔离、冷热隔离放的实战经验,通过上述描述,大家也能看到7.5的新功能可以规避很多非常规的骚操作。其实不仅如此,7.5在资源管控、DDL等后台任务控制、TiFlash等其他方面也对用户更加友好了,希望大家在测试环境多多验证,尽量早上生产。

0
2
4
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论