背景
用户在使用 4.0.14 版本进行数据库迁移时,发现通过 dumpling 导出的全量备份体积为 3.5T,而每个 TiKV 节点分别占用 1.36T 的存储空间,总共占用了 4.08T 的空间。用户想了解导出的 CSV 文件的体积应该如何估算,并且为什么导出的单副本 CSV 文件体积接近整个集群的体积。
说明
下面用实验来说明平面文件与数据体积相关的关系:
sysbench 造数 10 张表。
sysbench --config-file=tidb_config oltp_read_write --tables=10 --table-size=2000000 --db-ps-mode=auto --rand-type=uniform --mysql-ignore-errors=2013 prepare
根据官网的“如何预估 TiDB 中的一张表的大小”,我们可以查到表的压缩前和压缩后的体积。
https://docs.pingcap.com/zh/tidb/v6.5/manage-cluster-faq#%E5%A6%82%E4%BD%95%E9%A2%84%E4%BC%B0-tidb-%E4%B8%AD%E4%B8%80%E5%BC%A0%E8%A1%A8%E7%9A%84%E5%A4%A7%E5%B0%8F
SELECT
db_name,
table_name,
ROUND(SUM(total_size / cnt), 2) Approximate_Size,
ROUND(SUM(total_size / cnt / (SELECT
ROUND(AVG(value), 2)
FROM
METRICS_SCHEMA.store_size_amplification
WHERE
value > 0)),
2) Disk_Size
FROM
(SELECT
db_name,
table_name,
region_id,
SUM(Approximate_Size) total_size,
COUNT(*) cnt
FROM
information_schema.TIKV_REGION_STATUS
WHERE
db_name = 'sbtest'
GROUP BY db_name , table_name , region_id) tabinfo
GROUP BY db_name , table_name;
#sum(Approximate_Size)是 6.18 GB
#sum(Disk_Size)是 3.93 GB
+---------+------------+------------------+-----------+
| db_name | table_name | Approximate_Size | Disk_Size |
+---------+------------+------------------+-----------+
| sbtest | sbtest5 | 609.00 | 392.90 |
| sbtest | sbtest6 | 631.00 | 407.10 |
| sbtest | sbtest9 | 699.00 | 450.97 |
| sbtest | sbtest4 | 711.00 | 458.71 |
| sbtest | sbtest3 | 712.00 | 459.35 |
| sbtest | sbtest2 | 597.00 | 385.16 |
| sbtest | sbtest8 | 597.00 | 385.16 |
| sbtest | sbtest10 | 711.00 | 458.71 |
| sbtest | sbtest1 | 593.00 | 382.58 |
| sbtest | sbtest7 | 728.00 | 469.68 |
+---------+------------+------------------+-----------+
10 rows in set (0.01 sec)
在 grafana 的 <tidb-cluster>-PD 的 Statistics-balance 面板的 Store used 、Store Region size、Size amplification 也可以观察到。
Store Region size 可以理解为“压缩前的体积”;
Store used 可以理解为“压缩后的体积”;
Size amplification 是“压缩比平均值”。
Store used ≈ Store Region size / Size amplification
注:本实验的数据体积过小,没有多少压缩比可言。TiDB 默认的层级压缩参数为compression-per-level = ["no", "no", "lz4", "lz4", "lz4", "zstd", "zstd"]
, 在数据量大时,压缩比高。
导出的 CSV 体积是否等于 "Store Region size 总和 / 3 (副本)" 呢?
不是的。CSV 导入到 TiDB 后,转换为 region 的体积会有所放大。此外,还需要考虑索引大小的影响,因此 Store Region size 通常会显得偏大。
导出的 CSV 文件实际上是单一副本,不包含索引数据量,可以理解为略大于 DATA_LENGTH 的总和。
为什么说是大于 DATA_LENGTH 总和呢?
由于 DATA_LENGTH
的计算依赖于数据库的统计信息,而实际情况中,数据库中常常存在统计信息尚未被收集或已经过时,但尚未达到自动触发 auto analyze
操作的标准的情况。
验证过程:
################ 1.CSV 体积基本等于 DATA_LENGTH 总和的情况 ########################
#数据是 sysbench insert 而来,会自动触发统计信息收集
MySQL [(none)]> select table_schema,sum(DATA_LENGTH)/1024/1024/1024 from information_schema.tables where TABLE_SCHEMA = 'sbtest' group by table_schema;
+--------------+---------------------------------+
| table_schema | sum(DATA_LENGTH)/1024/1024/1024 |
+--------------+---------------------------------+
| sbtest | 3.764550373890 |
+--------------+---------------------------------+
1 row in set (0.01 sec)
nohup ./dumpling \
-u root \
-P 4000 \
-h 172.16.201.22 \
-t 8 \
-o /data/backup \
-r 200000 \
-F 256MiB \
--filetype csv \
-B sbtest > nohup.out &
[tidb@vm172-16-201-22 backup]$ du -sh
3.8G
# 表的平均行长为 199 bytes,抽样验证导出的 CSV 文件也是每行 199 bytes 左右。
MySQL [(none)]> select TABLE_NAME,AVG_ROW_LENGTH from information_schema.tables where table_schema='sbtest' AND table_name='sbtest1';
+------------+----------------+
| TABLE_NAME | AVG_ROW_LENGTH |
+------------+----------------+
| sbtest1 | 199 |
+------------+----------------+
1 row in set (0.01 sec)
[tidb@vm172-16-201-22 backup]$ lines=$(wc -l < sbtest.sbtest1.0000000000000.csv)
[tidb@vm172-16-201-22 backup]$ bytes=$(wc -c < sbtest.sbtest1.0000000000000.csv)
[tidb@vm172-16-201-22 backup]$ avg_line_length=$(echo "scale=2; $bytes / $lines" | bc)
[tidb@vm172-16-201-22 backup]$ echo "Average line length: $avg_line_length bytes"
Average line length: 198.84 bytes
################ 2.CSV 体积大于 DATA_LENGTH 总和的情况 ########################
#数据是 lightning 设置 analyze = "off" 导入的,缺失统计信息
mysql> select DATA_LENGTH from information_schema.tables where table_name='sbtest1';
+-------------+
| DATA_LENGTH |
+-------------+
| 0 |
+-------------+
1 row in set (0.01 sec)
#lightning local 模式导入,Modify_count 和 Row_count 会为 0,所以也不会自动触发统计信息收集
mysql> show stats_meta where table_name='sbtest1';
+---------+------------+----------------+---------------------+--------------+-----------+
| Db_name | Table_name | Partition_name | Update_time | Modify_count | Row_count |
+---------+------------+----------------+---------------------+--------------+-----------+
| sbtest | sbtest1 | | 2024-05-21 20:43:12 | 0 | 0 |
+---------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.00 sec)
#不能用健康度判断统计信息是否准确,即使是新建一张空表,没收集统计信息时,也显示 100
mysql> show stats_healthy where table_name='sbtest1';
+---------+------------+----------------+---------+
| Db_name | Table_name | Partition_name | Healthy |
+---------+------------+----------------+---------+
| sbtest | sbtest1 | | 100 |
+---------+------------+----------------+---------+
1 row in set (0.00 sec)
mysql> select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
| 2000000 |
+----------+
1 row in set (0.51 sec)
用户验证
导出的 CSV 大小等于 DATA_LENGTH 的总和。
后话
估算导出的 CSV 平面文件的体积相对容易。但如果要估算 CSV 文件导入后的体积,这就比较困难了。因为每个行业的数据特点不同,压缩率也会有所差异。举个不严谨的例子,一亿条相同的数据,压缩后可能只占用一条的空间。较为精确的估算方法是,先导入 10% 的 CSV 文件,然后根据观察到的体积来估算最终的导入体积。
如果一定要给出一个经验比例值,那么大致可以认为 3T 的 CSV 文件导入后会占用 4.5 至 6T 的存储空间(包括三副本),就算宽裕估算比值也是 1 : 2 的关系。相比于 MySQL 三副本使用的数据体积,这个压缩率已经相当可观。