PU-系统性能优化

TiDB Server

性能相关基本原理

CPU

CPU 是 TiDB 消耗的主要资源

  • SQL 执行流程
    • SQL:执行语句
    • AST:抽象语法树
    • Logical:逻辑优化
    • Optimized Logical Plan:物理优化
    • Cost Model:代价模型
    • Selected Physical Plan:向下发的执行计划
  • 火焰图
    • go 的 pprof 抓取
    • go tool pprof -http 127.0.0.1:8080 http://127.0.0.1:10080/debug/pprof/profile

内存

  • 内存分配次数过多 –> golang GC 压力大,其实 CPU 的使用过多,一般内存使用也会较多
  • 火焰图抓取:go tool pprof -http 127.0.0.1:8080 http://127.0.0.1:10080/debug/pprof/heap

网络

  • 一些常见的参考值(PPT 中标黑的部分是:内存和网络读 1 MB 数据的耗时)
  • 介绍下一个事务开启,走那些网络
    • PD tso,全局唯一标识,两次(start ts, commit ts),点查例外。
    • KV commit,需要走数据和索引两部分
    • Raft 提交

介绍下例子(两阶段提交)

1
2
3
CREATE TABLE T (I INT UNIQUE KEY); -- 如果是 PRIMARY KEY 呢?
INSERT INTO T VALUES (1);
UPDATE T SET I = 2;
  • 启动阶段:获取 start ts
  • 读阶段:读一次索引,再读一次数据(也就是常说的回表)
  • 提交阶段:获取 commit ts、两次 tikv 写入(数据+索引) * 2(prewrite + commit)、TiKV 之间 raft【写入 key 个数 * (副本数/2 + 1)】
  • 上面的为协调者,TiKV 是参与者

乐观事务模型下,只有提交才加锁

  • prewirte 之后,会有一段时间才会进行 commit。
  • 那么在这个时间中,当其他事务对相关数据进行读写请求,那么就会有一个等待解锁的过程。

  • Backoff 机制:不断增加等待时间,直到超时(Backoff 是递增的)。

不同负载对性能的影响

  • 读请求
    • TiDB 内部计算
      • SQL 优化
    • KV 读
      • 通过 TiKV Storage read pool 点查/更新/删除(2.1 的优化,性能损耗较低)
    • Coprocessor 读(逻辑要比较复杂)
      • TiKV Coprocessor
  • 写请求
    • TiDB 内部计算(内部变换,将数据转换成要写入的数据)
    • 事务提交

读请求和 MySQL 的差异

  • TiDB 中没有任何读缓存(都会从 TIKV 中将数据读上来),MySQL 有 buffer pool
  • TiDB 中有事务私有写缓存,事务内写的数据会缓存在事务自身的 buffer 中
  • 当读请求也要读到缓存数据的时候,会与 TiKV 中结果进行合并

例子(一个 session 中):

Insert into test (i) values (1);
Begin;
Insert into test (i) values (2);
Select * from test; -- TiKV 中的结果(1) + 事务内的结果(2)

合并过程仅在 TiDB 单点内部处理,事务内数据量过大将会大大降低性能

写请求和 MySQL 的差异

  • 事务提交前,TiDB 缓存所有即将提交的修改
  • 写请求由于一致性检查的要求,也会进行大量的读
  • TiDB 的 insert lazy check (优化:延迟检查)
    • 普通 Insert
    • Insert ignore?(写入是否有冲突)
    • Insert on duplicate key update?(重复主键是否要更新)

例子:

CREATE TABLE T (I INT KEY);
INSERT INTO T VALUES (1);
BEGIN;
INSERT INTO T VALUES (1); -- MySQL 返回错误
INSERT INTO T VALUES (2);
COMMIT; -- TiDB 返回错误(发现主键冲突,直接回滚)
SELECT * FROM T; -- MySQL 返回 1 2;TiDB 返回 1
  • 乐观事务模型(OCC)
    • 对于 TiKV update 仅仅做了一次读。提交时冲突了怎么办?(重试,可关闭)
  • 事务重试(对于客户无感知的,过多会有性能损耗)
    • 重新取 start ts,重新执行事务内所有的写操作的 SQL。
    • 相当于把事务启动时间向后挪动。
    • 所有的操作都算在了 commit 语句自身的执行中,用户无感知。

例子:

session1 session2
Begin; -
Update t set i = 2 where i = 1; -
- update t set i = 3 where i = 1;
Commit; – 初次提交会失败,并进行自动重试 -

监控

  • Duration
  • QPS:系统 SQL(internal QPS) 和 业务 SQL
  • 连接数(token-limit,默认 1000)
  • Heap Memory Usage,内存使用,可抓火焰图分析
  • Transaction Statement Num,排查事务语句太多带来的相关问题(事务中有多少 SQL)。
  • Transaction Retry Num:事务冲突,提交异常慢的原因之一
  • Backoff & Lock:事务冲突有关,Lock 是超过了限制的时间,那么就会解锁,事务被 kill
  • KV Cmd Duration:区分 TIDB 和 TIKV 慢的关键
  • PD TSO Wait/RPC Duration:wait duration 高,可能是 TiDB 自身慢(TiDB 获取 start ts,但是可能 TIDB 比较忙,一直没有用)。RPC duration 高,原因是 TiDB 与 PD 之间延迟太大(TiDB 从 PD 获取 TSO 的实际时间)。这两个监控如果基本类似,那么大概率是 TIDB 和 PD 之间网络延迟比较大。

慢日志及常用工具介绍

性能调参

TiKV Server

以 TIKV 角度来找性能瓶颈

TIKV 架构

  • PD:存储元数据,分发 TSO,manager
  • TIDB:计算
  • TIKV:存储 + 计算下推,分布式计算

监控

  • Grpc
    • grpc message count:grpc 请求数
    • grpc message duration:TIKV Grpc 通讯响应时间
    • 如果 TiKV 端显示延迟不高,但是 TiDB 那边又说 TiKV 延迟比较高,说明中间环节可能有问题,比如网络,需要查看下网卡流量是否打满以及延迟情况。比如 ping 一下延迟是否比较高

TIKV 中数据存储

  • 1、table 数据如何映射成 KV(key -> values)?
    • 比如一张表,有主键、唯一索引、普通索引、普通列。一行数据的存储是什么样的呢?
      • 行数据:row-key(行 key) -> column-1 + column-2 + column-3
      • 索引数据:index-key -> row-id(uint64)
    • 编码方式
      • Row key 编码方式(保证全局唯一):table-id(占 8 个字节) + _r(row key 标志位) + row-id(uint64)
      • Unique Index key 编码方式:table-id + _i(index key 标志位) + index-id + index-content -> row id
      • No Unique Index key 编码方式:table-id + _i(index key 标志位) + index-id + index-content + row-id -> empty
    • 说明
      • TIDB 将所有的数据都放到 TIKV 上,对于 TIKV 而言,存储数据是一个全局的数据集,是从最小 key 到最大 key 的一个数据区间,没有表这个概念。
      • 为了在 TIKV 中,不同表的数据容易区分,因此就有了 table-id。好处是:区分表、表数据连续(对 scan 操作友好)
      • key 需要全局唯一,有序
      • 这里的 KV 是逻辑 KV,当不考虑事务的话,我们编码好的 KV 对,就可以直接落盘。但是 TIDB 是支持分布式事务的,因此就需要一个两阶段的事务提交,不能直接将编码好的 KV 直接写盘,中间会有一些中间状态,因此要了解下 RocksDB 的 Column Family。
  • 2、RocksDB 的 Column Family
    • 要了解两阶段提交的中间状态
    • LMS tree 结构:
      • 对于写,会先写 WAL(disk) 日志中
      • 然后将写入的内容写入内存(memtable)
      • 当 memtable 涨到一定的大小,就会将内存里面的数据,flush 成 SST 文件,新的写入会在新的 memtable 中写入
      • 随着时间的推移,写的 SST 文件就会越来越多(有很多内容是有重叠的),这样对于读取是不友好的。
      • 因此会对 SST 文件进行整理,这个过程就是 compaction。整理成一个塔型的结构,越到下面的数据越老,越往上就越新,memtable 中是最新的。
    • 对于 RocksDB 的 Column Family 而言,他有三个 cf 空间,是完全隔离开的,但是共享了一个 WAL 日志
    • 多个 cf 之间共享 WAL(保证原子性)
    • 每个 cf 有单独的 memtable、block-cache 和 sst
      • default cf:真正数据
      • lock cf:锁信息
      • write cf:CF 存储的是数据的版本信息 (MVCC) 以及索引相关的数据
  • 3、KV 如何存储在 RocksDB 中,编码的逻辑 KV 如何落盘成物理 KV
    • 物理 KV 如何存储,对于理解 TiKV 写入是有帮助的。
    • KV 是如何存储在底层 RocksDB:
      • default cf:一个 KV 过来,需要写对应的 Key 和 Value,Key 有对应的版本:key_start_ts(key+事务起始时间)
      • lock cf:提交的第一阶段(prepare 阶段),会加锁,保证其他事务不会发生冲突
      • 进入 commit 阶段:删除 lock cf(释放锁),提交记录写入 write cf。其中的内容就是:key + commit_ts(事务的提交时间) ->(指向) start_ts,通过 start_ts 可以在 default cf 找到对应的数据,并且记录了操作的类型(比如 PUT,也就是 insert)
    • 整个过程总结:
      • 通过 2PC 来支持分布式事务。
      • prepare 阶段写 default 和 lock(两个操作是原子的)。
      • commit 阶段删 lock 写 write(两个操作是原子的)。
    • 细节优化 –> 短 value 处理:
      • 通过 key 的编码方式我们知道,row key 大概在 20 多个 bytes 左右,index key 在 30 ~ 几百 bytes 左右。
      • 如果 value 特别短,写多个 cf 导致的存储空间和写入开销都会增大。
      • value <= 64 bytes, prepare 阶段把 value 携带在 lock 里面,commit 阶段写入到 write 里,也就是说 write cf 中也会有真正的数据。
      • 这样我们少写一个 default cf,读的时候,只需要扫 write cf 数据即可(不需要通过 start_ts 去 default cf 中找真实数据了)。

写优化

1、TiKV 数据写入流程

  • 写入请求到 TIKV,首先会先到 scheduler pool 组件:进行写之前的约束校验,比如写入的 ts 是否比已有版本的 ts 的更大(写写冲突)等等。(raw_put 和直接调用 TIKV 接口,会跳过该部分,使用 TIDB 写入数据,则不会)
  • 然后请求就会到 raftstore 线程:处理 raft 任务:处理 raft 消息(复制给其他的 TIKV)和写 raft log(相关参数是 sync-log,默认为 true)
  • raft 日志会 append log 落盘,日志落盘成功,就会回应 commit,这样 commit 成功后,就会进入 apply 线程,写入状态机,apply 到 db –> RocksDB data。(实际 commit = append + apply 成功)
  • append 和 apply,单线程 –> 多线程。可能会成为瓶颈。

2、写入瓶颈分析

看 TIKV 监控

  • CPU 使用是否高
  • duration 是否高
    • 有一个 stall 流控

3、写入热点问题分析

  • 判断是否可优化
  • 索引热点
  • 组件热点
  • 数据热点

4、写入参数优化

  • RocksDB 写入相关参数
    • 要考虑 L0 数据有重叠,导致不能简单的做 range。
    • L0 compact 并发
    • 压缩比:CPU 换 IO
    • compression-per-level = [“no”, “no”, “lz4”, “lz4”, “lz4”, “zstd”, “zstd”] # 各个层的压缩算法
    • level0-file-num-compaction-trigger = 4 # L0 compact 触发
    • level0-slowdown-writes-trigger = 20 # L0 文件到达 20,RocksDB 流控控制写入速度
    • level0-stop-writes-trigger = 36 # L0 文件到达 36,RocksDB 流控控制写入停止
  • 当业务只是有高峰,时间较短,而且业务高峰时,对读的响应时间要求不高,那么可以放宽 RocksDB 流控的限制。
  • Titan:将大 value 单独拆出来,存为 log 文件,减少写放大

读优化

1、TiKV 数据读取流程

  • 读流程要比写简单一点
    • KV 读:走 storage readpool(默认 4)
    • 下推读:coprocessor readpool(默认 CPU 80%)
    • 其中各对应 3 个 pool 配置,3 个优先级:
      • high:高优先级请求,比如查系统变量等。
      • normal:一些正常的请求。
      • low:比如一些 scan

2、读取瓶颈分析

  • 查看监控
    • CPU:不同的池子(storage & coprocessor)
      • 调整原则:太多 or 太低
      • 有没有热点呢
    • duration
      • 请求时间
      • 等待时间
      • 处理时间

3、RocksDB block-cache 原理与配置

  • RocksDB Column Family
    • 多个 cf 之间共享 WAL
    • 每个 cf 有单独的 memtable、block-cache 和 sst
    • 看下各个组件的作用(PPT 中有)
  • 内存占用:
    • memtable
    • block cache
    • 系统的 page cache
王军 wechat
0%