【AI】 Clickhouse MergeTree基本原理

【AI】 Clickhouse MergeTree基本原理

ClickHouse 的 MergeTree 引擎系列是其最核心、最强大的存储引擎,为 ClickHouse 提供了卓越的列式存储性能、高效的数据插入和近乎实时的查询能力。理解其原理对于高效使用 ClickHouse 至关重要。

核心原理围绕着 ​​分片(Partition)、排序(Ordering)、稀疏索引(Sparse Indexing)、标记文件(Mark Files)和异步合并(Asynchronous Merges)​​ 这几个关键概念:

  1. ​分区(Partitioning)​​:

    • ​目的​:加速查询,通过分区裁剪(Partition Pruning)减少数据扫描范围。
    • ​机制​ :数据表在磁盘上的物理存储按分区键(PARTITION BY表达式)被分割成不同的​子目录​ 。例如,按日期 PARTITION BY toYYYYMM(date)
    • ​物理结构​ :每个分区对应磁盘上的一个独立目录(如 202308, 202309)。查询时,WHERE 条件如 date >= '2023-09-01' AND date < '2023-10-01',可以快速定位到 202309分区目录,忽略其他分区数据。
    • ​注意​ :分区粒度需谨慎。分区​过多​ (如按小时分区)会导致小文件过多,增加元数据负担,降低合并效率;分区​过少​(如单分区)则失去分区裁剪的优势。
  2. ​数据排序(Ordering)​​:

    • ​目的​:高效定位数据范围,支持快速范围查询、ORDER BY、GROUP BY 以及数据压缩(相似数据邻近存储压缩率更高)。
    • ​机制​ :在每个分区的内部,数据行​严格地​ 按照​排序键(ORDER BY 键或 PRIMARY KEY)​定义的顺序存储在磁盘上。
    • ​物理结构​ :每个列字段(.bin文件)内部的数据都是按照排序键有序排列的。
    • ​影响​:排序键的设计是查询性能的关键。将最常用于过滤(WHERE)、分组(GROUP BY)和排序(ORDER BY)的列放在前面。
  3. ​稀疏主键索引(Sparse Primary Index)​​:

    • ​目的​ ​:在大数据集中快速定位数据块位置,避免全扫描,同时​​极小化索引存储开销​​。

    • ​机制​​:

      • ​索引粒度(index_granularity)​ ​:默认 8192 行。索引并不指向每一行,而是指向一个​​数据块​ ​的起始位置(一个数据块包含 index_granularity行)。

      • ​索引文件(.idx)​ ​:存储每个索引粒度对应的数据块中,​​排序键第一个字段的最小值​ ​(或前几个字段的布隆过滤器等)。想象一下电话簿的索引页只列出每 N页的第一个名字。

      • ​工作流程​​:

        1. 查询条件如 WHERE user_id = 123
        2. 在稀疏索引文件 (.idx) 中查找 user_id = 123可能位于哪些​数据块​(查找满足条件的最小和最大边界数据块)。
        3. 读取这些候选数据块对应的 .bin文件偏移信息(来自 .mrk文件)。
        4. 从磁盘只读取​这些特定的数据块​ (通常包含多个 index_granularity行)到内存。
        5. 在内存中对这些加载进来的完整数据块进行精确扫描,找到 user_id = 123的实际行。
    • ​优势​ ​:索引文件非常小(只有行数/index_granularity个条目),可以常驻内存。即使表有 PB 级数据,索引通常也只有几 MB 到几百 MB。

    • ​局限性​ ​:索引只包含第一个字段的最小值。对于复合键 (a, b, c),索引只记录每个数据块中 a的最小值。高效查找 bc需要a的等值查询或使用辅助索引(如跳数索引)。点查询 WHERE user_id = 123效率极高,因为只需加载少数数据块;但如果查询条件是低基数字段(如 status = 1,满足条件的行分布在大量数据块中),效率会下降。

  4. ​标记文件(Mark Files, .mrk)​​:

    • ​目的​​:连接稀疏索引(.idx)和真实列数据文件(.bin),提供从逻辑索引位置到物理磁盘偏移量的精确映射。

    • ​机制​​:

      • 对于每个列的 .bin文件,都有一个对应的 .mrk文件。

      • .mrk文件中的每个"标记"对应索引粒度中的一个条目(.idx文件中的一个索引条目)。

      • 每个"标记"包含两部分信息:

        1. ​块在 .bin文件中的偏移量​ :用于定位到包含目标行的​压缩数据块​的开头。
        2. ​解压后块内的行偏移量​:压缩块会被完整读入内存解压。该偏移量指明目标行在这个解压块内的具体起始位置。
    • ​工作流程​ ​:索引查找定位到候选的索引条目后,通过索引条目在 .mrk文件中的位置,找到对应的标记,读出 .bin文件偏移量和块内行偏移量,然后精准读取所需的数据部分。

  5. ​异步数据合并(Asynchronous Merges)​​:

    • ​问题来源​ ​:数据是分批次(称为 ​​"部分(part)"​ ​ 或 ​​"片段(fragment)"​​ )插入的(每次 INSERT 生成一个新的 part)。物理上,每个 part 是位于磁盘上的一个独立的目录,包含其分区内的数据块、索引、标记等。每个 part 内部按排序键有序,但不同的 part 之间可能存在重叠(同一个用户数据分散在多个 part 里)。

    • ​目的​​:

      • 减少文件数量,提高查询效率(避免打开太多文件)。
      • 将小的部分聚合成大的、更连续有序的部分,优化顺序读取性能。
      • 强制执行数据排序(合并后分区内部完全有序)。
      • 对于 Aggregating/Summing/Collapsing 等变体引擎,执行聚合或折叠操作。
      • 物理删除被标记删除的数据(通过 ALTER ... DELETE)或过期数据(通过 TTL)。
    • ​机制​​:

      • 后台线程池(background_pool_size)负责自动检测和调度合并任务。
      • 合并规则:系统会选择多个​相邻且位于同一分区内​的、较小的部分(基于大小、创建时间等)。
      • 合并过程:读取选定部分的已排序数据,​重新归并排序​ (对于主引擎),或应用聚合/折叠逻辑(对于变体引擎),写入一个​新的、更大的有序部分目录​
      • 提交新部分:新部分写入完成并通过校验后,被"提交"到活动数据集。
      • 清理旧部分:原始的被合并的旧部分目录会被标记为"非活动"状态(通过文件系统重命名),稍后被异步安全删除。
    • ​特点​​:

      • ​完全异步​:INSERT 操作本身非常快,因为只需写入一个新的小部分。合并操作在后台不阻塞查询和插入。
      • ​无事务性保证​:SELECT 在旧部分被删除前,仍然能看到旧数据(直到合并完成提交)。合并过程通常很快(秒到分钟级别)。
      • ​可调优​:合并策略(大小、时间触发)、线程数等可以配置。
      • ​"最终有序和聚合"​:对于主引擎,数据在分区内达到最终有序是在合并完成后;对于变体引擎,聚合或折叠逻辑也是在合并时最终生效。

​数据写入流程简化:​

  1. 用户执行 INSERT INTO table ...

  2. ClickHouse 接收数据行。

  3. ​按分区键计算分区​​:决定数据属于哪个分区目录。

  4. ​按排序键对数据排序​​:在内存中对这批数据进行排序。

  5. ​生成新部分​​:

    • 在对应分区目录下创建一个新的子目录(例如 all_1_1_0)。
    • 将排序后的数据按列写入 .bin 文件(可选压缩)。
    • 生成基于该部分数据的​稀疏主键索引 (.idx)​
    • 生成连接索引和数据的​标记文件 (.mrk)​
    • 写入其他元数据(如校验和)。
  6. 通知系统该新部分已就绪,可被查询。

  7. 后台合并线程会在适当时机检测并合并这些小的部分。

​变种引擎的原理增强:​

  • ​ReplacingMergeTree​:在合并时,根据排序键去重(只保留最后插入的或指定版本号的)。
  • ​SummingMergeTree/AggregatingMergeTree​:在合并时,对指定的数值列进行预聚合(SUM/COUNT 等),或应用聚合函数(如 uniqState, sumState)。
  • ​CollapsingMergeTree/VersionedCollapsingMergeTree​ :在合并时,根据特定标记列(signversion+ sign)折叠/删除成对出现的行。
  • ​GraphiteMergeTree​:专为存储 Graphite 监控数据设计,在合并时做高级聚合(降采样)。

​关键优势总结:​

  1. ​高效写入​:小部分插入非常快(内存排序写文件),不阻塞。
  2. ​高效查询​:分区裁剪、稀疏索引数据块定位、顺序IO读取、列式压缩、向量化执行。
  3. ​存储优化​:列式压缩、后台合并减少碎片、清理过期/删除数据。
  4. ​灵活可扩展​:通过不同的变种引擎支持不同场景(去重、聚合、折叠等)。

​注意事项:​

  • 最终一致性:数据插入立即可见,但聚合、去重、折叠的效果需要等待合并后才能最终体现。
  • 删除是异步的:通过 ALTER ... DELETE 标记数据,实际物理删除发生在后续合并。
  • TTL 删除也是异步的,同样依赖合并。
  • 需要合理设计​分区键​​排序键​ (主键索引)和​索引粒度​

理解 MergeTree 的这些核心原理(分区、排序、稀疏索引、标记、异步合并)对于设计高性能的 ClickHouse 表、诊断性能问题以及合理配置系统至关重要。它完美体现了 ClickHouse "空间换时间"和"利用硬件特性(顺序IO)"的设计哲学。

相关推荐
少女孤岛鹿8 分钟前
微服务注册中心详解:Eureka vs Nacos,原理与实践 | 一站式掌握服务注册、发现与负载均衡
后端
CodeSaku19 分钟前
是设计模式,我们有救了!!!(四、原型模式)
后端
Ray6627 分钟前
「阅读笔记」零拷贝
后端
二闹31 分钟前
什么?你的 SQL 索引可能白加了!?
后端·mysql·性能优化
lichenyang45332 分钟前
基于Express+Ejs实现带登录认证的多模块增删改查后台管理系统
后端
精品源码屋1 小时前
基于JAVA17的仿向日葵远程控制软件源码+最新自研完整版
后端
叉烧钵钵鸡1 小时前
Java ++i 与 i++ 底层原理
java·开发语言·后端
JuiceFS1 小时前
JuiceFS on Windows: 首个 Beta 版的探索与优化之路
后端·云原生·云计算
JavaGuide1 小时前
美团OC了,给的挺多!很满意!!
后端·面试
excel1 小时前
Nuxt 3 + PWA 通知完整实现指南(Web Push)
前端·后端