StarRocks fragment的执行流程

在 StarRocks 中,SQL 查询的生命周期分为三个阶段:查询解析(Parsing)、查询规划(Planning)和查询执行(Execution)。查询计划由 Frontend (FE) 生成并拆分为多个 fragment,这些 fragment 被分发到多个 BE 节点并行执行。每个 BE 节点接收到的 fragment 包含具体的执行逻辑,例如扫描数据、执行算子(比如 JOIN、AGGREGATE)以及结果返回。本文主要分析在 BE 侧对 fragment的执行流程,基于StarRocks3.4版本。

ExecNode 和 DataSink

通过 explain sql 查看拆分成几个 fragment 和 fragment 的结构。比如,查询SQL:select dt, avg(imp_cnt) from xxx group by dt limit 3; 通过explain语句可以看到分成了3个fragment,每个fragment都至少会有一个 ExecNode 和 DataSink 节点。

每个plan_fragment 都会有ExecNode节点和DataSink节点。ExecNode节点执行该fragment需要完成的动作,DataSink节点上报ExecNode节点执行产生的结果。

ExecNode

ExecNode 是一个抽象基类,为查询执行计划树中的所有节点提供通用接口和功能。每种节点类型(例如 ScanNode、JoinNode、AggregateNode)都继承自 ExecNode,并实现特定行为。

  • 初始化和准备节点以进行执行。
  • 管理查询执行的生命周期,包括 open、get_next、reset 和 close 操作。

DataSink

DataSink 是一个抽象基类,为查询执行计划中的数据接收器提供通用接口和功能。子类(如 ResultSink、ExportSink、TableFunctionTableSink

)实现特定类型的数据输出逻辑。

  • 初始化和准备数据接收器。
  • 管理数据发送、打开和关闭的生命周期。

我们可以根据explain查看对应的sql语句,知道对应的node类型,然后直接在每个fragment所对应节点类型的ExecNode类的子类中查看其prepare、open、get_next等函数的实现来分析其行为。

**

**

调度

BE 在接收到 FE 发来的 fragment 信息以及做对应的处理主要由internal_service,fragment_executor,pipeline_driver,pipeline_driver_exectuor 这几个组件做的处理,具体如下:

internal_service

接收 fragment 执行请求:FE 将查询计划的 fragment(TExecPlanFragmentParams)通过 gRPC 传递给 BE,InternalService 负责接收并触发执行。

fragment_executor

  • 将 fragment 的执行计划分解为多个 pipeline,并为每个 pipeline 创建对应的 PipelineDriver。
  • 协调执行:通过 PipelineDriverExecutor 调度所有 PipelineDriver,确保 fragment 的所有 pipeline 按依赖关系正确执行。

pipeline_driver

  • 表示一个 pipeline 的执行实例。pipeline 是 StarRocks 中查询执行的基本单元,包含一系列算子(operators,如 ScanOperator、JoinOperator),这些算子以流式方式处理数据。
  • 每个 PipelineDriver 负责执行一个 pipeline 的完整逻辑,包括从数据读取到结果输出。

pipeline_driver_exectuor

  • 是一个全局的管理组件,负责调度和管理多个 PipelineDriver 的执行。

  • 它维护一个线程池或工作队列,将 PipelineDriver 分配到线程中执行,并处理任务的并发、优先级和资源管理。

案例分析:查询Hive表并导出到Hdfs

以一个查询hive表然后导出到hdfs上的sql为例,看看execnode和datasink 的创建

insert into files("path" = "hdfs://xxx/xx", "format" = "csv") select * from xxx limit 1;

通过如下堆栈:fragment_executor:: _prepare_exec_plan -> exec_node:: create_tree -> create_vectorized_node,在create_vectorized_node方法里,创建对应的 exec_node 对象,如下:

ini 复制代码
Status ExecNode::create_vectorized_node(starrocks::RuntimeState* state, starrocks::ObjectPool* pool,
                                        const starrocks::TPlanNode& tnode, const starrocks::DescriptorTbl& descs,
                                        starrocks::ExecNode** node) {
    switch (tnode.node_type) {
.............

    case TPlanNodeType::EXCHANGE_NODE:
        *node = pool->add(new ExchangeNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::HASH_JOIN_NODE:
        *node = pool->add(new HashJoinNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::ANALYTIC_EVAL_NODE:
        *node = pool->add(new AnalyticNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::SORT_NODE:
        *node = pool->add(new TopNNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::CROSS_JOIN_NODE:
    case TPlanNodeType::NESTLOOP_JOIN_NODE:
        *node = pool->add(new CrossJoinNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::UNION_NODE:
        *node = pool->add(new UnionNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::INTERSECT_NODE:
        *node = pool->add(new IntersectNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::EXCEPT_NODE:
        *node = pool->add(new ExceptNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::SELECT_NODE:
        *node = pool->add(new SelectNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::FILE_SCAN_NODE: {
        if (tnode.file_scan_node.__isset.enable_pipeline_load && tnode.file_scan_node.enable_pipeline_load) {
            TPlanNode new_node = tnode;
            TConnectorScanNode connector_scan_node;
            connector_scan_node.connector_name = connector::Connector::FILE;
            new_node.connector_scan_node = connector_scan_node;
            *node = pool->add(new ConnectorScanNode(pool, new_node, descs));
        } else {
            *node = pool->add(new FileScanNode(pool, tnode, descs));
        }
    }
        return Status::OK();
    case TPlanNodeType::REPEAT_NODE:
        *node = pool->add(new RepeatNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::ASSERT_NUM_ROWS_NODE:
        *node = pool->add(new AssertNumRowsNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::PROJECT_NODE:
        *node = pool->add(new ProjectNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::TABLE_FUNCTION_NODE:
        *node = pool->add(new TableFunctionNode(pool, tnode, descs));
        return Status::OK();
    case TPlanNodeType::HDFS_SCAN_NODE:
    case TPlanNodeType::KUDU_SCAN_NODE: {
        TPlanNode new_node = tnode;
        TConnectorScanNode connector_scan_node;
        connector_scan_node.connector_name = connector::Connector::HIVE;
        new_node.connector_scan_node = connector_scan_node;
        *node = pool->add(new ConnectorScanNode(pool, new_node, descs));
        return Status::OK();
    }
............
}

在这个例子里,最终会创建 ConnectorScanNode,简单分析下堆栈:

get_next -> ::_start_scan_thread -> ::_submit_scanner -> ::_scanner_thread -> ::open --> HiveDataSource::open. --> _init_scanner 在这里会判断是生成哪种Scanner,走JNI还是不走JNI,然后读取数据源数据。

而data sink则是fragment_executor在方法 _prepare_pipeline_driver --> create_data_sink,具体代码如下,在这里会创建对应的 data sink 对象:

php 复制代码
Status DataSink::create_data_sink(RuntimeState* state, const TDataSink& thrift_sink,
                                  const std::vector<TExpr>& output_exprs, const TPlanFragmentExecParams& params,
                                  int32_t sender_id, const RowDescriptor& row_desc, std::unique_ptr<DataSink>* sink) {
    DCHECK(sink != nullptr);
    switch (thrift_sink.type) {
    case TDataSinkType::DATA_STREAM_SINK: {
        if (!thrift_sink.__isset.stream_sink) {
            return Status::InternalError("Missing data stream sink.");
        }
        *sink = create_data_stream_sink(state, thrift_sink.stream_sink, row_desc, params, sender_id,
                                        params.destinations);
        break;
    }
    case TDataSinkType::RESULT_SINK:
        if (!thrift_sink.__isset.result_sink) {
            return Status::InternalError("Missing data buffer sink.");
        }

    case TDataSinkType::EXPORT_SINK: {
        if (!thrift_sink.__isset.export_sink) {
            return Status::InternalError("Missing export sink sink.");
        }
        *sink = std::make_unique<ExportSink>(state->obj_pool(), row_desc, output_exprs);
        break;
    }
    case TDataSinkType::OLAP_TABLE_SINK: {
        Status status;
        DCHECK(thrift_sink.__isset.olap_table_sink);
        *sink = std::make_unique<OlapTableSink>(state->obj_pool(), output_exprs, &status, state);
        RETURN_IF_ERROR(status);
        break;
    }
    case TDataSinkType::MULTI_OLAP_TABLE_SINK: {
        Status status;
        DCHECK(thrift_sink.__isset.multi_olap_table_sinks);
        *sink = std::make_unique<MultiOlapTableSink>(state->obj_pool(), output_exprs);
        break;
    }
    case TDataSinkType::HIVE_TABLE_SINK: {
        if (!thrift_sink.__isset.hive_table_sink) {
            return Status::InternalError("Missing hive table sink");
        }
        *sink = std::make_unique<HiveTableSink>(state->obj_pool(), output_exprs);
        break;
    }
    case TDataSinkType::TABLE_FUNCTION_TABLE_SINK: {
        if (!thrift_sink.__isset.table_function_table_sink) {
            return Status::InternalError("Missing table function table sink");
        }
        *sink = std::make_unique<TableFunctionTableSink>(state->obj_pool(), output_exprs);
        break;
    }
    case TDataSinkType::BLACKHOLE_TABLE_SINK: {
        *sink = std::make_unique<BlackHoleTableSink>(state->obj_pool());
        break;
    }
    case TDataSinkType::DICTIONARY_CACHE_SINK: {
        if (!thrift_sink.__isset.dictionary_cache_sink) {
            return Status::InternalError("Missing dictionary cache sink");
        }
        if (!state->enable_pipeline_engine()) {
            return Status::InternalError("dictionary cache only support pipeline engine");
        }
        *sink = std::make_unique<DictionaryCacheSink>();
        break;
    }

}

在这个例子里创建的是 TableFunctionTableSink

这里准备 sink context 上下文信息,包括 hdfs path地址和 hdfs conf的一些配置信息

根据format创建CSVFileWriterFactory。

到这里就找到了fs实例的创建,通过fs把数据写到hdfs上。

更多大数据干货,欢迎关注我的微信公众号---BigData共享

相关推荐
liupenglove3 小时前
自动驾驶数据仓库:时间片合并算法。
大数据·数据仓库·算法·elasticsearch·自动驾驶
全能搬运大师6 小时前
win10安装Elasticsearch
大数据·elasticsearch·搜索引擎
Guheyunyi7 小时前
电气安全监测系统:筑牢电气安全防线
大数据·运维·网络·人工智能·安全·架构
阿里云大数据AI技术8 小时前
阿里云 EMR Serverless Spark: 面向 Data+AI 的高性能 Lakehouse 产品
大数据·人工智能·数据分析
杨小扩8 小时前
AI驱动的软件工程(中):文档驱动的编码与执行
大数据·人工智能·软件工程
技术+ywxs57878 小时前
如何提高微信小店推客系统的推广效果?
大数据·微信开放平台·微信小店·推客系统·系统搭建
杨超越luckly10 小时前
HTML应用指南:利用GET请求获取全国永辉超市门店位置信息
大数据·信息可视化·数据分析·html·argis·门店
拓端研究室12 小时前
专题:2025机器人产业深度洞察报告|附136份报告PDF与数据下载
大数据·人工智能·物联网
阿里云大数据AI技术13 小时前
NL2SQL 再创佳绩!阿里云论文中选 SIGMOD 2025
大数据·人工智能·云计算