【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)"的设计哲学。

相关推荐
平凡但不平庸的码农22 分钟前
Go 语言基础语法
开发语言·后端·golang
是宇写的啊25 分钟前
SpringBoot 统一功能处理
java·spring boot·后端
等....26 分钟前
Spring Boot多模块项目部署
java·spring boot·后端
20岁30年经验的码农31 分钟前
Spring Boot 配置文件生效规则
spring boot·后端·pycharm
霸道流氓气质1 小时前
SpringBoot+LangChain4j+Ollama+MCP实现智能天气工具调用示例
java·spring boot·后端
ErizJ1 小时前
Go|腾讯面经总结
开发语言·后端·golang
geovindu1 小时前
go: Registry Pattern
开发语言·后端·设计模式·golang·注册模式
Le_ee1 小时前
ctfweb:flask+ssti
后端·python·flask
木易 士心1 小时前
一文彻底搞懂 Elasticsearch:原理、场景、避坑与优化
大数据·后端·elasticsearch·搜索引擎
IT 行者2 小时前
Spring Boot 4.1.0-RC1 发布:核心新特性解析
java·spring boot·后端