跳到主要内容
版本: 最新版本-3.5

表聚簇

一个经过深思熟虑的排序键是 StarRocks 中最高效的物理设计技巧。 本指南解释了排序键的底层工作原理、它解锁的系统性优势,以及为您的工作负载选择有效键的具体方法。

示例

假设您运行一个遥测系统,该系统每天接收数十亿行数据,每行数据都标有 device_idts(时间戳)。 在事实表上定义 ORDER BY (device_id, ts) 确保

  • device_id 的点查询以毫秒为单位返回。
  • 仪表板过滤每个设备的最近时间窗口,从而剪除大部分数据。
  • 诸如 GROUP BY device_id 之类的聚合受益于流式聚合。
  • 由于每个设备附近的多个时间戳运行,压缩得到改善。

这个简单的两列排序键 ORDER BY (device_id, ts) 可减少 I/O、节省 CPU 并在数十亿行数据中实现更稳定的查询性能。

CREATE TABLE telemetry (
device_id VARCHAR,
ts DATETIME,
value DOUBLE
)
ENGINE=OLAP
PRIMARY KEY(device_id, ts)
PARTITION BY RANGE (date_trunc('day', ts))
DISTRIBUTED BY HASH(device_id) BUCKETS 16
ORDER BY (device_id, ts);

深入了解优势

  1. 大量 I/O 消除 - 段和页面剪枝

    工作原理

    每个段和 64 KB 页面存储所有列的最小值/最大值。 如果谓词超出该范围,StarRocks 会跳过整个块,而不会触及磁盘。

    示例

    SELECT count(*)
    FROM events
    WHERE tenant_id = 42
    AND ts BETWEEN '2025-05-01' AND '2025-05-07';

    使用 ORDER BY (tenant_id, ts) 只考虑第一个键等于 42 的段,并且在这些段中只考虑 ts 窗口与这 7 天重叠的页面。 一个 1000 亿行的表可能扫描不到 10 亿行,从而将分钟变成秒。


  1. 毫秒级点 查找 - 稀疏前缀索引

    工作原理

    稀疏前缀索引存储每个约 1K 个排序键值。 二进制搜索落在正确的页面上,然后单个磁盘读取(通常已缓存)返回该行。

    示例

    SELECT *
    FROM orders
    WHERE order_id = 982347234;

    使用 ORDER BY (order_id),探测需要在 500 亿行的表中进行大约 50 个键比较 - 即使在冷数据缓存上,延迟也低于 10 毫秒。


  1. 更快的排序聚合

    工作原理

    当排序键与 GROUP BY 子句对齐时,StarRocks 在扫描时执行流式聚合 - 无需排序或哈希表。

    此排序聚合计划按排序键顺序扫描行并动态发出组,从而利用 CPU 缓存局部性并跳过中间物化。

    示例

    SELECT device_id, COUNT(*)
    FROM telemetry
    WHERE ts BETWEEN '2025-01-01' AND '2025-01-31'
    GROUP BY device_id;

    如果表是 ORDER BY (device_id, ts),则引擎会在行流入时对行进行分组 - 无需构建哈希表或重新排序。 对于像 device_id 这样高基数的键,这可以显著减少 CPU 和内存使用。

    对于大组基数,具有排序输入的流式聚合通常将吞吐量提高 2-3 倍,超过哈希聚合。


  1. 更高的 压缩 &  更热的 缓存

    工作原理

    排序的数据显示小增量或长运行,从而加速字典、RLE 和参考帧编码。 紧凑的页面按顺序流经 CPU 缓存。

    示例

    按 (device_id, ts) 排序的遥测表比未排序的相同数据实现了 1.8 倍更好的压缩 (LZ4) 和 25% 更低的 CPU/扫描。


  1. 对于主要键表,更快的合并写入

    工作原理

    在 upsert 期间,引擎仅重写其键范围与批次重叠的切片,而不是整个平板。

    示例

    UPDATE balances
    SET amount = amount + 100
    WHERE account_id = 123;

    使用 ORDER BY (account_id),与未排序时相比,只有不到 1% 的段数据被触及 - 在更新密集型工作负载上产生 2-4 倍更高的写入吞吐量。


排序键如何工作

排序键的影响从写入行的那一刻开始,并持续到每次读取时优化。 本节将介绍该生命周期 - 写入路径 ➜ 存储层次结构 ➜ 段内部结构 ➜ 读取路径 - 以显示每一层如何复合排序值的。

  1. 写入路径
    1. 提取:行落在 MemTable 中,按声明的排序键排序,然后刷新为包含一个或多个有序段的新 Rowset。
    2. 压缩:后台累积/基本作业将许多小 Rowset 合并为更大的 Rowset,回收删除并降低段计数而不重新排序,因为每个源 Rowset 已经共享相同的顺序。
    3. 复制:每个平板(拥有 Rowset 的分片)都会同步复制到对等后端节点,从而保证排序顺序在副本之间保持一致。

write path steps

  1. 存储层次结构
对象它是什么为什么它对排序键很重要
分区表的粗粒度逻辑切片(例如,日期或 tenant_id)。启用计划时分区剪枝并隔离生命周期操作(TTL、批量加载)。
平板分区内的哈希/随机桶,在后端节点之间独立复制。行按排序键物理排序的单元;所有分区内剪枝都从这里开始。
MemTable内存写入缓冲区(约 96 MB),在刷新到磁盘之前按声明的键排序。保证每个磁盘段都已经排序 - 以后不需要外部排序。
Rowset刷新、流式加载或压缩周期产生的一个或多个段的不可变捆绑包。仅附加设计允许 StarRocks 并发提取,同时读取器保持无锁。
Rowset 内的自包含列文件(约 512 MB),携带数据页面加上剪枝索引。段级区域映射和前缀索引依赖于 MemTable 阶段建立的顺序。
  1. 段文件内部

write path steps

每个段都是自描述的。 从上到下,您会发现

  • 列数据页面 64 KB 块编码(字典、RLE、Delta)和压缩(LZ4 默认)。
  • 序号索引 将行序号映射到页面偏移量,因此引擎可以直接跳转到页面 n。
  • 区域映射索引 每个页面和整个段的最小值、最大值和 has_null - 剪枝的第一道防线。
  • 短键(前缀)索引 稀疏二进制搜索表,包含每大约 1K 行的排序键的前 36 个字节 - 实现毫秒级点/范围查找。
  • 页脚和魔数 每个索引的偏移量和用于完整性的校验和;允许 StarRocks 仅对尾部进行内存映射以发现其余部分。

由于页面已经按键排序,因此这些索引非常小,但效果却非常有效。

  1. 读取路径

    1. 分区剪枝(计划时) 如果 WHERE 子句约束分区键(例如 dt BETWEEN '2025-05-01' AND '2025-05-07'),则优化器仅打开匹配的分区目录。
    2. 平板剪枝(计划时) 当相等性过滤器包含哈希分布列时,StarRocks 会计算目标平板 ID 并仅计划这些平板。
    3. 前缀索引查找 前导排序列上的稀疏短键索引锁定确切的段或页面。
    4. 区域映射剪枝 每个段和 64 KB 页面的 min/max 元数据丢弃错过谓词窗口的块。
    5. 向量化扫描和后期物化 幸存的列页面按顺序流经 CPU 缓存;仅物化引用的行和列,从而保持内存紧张。

    由于数据在每次刷新时都按键顺序提交,因此每个读取时剪枝层都建立在它之前的层上,从而在数十亿行的表上实现亚秒级扫描。


如何选择有效的排序键

  1. 从工作负载智能开始

    首先分析前 N 个查询模式

    • 相等谓词(= / IN)。 几乎总是按相等性过滤的列是理想的前导候选列。
    • 范围谓词。 时间戳和数字范围通常在排序键中跟随相等列。
    • 聚合键。 如果范围列也出现在 GROUP BY 子句中,则将其放在键中较早的位置(在选择性过滤器之后)可以启用排序聚合。
    • 连接/分组依据键。 如果连接或分组键很常见,请考虑将其尽早放置

    衡量列基数:高基数列(数百万个不同的值)剪枝效果最佳。

  2. 启发法和经验法则

    1. 排序规则:(高选择性相等列)→(主范围列)→(集群辅助列)。
    2. 基数排序:将低基数列放在高基数列之前可以增强数据压缩。
    3. 宽度:保持 3-5 列。 非常宽的键会减慢提取速度并使 36 字节前缀索引限制溢出。
    4. 字符串列:长的前导字符串列可能占用前缀索引中 36 字节限制的大部分或全部,从而阻止排序键中的后续列被有效索引。

    这降低了前缀索引的剪枝能力并降低了点查询性能。

  3. 与其他设计技巧协调

    • 分区:选择比前导排序列更粗的分区键(例如,PARTITION BY dateORDER BY (tenant_id, ts))。 这样,分区剪枝首先删除整个日期范围,然后排序剪枝在内部清理。
    • 分桶:对分桶和聚簇使用相同的列具有不同的目的。 分桶确保数据在集群中均匀分布,而排序则实现有效的 I/O 消除。
    • 表类型:主要键表默认使用主键作为排序键,但它们也可以指定其他列来优化物理顺序并增强剪枝。 聚合表和重复表应遵循上面讨论的分析谓词驱动的排序键策略。

  1. 参考模板
场景分区排序键理由
B2C 订单date_trunc('day', order_ts)(user_id, order_ts)大多数查询首先按用户过滤,然后按最近的时间范围过滤。
物联网遥测date_trunc('day', ts)(device_id, ts)设备范围的时间序列读取占主导地位。
SaaS 多租户tenant_id(dt, event_id)通过分区进行租户隔离;按天对排序集群进行仪表板处理。
维度查找(dim_id)小表,纯点查找 - 单列就足够了。

结论

一个精心设计的排序键用小的、可预测的提取开销换取了扫描延迟、存储效率和 CPU 利用率的显着提高。 通过将您的选择建立在工作负载现实的基础上、尊重基数并使用 EXPLAIN 进行验证,即使数据和用户数量增长 10 倍及以上,您也可以保持 StarRocks 的正常运行。