TiDB 测试报告

# 1. 背景与目标

单机 tpg 在存储和 SQL 性能上有 scale-up 上限,TiDB 作为分布式 NewSQL 的一个实现,在线性扩展,容灾等方面有架构上的优势,本次测试主要研究 TiDB 是否能满足目前运营数据服务场景下的功能和性能需求。

# 2. TiDB 系统架构

参考 https://pingcap.com/docs-cn/v3…

# 3. 部署方式

4 台 TS80A

官方推荐生产环境只使用 ansible 方式部署,但是 ansible 方式在内网 idc 下部署繁琐阻碍较多,为快速测试验证,本次使用 docker 方式部署,理论上 docker 对 CPU 和内存以及磁盘 IO 的使用相比物理机不会有损失,网络 IO 方面需要经过一次转发,整体性能损失在 5% -10% 之间。

本次 docker 部署使用官方的 3.0 版本镜像,每台机器上均部署了一个 TiKV 实例,一个 PD 实例,和一个 TiDB 实例。使用默认配置文件。

# 4. 数据写入性能

我们的场景需要一批次写入大量数据,通常单次写入在 10 – 100w 行之间。

## 4.1 TiDB-Lightning

对于需要写入大量数据的场景,TiDB 官方提供了一个高性能解决方案,TiDB-Lightning,其核心思路是绕过 TiDB 的 SQL 层,直接生成底层 TiKV 文件替换进去,从而优化性能,据官方说法,这个是最快的数据写入方案

TiDB Lightning 主要包含两个部分:

tidb-lightning(“前端”):主要完成适配工作,通过读取数据源,在下游 TiDB 集群建表、将数据转换成键/值对 (KV 对) 发送到 tikv-importer、检查数据完整性等。

tikv-importer(“后端”):主要完成将数据导入 TiKV 集群的工作,把 tidb-lightning 写入的 KV 对缓存、排序、切分并导入到 TiKV 集群。

进一步参考资料详见 https://pingcap.com/docs-cn/v2…

需要说明的是,TiDB-lightning 在写入数据时,会将整个 db 集群切换到“导入模式” (import mode),优化写入效率并停止自动压缩 (compaction)。在此模式下,db 对外不可读写,因此,这种方案官方只推荐用于集群初次搭建后,初始化大批量数据时写入使用

虽然这种方案无法作为日常写入方式,但是我们仍然对其进行了测试,以此作为写入性能的 benchmark 上限。

TiDB-lightning 的性能数据如下:

首先测试导入单个文件的性能

| 数据源(CSV格式)文件个数 | 总导入数据行数 | 总导入数据体积 | 导入耗时(秒) | 导入速度(行每秒) |
|—————-|———|———|———|———|
| 1 | 38w | 450MiB | 120 | 3.1 k |

作为对比,我们在实际业务场景中,将同样的数据量在 spark 任务中通过 copy 方式写入 tpg 的耗时为

| 数据源(Spark RDD) | 总导入数据行数 | 总导入数据体积 | 导入耗时(秒) | 导入速度(行每秒) |
|—————-|———|———|———|———|
| 1 | 38w | 450MiB | 66 | 5.7 k |

可以看到这种场景下 TiDB-Lightning 的写入性能并不高,官方文档中提到,TiDB-lightning 支持多文件并发导入,为了测试并发性能,我们将文件拆细为 5w 行一个文件,共构造了 6 个文件:
| 数据源(CSV格式)文件个数 | 总导入数据行数 | 总导入数据体积 | 导入耗时(秒) | 导入速度(行每秒) |
|—————-|———|———|———|———–|
| 6 | 30w | ~350MiB | 50 | 6k |

可以看到随着文件个数增多,并发写入方式下,总的速度有所提升,因此继续增加文件个数,提高并发度
| 数据源(CSV格式)文件个数 | 总导入数据行数 | 总导入数据体积 | 导入耗时(秒) | 导入速度(行每秒) |
|—————-|———|———|———|———–|
| 19 | 95w | ~1100MiB | 115 | 8.2k |

在 TiDB-Lightning 导入数据的过程,默认开启了一个 analyze 步骤,主要用于写入数据后分析数据的一些分布的统计数据等,其实这个时候数据已经写入完成,如果我们跳过 analyze步骤,速度可以进一步提高
| 数据源(CSV格式)文件个数 | 总导入数据行数 | 总导入数据体积 | 导入耗时(秒) | 导入速度(行每秒) |
|—————-|———|———-|———|———–|
| 19 | 95w | ~1100MiB | 88 | 1.07w |

最终,我们测得 100 个文件下,关闭 analyze,TiDB-lightning 导入速度为
| 数据源(CSV格式)文件个数 | 总导入数据行数 | 总导入数据体积 | 导入耗时(秒) | 导入速度(行每秒) |
|—————-|———|———|———|———–|
| 100 | 3.5kw | 33G | 16min6s | 3.6w |
这个速度可以视为是导入速度的上限。

## 4.2 Load data

通过 TiDB-Lightning 导入数据的方式会锁库,不能用于日常写入和更新数据,TiDB 支持 MySQL 标准协议下的 load data 方式,类似 tpg 的 copy,这种方式不会锁表

我们的测试方式是直接从一个 tpg 实例中读取实际业务表的数据到 jvm,然后在 jvm 内存中通过管道对接,直接将从 tpg 读取到的数据写入到 TiDB 中,中间不落地

其测试性能数据如下:
| 数据源 | 总导入数据行数 | 导入耗时(秒) | 导入速度(行每秒) |
|——————-|———|———|———–|
| tpg 通过内存管道对接 TiDB | 47w | 260 | 1.8k |
这种方式可以开启多线程并行导入来提高速度
| 数据源 | 总导入数据行数 | 导入耗时(秒) | 导入速度(行每秒) |
|—————————-|———|———|———–|
| tpg 通过内存管道对接 TiDB(7 线程并发) | 35w | 50 | 7k |
| tpg 通过内存管道对接 TiDB(15 线程并发) | 75w | 100 | 7.5k |

在上述测试过程中,我们的数据都通过同一个 TiDB 节点往里写入,而在 TiDB 的架构中,是支持多个 TiDB 并发写入数据的,并且从架构原理上,这种写入方式会进一步提高性能,实测数据如下
| 数据源 | 接受数据的 TiDB 节点数量 | 总导入数据行数 | 导入耗时(秒) | 导入速度(行每秒) |
|—————————-|—————–|———|———|———–|
| tpg 通过内存管道对接 TiDB(15 线程并发) | 4 | 75w | 47 | 1.6w |
| tpg 通过内存管道对接 TiDB(30 线程并发) | 4 | 190w | 105 | 1.8w |
| tpg 通过内存管道对接 TiDB(30 线程并发) | 4 | 642w | 346 | 1.85w |
| tpg 通过内存管道对接 TiDB(50+ 线程并发) | 4 | 3.6kw | 26m5s | 2.3w |

进一步加大线程数对效果提升不大,实测通过 load 数据写入,性能上限大约在 2.3w 行每秒

# 5. 查询性能

在我们的业务场景中,需要对数千万行量级的数据进行 group by,也需要在 group by 后的结果上,对数十万至上百行量级的数据进行 join,以下分别进行测试,并对比与 tpg 的表现
测试数据量

select count(1) from wxad_stat.t_stat_friend_biz_service_appid_mul_day where ds between 20180101 and 20190631;

该 SQL 涉及 2.4kw 行数据,在 tpg 上冷启动耗时 37s,多次反复运行后平均稳定在 13s

在 TiDB 中执行时间 4s,没有冷热启动区别

## 5.1 聚合性能

测试 SQL

select uid
, count(1)
, sum(paidwater) sum_paid 
from wxad_stat.t_stat_friend_biz_service_appid_mul_day 
where ds between 20180101 and 20190631 
and paidwater > 0
group by uid 
order by sum_paid desc 
limit 10;

耗时对比
| TiDB | tpg(反复多次稳定后) |
|——|————–|
| 8.7s | 110s |
测试 SQL 2

select product_type
, count(1)
, sum(paidwater) sum_paid 
from wxad_stat.t_stat_friend_biz_service_appid_mul_day 
where ds between 20180101 and 20190631 
and paidwater > 0
group by product_type 
order by sum_paid desc 
limit 10;

| TiDB | tpg(反复多次稳定后) |
|——|————–|
| 6s | 45s |

## 5.2 关联性能

测试 SQL

select t1.uid
, abs(coalesce(t1.sum_paid, 0) - coalesce(t2.sum_paid, 0)) as abs_diff
from (
select uid
, count(1)
, sum(paidwater) sum_paid 
from wxad_stat.t_stat_friend_biz_service_appid_mul_day 
where ds between 20180101 and 20180631 
and paidwater > 0
group by uid 
) t1 left join (
select uid
, count(1)
, sum(paidwater) sum_paid 
from wxad_stat.t_stat_friend_biz_service_appid_mul_day 
where ds between 20180701 and 20181231 
and paidwater > 0
group by uid 
) t2
on t1.uid = t2.uid
order by abs_diff desc
limit 10;

| TiDB | tpg(反复多次稳定后) |
|——|————–|
| 6s | 97s |

# 6. 事务模型评估

PostgreSQL 通过 WAL 和 MVCC 实现了事务,因此对单个事务大小没有限制,(见 https://stackoverflow.com/ques… ),而 TiDB 的事务模型与 MySQL 保持一致,通过回滚段实现,因此对单个事务大小有限制,导致一次性删除大量数据或者插入数据时会报错 `ERROR 8004 (HY000): transaction too large`,需要进行以下设置

set @@tidb_batch_delete = ON;
set @@tidb_batch_insert = ON;

以上设置会自动将一个大事务拆分,分小段提交,由此带来的问题是整个大事务的原子性被打破。在我们的场景中,这个问题的一个办法是可以通过写入数据时先写到临时表来绕过。
# 7. 字段类型支持与 SQL 语义完整性

## 7.1 字段类型

tpg 支持 hstore 和 array 这两种类型,是我们目前使用较多的,TiDB 不支持 hstore,但是支持 json。针对这个问题,一种解决思路是用 json 代替 hstore 原来的场景,另外一种是将 hstore 展平成二维表,由于 TiDB 支持 online DDL,因此添加字段的风险和成本相对较低。

array 类型主要用于运营标签,通过一些存储方式和查询方式上配合的改动,运营标签可以通过基本字段来承载。

## 7.2 SQL 语义完整性

TiDB 与 MySQL 协议保持兼容,也不支持 full join 语义,针对这个问题,MySQL 社区的解决方案是通过一次 left join 和一次 right join 然后 union 来模拟,(见 https://stackoverflow.com/ques… ),这种做法在 TiDB 上也同样可以使用。

# 8. 容灾测试

实测在机器上使用 root 权限直接杀掉一个机器上的 TiKV 和 TiDB 和 PD 节点,整个集群服务不受影响,依然可以正常写入和查询数据。

这里由于 docker 方式没有附带监控,尚未完整监控整个容灾过程中的监控变化情况,待完善补充。

# 9. 可扩展性评估

## 9.1 存储能力

TiKV 通过 raft 默认采用 3 副本,通过简单的添加机器,集群的存储能力可以线性扩展。

## 9.2 计算能力

需要注意的是,在执行一条大数据量的 SQL 时,TiDB 与 GreenPlum 等 MMP 架构的 db 不同,GreenPlum 会同时调度多个节点,并行计算这条 SQL 涉及的数据,计算节点之间涉及了数据的交换。而 TiDB 架构中,虽然可以起多个 TiDB 节点,并且这些 TiDB 节点可以同时并发对外提供读写能力,但是对于一个大数据量的 SQL 来说,并不会有多个 TiDB 节点协同参与计算,而还是由某一个 TiDB 负责计算。

因此,扩充 TiDB 的计算节点,对单个大 SQL 的执行性能并没有太大帮助,但是可以有助于多个并发请求同时发起时,整个集群的并发处理能力。

# 10. 小结

TiDB 虽然是一个定位 100% TP 和 80% AP 场景的 NewSQL,但是实际测试下来,其 AP 查询能力也比较高性能

结合运营数据服务需要经常回溯刷新标签数据的场景特点,目前评估认为引入 TiDB 作为运营服务,可以减少回溯刷新历史数据的任务复杂度,同时通过较强的 AP 能力,直接从天表聚合,避免周、月表等在回溯标签时的逻辑复杂性问题。

Leave a Reply

Your email address will not be published. Required fields are marked *