列存格式、编码与向量化执行引擎

共 23 题
📑 题目列表 23 题
#
★★★

1. 列存格式的核心,按列存储带来的压缩率与向量化执行优势?

列存格式的核心是什么?按列存储如何带来压缩率优势与向量化执行优势?

  • 行存 vs 列存的数据布局
  • 按列存储的压缩率提升机制
  • 向量化执行的适配

列存格式的核心是"按列连续存储数据":同一列的值在磁盘/内存中连续存放,而非行存那样按行组织。这带来两大优势:1)压缩率:同一列的数据类型相同、取值往往相似(低基数、重复值多),连续存储后能充分利用字典编码、RLE、delta 等高效压缩,压缩率远高于行存;2)向量化执行:分析查询往往只读取少数列(投影),列存只需读取所需列,减少 IO;且列数据连续、类型统一,可批量为多行执行同一操作(向量化),配合 CPU 缓存局部性提升吞吐。分析型查询(扫描、聚合、过滤)对列存极为友好:只读所需列 + 批量 SIMD 处理。行存则适合点查(按主键取整行)与事务更新。列存的核心价值在于"面向分析场景的 IO 与计算效率"。

列存的两个核心优势是"压缩率"(类型同质、重复多)与"向量化"(连续列、批量处理、缓存友好)。本质是把"面向行的访问"转为"面向列的批量计算",适配分析型负载。

#
★★★

2. Parquet/ORC 的文件布局(row group、column chunk、page、footer 元数据)与谓词下推的读取流程

解释 Parquet/ORC 的文件布局(row group、column chunk、page、footer 元数据)与谓词下推的读取流程?

  • Parquet/ORC 的分层结构(row group/column chunk/page)
  • footer 元数据与统计信息
  • 谓词下推与跳过读取

Parquet 与 ORC 是列式存储格式,文件分层次:Parquet 自下而上为 page(最小单元,含一列的若干行)→ column chunk(同一列在 row group 内的一组 page)→ row group(横向切分,包含所有列的一段行);文件末尾有 footer(元数据),记录各 column chunk 的统计信息(min/max、null count、字典)、偏移与 schema。ORC 类似:stripe(相当于 row group)→ 每列在 stripe 内有多个 row group 与 page 级别的 index(min/max、布隆过滤器)。读取流程与谓词下推:1)解析 footer 获取统计信息;2)对过滤列,用其 min/max 判断每个 row group/column chunk 是否满足谓词,不满足则整块跳过(跳过 IO);3)对满足的块,只读取所需的列(投影);4)读取时合并页内数据,结合 def/rep level 还原嵌套结构。这样谓词下推在"格式层"实现"跳过不相关数据块 + 只读所需列",大幅减少 IO 与解码量。

文件布局的核心是"分层 + 元数据统计"。谓词下推依赖 footer/stripe 的 min/max 统计做块级过滤,配合投影只读所需列,这是列式读取高效的关键。

#
★★

3. 列存的压缩(LZ4/Zstd)与向量化读取协同

列存的压缩(LZ4/Zstd)与向量化读取如何协同?

  • 压缩算法选择(LZ4 快 vs Zstd 压缩率高)
  • 解压与批处理
  • 压缩/解压比与执行效率

列存常把"编码压缩"与"向量化读取"结合:数据以压缩块/page 存储,读取时按块解压到内存,再作为连续列向量进行向量化处理。压缩算法选择权衡:LZ4 解压快(吞吐高、CPU 开销小),适合对延迟/吞吐敏感的场景,保证解压不是瓶颈;Zstd 压缩率高(可减存储与 IO),但解压略慢,适合 IO 为瓶颈、带宽受限的场景。协同方式:1)用 SIMD 优化的解压(LZ4/ZSTD 的 SIMD 实现)加速解压;2)解压后得到连续列向量,直接进行向量化运算,避免逐行处理;3)按需解压——只解压所需列或满足谓词的块,减少解压量。合理选择压缩级别与算法,使"解压成本 + 计算成本"最小化。列式压缩的"同质数据"让压缩率更高,解压后的连续内存又利于 SIMD,二者相互促进。

协同的核心是"解压是必要开销,但要快且只解必要部分"。LZ4 保吞吐、Zstd 保压缩率,配合 SIMD 解压与按需解压,让解压后的连续列向量高效参与向量化计算。

#
★★

4. 列存的稀疏索引(min/max/bloom)过滤

列存的稀疏索引(min/max、bloom)如何用于过滤?

  • min/max 索引的块级过滤
  • 布隆过滤器(bloom)的精确过滤
  • 稀疏索引与谓词下推

列存通过稀疏索引(每块/每页的统计信息)做粗粒度过滤,减少需要读取的数据。min/max 索引:每个数据块(row group/stripe/column chunk)记录该列的最小值与最大值,若查询谓词的目标值不在 [min, max] 区间内,则整块跳过,无需读取解码。布隆过滤器(bloom filter):对块内某列的值做哈希布隆,用于精确判断"目标值是否可能存在",尤其适合等值查询(如 id = 123),布隆为 false 则可确定不存在而跳过块。min/max 适合范围与等值,bloom 适合低基数的等值。稀疏索引是"元数据级别的过滤",与谓词下推结合,在读取前就排除不相关的块。代价是元数据本身占用一定空间,且 min/max 在没有排序时区间可能很宽导致过滤效果差。设计上,可对排序键列建立 min/max(效果最好),对高基数等值列用 bloom。

稀疏索引的本质是"用元数据换 IO"。min/max 做块级范围排除,bloom 做等值精确排除,二者协同提升谓词下推的过滤效率,是列式读取快的关键之一。

#
★★

5. 向量化执行按批(batch)处理列数据的原理

向量化执行按批(batch)处理列数据的原理是什么?

  • batch 的概念与列向量组织
  • 按批迭代与函数调用开销
  • 与逐行模型的对比

向量化执行的核心是"按批(batch)处理"。传统火山模型逐行(row-by-row)调用算子,每行产生一次函数调用与虚函数分派,CPU 开销大;向量化执行将一批行(如 1024 行)组织为列向量(ColumnVector),算子在每一批次上处理整列数据,把"每行一次"变成"每批一次"。原理:1)数据以"批"为单位在算子树间流动,批内列连续存储;2)算子对整列向量做循环/向量化运算(如过滤、比较、聚合),一次处理多行;3)减少函数调用次数、提升缓存局部性、便于 SIMD 并行。批处理让流水线每拍处理大量数据,吞吐显著提升,同时算子间传递的是"批"而非"单个 tuple",降低调度开销。实际实现中,向量化引擎用专门的批量算子(如 ColumnVectorScan、VectorizedFilter)替代逐行算子。

按批处理的本质是"把逐行循环改为批量向量处理",减少调用开销、提升缓存利用与 SIMD 潜力。这是向量化引擎吞吐提升的核心机制。

#
★★

6. 向量化执行消除虚函数与提升 ILP

向量化执行如何消除虚函数分派并提升指令级并行(ILP)?

  • 虚函数分派的开销
  • 消除虚函数的手段
  • 提升 ILP 的机制

传统火山模型每行调用算子的 next() 虚函数,产生虚函数分派(virtual dispatch)开销,且打断流水线、阻碍编译器优化。向量化执行通过"消除虚函数 + 提升 ILP"来加速:1)消除虚函数:把算子改为"在整批上执行的大函数",避免每行虚调用;用模板化/类型特化(如按列类型生成专门代码)替代运行时的动态分派,让编译器能内联与优化;2)提升 ILP(指令级并行):批量处理时,同一循环内对多行做独立运算,处理器可并行执行多条无依赖指令(多发射、流水线),隐藏延迟;向量化(SIMD)一次对多元素运算,进一步并行。此外,消除虚函数让编译器能进行循环优化、向量化、自动展开,提升整体吞吐。综合来看,向量化执行把"每行动态分派"变为"每批静态高效循环",从而显著提升每秒处理的元组数。

虚函数消除(类型特化/批量函数)与 ILP 提升(循环独立运算 + SIMD)是向量化加速的两个层面:前者减少动态开销,后者提升并行吞吐。

#
★★

7. 向量化执行利用 SIMD 指令加速比较/聚合

向量化执行如何利用 SIMD 指令加速比较与聚合?

  • SIMD 概念(单指令多数据)
  • 比较/过滤的 SIMD 化
  • 聚合的 SIMD 化

SIMD(Single Instruction Multiple Data)让一条指令同时对多个数据执行相同操作。向量化执行利用 SIMD 加速:1)比较/过滤:对列向量做条件比较(如 x > 10)时,用 SIMD 指令(如 AVX2 的 256 位寄存器一次比较 8 个 int32)一次比较多个元素,生成掩码(mask)位图,再据此做选择/过滤;2)聚合:对聚合列(如 sum、count、min/max)用 SIMD 做多元素的累加/归约,减少循环次数;3)哈希/布隆等也常用 SIMD 加速。前提是数据在连续内存中、类型统一(正好是列向量的特性)。实现上,编译器可自动向量化(auto-vectorization),也可用 intrinsics 手写 SIMD 循环。SIMD 加速比较/聚合能显著提升分析查询的吞吐,是向量化引擎的核心手段之一。

SIMD 的价值是"一次指令处理多个元素",前提是连续同质数据(列向量天然满足)。比较生成掩码、聚合做多元素归约,是 SIMD 高效加速的两大应用。

#
★★

8. ClickHouse 的向量化与 data skipping index

ClickHouse 的向量化执行与 data skipping index 是如何工作的?

  • ClickHouse 的列存与向量化
  • data skipping index(min/max、bloom、set)
  • 与分区/主键协同

ClickHouse 是列式数据库,核心特性是向量化执行与 data skipping index。向量化执行:ClickHouse 按列存储(MergeTree),查询时对整列数据用 SIMD 向量化处理,避免逐行解释,配合列压缩与按需读取,实现极高的分析吞吐。data skipping index(数据跳过索引):在一部分粒度(granule,如 8192 行)上建立索引(min/max、布隆过滤器、bloom_filter、set、ngram 等),查询时用索引判断该 granule 是否满足谓词,不满足则整块跳过,减少读取与解压。它按"采样粒度"(index_granularity)关联数据列,配合主键(稀疏索引,按主键列排序的 min/max)一起做粗粒度过滤。设计上,data skipping index 适合"过滤列不是主键、但查询常用"的列,通过 min/max 或 bloom 加速等值/范围过滤。它是 ClickHouse 在"列存 + 向量化"基础上进一步提升查询效率的关键。

向量化是 ClickHouse 的"计算快",data skipping index 是"读取少"。两者结合:向量化批量算,跳过索引避免读无关数据,共同支撑其分析性能。

#
★★

9. StarRocks 的向量化执行与 CBO

StarRocks 的向量化执行与 CBO(基于代价的优化器)如何工作?

  • StarRocks 的向量化引擎
  • CBO 的统计信息与代价模型
  • 优化器与执行引擎协同

StarRocks 是 MPP 分析数据库,采用向量化执行引擎与 CBO 优化器。向量化执行:StarRocks 原生向量化引擎按列批量处理数据,算子以批为单位用 SIMD 执行,减少虚函数与逐行开销,提升吞吐。CBO(Cost-Based Optimizer):基于统计信息(表行数、列基数、min/max、NDV、分布直方图)评估各执行计划的代价(IO 量、CPU、网络、内存),在多个候选计划中选择代价最低的执行计划,如选择 join 顺序、join 算法(hash join/broadcast/shuffle)、filter 下推、agg 两阶段优化等。协同:CBO 生成"查询计划"(含算子顺序、分布策略),向量化引擎按计划执行;CBO 的统计准确性影响计划质量,向量化引擎保证执行效率。实践上,StarRocks 通过 ANALYZE 收集统计,配合 CBO 选择最优 join 与聚合策略,避免笛卡尔积与低效扫描。

向量化解决"执行得有多快",CBO 解决"按什么路径执行"。两者合力:好的计划(CBO)+ 快的执行(向量化),是 StarRocks 高性能的两大支柱。

#
★★

10. 向量化的 null 处理与三值逻辑

向量化执行如何处理 null 与三值逻辑(三值逻辑)?

  • null 的表示(validity bitmap)
  • 三值逻辑(TRUE/FALSE/NULL)
  • 向量化 null 处理的优化

向量化执行中,null 通过"validity bitmap"(有效性位图)表示:每个列向量配一个位图,标记每行是否为 null,数据本身仍连续存储。处理 null 时,三值逻辑(TRUE/FALSE/NULL)是 SQL 语义的关键:比较运算遇 null 结果为未知(NULL),AND/OR/NOT 遵循三值真值表。向量化实现三值逻辑:1)对某列是否为 null,先生成 null 掩码;2)对"非 null"的行做条件运算,得到候选结果;3)按三值逻辑表合并 null 掩码与真值,得到结果位图(含 null 位)。优化:对"无 null 列"可使用无 null 快速路径(跳过 null 检查);对三值逻辑运算,用位运算(掩码与/或/非)一次计算多行,避免逐行 if。例如 WHERE col > 5 中,col 为 null 的行应判为 false(不满足),结果位图 = (col>5 且 非null)。处理 null 时,向量化用位图批量运算,而非逐行分支判断,兼顾语义正确与性能。

null 的三值逻辑是向量化必须处理的语义。用 validity bitmap + 位运算实现三值逻辑,并对无 null 列走快速路径,是兼顾正确性与性能的关键。

#
★★

11. 向量化执行的内存布局(列向量的连续内存)

向量化执行的内存布局(列向量的连续内存)有何特点与优势?

  • 列向量连续内存布局
  • 缓存局部性与 SIMD
  • 内存管理与可扩展性

向量化执行中,列向量以"连续内存"布局:一个列的一批数据在内存中连续存放(如一个 int32_t[] 数组),配上 null 位图。连续内存的优势:1)缓存局部性:连续访问符合 CPU 缓存行预取,扫描时命中率高;2)SIMD 友好:SIMD 需要连续对齐的数据,连续列向量可直接加载到寄存器批量运算;3)零散开销低:避免每行独立分配的指针间接,减少内存碎片与指针跳转;4)便于批量预取与内存带宽利用。对可变长类型(字符串),用"偏移数组 + 数据缓冲"(如 dictionary 或 offset+data),仍保持批量访问。内存布局还影响:内存对齐(SIMD 需要 16/32/64 字节对齐)、缓冲池复用(列向量可复用减少分配)、以及大列的分块处理。连续内存布局是向量化高性能的基础,同时需注意内存占用与 spill 策略。

连续内存布局是"向量化 + SIMD + 缓存友好"的物理基础。它让数据可批量加载、可预取、可对齐,是列向量处理高效的根本原因。

#
★★

12. 列存的编码,字典、RLE、Delta、Bitpacking

列存的编码有哪些(字典、RLE、Delta、Bitpacking)?各自适用场景?

  • 字典编码
  • RLE 编码
  • Delta 编码与 Bitpacking

列存用多种编码压缩数据,各适用场景:1)字典编码(Dictionary):把列中不同的值映射为字典 ID,数据列存 ID,适用于低基数(重复值多)的列,如枚举、状态、分类字段;2)RLE(Run-Length Encoding,游程编码):对连续重复的值,记录"值 + 重复次数",适用于大量连续重复的列(如排序后的列、分区键);3)Delta 编码:对每个值存与前一个值的差值,适用于有序/递增的列(如自增 ID、时间戳),差值小则压缩率高;4)Bitpacking(位打包):将整数按所需最小位数紧凑存储(如 0-255 用 8 位,实际值范围小则用更少位),适用于取值集中、低位宽的整数列。各编码可组合:如先字典再 RLE,或 delta 后 bitpacking。选择编码取决于数据特征(基数、分布、是否有序),目标是最大化压缩率且便于高效解码(SIMD 友好)。

编码的本质是"利用数据特征减少存储位"。字典、RLE、Delta、Bitpacking 分别针对低基数、连续重复、有序、低位宽四种特征,并可组合使用。

#
★★

13. 列存的谓词下推与跳块(skip index)

列存的谓词下推与跳块(skip index)如何工作?

  • 谓词下推的执行下推
  • skip index(块级元数据过滤)
  • 与存储格式协同

谓词下推(predicate pushdown)指把过滤条件下推到更底层的执行阶段——在列存中,下推到存储层,用元数据(min/max、bloom)在读取前过滤掉不满足条件的数据块,即"跳块"(skip block)。工作流程:1)查询的 WHERE 条件(如 age > 30)被下推到 scan 算子;2)scan 算子读取列数据块(column chunk/page)的元数据(min/max、bloom、null count);3)用谓词判断每个块是否可能满足:min/max 在区间外则跳过(范围过滤),bloom 判不存在则跳过(等值过滤);4)只对可能满足的块做读取与解码,再在块内做精确过滤。跳块减少"读取 + 解压 + 解码"的 IO 与 CPU 开销,是列式分析的关键。谓词下推还包括:把谓词下推到 join 之前、聚合之前,减少中间数据量。skip index 常在格式层(Parquet/ORC 页统计)或引擎层(如 ClickHouse data skipping index)实现。

谓词下推 + 跳块的本质是"用元数据先过滤、再读数据"。把谓词下推到存储层,用 min/max、bloom 排除无关块,是列式读取高效的核心手段。

#
★★

14. 列存的写入放大与合并(compaction)

列存的写入放大与合并(compaction)问题如何解决?

  • 列存的写路径(append-only / LSM)
  • 写入放大与随机更新
  • compaction 合并

列存本身不适合频繁随机更新(随机更新需重写整个列块),因此列存数据库常采用"append-only + LSM 风格"的写路径:新写入以不可变的小文件(sstable/小段)追加,不原地修改旧数据。但这样会产生大量小文件,读取时需合并多个文件,且存在"写入放大"(一次更新触发多次重写/合并)。解决手段是 compaction(合并):定期把多个小文件合并为更大的文件,减少文件数量、提升读取效率、清理已删除/过期数据。compaction 策略:1)size-tiered(按大小分层合并)适合写入密集、读取少的场景;2)leveled(分层合并,如 LSM)控制每层文件数与重叠,降低读放大。对列存,compaction 还会重新编码与压缩(新文件用更优编码),提升压缩率。写入放大的根源是"不改写原地、靠合并维护有序";通过分层 + 合适的 compaction 策略,在"写放大"与"读放大"间权衡。列存常配合"小文件合并 + 分区"来缓解。

列存写路径的本质是"append-only + 合并治理"。随机更新被转为追加 + 后台 compaction,用分层策略平衡写放大与读放大,是新写的高效读。

#
★★

15. Parquet/ORC 的编码,字典、RLE、Delta 编码与谓词下推?

Parquet/ORC 的编码(字典、RLE、Delta)与谓词下推如何配合?

  • Parquet/ORC 的编码方式
  • 编码与谓词下推(页统计)的配合
  • 编码对查询性能的影响

Parquet/ORC 采用多种编码:字典编码(Dictionary,低基数)、RLE(游程,连续重复)、Delta(有序/差值)、bit-packing(位打包)、以及 plain(未编码)。这些编码与谓词下推配合的原理:1)编码后仍保留页/块级统计(min/max、字典范围、bloom),谓词下推用这些统计判断块是否可跳过;2)字典编码下,谓词下推可在"字典 ID"层做过滤(对字典值比较,快速定位),且字典编码的块可用字典的 min/max 做块级过滤;3)RLE 块便于快速解码与范围判断;4)Delta 编码的块解压后仍是连续值,便于 SIMD 过滤。配合的关键:编码不影响"统计信息的存在"(min/max 仍记录),因此谓词下推仍能跳过块;且对编码后的数据,解码是批量、SIMD 友好的,过滤在解码后进行。编码主要压缩存储,谓词下推主要减少读取,二者叠加使"读取少 + 解码快 + 过滤早"。

编码与谓词下推是"存储层"与"读取层"的配合:编码减存储、保留统计支持跳块,谓词下推减读取。字典编码还能在字典层做过滤,进一步加速。

#
★★

16. 向量化执行引擎,SIMD 批量处理与火山模型逐行的性能差异?

向量化执行引擎(SIMD 批量处理)与火山模型(逐行)的性能差异有多大、原因是什么?

  • 火山模型逐行 vs 向量化批量
  • 性能差异的量级
  • 差异的根源

性能差异通常可达一个数量级(10x 甚至更高),根源在于:火山模型逐行处理,每行调用算子 next() 虚函数,产生虚函数分派、分支、函数调用开销,且数据逐行访问缓存局部性差、无法 SIMD;向量化引擎按批(如 1024 行)组织列向量,一次处理多行,消除虚函数分派、提升缓存命中、利用 SIMD 并行。差异体现在:1)CPU 指令效率(虚调用 vs 批量循环);2)缓存利用率(逐行随机访问 vs 列连续访问);3)ILP/SIMD(逐行单数据 vs 批量多数据)。对分析型查询(大扫描、聚合、过滤),向量化的吞吐可提升数倍到数十倍。但向量化在"小查询、点查、复杂表达式、低选择性"场景优势不那么明显,且实现复杂(需类型特化、null 处理、专业内存管理)。总体而言,向量化执行是现代分析引擎(ClickHouse、DuckDB、StarRocks、Velox)的核心,性能差异是数量级的。

差异本质是"数据处理方式":逐行动态分派 vs 批量静态向量处理。虚函数消除、缓存局部性、SIMD 三项叠加,使向量化在扫描/聚合分析场景获得数量级提速。

#
★★

17. 向量化聚合与哈希 Join 的实现(radix 分区、SIMD 哈希表)

向量化聚合与哈希 Join 如何实现(radix 分区、SIMD 哈希表)?

  • 向量化聚合的批量处理
  • 哈希 Join 的构建/探测
  • radix 分区与 SIMD 哈希表

向量化聚合:对聚合列批量处理,如 sum 用 SIMD 累加、count 用位图统计、min/max 用 SIMD 归约;分组聚合(group by)用哈希表按组聚合,对哈希表做批量插入与探测。哈希 Join:构建阶段(build)把内表按 join key 建立哈希表,探测阶段(probe)用外表逐批匹配。radix 分区:按 join key 的哈希值的若干位(radix)把数据分区,使每个分区可放入 CPU 缓存,提升缓存命中率(cache-friendly),避免随机访问导致 cache miss;radix 分区还便于并行(分区间独立)。SIMD 哈希表:用 SIMD 并行探测哈希表的多个槽位(如一次比较多个 key、用 SIMD 掩码加速碰撞链遍历),或用 SIMD 做哈希计算(如对 key 向量批量哈希)。向量化聚合/join 的关键是把"哈希 + 比较 + 累加"批量化为 SIMD 操作,并用 radix 分区 + 缓存优化控制随机访问。这些是高性能分析引擎(如 DuckDB、Velox、StarRocks)的常用技术。

向量化聚合/join 的核心是"批量哈希 + 缓存友好"。radix 分区提升缓存命中,SIMD 哈希表加速探测与插入,二者结合让大表 join 与聚合在海量数据时仍高效。

#

18. 列存相比行存在分析查询的 IO 优势

列存相比行存在分析查询上有什么 IO 优势?

  • 投影只读所需列
  • 压缩减少 IO
  • 分析查询的扫描模式

分析查询往往只读取表中少数列(如 SELECT region, sum(amount) GROUP BY region),列存能"只读所需列",避免读取整行,从而大幅减少 IO 量。行存需整行读取(即使只要一列),导致 IO 浪费。列存的 IO 优势还包括:1)压缩率高(同列同质),读入的数据量更小;2)列数据连续,顺序扫描高效,配合预取与缓存更优;3)谓词下推/跳块利用列统计跳过无关块,进一步减少 IO;4)只对需要的列做解码。对于"大表全扫描 + 少量列 + 聚合"的分析负载,列存能把 IO 从"全表字节"降到"所需列字节",往往减少一个数量级。这是分析查询在列存上快的重要因素。行存则更适合点查(按主键取整行)与事务型负载。

列存的 IO 优势核心是"投影裁剪 + 高压缩 + 跳块"。分析查询读少量列,列存只读这些列,IO 量大幅下降,这是其优于行存分析的关键。

#

19. 列存在 HTAP 中的行存列存同步

列存在 HTAP(混合事务/分析处理)中如何与行存同步?

  • HTAP 架构(行存 + 列存)
  • 同步机制(实时/准实时)
  • 一致性

HTAP 数据库同时处理事务(OLTP,行存)与分析(OLAP,列存),需要"行存与列存"两种存储并用并同步。同步机制:1)列存作为行存的"衍生副本",通过复制/订阅(如 binlog、redo log、WAL)实时地把行存的新增/修改同步到列存;2)列存侧用 LSM/append 方式维护,批量写入实现高效;3)同步通常"准实时"(秒级延迟),保证分析读到较新数据,同时容忍一定延迟。一致性:同步机制需保证行存与列存最终一致(或可配置强一致),列存副本不阻塞主事务。常见架构:TiDB 的 TiFlash(基于 Raft 的列存副本,通过 Raft 日志同步)、MySQL 生态的列存分析节点(通过 binlog 同步到列存)、以及 Pinot/Doris 等(通过导入/订阅)。HTAP 的列存同步要权衡"实时性"(延迟)与"一致性"(是否强一致),并处理 schema 变更、物化、与行存的数据对齐。

HTAP 列存同步的本质是"用复制机制把行存变更转到列存副本"。准实时同步 + 最终一致,让分析查询在列存上高效执行,同时避免阻塞事务。

#

20. 列存与行存的混合(PAX)布局

什么是列存与行存的混合(PAX)布局?

  • PAX 的概念(混合存储)
  • 页内列存 vs 行存
  • 适用场景

PAX(Partition Attributes Across,混合存储布局)是一种"在传统行存磁盘页内部做列存"的混合布局:数据仍按行组织分页(页/块),但页内按列存储(把一页内多行的同一列连续存放),从而兼具行存与列存的优点。与纯行存相比,PAX 在扫描时只读所需列(页内按列读取),减少 IO;与纯列存相比,PAX 保留了行定位与事务友好性(一个页对应若干行,便于点查与更新)。PAX 的变体还支持"混合列":同一表不同列既可按列存也可按行存,让查询优化器按需选择。PAX 常用于"宽表 + 部分列频繁分析"的场景,或作为 HTAP 的折中方案。它的优势是"页内列存 + 页级行定位",避免纯列存对更新/点查的劣势,同时获得列式扫描的 IO 优势。典型实现如 SQL Server 的 columnstore 的某些结构、以及一些"混合列存"数据库。

PAX 的本质是"在页/块粒度上做列存,在行粒度上保留定位"。它折中行存与列存,兼顾分析扫描与事务更新,适合宽表混合负载。

#

21. 向量化对比逐行解释执行的性能量级

向量化执行对比逐行解释执行的性能量级是多少?

  • 性能差异的量级
  • 差异来源
  • 适用场景

向量化执行对比逐行解释执行,性能通常相差一个数量级(约 10x 甚至更高)。原因:逐行解释执行每行都要解释算子、调用 next()、经历虚函数分派与分支,且数据逐行访问缓存差;向量化执行按批处理列向量,消除虚函数分派、利用缓存局部性与 SIMD 并行,减少了大量 CPU 指令开销。在一些纯计算/扫描密集场景,向量化可比解释执行快数十倍。但要注意:这个量级取决于查询类型与实现——对"点查、小数据、复杂嵌套表达式、低选择性"场景,向量化优势缩小;对"大扫描、聚合、过滤"场景,优势显著。现代分析引擎(ClickHouse、DuckDB、Velox、StarRocks)普遍采用向量化(或 JIT 编译)替代解释执行,以获得数量级性能提升。JIT 编译(把查询编译成机器码)也能达到类似或更高性能,但编译有启动开销。

性能量级约 10x 甚至更高,源于"解释开销消除 + 缓存/SIMD 利用"。理解这一点,就能明白向量化为何是分析引擎标配。

#

22. 列存下的写路径,如何解决随机更新与小写入的性能问题?

列存下如何解决随机更新与小写入的性能问题?

  • 随机更新的挑战
  • append-only / LSM / 缓冲区
  • 合并与读写分离

列存按列连续存储,随机更新(改单行/少数行)会破坏列块连续性,需重写整个列块,代价高。解决随机更新与小写入性能的方法:1)append-only + LSM:把更新转为"追加新版本 + 删除标记",不原地改列块,写入作为小文件追加,成本低;2)写缓冲(write buffer / memtable):小写入先缓存在内存(或行存小缓冲),定期批量刷入列存,把"小写"合并为"大批量写",减少列块重写;3)合并(compaction):后台把追加的小文件合并为更大的列文件,重写并清理冗余,提升读性能;4)读写分离:事务写入走行存/日志,分析读取走列存,通过同步把变更转成列存(HTAP 思路);5)主键/分区:按主键或分区组织,使随机更新落在局部,减少重写范围。列存写路径的本质是"用追加与合并代替原地更新",牺牲一点读实时性换取写性能。

列存写路径的核心是"避免原地改列块"。用写缓冲 + append + 后台 compaction 把随机小写转化为批量顺序写,配合读写分离,解决随机更新性能问题。

#

23. 嵌套类型(Struct/Array/Map)在列存中的存储与 flatten 优化

嵌套类型(Struct/Array/Map)在列存中如何存储?flatten 优化是什么?

  • 嵌套类型的列存表示(def/rep level)
  • flatten 优化
  • 与平面化的对比

列存中的嵌套类型(Struct/Array/Map)用 Dremel 风格的"重复/定义级别"(repetition & definition level)表示:为每个嵌套层级记录 def level(当前值到哪一层有效)与 rep level(数组元素的重复位置),把嵌套结构扁平化为"列 + 级别"存储,使嵌套数据也能按列高效压缩与读取。读取时根据 def/rep level 还原嵌套结构。flatten 优化:把嵌套类型"展平"为多个平面列(如 Struct 的每个字段拆成独立列、Array 的元素列 + 偏移列),从而:1)让查询算子处理平面列(更简单、更快、可向量化);2)只有访问的字段被读取(投影裁剪);3)offset 数组记录数组边界,避免逐元素遍历。flatten 的核心是"把复杂嵌套结构解耦为平面列 + 偏移/级别",既保留语义又提升列式处理效率。这是 Parquet/ORC 与 Arrow 中处理嵌套的关键技术。

嵌套类型列存的关键是"用 def/rep level 或 offset 扁平化嵌套"。flatten 把嵌套拆成平面列 + 偏移,使向量化算子能高效处理,同时保留嵌套语义。