源码分析 | ClickHouse和他的朋友们(4)Pipeline处理器和调度器

Clickhouse 计算引擎架构 —— Clickhouse 架构篇(三) 相比较于存储引擎的精妙设计,ClickHouse的计算引擎一直是一个争议非常大的话题。对ClickHouse计算引擎的各种评价都有,两极分化很严重。有人认为ClickHouse计算引擎的向量化设计得巧夺天工,也有很多人认为ClickHouse的计算引擎缺乏优化和对分布式的支持,就是个半成品。这些对ClickHouse计算引擎的评价都在一定程度上反映了ClickHouse计算引擎的某些方面,如果从这些方面来看待ClickHouse的计算引擎,难免陷入盲人摸象的状态。 阅读详情

本文首发于 2020-06-12 20:57:10

《ClickHouse和他的朋友们》系列文章转载自圈内好友 BohuTANG 的博客,原文链接:
https://bohutang.me/2020/06/11/clickhouse-and-friends-processor/
以下为正文。

最后更新: 2020-08-15

本文谈下 ClickHouse 核心科技:处理器 Processor 和有向无环调度器 DAG Scheduler。

这些概念并不是 ClickHouse 首创,感兴趣的同学可以关注下 materialize 的 timely-dataflow,虎哥用 golang 也写过一个原型。

拼的是实现细节,正是这些模块的精良设计,才有了 ClickHous e整体的高性能。

Pipeline问题

在传统数据库系统中,一个 Query 处理流程大体是:

其中在 Plan 阶段,往往会增加一个 Pipeline 组装(一个 transformer 代表一次数据处理):

所有 transformer 被编排成一个流水线(pipeline),然后交给 executor 串行式执行,每执行一个 transformer 数据集就会被加工并输出,一直到下游的 sinker。
可以看到,这种模型的优点是简单,缺点是性能低,无法发挥 CPU 的并行能力,通常叫火山模型(volcano-style),对于 OLTP 低延迟来说足够,对于计算密集的 OLAP 来说是远远不够的,CPU 不到 100% 就是犯罪!

对于上面的例子,如果 transformer1 和 transformer2 没有交集,那么它们就可以并行处理:

这样就涉及到一些比较灵魂的问题:

  1. 如何实现 transformer 的灵活编排?
  2. 如何实现 transformer 间的数据同步?
  3. 如何实现 transformer 间的并行调度?

Processor 和 DAG Scheduler

1. Transformer 编排

ClickHouse 实现了一系列基础 transformer 模块,见 src/Processors/Transforms,比如:

  • FilterTransform – WHERE 条件过滤
  • SortingTransform – ORDER BY 排序
  • LimitByTransform – LIMIT 裁剪

当我们执行:

SELECT * FROM t1 WHERE id=1 ORDER BY time DESC LIMIT 10

对于 ClickHouse 的 QueryPipeline 来说,它会按照以下方式进行编排组装:

QueryPipeline::addSimpleTransform(Source)
QueryPipeline::addSimpleTransform(FilterTransform)
QueryPipeline::addSimpleTransform(SortingTransform)
QueryPipeline::addSimpleTransform(LimitByTransform)
QueryPipeline::addSimpleTransform(Sinker)

这样就实现了 Transformer 的编排,但是执行时数据如何进行同步呢?

2. Transformer 数据同步

当 QueryPipeline 进行 transformer 编排时,我们还需要进行更加底层的 DAG 连通构建。

connect(Source.OutPort, FilterTransform.InPort)
connect(FilterTransform.OutPort, SortingTransform.InPort)
connect(SortingTransform.OutPort, LimitByTransform.InPort)
connect(LimitByTransform.OutPort, Sinker.InPort)

这样就实现了数据的流向关系,一个 transformer 的 OutPort 对接另外一个的 InPort,就像我们现实中的水管管道一样,接口有 3 通甚至多通。

3. Transformer 执行调度

现在管道组装起来了,那么管道内的水如何进行处理和给压流动呢?

ClickHouse 定义了一套 transform 状态,processor 根据这些状态来实现调度。

enum class Status
{
    NeedData  // 等待数据流进入
    PortFull, // 管道流出端阻塞
    Finished, // 完成状态,退出
    Ready,    // 切换到 work 函数,进行逻辑处理
    Async,    // 切换到 schedule 函数,进行异步处理
    Wait,     // 等待异步处理
    ExpandPipeline,      // Pipeline 需要裂变
};

当 source 生成数据后,它的状态会设置为 PortFull,意思是等着流入其他 transformer 的 InPort,processor 会开始调度 FilterTransformer(NeedData) 的 Prepare,进行 PullData,然后它的状态设置为 Ready,等待 processor 调度 Work 方法进行数据Filter处理,大家就这样靠状态让 processor 去感知,来调度和做状态迁移,直到 Finished 状态。

这里值得一提的是 ExpandPipeline 状态,它会根据 transformer 的实现,可以把一个 transformer 裂变出更多个 transformer 并行执行,达到一个爆炸效果。

Example

SELECT number + 1 FROM t1;

为了更加深入理解 ClickHouse 的 processor 和 scheduler 机制,我们来一个原生态的 example:

  1. 一个 Source:{0,1,2,3,4}
  2. AdderTransformer 对每个数字做加1操作
  3. 一个 Sinker,输出结果

1. Source

class MySource : public ISource
{
public:
    String getName() const override { return "MySource"; }

    MySource(UInt64 end_)
        : ISource(Block({ColumnWithTypeAndName{ColumnUInt64::create(), std::make_shared<DataTypeUInt64>(), "number"}})), end(end_)
    {
    }

private:
    UInt64 end;
    bool done = false;

    Chunk generate() override
    {
        if (done)
        {
            return Chunk();
        }
        MutableColumns columns;
        columns.emplace_back(ColumnUInt64::create());
        for (auto i = 0U; i < end; i++)
            columns[0]->insert(i);

        done = true;
        return Chunk(std::move(columns), end);
    }
};

2. MyAddTransform

class MyAddTransformer : public IProcessor
{
public:
    String getName() const override { return "MyAddTransformer"; }

    MyAddTransformer()
        : IProcessor(
            {Block({ColumnWithTypeAndName{ColumnUInt64::create(), std::make_shared<DataTypeUInt64>(), "number"}})},
            {Block({ColumnWithTypeAndName{ColumnUInt64::create(), std::make_shared<DataTypeUInt64>(), "number"}})})
        , input(inputs.front())
        , output(outputs.front())
    {
    }

    Status prepare() override
    {
        if (output.isFinished())
        {
            input.close();
            return Status::Finished;
        }

        if (!output.canPush())
        {
            input.setNotNeeded();
            return Status::PortFull;
        }

        if (has_process_data)
        {
            output.push(std::move(current_chunk));
            has_process_data = false;
        }

        if (input.isFinished())
        {
            output.finish();
            return Status::Finished;
        }

        if (!input.hasData())
        {
            input.setNeeded();
            return Status::NeedData;
        }
        current_chunk = input.pull(false);
        return Status::Ready;
    }

    void work() override
    {
        auto num_rows = current_chunk.getNumRows();
        auto result_columns = current_chunk.cloneEmptyColumns();
        auto columns = current_chunk.detachColumns();
        for (auto i = 0U; i < num_rows; i++)
        {
            auto val = columns[0]->getUInt(i);
            result_columns[0]->insert(val+1);
        }
        current_chunk.setColumns(std::move(result_columns), num_rows);
        has_process_data = true;
    }

    InputPort & getInputPort() { return input; }
    OutputPort & getOutputPort() { return output; }

protected:
    bool has_input = false;
    bool has_process_data = false;
    Chunk current_chunk;
    InputPort & input;
    OutputPort & output;
};

3. MySink

class MySink : public ISink
{
public:
    String getName() const override { return "MySinker"; }

    MySink() : ISink(Block({ColumnWithTypeAndName{ColumnUInt64::create(), std::make_shared<DataTypeUInt64>(), "number"}})) { }

private:
    WriteBufferFromFileDescriptor out{STDOUT_FILENO};
    FormatSettings settings;

    void consume(Chunk chunk) override
    {
        size_t rows = chunk.getNumRows();
        size_t columns = chunk.getNumColumns();

        for (size_t row_num = 0; row_num < rows; ++row_num)
        {
            writeString("prefix-", out);
            for (size_t column_num = 0; column_num < columns; ++column_num)
            {
                if (column_num != 0)
                    writeChar('\t', out);
                getPort()
                    .getHeader()
                    .getByPosition(column_num)
                    .type->serializeAsText(*chunk.getColumns()[column_num], row_num, out, settings);
            }
            writeChar('\n', out);
        }

        out.next();
    }
};

4. DAG Scheduler

int main(int, char **)
{
    auto source0 = std::make_shared<MySource>(5);
    auto add0 = std::make_shared<MyAddTransformer>();
    auto sinker0 = std::make_shared<MySink>();

    /// Connect.
    connect(source0->getPort(), add0->getInputPort());
    connect(add0->getOutputPort(), sinker0->getPort());

    std::vector<ProcessorPtr> processors = {source0, add0, sinker0};
    PipelineExecutor executor(processors);
    executor.execute(1);
}

总结

从开发者角度看还是比较复杂,状态迁移还需要开发者自己控制,不过 upstream 已经做了大量的基础工作,比如对 source的封装 ISource,对 sink 的封装 ISink,还有一个基础的 ISimpleTransform,让开发者在上层使用 processor 时更加容易,可以积木式搭建出自己想要的 pipeline。

ClickHouse 的 transformer 数据单元是 Chunk,transformer 对上游 OutPort 流过来的 Chunk 进行加工,然后输出给下游的 InPort,图连通式的流水线并行工作,让 CPU 尽量满负荷工作。

当一个 SQL 被解析成 AST 后,ClickHouse 根据 AST 构建 Query Plan,然后根据 QueryPlan 构建出 pipeline,最后由 processor 负责调度和执行。
目前,ClickHouse 新版本已经默认开启 QueryPipeline,同时这块代码也在不停的迭代。


欢迎关注我的微信公众号【数据库内核】:分享主流开源数据库和存储引擎相关技术。

标题网址
GitHubhttps://dbkernel.github.io
知乎https://www.zhihu.com/people/…
思否(SegmentFault)https://segmentfault.com/u/db…
掘金https://juejin.im/user/5e9d3e…
开源中国(oschina)https://my.oschina.net/dbkernel
博客园(cnblogs)https://www.cnblogs.com/dbkernel
Clickhouse系列六:源码解读GroupBy 单机Clickhouse GroupBy的功能是被业界津津乐道的。那么它又是怎么能达到这么好的性能的呢?优化点在哪里呢。这里我们来去探索下Clickhouse关于GroupBy的秘密。 说到GroupBy那就不能不提聚合函数了,在Clickhouse里面关于GroupBy的设计是非常优秀的,Clickhouse计算引擎是通过火山模型来实现的,但是火山模型有一个非常大的性能损耗就是虚函数调用的开销 阅读详情

相关推荐

别再乱调了!Zygo MetroPro软件滤波功能保姆级指南,从均值到鲁棒高斯样条一次讲清

本文深入解析Zygo MetroPro软件的滤波功能,从均值滤波到鲁棒高斯样条等九种算法,帮助用户根据测量对象特性选择最佳滤波组合。通过真实工业案例和参数配置指南,提升光学测量数据的准确性和效率,特别适用于硅晶圆、光学镜片等精密测量场景。

weixin_30888413的博客 160

ClickHouse源码简析

ClickHouse源码简析

driftingman的专栏 1787

单卡也能玩转Qwen2.5-VL微调!3090实测Grounding任务调参技巧大全

本文详细介绍了如何在单张RTX 3090显卡上,通过梯度累积、BF16混合精度训练、梯度检查点等关键技术,成功微调Qwen2.5-VL多模态大模型以完成Grounding视觉定位任务。文章提供了从数据处理、参数配置到训练监控的完整实战调参技巧,为资源有限的开发者提供了可行的解决方案。

weixin_29098117的博客 425

ClickHouse 聚合函数的执行过程

基本的概念 Block:数据块,ClickHouse进行数据读、写的基本单元,每一个Block实例,不仅包含数据域,还包含了每个列的meta信息。 Chunk:数据块,保存实际数据的单元,Block中的数据域的指向的就是这个类型的实例。 Row:一行记录,包含多个列索引,Chunk可以认为是由多个Row组成的。 Column:一列数据,包含一个列上的Block Size数量的行。 一个Block对象,可以简单理解为一张表,它的每一列都有相同的长度,每一行长度也等: Block/Chunk Colum

Dreamming Time的博客 3272

ArcFace vs 传统算法:实测MTCNN+ArcFace在PyCharm中的识别准确率(含完整对比数据)

本文通过PyCharm开发环境实测MTCNN+ArcFace组合在人脸识别中的性能表现,并与传统算法进行多维度对比。实验数据显示,ArcFace在LFW数据集上达到99.83%的准确率,显著优于传统方法。文章还提供了完整的代码实现和工程优化建议,为开发者提供实用的技术参考。

醉三国 236

MBIST --- PATR1.Memorybist测试原理

mem bist作为现在design设计中不可或缺的DFT设计内容,越发重要。本章节主要介绍mem bist组成部分,测试的原理以及注意事项。

sinat_27691203的博客 1万+

ClickHouse之Explain查看执行计划

基本语法 EXPLAIN 类型 PLAN:用于查看执行计划,默认值。 AST:用于查看语法树; SYNTAX:用于优化语法,有时我们指定的查询语句未必是最优的,那么 ClickHouse 在底层会进行优化,EXPLAIN SYNTAX 可以返回对一条 SQL 语句进行优化后的结果。通过对比优化前和优化后的 SQL 语句,可以有助于我们理解 ClickHouse 的优化机制 PIPELINE:用于查看 PIPELINE 计划。 ESTIMATE:显示处理查询时要从表中读取的估计行数、标记数和部分数

致力于不留技术债务cuizb.top 4333

基于反相器的超声波发生和气泡检测电路

本文主要介绍了关于超声波的两种电路——基于反相器的振荡电路和基于逻辑们的气泡检测电路

xehagy的博客 1960

2023-01-18 ClickHouse之聚合功能源码分析

本来想再写详细一点的,但是发现现在这个程度已经2w多字了,再往下写就是一些过于细节繁杂的内容,写了估计也没耐心看,把握住框架,需要的时候再看细节吧。聚合分析这部分代码充分说明了这样一个事实:ClickHouse之所以有如此优秀的性能,不是因为在某个或者某几个地方采用了特别牛逼的优化技巧和方法,而是作为一个软件系统,它能够将大部分常见的工程优化整合到系统的各个链路上下游中,并且还能保证一定的代码质量和合理的抽象水平,避免随着不断迭代开发导致代码完全屎山化。

悟世之路 2014

华为交换机实战:如何用ACL+流分类精准限制特定IP的网速(附完整命令)

本文详细介绍了华为交换机如何通过ACL+流分类技术实现精准IP限速,包括ACL规则配置、流分类关联、流行为定义及流策略应用等完整步骤。文章提供了实战命令和配置验证方法,帮助网络管理员有效管理带宽资源,解决特定IP占用过多带宽的问题。

weixin_29229055的博客 100

openclaw通俗入门系列9——流式传输 + 分块

流式传输 = 不等写完就开始发。分块流式传输是把长回复切成几条消息发(所有渠道),草稿流式传输是像打字一样实时显示(仅 Telegram)。可以配置块大小、发送时机、合并规则、类人延迟。

m0_60605453的博客 507

ClickHouse和他的朋友们(4)Pipeline处理器和调度器

原文出处:https://bohutang.me/2020/06/11/clickhouse-and-friends-processor/本文谈下 ClickHouse 核心科技:处理器...

老叶茶馆 1432

达梦数据库导入报错:[警告]服务器与文件内数据的 CASE_SENSITIVE 参数不匹配,成功终止导入, 但出现警告dimp V8,本地编码:PG_GBK, 导入文件编码:PG_UTF8

达梦数据库管理系统的最新版本是8.0版本,简称DM8。DM8采用全新的体系架构,在保证大型通用的基础上,针对可靠性、高性能、海量数据处理和安全性做了大量的研发和改进工作,极大提升了达梦数据库产品的性能、可靠性、可扩展性,能同时兼顾OLTP和OLAP请求,从根本上提升了DM8产品的品质。达梦数据库管理系统是达梦公司推出的具有完全自主知识产权的高性能数据库管理系统,简称DM。[警告]服务器与文件内数据的 CASE_SENSITIVE 参数不匹配,成功终止导入, 但出现警告。4.无视警告,输入y,回车。

十三月的博客 1万+

Clickhouse 学习笔记(7)—— 查看执行计划

本文介绍了clickhouse如何查看执行计划

WHY的博客 2139

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Flink入门(8)-Flink作业提交流程

Flink作业提交流程概述 概述 1.在Flink Client中,通过反射启动jar中的main函数,生成FlinkStreamGraph和JobGraph,将JobGraph提交给Flink集群 2.Flink集群收到JobGraph(JobManager收到)后,将JobGraph翻译成ExecutionGraph,然后开始调度,启动成功之后开始消费数据。 pipelineExecutor有多种实现, session模式下:AbstractSessionClusterExecutor per-jo

weixin_38813363的博客 777

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ClickHouse进阶

普通视图不保存数据,保存的仅仅是查询语句,查询的时候还是从原表读取数据,可以将普通视图理解为是个子查询。物化视图则是把查询的结果根据相应的引擎存入到了磁盘或内存中,对数据重新进行了组织,你可以理解物化视图是完全的一张新表.MySQL 的用户群体很大,为了能够增强数据的实时性,很多解决方案会利用 binlog 将数据写入到 ClickHouse。为了能够监听 binlog 事件,我们需要用到类似 canal 这样的第三方中间件,这无疑增加了系统的复杂度。

厚积薄发 2650

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

clickhouse源码分析

clickhouse源码分析(一) 简单介绍 clichouse是由俄罗斯yandex公司的大数据引擎团队开发的分析型数据库。在2016年发布,经过几年的跌代现在被国内外很多大小公司在各种场景使用。clickhouse的轻量级,多种协议接口,SQL兼容性,优秀的导入和查询速度等等特性。被国内外的企业和数据分析从业人员所喜欢。 代码下载 clickhouse github链 接https://github.com/ClickHouse/ClickHouse.git 下载的时候最好使用git下载(下载的代码的

a615549958的专栏 1528

Clickhouse源码分析

继承IFunctionpublic::定义返回类型:实现函数逻辑继承IStoragepublic:read:定义数据读取逻辑write:定义数据写入逻辑(可选)alter:支持表结构修改});继承IDataTypepublic::序列化逻辑:反序列化逻辑:创建对应列类型});

Made In SQL 1286

ClickHouse Query执行流程浅析

语法树生成及执行涉及的UML图 HTTP Handler 接收Query请求 调用 Interpreters::executeQuery.h::executeQuery(…) 调用 Interpreters::executeQuery.h::executeQueryImpl(…) 生成 ASTPtr ast = Parsers::ParserQuery::parseImpl(…) using ASTPtr = std::shared_ptr<IAST> IAST是所有SQL语句解析后,生成

Dreamming Time的博客 5401

【ClickHouse内核】Select语句的执行链路

摘要: 本文主要讲述ClickHouse开源数据库查询语句的执行流程。主要内容如下: 概述 服务器实例启动逻辑 语句的执行链路 结论

Night_ZW的博客 1917

Clickhouse源码分析-TTL执行流程

如果没有ttl_only_drop_parts的配置,过期数据的删除(这里是删除,是将过期的数据从这个part删除,并将过期的数据构成一个part,这个过期的part标记为inactive,没过期的变为新part)会在TTL Merge任务中进行。示例如下,ttl_only_drop_parts = 0:插入数据:SELECT。

qq_51950769的博客 1106

Clickhouse源码分析-Replicated Database创建流程

DDL: 这里需要你提前启动1个clickhouse,1个clickhouse-keeper。断点:前台线程调用栈:后台线程调用栈: 可以看到前台线程执行成功后直接返回给客户端:主要看一下initializeReplication: 再看一下recoverLostReplica:这一部分为了构建依赖:执行创建表语句: 未完待续......不显示源码的问题:info source查看:解决:/home/user/clickhouse/src为调试的Clickhouse的源码路径

qq_51950769的博客 888

开源基于docker的任务调度器pipeline,比`quartzs` 更强大的分布式任务调度器

pipeline 分布式任务调度器 目标: 基于docker的布式任务调度器, 比quartzs,xxl-job 更强大的分布式任务调度器。 可以将要执行的任务打包为docker镜像,或者选择已有镜像,自定义脚本程序,通过pipeline框架来实现调度。 开源地址: https://github.com/jadepeng/docker-pipeline 架构 pipeline master 中心节点,管理和调度任务 pipeline agent 执行任务的节点,接收到任务后,调用docker执行pipe

JadePeng的技术博客 1273

流水线(Pipeline)技术

流水线(Pipeline)技术是一种在程序执行时多条指令重叠进行操作的一种准并行处理实现技术

buwangchuxinking的博客 1346

技术内幕 | StarRocks Pipeline 执行框架(上)

助你从 0 开始快速上手 StarRocks

StarRocks的博客 1488

clickhouse源码解析

大家如果对replicatedMergetree 感兴趣后续可以更新 clickhouse code trace 1、写入 excuteQuery() // called in TCPHandler::runImpl() executeQueryImpl() parseQuery() interpreter->execute() // InterpreterInsertQuery BlockIO InterpreterInsertQuery::execute() // 构造一个MergeTr

wangxuewei的博客 1134
上一篇: 源码分析 | ClickHouse和他的朋友们(3)MySQL Protocol和Write调用栈
下一篇: 源码分析 | ClickHouse和他的朋友们(5)存储引擎技术进化与MergeTree
dbkernel
博客等级 码龄4年 60粉丝 42原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值