Starrocks 主键查询和分区裁剪/bucket裁剪

背景

在之前的Starrocks ShortCircuit短路径的调度,我们只是提到经过了SQL优化模块,就直接进入到了发送BE阶段,其实在这里还有一个很重要的部分,就是分区裁剪tablet裁剪,经过这两个步骤,才会发送到对应的BE节点上,也从侧面解释了StarRocks 各类索引以及存储位置详解中的主键为啥会在tablet层级(经过了tablet裁剪后,就可以直接通过主键索引获取到对应的数据),这里也顺便了解一下 分区裁剪tablet裁剪 的实现。

分区裁剪PartitionPruneRule

  • 作用:在"分区(Partition)"维度剔除不相关的粗粒度数据集合。
  • 原理: 该 Rule 提取查询中的过滤条件(例如 WHERE pk = 123 或者 date = '2023-01-01'),并将这些条件(Predicate)与表的分区信息(Range 或 List)进行求交集运算。
  • 在主键查询中的表现:
  1. 如果你的表是非分区表(即整表只有一个默认分区),或者查询条件中未带上分区列,这一步会原封不动,保留全部分区或者这单一分区。
  2. 如果查询条件中包含了分区列(例如通常按时间范围分区,查询包含时间条件),PartitionPruneRule会计算出特定的值落在哪个分区,剔除(Prune)掉所有不符合条件的 Partition。
  • 收益:避免扫描几百到上千个无关的分区元数据和底层文件。
    这里的关键代码是在OptOlapPartitionPruner.prunePartitions:

    public static LogicalOlapScanOperator prunePartitions(LogicalOlapScanOperator logicalOlapScanOperator) {
    List selectedPartitionIds = null;
    OlapTable table = (OlapTable) logicalOlapScanOperator.getTable();

    复制代码
          PartitionInfo partitionInfo = table.getPartitionInfo();
    
          if (partitionInfo.isRangePartition()) {
              selectedPartitionIds = rangePartitionPrune(table, (RangePartitionInfo) partitionInfo, logicalOlapScanOperator);
          } else if (partitionInfo.getType() == PartitionType.LIST) {
              selectedPartitionIds = listPartitionPrune(table, (ListPartitionInfo) partitionInfo, logicalOlapScanOperator);
          }

这里会根据分区类型不同,选择不同的处理路径:

  • Range 分区裁剪 (rangePartitionPrune):内部利用查询中解析出的 where col1 >= 'X' and col1 <= 'Y',构建出一个 Range 过滤器,遍历所有 Partition 的区间 [low, high),找交集,交集不为空则保留这个分区。
  • List 分区裁剪 (listPartitionPrune):如果是 city in ('beijing', 'shanghai') 这种分区,它会直接找到这几个离散的分区 ID
    这里的代码和其他引擎的大同小异,读者自行查看即可

tablet裁剪DistributionPruneRule

  • 作用:在裁剪后剩余的分区中,进一步在"分桶 / 分片(Tablet)"维度精确定位数据。这个规则必须在 PartitionPruneRule 执行完成之后进行。

  • 原理: StarRocks 的分布式存储架构中,每个分区会被切分成多个分桶(Tablet),通常是通过主键 Hash 的方式来切分(即主键表的 hash distribution 配置)。
    DistributionPruneRule根据查询的分布列(Distribution columns,主键表里即对应主键字段)条件进行哈希计算。如果查询谓词给出的是主键的精确匹配等值条件(如 id = 1):规则类底层的 HashDistributionPruner 会对查询值 "1" 运行与导入时一样的数据 Hash 函数,直接计算出该数据必然落在该分区内的**某 1 个特定的 Tablet(Bucket)**上。

  • 在主键查询中的表现: 经过这一步,原本需要在选定分区下扫描的所有 Tablet (通常会有几到几百个),会被硬核裁剪到只剩 1 个指定的 Tablet ID。

  • 收益:彻底消除无需读取的分桶 I/O 开销和分布式执行资源调度的开销。

这里只说核心部分:

以 Hash 分桶为例,关键的代码在于HashDistributionPruner.prune

复制代码
public Collection<Long> prune(int columnId, HashDistributionKey hashKey, int complex) {
        if (columnId == distributionColumns.size()) {
            // compute Hash Key
            long hashValue = hashKey.getHashValue();
            return Lists.newArrayList(tabletIdsInOrder.get((int) ((hashValue & 0xffffffff) % tabletIdsInOrder.size())));
        }
        Column keyColumn = distributionColumns.get(columnId);
...
  public long getHashValue() {
        CRC32 hashValue = new CRC32();
        int i = 0;
        for (LiteralExpr expr : keys) {
            ByteBuffer buffer = expr.getHashValue(types.get(i));
            hashValue.update(buffer.array(), 0, buffer.limit());
            i++;
        }
        return hashValue.getValue();
    }

bucketId是通过如下方法获取的bucketId = (hashValue & 0xffffffff) % bucketNum ,用这个 hashValue 对 bucket 数取模,得到 bucket 序号,然后用该序号去 tabletIdsInOrder 里取对应的 tabletId

而这里的分桶算法和当初写入数据时计算的bucketId是一致的,核心代码在OlapTablePartitionParam::find_tablets():

C++ 复制代码
Status OlapTablePartitionParam::find_tablets(Chunk* chunk, std::vector<OlapTablePartition*>* partitions,
                                             std::vector<uint32_t>* hashes, std::vector<uint8_t>* selection,
                                             std::vector<int>* invalid_row_indexs, int64_t txn_id,
                                             std::vector<std::vector<std::string>>* partition_not_exist_row_values) {
    size_t num_rows = chunk->num_rows();
    partitions->resize(num_rows);

    _compute_hashes(chunk, hashes);
...

void OlapTablePartitionParam::_compute_hashes(const Chunk* chunk, std::vector<uint32_t>* hashes) {
    size_t num_rows = chunk->num_rows();
    hashes->assign(num_rows, 0);

    if (is_hash_distribution()) {
        for (size_t i = 0; i < _distributed_slot_descs.size(); ++i) {
            _distributed_columns[i] = chunk->get_column_by_slot_id(_distributed_slot_descs[i]->id()).get();
            _distributed_columns[i]->crc32_hash(&(*hashes)[0], 0, num_rows);
        }
    } else if (is_random_distribution()) {
        uint32_t r = _rand.Next();
        for (auto i = 0; i < num_rows; ++i) {
            (*hashes)[i] = r++;
        }
    }
}

对于这里的Hash算法,采用的是CRC32,和tablet裁剪的时候是一致的:

  • 单列分桶:hash = CRC32(col)
  • 多列分桶:hash = CRC32( CRC32(0, col1), col2, ... )(按列顺序累积)
相关推荐
AC赳赳老秦16 分钟前
司法公开数据采集应用:OpenClaw 抓取裁判文书公开信息,批量整理同类参考案例
java·大数据·python·数据挖掘·数据分析·php·openclaw
yurenshi16682 小时前
自媒体多账号管理工具 4 款实测:聚媒通 / 融媒宝 / 新榜小豆芽 / 矩阵通横向对比,快速选型
大数据·矩阵·媒体
nVisual3 小时前
机柜PDU安装位置与空间建模方案
大数据·网络·数据库·信息可视化·数据中心基础设施管理
@insist1233 小时前
系统规划与管理师-流程评价与持续改进核心知识-终章
大数据·人工智能·软考·系统规划与管理师·软件水平考试·系统规划与管理工程师
TechEdu2026064 小时前
[计算机科学]大数据硬件架构与软件架构概览
大数据·计算机科学
爱莉希雅&&&4 小时前
elasticsearch+kibana+logstash+filebeat链路部署流程
大数据·elasticsearch·搜索引擎·kibana·logstash·filebeat
FII工业富联科技服务5 小时前
从灯塔工厂到AI Factory新模式:AI正在重构制造业的四大核心能力
大数据·运维·人工智能·重构·ar·制造
小五传输5 小时前
自主可控建设指南:Serv-u替代方案,实现平滑迁移业务不中断
大数据·运维·安全
Spey_Events5 小时前
【核心议题发布】聚焦深度维修与 AI 智慧革新!2026中国民用航空维修智造展——议题重磅发布!
大数据·人工智能
czzxxxxxx5 小时前
创客匠人AI智能体:知识付费从“重人力”走向“自动化”的拐点
大数据·人工智能