【ClickHouse系列】如何解决 Too Many Parts 问题

文章讲述了在MergeTree表引擎中,当INSERT操作导致partition中的active或inactiveparts超过特定阈值时,系统会抛出异常或延迟INSERT。参数如`max_parts_in_total`,`parts_to_throw_insert`,`part_to_delay_insert`等控制这些行为。当INSERT被延迟时,其延迟时间由一个公式计算得出。解决方案包括调整设置、改变插入频率和批量大小,以及启用WAL模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

几个相关参数

max_parts_in_total

如果一个table的所有partition中的active的part总数超过该值,会在INSERT时报Too many parts异常

默认值:100000

parts_to_throw_insert

如果单个partition中active的part总数超过该值,会在INSERT时报Too many parts异常

默认值:300

part_to_delay_insert

如果单个partition中active的part总数超过该值,则INSERT人为地减慢

默认值:150

inactive_parts_to_throw_insert

如果单个partition中inactive的part总数超过该值,会在INSERT时报Too many inactive parts 异常

默认值:0(无限制)

inactive_parts_to_delay_insert

如果表中单个partition中的inactive的part总数达到该值,则`INSERT会被减慢

默认值:0(无限制)

max_delay_to_insert

以秒为单位的值,用于计算INSERT延迟

默认值:1

计算公式如下:

max_k = parts_to_throw_insert - parts_to_delay_insert
k = 1 + parts_count_in_partition - parts_to_delay_insert
delay_milliseconds = pow(max_delay_to_insert * 1000, k / max_k)

例如,如果一个partition有 299 个active的part,parts_to_throw_insert = 300,parts_to_delay_insert = 150,max_delay_to_insert = 1,INSERT会延迟pow( 1 * 1000, (1 + 299 - 150) / (300 - 150) ) = 1000毫秒。

相关代码

在数据写入前会调用writePrefix()检查是否要抛Too many parts异常

void MergeTreeBlockOutputStream::writePrefix()
{
    /// Only check "too many parts" before write,
    /// because interrupting long-running INSERT query in the middle is not convenient for users.
    storage.delayInsertOrThrowIfNeeded();
}

MergeTree系列的实现是如下方法

void MergeTreeData::delayInsertOrThrowIfNeeded(Poco::Event * until) const
{
    const auto settings = getSettings();
    const size_t parts_count_in_total = getPartsCount();
    
    /// max_parts_in_total默认100000
    if (parts_count_in_total >= settings->max_parts_in_total)
    {
        ProfileEvents::increment(ProfileEvents::RejectedInserts);
        throw Exception("Too many parts (" + toString(parts_count_in_total) + ") in all partitions in total. This indicates wrong choice of partition key. The threshold can be modified with 'max_parts_in_total' setting in <merge_tree> element in config.xml or with per-table setting.", ErrorCodes::TOO_MANY_PARTS);
    }

    size_t parts_count_in_partition = getMaxPartsCountForPartition();
    ssize_t k_inactive = -1;
    if (settings->inactive_parts_to_throw_insert > 0 || settings->inactive_parts_to_delay_insert > 0)
    {
        size_t inactive_parts_count_in_partition = getMaxInactivePartsCountForPartition();
        if (inactive_parts_count_in_partition >= settings->inactive_parts_to_throw_insert)
        {
            ProfileEvents::increment(ProfileEvents::RejectedInserts);
            throw Exception(
                ErrorCodes::TOO_MANY_PARTS,
                "Too many inactive parts ({}). Parts cleaning are processing significantly slower than inserts",
                inactive_parts_count_in_partition);
        }
        k_inactive = ssize_t(inactive_parts_count_in_partition) - ssize_t(settings->inactive_parts_to_delay_insert);
    }

    /// parts_to_throw_insert默认300,单partition中的part超过该值会报错
    if (parts_count_in_partition >= settings->parts_to_throw_insert)
    {
        ProfileEvents::increment(ProfileEvents::RejectedInserts);
        throw Exception(
            ErrorCodes::TOO_MANY_PARTS,
            "Too many parts ({}). Parts cleaning are processing significantly slower than inserts",
            parts_count_in_partition);
    }

    if (k_inactive < 0 && parts_count_in_partition < settings->parts_to_delay_insert)
        return;

    /// 根据active的和inactive的part来计算要将下一个insert延迟多久
    const ssize_t k_active = ssize_t(parts_count_in_partition) - ssize_t(settings->parts_to_delay_insert);
    size_t max_k;
    size_t k;
    if (k_active > k_inactive)
    {
        max_k = settings->parts_to_throw_insert - settings->parts_to_delay_insert;
        k = k_active + 1;
    }
    else
    {
        max_k = settings->inactive_parts_to_throw_insert - settings->inactive_parts_to_delay_insert;
        k = k_inactive + 1;
    }
    const double delay_milliseconds = ::pow(settings->max_delay_to_insert * 1000, static_cast<double>(k) / max_k);

    ProfileEvents::increment(ProfileEvents::DelayedInserts);
    ProfileEvents::increment(ProfileEvents::DelayedInsertsMilliseconds, delay_milliseconds);

    CurrentMetrics::Increment metric_increment(CurrentMetrics::DelayedInserts);

    LOG_INFO(log, "Delaying inserting block by {} ms. because there are {} parts", delay_milliseconds, parts_count_in_partition);

    /// 通过sleep来减缓insert速度
    if (until)
        until->tryWait(delay_milliseconds);
    else
        std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<size_t>(delay_milliseconds)));
}

解决方法

  1. 增加parts_to_throw_insert,有时候业务高峰时会将单partition中的part撑到几百上千后就不会再增长了,所以可以通过调大parts_to_throw_insert来解决;
  2. 业务方减小频次、增大每批数据总数,即大批量小频率写数据;
  3. 业务方insert时尽量根据partition排序写入,避免每个insert都包含很多parttition,若partition过多,会生成大于等于分区数都part,虽然数据量增大了,频率低了,但是也不能对too many part有太多缓解。
  4. 开启wal模式,减少小part的生成
clickhouse TOO_MANY_UNEXPECTED_DATA_PARTS错误是指在ClickHouse数据库中出现了过多的意外数据分区。这种错误通常发生在进行数据插入或合并操作时,由于某些原因导致系统无法正确处理数据分区。解决这个问题的方法主要包括以下几步: 1. 首先,需要查看ClickHouse运行日志,以确定具体的错误信息和堆栈跟踪。错误信息可能会提供一些关于出现这个错误的原因的线索。 2. 检查系统的硬盘空间和可用内存情况。如果硬盘空间不足或可用内存不够,可能会导致数据分区失败。确保系统有足够的资源来处理数据分区操作。 3. 如果出现TOO_MANY_UNEXPECTED_DATA_PARTS错误,可以尝试使用ClickHouse提供的修复工具来解决问题。可以使用`CLEANUP`语句来清理无效的数据分区,或使用`OPTIMIZE`语句来优化表的物理结构。 4. 如果以上方法无法解决问题,可以考虑重新创建表并重新加载数据。备份数据后,可以尝试删除原来的表,并使用相同的表结构重新创建表。然后将备份的数据文件复制到新表的数据目录中。 5. 确保ClickHouse的配置文件中设置了合适的参数,如max_memory_usage_for_all_queries和max_partitions_per_insert等参数。这些参数可以控制ClickHouse的内存使用和数据分区情况,避免出现过多的意外数据分区。 综上所述,解决clickhouse TOO_MANY_UNEXPECTED_DATA_PARTS错误的方法包括查看运行日志、检查系统资源、使用修复工具、重新创建表并重新加载数据,以及配置合适的参数来控制内存使用和数据分区。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [ClickHouse 问题总结(持续更新)](https://blog.csdn.net/qq_40694671/article/details/122516999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只努力的微服务

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值