应用场景
使用场景
- 绝大多数请求都是用于读访问的,数据一次写入,多次查询
- 数据需要以大批次(大于1000行)进行更新,而不是单行更新;或者根本没有更新操作
- 数据只是添加到数据库,没有必要修改
- 读取数据时,会从数据库中提取出大量的行,但只用到一小部分列
- 表很“宽”,即表中包含大量的列
- 查询频率相对较低(通常每台服务器每秒查询数百次或更少)
- 对于简单查询,允许大约50毫秒的延迟
- 列的值是比较小的数值和短字符串(例如,每个URL只有60个字节)
- 在处理单个查询时需要高吞吐量(每台服务器每秒高达数十亿行)
- 不需要事务
- 数据一致性要求较低
业务场景
流量分析、精准营销、广告实时竞价、BI 报表分析、用户行为分析、日志分析、实时大屏等。
- 数据仓库:ClickHouse 可以快速地处理大量的数据,支持高并发查询和复杂的聚合分析,是数据仓库和数据分析的首选工具。
- 实时数据分析:ClickHouse 支持实时数据分析和实时查询,可以快速地处理实时数据流,是实时数据分析和实时监控的首选工具。
- 时序数据存储:ClickHouse 支持时序数据存储和时序数据分析,可以快速地处理时间序列数据,是时序数据存储和时序数据分析的首选工具。
- 数据可视化:ClickHouse 支持数据可视化和报表生成,可以快速地生成各种类型的报表和图表,是数据可视化和报表生成的首选工具。
缺点
- 没有完整的事务支持;
- 不支持分词查询;
- 缺少高频率,低延迟的修改或删除已存在数据的能力。仅能用于批量删除或修改数据;
- 不擅长 join 操作;
- 不支持高并发,官方建议qps为100。原因: ClickHouse 将数据划分为多个 partition,每个 partition 再进一步划分为多个 index_granularity(索引粒度),然后通过多个 CPU核心分别处理其中的一部分来实现并行数据处理。在这种设计下, 单条 Query 就能利用整机所有 CPU。 极致的并行处理能力,极大的降低了查询延时。所以,ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多 cpu,就不利于同时并发多条查询。所以对于高 qps 的查询业务, ClickHouse 并不是强项。
优点
查询快
利用存储引擎的特殊设计充分减少磁盘I/O对查询速度的影响。从用户提交一条SQL语句进行查询到最终输出结果的过程中,大量的时间是消耗在了磁盘I/O上,在很多情况下,I/O所占用的时间可以达到整个时间的90%以上。对存储引擎磁盘I/O的优化可以获得非常大的收益。ClickHouse的存储引擎设计中大量优化的目的也是为了减少磁盘I/O。
- 列存
- 预排序:在实现范围查找时,可以将大量的随机读转换为顺序读,从而有效提高I/O效率,降低范围查询时的I/O时间;
- 数据压缩:可以减少读取和写入的数据量,从而减少I/O时间。
- 向量化引擎,尽可能多地使用内置函数
- 尽可能避免Join操作,可以用Spark替代
- ClickHouse 会在内存中进行 GROUP BY,并且使用 HashTable 装载数据。
- 索引
- 多线程和分布式
算法:ClickHouse针对不同的应用场景,选择不同的算法:
对于常量字符串查询,使用volnitsky算法
对于非常量字符串,使用CPU的向量化执行SIMD,进行暴力优化
对于字符串正则匹配,使用re2和hyperscan算法
写入性能好
- 列存
- 数据压缩:能够有效地减少数据的存储空间,从而提高写入性能
- 分布式架构
- 多线程写入:ClickHouse采用多线程写入机制,能够同时处理多个写入请求,从而提高写入性能。
LSM-Tree存储结构。
先明白一个测试数据:磁盘顺序读写和随机读写的性能差距大概是1千到5千倍之间
连续 I/O 顺序读写,磁头几乎不用换道,或者换道的时间很短,性能很高,比如0.03 * 2000 MB /s
随机 I/O 随机读写,会导致磁头不停地换道,造成效率的极大降低,0.03MB/sClickHouse中的MergeTree也是类LSM树的思想,日志结构合并树,但不是树,而是利用磁盘顺序读写能力,实现一个多层读写的存储结构 是一种分层,有序,面向磁盘的数据结构,核心思想是利用了磁盘批量的顺序写要远比随机写性能高出很多 大大提升了数据的写入能力。 充分利用了磁盘顺序写的特性,实现高吞吐写能力,数据写入后定期在后台Compaction。在数据导入时全部是顺序append写,在后台合并时也是多个段merge sort后顺序写回磁盘。官方公开benchmark测试显示能够达到50MB-200MB/s的写入吞吐能力,按照每行100Byte估算,大约相当于50W-200W条/s的写入速度。
存储引擎
MergeTree
MergeTree 表引擎主要用于海量数据分析,支持数据分区、主键索引、稀疏索引和数据 TTL 等。
- 列式存储:只读取需要的列,节省 IO 和 CPU 资源。
- 数据分区:按照日期或其他条件将数据分割成多个部分,方便管理和查询。
- 稀疏主键索引:按照主键或排序键对数据进行排序和索引,加速范围查询。
- 次级跳过索引:根据列的最小值和最大值等统计信息跳过不符合条件的数据,进一步提高查询效率。
- 数据合并:后台定期将多个小的数据部分合并成一个大的数据部分,减少数据冗余和碎片
MergeTree 引擎还有一些变种,如 ReplicatedMergeTree、AggregatingMergeTree 和 SummingMergeTree 等,它们在基本的 MergeTree 功能上增加了数据复制、数据聚合、数据求和等特性。
索引
一级索引
一级索引是稀疏索引,意思就是说:每一段数据生成一条索引记录,而不是每一条数据都生成索引, 如果是每一条数据都生成索引,则是稠密索引。用一个形象的例子来说明:如果把MergeTree比作一本书,那么稀疏索引就好比是这本书的一级章节目录。一级章节目录不会具体对应到每个字的位置,只会记录每个章节的起始页码。
MergeTree 的主键使用 PRIMARY KEY 定义,待主键定义之后,MergeTree 会依据 index_granularity 间隔(默认 8192 行),为数据表生成一级索引并保存至 primary.idx 文件内。
稀疏索引的优势是显而易见的,它仅需使用少量的索引标记就能够记录大量数据的区间位置信息,且数据量越大优势越为明显。以默认的索引粒度(8192)为例,MergeTree只需要12208行索引标记就能为1亿行数据记录提供索引。由于稀疏索引占用空间小,所以primary.idx内的索引数据常驻内存,取用速度自然极快。
在 ClickHouse 中,一级索引常驻内存。总的来说:一级索引和标记文件一一对齐,两个 索引标记之间的数据,就是一个数据区间,在数据文件中,这个数据区间的所有数据,生成一个压缩数据块。每列压缩数据文件,存储每一列的数据,每一列字段都有独立的数据文件,每一列都有对应的标记文件,保存了列压缩文件中数据的偏移量信息,与稀疏索引对齐,又与压缩文件对应,建立了稀疏索引与数据文件的映射关系。不能常驻内存,使用LRU缓存策略加快其取用速度。
需要注意的是:ClickHouse 的主键索引与 MySQL 等数据库不同,它并不用于去重,即便 primary key 相同的行,也可以同时存在于数据库中。 要想实现去重效果,需要结合具体的表引擎 ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree 实现。
二级索引
二级索引:又称之为跳数索引。目的和一级索引一样,是为了减少待搜寻的数据的范围。
跳数索引的默认是关闭的,需要通过参数来开启,索引生成粒度由 granularity 控制,如果生成了二级索引,则会在分区目录下生成额外的:skp_idx_[Column].idx 与 skp_idx_[Column].mrk 文件。
跳数索引的生成规则:按照特定规则每隔 granularity 个 index_granularity 条数据,就会生成一条跳数索引。比如 minmax 跳数索引,生成的是:granularity 个 index_granularity 条数据内的最大值最小值生成一条索引,如果将来需要针对构建二级索引的这个字段求最大值最小值,则可以帮助提高效率。
跳数索引一共支持四种类型:minmax(最大最小)、set(去重集合)、 ngrambf_v1(ngram 分词布隆索引)和 tokenbf_v1(标点符号分词布隆索引),一张数据表支持同时声明多个跳数索引。
实际案例
笔者在一次业务开发中,使用了 clickhouse
存储设备的时序数据,下面通过实际案例测试了一下 clickhouse
在此业务场景下的性能、压缩率。
集群配置
集群高可用
内核版本:22.8.5.1
计算节点类型:标准型
计算节点规格:32核128GB
计算节点数量:2
计算节点存储:增强型SSD云硬盘500GB
ZK节点规格:4核16GB
ZK节点存储:增强型SSD云硬盘100GB
ZK节点数量:3
测试数据写入
笔者自己写了一个伪造数据的脚本,以 100
万台设备为维度,总共写入了 20
亿条数据,平均每条数据将近 1k
。
1 | CREATE DATABASE IF NOT EXISTS event_message ON CLUSTER default_cluster; |
- 数据示例
1 | ed069b3366a6 :) select * from event_message.event limit 1\G |
- 批量异步写入
1 | [ops@VM-0-45-tencentos event]$ ./ck_event_tool -addr "10.32.32.10:9000" -file ./d3301000000044702.csv -num 20000 |
写入速度能达到 200M/s,这点已经满足业务数据写入的需求了。
基准测试
SQL 1
1 | SELECT * |
SQL 2
1 | SELECT count(*) |
SQL 3
1 | SELECT * |
SQL 4
1 | SELECT |
基准测试结果
1 | docker exec -it ck-server ClickHouse-benchmark -h 10.32.32.10 --user "" --password "" -c 50 --query "" |
1分片2节点、32核128GB、SSD云硬盘500GB
index_granularity = 2048
- 150并发
SQL | P50(秒) | P70(秒) | P80(秒) | P90(秒) | P95(秒) | P99(秒) | P99.9(秒) |
---|---|---|---|---|---|---|---|
SQL 1 | 0.146 | 0.164 | 0.176 | 0.192 | 0.207 | 0.238 | 0.280 |
SQL 2 | 0.079 | 0.092 | 0.100 | 0.113 | 0.124 | 0.147 | 0.177 |
SQL 3 | 0.209 | 0.303 | 0.357 | 0.421 | 0.496 | 0.661 | 0.700 |
SQL 4 | 0.135 | 0.155 | 0.168 | 0.186 | 0.204 | 0.241 | 0.280 |
- 200并发
SQL | P50(秒) | P70(秒) | P80(秒) | P90(秒) | P95(秒) | P99(秒) | P99.9(秒) |
---|---|---|---|---|---|---|---|
SQL 1 | 0.198 | 0.223 | 0.239 | 0.264 | 0.285 | 0.330 | 0.388 |
SQL 2 | 0.107 | 0.126 | 0.138 | 0.156 | 0.173 | 0.204 | 0.233 |
SQL 3 | 0.395 | 0.462 | 0.491 | 0.544 | 0.580 | 0.627 | 0.696 |
SQL 4 | 0.180 | 0.211 | 0.231 | 0.262 | 0.300 | 0.340 | 0.432 |
- 250并发
SQL | P50(秒) | P70(秒) | P80(秒) | P90(秒) | P95(秒) | P99(秒) | P99.9(秒) |
---|---|---|---|---|---|---|---|
SQL 1 | 0.249 | 0.281 | 0.302 | 0.334 | 0.361 | 0.416 | 0.479 |
SQL 2 | 0.136 | 0.161 | 0.179 | 0.203 | 0.224 | 0.271 | 0.330 |
SQL 3 | 0.543 | 0.669 | 0.727 | 0.822 | 0.855 | 0.909 | 1.080 |
SQL 4 | 0.238 | 0.259 | 0.295 | 0.353 | 0.370 | 0.444 | 0.572 |
附录
- SQL 1
10 rows in set. Elapsed: 0.044 sec. Processed 10.24 thousand rows, 7.28 MB (232.86 thousand rows/s., 165.60 MB/s.)- 150 并发
10.32.32.10:9000, queries: 2893, QPS: 855.413, RPS: 8759429.869, MiB/s: 5940.758, result RPS: 8554.131, result MiB/s: 7.089.
- 150 并发
- SQL 2
1 rows in set. Elapsed: 0.029 sec. Processed 10.24 thousand rows, 3.45 MB (357.13 thousand rows/s., 120.16 MB/s.)- 200 并发
10.32.32.10:9000, queries: 7049, QPS: 1639.300, RPS: 16786436.834, MiB/s: 5386.267, result RPS: 1639.300, result MiB/s: 0.013.
- 200 并发
- SQL 4
5 rows in set. Elapsed: 0.049 sec. Processed 26.63 thousand rows, 6.19 MB (538.26 thousand rows/s., 125.22 MB/s.)- 200 并发
10.32.32.10:9000, queries: 5020, QPS: 946.233, RPS: 25196304.941, MiB/s: 5589.924, result RPS: 4731.167, result MiB/s: 0.050.
- 200 并发
常见问题
- 集群高可用副本同步,需要使用 ReplicatedMergeTree 引擎,否则不会同步消息
- 并发查询限制,默认 100,并发越高查询效率越慢,相关参数:config.xml -> max_concurrent_queries
- 目前调整至 300
- 查询内存限制,DB::Exception: Memory limit 相关参数:users.xml -> max_memory_usage
- 目前调整至 25G
- 根据业务情况调整主键顺序以及主键索引颗粒度
- 主键顺序按字段基数从小到大排序有利于压缩率
- 主键索引顺序问题需要通过多主键索引进行调优,原理都是基于附加表进行数据查询