Loading [MathJax]/jax/input/TeX/config.js
首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >ClickHouse源码笔记2:聚合流程的实现

ClickHouse源码笔记2:聚合流程的实现

作者头像
HappenLee
发布于 2020-07-20 01:59:42
发布于 2020-07-20 01:59:42
2.3K1
举报

上篇笔记讲到了聚合函数的实现并且带大家看了聚合函数是如何注册到ClickHouse之中的并被调用使用的。这篇笔记,笔者会续上上篇的内容,将剖析一把ClickHouse聚合流程的整体实现。 第二篇文章,我们来一起看看聚合流程的实现~~ 上车!

1.基础知识的梳理

ClickHouse的实现接口
  • Block类 前文我们聊到ClickHouse是一个列式存储数据库,在内存之中用IColumn接口来作为数据结构表示数据。 而Block则是这些列的集合,也就是说Block包含了一组列,而无数个Block就构成了我们通常理解的表了。 在ClickHouse进行查询之中,数据的最小处理单位是 Block 。由下面代码可以看到,Block就是由一组列以及列名对应列的偏移map组成的。
代码语言:javascript
AI代码解释
复制
class Block
{
private:
    using Container = ColumnsWithTypeAndName;
    using IndexByName = std::map<String, size_t>;

    Container data;
    IndexByName index_by_name;

这是一个很重要的类,实现的也并不复杂。Block类作为ClickHouse的核心,后续的工作都是基于Block类展开的。

  • 抽象类IBlockInputStream 由名字可以看出,IBlockInputStream是一个实现接口。 这也同样是一个十分重要的接口,ClickHouse的调用模型就建立在IBlockInputStream接口之上。该接口最为核心的就是方法便是read函数,它返回一个被对应Stream处理过的Block。 想必看到这里应该明白了,ClickHouse就是通过IBlockInputStream实现的火山模型,每一个不同的Stream处理不同的查询逻辑,最后层层迭代,完成最终输出流就是用户需要的结果了。 IBlockInputStream类还有一个孪生兄弟IBlockoutputStream,顾名思义,需要进行写操作的时候就要用到它了。
代码语言:javascript
AI代码解释
复制
class IBlockInputStream : public TypePromotion<IBlockInputStream>
{
    friend struct BlockStreamProfileInfo;

public:
    IBlockInputStream() { info.parent = this; }
    virtual ~IBlockInputStream() {}

    IBlockInputStream(const IBlockInputStream &) = delete;
    IBlockInputStream & operator=(const IBlockInputStream &) = delete;

    /// To output the data stream transformation tree (query execution plan).
    virtual String getName() const = 0;

    /** Get data structure of the stream in a form of "header" block (it is also called "sample block").
      * Header block contains column names, data types, columns of size 0. Constant columns must have corresponding values.
      * It is guaranteed that method "read" returns blocks of exactly that structure.
      */
    virtual Block getHeader() const = 0;

    virtual const BlockMissingValues & getMissingValues() const
    {
        static const BlockMissingValues none;
        return none;
    }

    /// If this stream generates data in order by some keys, return true.
    virtual bool isSortedOutput() const { return false; }

    /// In case of isSortedOutput, return corresponding SortDescription
    virtual const SortDescription & getSortDescription() const;

    /** Read next block.
      * If there are no more blocks, return an empty block (for which operator `bool` returns false).
      * NOTE: Only one thread can read from one instance of IBlockInputStream simultaneously.
      * This also applies for readPrefix, readSuffix.
      */
    Block read();
  • AggregatingBlockInputStream类 终于引出我们的主角了,AggregatingBlockInputStream类,作为上面IBlockInputStream的子类,也就是我们今天要重点分析的类。
代码语言:javascript
AI代码解释
复制
class AggregatingBlockInputStream : public IBlockInputStream
{
public:
    /** keys are taken from the GROUP BY part of the query
      * Aggregate functions are searched everywhere in the expression.
      * Columns corresponding to keys and arguments of aggregate functions must already be computed.
      */
    AggregatingBlockInputStream(const BlockInputStreamPtr & input, const Aggregator::Params & params_, bool final_)
        : params(params_), aggregator(params), final(final_)
    {
        children.push_back(input);
    }

    String getName() const override { return "Aggregating"; }

    Block getHeader() const override;

protected:
    Block readImpl() override;

    Aggregator::Params params;
    Aggregator aggregator;
    bool final;

    bool executed = false;

    std::vector<std::unique_ptr<TemporaryFileStream>> temporary_inputs;

     /** From here we will get the completed blocks after the aggregation. */
    std::unique_ptr<IBlockInputStream> impl;
};

首先看它的构造方法,参数有:

  • BlockInputStreamPtr: 这个很好理解,就是它的子流,也就是实际产生数据的流,后续的聚合计算将会在子流返回的结果上展开。
  • params: 聚合参数,这个参数十分重要。它记录了那些key属于聚合,调用那些聚合参数等核心信息。并且aggregator也就是执行聚合的类,也是通过该参数构造的,它是Aggregator的内部类。
  • final: 指明该Stream是否是最终结果,还是要继续进行计算。

这里最为核心的就是AggregatingBlockInputStream类通过继承override对应的readImpl()的接口来实现对应的具体逻辑。AggregatingBlockInputStream类还有一个孪生兄弟:ParallelAggregatingBlockInputStream类,通过并行化来进一步加快聚合流程的执行效率。(通过笔者进行的测试,在简单查询聚合查询下,并行化能够提高近一倍的效率~~)

  • Aggregator::Params类 Aggregator::Params类Aggregator的内部类。这个类是整个聚合过程之中最重要的类,查询解析优化后生成聚合查询的执行计划。 而对应的执行计划的参数都通过Aggregator::Params类来初始化,比如那些列要进行聚合,选取的聚合算子等等,并传递给对应的Aggregator来实现对应的聚合逻辑。
代码语言:javascript
AI代码解释
复制
 struct Params
    {
        /// Data structure of source blocks.
        Block src_header;
        /// Data structure of intermediate blocks before merge.
        Block intermediate_header;

        /// What to count.
        const ColumnNumbers keys;
        const AggregateDescriptions aggregates;
        const size_t keys_size;
        const size_t aggregates_size;

        /// The settings of approximate calculation of GROUP BY.
        const bool overflow_row;    /// Do we need to put into AggregatedDataVariants::without_key aggregates for keys that are not in max_rows_to_group_by.
        const size_t max_rows_to_group_by;
        const OverflowMode group_by_overflow_mode;



        /// Settings to flush temporary data to the filesystem (external aggregation).
        const size_t max_bytes_before_external_group_by;        /// 0 - do not use external aggregation.

        /// Return empty result when aggregating without keys on empty set.
        bool empty_result_for_aggregation_by_empty_set;

        VolumePtr tmp_volume;

        /// Settings is used to determine cache size. No threads are created.
        size_t max_threads;

        const size_t min_free_disk_space;
        Params(
            const Block & src_header_,
            const ColumnNumbers & keys_, const AggregateDescriptions & aggregates_,
            bool overflow_row_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_,
            size_t group_by_two_level_threshold_, size_t group_by_two_level_threshold_bytes_,
            size_t max_bytes_before_external_group_by_,
            bool empty_result_for_aggregation_by_empty_set_,
            VolumePtr tmp_volume_, size_t max_threads_,
            size_t min_free_disk_space_)
            : src_header(src_header_),
            keys(keys_), aggregates(aggregates_), keys_size(keys.size()), aggregates_size(aggregates.size()),
            overflow_row(overflow_row_), max_rows_to_group_by(max_rows_to_group_by_), group_by_overflow_mode(group_by_overflow_mode_),
            group_by_two_level_threshold(group_by_two_level_threshold_), group_by_two_level_threshold_bytes(group_by_two_level_threshold_bytes_),
            max_bytes_before_external_group_by(max_bytes_before_external_group_by_),
            empty_result_for_aggregation_by_empty_set(empty_result_for_aggregation_by_empty_set_),
            tmp_volume(tmp_volume_), max_threads(max_threads_),
            min_free_disk_space(min_free_disk_space_)
        {
        }

        /// Only parameters that matter during merge.
        Params(const Block & intermediate_header_,
            const ColumnNumbers & keys_, const AggregateDescriptions & aggregates_, bool overflow_row_, size_t max_threads_)
            : Params(Block(), keys_, aggregates_, overflow_row_, 0, OverflowMode::THROW, 0, 0, 0, false, nullptr, max_threads_, 0)
        {
            intermediate_header = intermediate_header_;
        }
    };
  • Aggregator类 顾名思义,这个是一个实际进行聚合工作展开的类。它最为核心的方法是下面两个函数:
    • execute函数:将输入流的stream依照次序进行blcok迭代处理,将聚合的结果写入result之中。
    • mergeAndConvertToBlocks函数:将聚合的结果转换为输入流,并通过输入流的read函数将结果继续返回给上一层。 通过上面两个函数的调用,我们就可以完成被聚合的数据输入-》 数据聚合 -》 数据输出的流程。具体的细节笔者会在下一章详细的进行剖析。
代码语言:javascript
AI代码解释
复制
class Aggregator
{
public:
    Aggregator(const Params & params_);

    /// Aggregate the source. Get the result in the form of one of the data structures.
    void execute(const BlockInputStreamPtr & stream, AggregatedDataVariants & result);

    using AggregateColumns = std::vector<ColumnRawPtrs>;
    using AggregateColumnsData = std::vector<ColumnAggregateFunction::Container *>;
    using AggregateColumnsConstData = std::vector<const ColumnAggregateFunction::Container *>;
    using AggregateFunctionsPlainPtrs = std::vector<IAggregateFunction *>;

    /// Process one block. Return false if the processing should be aborted (with group_by_overflow_mode = 'break').
    bool executeOnBlock(const Block & block, AggregatedDataVariants & result,
        ColumnRawPtrs & key_columns, AggregateColumns & aggregate_columns,    /// Passed to not create them anew for each block
        bool & no_more_keys);

    bool executeOnBlock(Columns columns, UInt64 num_rows, AggregatedDataVariants & result,
        ColumnRawPtrs & key_columns, AggregateColumns & aggregate_columns,    /// Passed to not create them anew for each block
        bool & no_more_keys);

    /** Convert the aggregation data structure into a block.
      * If overflow_row = true, then aggregates for rows that are not included in max_rows_to_group_by are put in the first block.
      *
      * If final = false, then ColumnAggregateFunction is created as the aggregation columns with the state of the calculations,
      *  which can then be combined with other states (for distributed query processing).
      * If final = true, then columns with ready values are created as aggregate columns.
      */
    BlocksList convertToBlocks(AggregatedDataVariants & data_variants, bool final, size_t max_threads) const;

    /** Merge several aggregation data structures and output the result as a block stream.
      */
    std::unique_ptr<IBlockInputStream> mergeAndConvertToBlocks(ManyAggregatedDataVariants & data_variants, bool final, size_t max_threads) const;
    ManyAggregatedDataVariants prepareVariantsToMerge(ManyAggregatedDataVariants & data_variants) const;

    /** Merge the stream of partially aggregated blocks into one data structure.
      * (Pre-aggregate several blocks that represent the result of independent aggregations from remote servers.)
      */
    void mergeStream(const BlockInputStreamPtr & stream, AggregatedDataVariants & result, size_t max_threads);

    using BucketToBlocks = std::map<Int32, BlocksList>;
    /// Merge partially aggregated blocks separated to buckets into one data structure.
    void mergeBlocks(BucketToBlocks bucket_to_blocks, AggregatedDataVariants & result, size_t max_threads);

    /// Merge several partially aggregated blocks into one.
    /// Precondition: for all blocks block.info.is_overflows flag must be the same.
    /// (either all blocks are from overflow data or none blocks are).
    /// The resulting block has the same value of is_overflows flag.
    Block mergeBlocks(BlocksList & blocks, bool final);

     std::unique_ptr<IBlockInputStream> mergeAndConvertToBlocks(ManyAggregatedDataVariants & data_variants, bool final, size_t max_threads) const;

    using CancellationHook = std::function<bool()>;

    /** Set a function that checks whether the current task can be aborted.
      */
    void setCancellationHook(const CancellationHook cancellation_hook);

    /// Get data structure of the result.
    Block getHeader(bool final) const;

2.聚合流程的实现

这里我们就从上文提到的Aggregator::execute(const BlockInputStreamPtr & stream, AggregatedDataVariants & result)函数作为起点来梳理一下ClickHouse的聚合实现:

代码语言:javascript
AI代码解释
复制
void Aggregator::execute(const BlockInputStreamPtr & stream, AggregatedDataVariants & result)
{
    Stopwatch watch;

    size_t src_rows = 0;
    size_t src_bytes = 0;

    /// Read all the data
    while (Block block = stream->read())
    {
        if (isCancelled())
            return;

        src_rows += block.rows();
        src_bytes += block.bytes();

        if (!executeOnBlock(block, result, key_columns, aggregate_columns, no_more_keys))
            break;
    }

由上述代码可以看出,这里就是依次读取子节点流生成的Block,然后继续调用executeOnBlock方法来执行聚合流程处理每一个Block的聚合。接着我们按图索骥,继续看下去,这个函数比较长,我们拆分成几个部分,并且把无关紧要的代码先去掉:这部分主要完成的工作就是将param之中指定的key列与聚合列的指针作为参数提取出来,并且和聚合函数一起封装到AggregateFunctionInstructions的结构之中。

代码语言:javascript
AI代码解释
复制
bool Aggregator::executeOnBlock(Columns columns, UInt64 num_rows, AggregatedDataVariants & result,
    ColumnRawPtrs & key_columns, AggregateColumns & aggregate_columns, bool & no_more_keys)
{
    /// `result` will destroy the states of aggregate functions in the destructor
    result.aggregator = this;

    /// How to perform the aggregation?
    if (result.empty())
    {
        result.init(method_chosen);
        result.keys_size = params.keys_size;
        result.key_sizes = key_sizes;
        LOG_TRACE(log, "Aggregation method: " << result.getMethodName());
    }

    for (size_t i = 0; i < params.aggregates_size; ++i)
        aggregate_columns[i].resize(params.aggregates[i].arguments.size());

    /** Constant columns are not supported directly during aggregation.
      * To make them work anyway, we materialize them.
      */
    Columns materialized_columns;

    /// Remember the columns we will work with
    for (size_t i = 0; i < params.keys_size; ++i)
    {
        materialized_columns.push_back(columns.at(params.keys[i])->convertToFullColumnIfConst());
        key_columns[i] = materialized_columns.back().get();

        if (!result.isLowCardinality())
        {
            auto column_no_lc = recursiveRemoveLowCardinality(key_columns[i]->getPtr());
            if (column_no_lc.get() != key_columns[i])
            {
                materialized_columns.emplace_back(std::move(column_no_lc));
                key_columns[i] = materialized_columns.back().get();
            }
        }
    }

    AggregateFunctionInstructions aggregate_functions_instructions(params.aggregates_size + 1);
    aggregate_functions_instructions[params.aggregates_size].that = nullptr;

    std::vector<std::vector<const IColumn *>> nested_columns_holder;
    for (size_t i = 0; i < params.aggregates_size; ++i)
    {
        for (size_t j = 0; j < aggregate_columns[i].size(); ++j)
        {
            materialized_columns.push_back(columns.at(params.aggregates[i].arguments[j])->convertToFullColumnIfConst());
            aggregate_columns[i][j] = materialized_columns.back().get();

            auto column_no_lc = recursiveRemoveLowCardinality(aggregate_columns[i][j]->getPtr());
            if (column_no_lc.get() != aggregate_columns[i][j])
            {
                materialized_columns.emplace_back(std::move(column_no_lc));
                aggregate_columns[i][j] = materialized_columns.back().get();
            }
        }

        aggregate_functions_instructions[i].arguments = aggregate_columns[i].data();
        aggregate_functions_instructions[i].state_offset = offsets_of_aggregate_states[i];
        auto that = aggregate_functions[i];
        /// Unnest consecutive trailing -State combinators
        while (auto func = typeid_cast<const AggregateFunctionState *>(that))
            that = func->getNestedFunction().get();
        aggregate_functions_instructions[i].that = that;
        aggregate_functions_instructions[i].func = that->getAddressOfAddFunction();

        if (auto func = typeid_cast<const AggregateFunctionArray *>(that))
        {
            /// Unnest consecutive -State combinators before -Array
            that = func->getNestedFunction().get();
            while (auto nested_func = typeid_cast<const AggregateFunctionState *>(that))
                that = nested_func->getNestedFunction().get();
            auto [nested_columns, offsets] = checkAndGetNestedArrayOffset(aggregate_columns[i].data(), that->getArgumentTypes().size());
            nested_columns_holder.push_back(std::move(nested_columns));
            aggregate_functions_instructions[i].batch_arguments = nested_columns_holder.back().data();
            aggregate_functions_instructions[i].offsets = offsets;
        }
        else
            aggregate_functions_instructions[i].batch_arguments = aggregate_columns[i].data();

        aggregate_functions_instructions[i].batch_that = that;
    }

将需要准备的参数准备好了之后,后续就通过按部就班的调用executeImpl(*result.NAME, result.aggregates_pool, num_rows, key_columns, aggregate_functions_instructions.data(), no_more_keys, overflow_row_ptr)聚合运算了。我们来看看它的实现,它是一个模板函数,内部通过调用了 executeImplBatch(method, state, aggregates_pool, rows, aggregate_instructions)来实现的,数据库都会通过Batch的形式,一次性提交一组需要操作的数据来减少虚函数调用的开销。

代码语言:javascript
AI代码解释
复制
template <typename Method>
void NO_INLINE Aggregator::executeImpl(
    Method & method,
    Arena * aggregates_pool,
    size_t rows,
    ColumnRawPtrs & key_columns,
    AggregateFunctionInstruction * aggregate_instructions,
    bool no_more_keys,
    AggregateDataPtr overflow_row) const
{
    typename Method::State state(key_columns, key_sizes, aggregation_state_cache);

    if (!no_more_keys)
        executeImplBatch(method, state, aggregates_pool, rows, aggregate_instructions);
    else
        executeImplCase<true>(method, state, aggregates_pool, rows, aggregate_instructions, overflow_row);
}

那我们就继续看下去,executeImplBatch同样也是一个模板函数。

  • 首先,它构造了一个AggregateDataPtr的数组places,这里是这就是后续我们实际聚合结果存放的地方。这个数据的长度也就是这个Batch的长度,也就是说,聚合结果的指针也作为一组列式的数据,参与到后续的聚合运算之中。
  • 接下来,通过一个for循环,依次调用state.emplaceKey,计算每列聚合key的hash值,进行分类,并且将对应结果依次和places对应。
  • 最后,通过一个for循环,调用聚合函数的addBatch方法,(这个函数我们在上一篇之中介绍过)。每个AggregateFunctionInstruction都有一个制定的places_offset和对应属于进行聚合计算的value列,这里通过一个for循环调用AddBatch,将places之中对应的数据指针和聚合value列进行聚合,最终形成所有的聚合计算的结果。

到这里,整个聚合计算的核心流程算是完成了,后续就是将result的结果通过上面的convertToBlock的方式转换为BlockStream流,继续返回给上层的调用方。

代码语言:javascript
AI代码解释
复制
template <typename Method>
void NO_INLINE Aggregator::executeImplBatch(
    Method & method,
    typename Method::State & state,
    Arena * aggregates_pool,
    size_t rows,
    AggregateFunctionInstruction * aggregate_instructions) const
{
    PODArray<AggregateDataPtr> places(rows);

    /// For all rows.
    for (size_t i = 0; i < rows; ++i)
    {
        AggregateDataPtr aggregate_data = nullptr;

        auto emplace_result = state.emplaceKey(method.data, i, *aggregates_pool);

        /// If a new key is inserted, initialize the states of the aggregate functions, and possibly something related to the key.
        if (emplace_result.isInserted())
        {
            /// exception-safety - if you can not allocate memory or create states, then destructors will not be called.
            emplace_result.setMapped(nullptr);

            aggregate_data = aggregates_pool->alignedAlloc(total_size_of_aggregate_states, align_aggregate_states);
            createAggregateStates(aggregate_data);

            emplace_result.setMapped(aggregate_data);
        }
        else
            aggregate_data = emplace_result.getMapped();

        places[i] = aggregate_data;
        assert(places[i] != nullptr);
    }

    /// Add values to the aggregate functions.
    for (AggregateFunctionInstruction * inst = aggregate_instructions; inst->that; ++inst)
    {
        if (inst->offsets)
            inst->batch_that->addBatchArray(rows, places.data(), inst->state_offset, inst->batch_arguments, inst->offsets, aggregates_pool);
        else
            inst->batch_that->addBatch(rows, places.data(), inst->state_offset, inst->batch_arguments, aggregates_pool);
    }

3. 小结

好了,到这里也就把ClickHouse聚合流程的代码梳理完了。 除了聚合计算外,其他的物理执行操作符也是同样通过流的方式依次对接处理的,源码阅读的步骤也可以参照笔者的分析流程来参考。

4. 参考资料

官方文档 ClickHouse源代码

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
可以指点一下AggregatingBlockInputStream和ParallelAggregatingBlockInputStream是在哪里决定调用哪一个stream的呢?我发现数据量大的时候会选择后者,但没找到具体是在哪决定调用的
可以指点一下AggregatingBlockInputStream和ParallelAggregatingBlockInputStream是在哪里决定调用哪一个stream的呢?我发现数据量大的时候会选择后者,但没找到具体是在哪决定调用的
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
ClickHouse源码笔记5:聚合函数的源码再梳理
话不多说,直接上代码,笔者这里会将所有聚合函数的核心接口代码全部列出,一一梳理各个部分:
HappenLee
2021/04/20
1K0
>>技术应用:clickhouse 帮助命令一览表
常见的列式数据库有: Vertica、 Paraccel (Actian Matrix,Amazon Redshift)、 Sybase IQ、 Exasol、 Infobright、 InfiniDB、 MonetDB (VectorWise, Actian Vector)、 LucidDB、 SAP HANA、 Google Dremel、 Google PowerDrill、 Druid、 kdb+。下面是clickhouse命令的帮助文档,当前CK的版本为:ClickHouse server /client version 22.3.1.1,其他版本酌情参考。
艾特
2023/10/10
1.6K0
Apache Doris 聚合函数源码阅读与解析|源码解读系列
聚合函数,顾名思义,即对一组数据执行聚合计算并返回结果的函数,在统计分析过程中属于最常见的函数之一,最典型的聚合函数包括 count、min、max、sum 等。基于聚合函数可以实现对大量数据的汇总计算,以更简洁的形式呈现数据并支持数据可视化。
SelectDB技术团队
2024/01/10
1.1K0
ClickHouse 查询优化详细介绍
作者:oliverdding,腾讯 CSIG 测试开发工程师 你想要的 ClickHouse 优化,都在这里。 ClickHouse 是 OLAP(Online analytical processing)数据库,以速度见长[1]。ClickHouse 为什么能这么快?有两点原因[2]: 架构优越 列式存储 索引 数据压缩 向量化执行 资源利用 关注底层细节 但是,数据库设计再优越也拯救不了错误的使用方式,本文以 MergeTree 引擎家族为例讲解如何对查询优化。 ClickHouse 查询执行过程 ⚠️
腾讯技术工程官方号
2023/01/09
2.7K1
ClickHouse 查询优化详细介绍
ClickHouse查询优化
ClickHouse是OLAP(Online analytical processing)数据库,以速度见长^clickhouse_bench。ClickHouse为什么能这么快?有两点原因^why_clickhouse_is_so_fast:
charmer
2022/11/14
2.6K0
ClickHouse查询优化
ClickHouse源码笔记1:聚合函数的实现
聚合函数: 顾名思义就是对一组数据执行聚合计算并返回结果的函数。 这类函数在数据库之中很常见,如:count, max, min, sum等等。
HappenLee
2020/06/02
3.3K2
ClickHouse源码笔记6:探究列式存储系统的排序
老规矩,咱们还是先从一个简单的查询出发,通过一步步的通过执行计划按图索骥ClickHouse的执行逻辑。
HappenLee
2021/07/01
1.1K0
ClickHouse和他的朋友们(15)Group By 为什么这么快
在揭秘 ClickHouse Group By 之前,先聊聊数据库的性能对比测试问题。在虎哥看来,一个“讲武德”的性能对比测试应该提供什么信息呢?
老叶茶馆
2021/02/23
1.4K0
​深入浅出 ClickHouse 物化视图
数据库查询语言(query language)是数据库管理系统(DBMS)提供给用户和数据库交互的工具,查询语言分为三类 [^1]:
腾讯技术工程官方号
2023/07/15
3K0
​深入浅出 ClickHouse 物化视图
ClickHouse源码笔记4:FilterBlockInputStream, 探寻where,having的实现
Selection是关系代数之中重要的一个的一个运算,通常也会用σ符合来selection的实现。
HappenLee
2021/03/01
1.2K0
ClickHouse 资源隔离
默认情况下,配额仅跟踪每小时的资源消耗,而没有限制使用情况。在每个请求之后,将为每个时间间隔计算的资源消耗输出到服务器日志。
jasong
2022/03/29
3.2K0
ClickHouse源码笔记3:函数调用的向量化实现
这里调用一个abs的函数,我们先打开ClickHouse的Debug日志看一下执行计划。(当前ClickHouse不支持使用Explain语句来查看执行计划,这个确实是很蛋疼的~~)
HappenLee
2021/02/22
2.3K0
[​DuckDB] 多核算子并行的源码解析
DuckDB 是近年来颇受关注的OLAP数据库,号称是OLAP领域的SQLite,以精巧简单,性能优异而著称。笔者前段时间在调研Doris的Pipeline的算子并行方案,而DuckDB基于论文《Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age》实现SQL算子的高效并行化的Pipeline执行引擎,所以笔者花了一些时间进行了学习和总结,这里结合了Mark Raasveldt进行的分享和原始代码来一一剖析DuckDB在执行算子并行上的具体实现。
HappenLee
2023/02/12
3.5K0
[​DuckDB] 多核算子并行的源码解析
ClickHouse Lambda 组合拳
https://en.cppreference.com/w/cpp/language/lambda
jasong
2022/03/08
1.2K0
ClickHouse和他的朋友们(4)Pipeline处理器和调度器
原文出处:https://bohutang.me/2020/06/11/clickhouse-and-friends-processor/
老叶茶馆
2020/11/03
1.8K0
ClickHouse和他的朋友们(4)Pipeline处理器和调度器
ClickHouse 24.5.3:全面解析与RPM单节点轻松部署!
ClickHouse是由俄罗斯的Yandex公司开发的开源列式数据库管理系统。它最早于2016年开源,主要用于实时数据分析。ClickHouse通过列存储、向量化执行、并行计算等技术,实现了对大规模数据集的快速查询和分析,特别适合实时数据分析和商业智能需求。
DBA实战
2024/09/06
3630
ClickHouse 24.5.3:全面解析与RPM单节点轻松部署!
TiFlash 源码解读(八)TiFlash 表达式的实现与设计
表达式是承载 SQL 大部分逻辑的一个重要部分。SQL 中的表达式和编程语言中的表达式并没有差异。表达式可以大致分为函数、常量、列引用。如 select a + 1 from table 中的 a + 1 是一个表达式,其中 + 是函数,1 是常量,a 是列引用。
PingCAP
2022/09/06
5450
【ClickHouse为什么这么快?】MergeTree 表存储引擎图文实例详解
ClickHouse 是俄罗斯最大的搜索引擎Yandex在2016年开源的数据库管理系统(DBMS),主要用于联机分析处理(OLAP)。其采用了面向列的存储方式,性能远超传统面向行的DBMS,近几年受到广泛关注。
一个会写诗的程序员
2021/12/16
2.2K0
【ClickHouse为什么这么快?】MergeTree 表存储引擎图文实例详解
【ClickHouse 极简教程-图文详解原理系列】ClickHouse 主键索引的存储结构与查询性能优化
这是 Alexey Milovidov(ClickHouse 的创建者)给出的关于复合主键的答案的翻译。 原文: https://groups.google.com/g/clickhouse/c/eUrsP30VtSU/m/p4-pxgdXAgAJ
一个会写诗的程序员
2022/03/07
3.7K0
【ClickHouse 极简教程-图文详解原理系列】ClickHouse 主键索引的存储结构与查询性能优化
聊聊flink Table的AggregateFunction
flink-table_2.11-1.7.1-sources.jar!/org/apache/flink/table/functions/AggregateFunction.scala
code4it
2019/02/09
2.9K0
聊聊flink Table的AggregateFunction
推荐阅读
相关推荐
ClickHouse源码笔记5:聚合函数的源码再梳理
更多 >
领券
社区新版编辑器体验调研
诚挚邀请您参与本次调研,分享您的真实使用感受与建议。您的反馈至关重要,感谢您的支持与参与!
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
首页
学习
活动
专区
圈层
工具
MCP广场
首页
学习
活动
专区
圈层
工具
MCP广场