clickhouse 中 ReplicasMaxAbsoluteDelay 的计算

小白上路,如有错误,还请指正,谢谢。

一、 问题背景

最近偶尔会收到延迟超时的告警,随后恢复

时间:2022.09.30-10:04:48

replication lag across all tables (ads_ch03:ch_params[ReplicasMaxAbsoluteDelay]): 52y 9m 15d

历史告警日期如下

      可以看到ReplicasMaxAbsoluteDelay的时间,随着告警发送的日期在增长,但是始终是52年左右。微信群里的朋友发现52年前刚好是1970年,即unix的起始时间附近。

       因此大致可以猜测这个值是currenttime减去unix起始时间的秒数,表示一个无穷大而不是真正的延迟。

二、 源码分析

       其实本来是想在网上直接搜资料的,但实在太少了,只能搜ClickHouse ReplicasMaxAbsoluteDelay source code看看。

1. AsynchronousMetrics.cpp文件

       可以查到设置ReplicasMaxAbsoluteDelay的入口函数在AsynchronousMetrics.cpp文件,也就是它对应的系统表名字。

// 循环检查各db
for (const auto & db : databases)
        {
            // Check if database can contain MergeTree tables
             // 检查db中是否可以包含MergeTree引擎表,若不能则跳到下个db
            if (!db.second->canContainMergeTreeTables())
                continue;

             // 循环检查db中每个表
            for (auto iterator = db.second->getTablesIterator(getContext()); iterator->isValid(); iterator->next())
            {
                ++total_number_of_tables;
                …
             // 如果是ReplicatedMergeTree引擎表
                if (StorageReplicatedMergeTree * table_replicated_merge_tree = typeid_cast<StorageReplicatedMergeTree *>(table.get()))
                {
                    StorageReplicatedMergeTree::Status status;
                    table_replicated_merge_tree->getStatus(&status, false);
                    …
             // 如果不是只读状态
                    if (!status.is_readonly)
                    {
                        try
                        {
                            time_t absolute_delay = 0;
                            time_t relative_delay = 0;
                     // 这步是主要函数,给两个变量赋值,下面会详细看
                            table_replicated_merge_tree->getReplicaDelays(&absolute_delay, &relative_delay);
                     // calculateMax是个获取最大值的函数:比较max_absolute_delay与absolute_delay,较大者存入max_absolute_delay
                            calculateMax(&max_absolute_delay, absolute_delay);
                            calculateMax(&max_relative_delay, relative_delay);
                        }
                        catch (...)
                        {
                            tryLogCurrentException(__PRETTY_FUNCTION__,
                                "Cannot get replica delay for table: " + backQuoteIfNeed(db.first) + "." + backQuoteIfNeed(iterator->name()));
                        }
                    }
                }
            }
        }
        
…
// 循环获取到每个库每个表的max_absolute_delay后,赋给ReplicasMaxAbsoluteDelay
        new_values["ReplicasMaxAbsoluteDelay"] = max_absolute_delay;

 很简单的calculateMax函数

static void calculateMax(Max & max, T x)
{
    if (Max(x) > max)
        max = x;
}

2. getReplicaDelays函数

        前面的getReplicaDelays(&absolute_delay, &relative_delay); 主要是调用该函数,并给absolute_delay和relative_delay变量赋值,这里我们只看absolute_delay。

void StorageReplicatedMergeTree::getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay)
{
    assertNotReadonly();
    time_t current_time = time(nullptr);
    out_absolute_delay = getAbsoluteDelay();
    out_relative_delay = 0;
…
}

         首先执行一个assertNotReadonly,判断表非只读

void StorageReplicatedMergeTree::assertNotReadonly() const
{
    if (is_readonly)
        throw Exception(ErrorCodes::TABLE_IS_READ_ONLY, "Table is in readonly mode (replica path: {})", replica_path);
}

然后是主要的,执行getAbsoluteDelay函数,获取延迟值。

3. getAbsoluteDelay函数

time_t StorageReplicatedMergeTree::getAbsoluteDelay() const
{
    // 队列中未处理日志的最早插入时间
    time_t min_unprocessed_insert_time = 0;
    // 队列中已处理日志的最大插入时间
    time_t max_processed_insert_time = 0;
    // 获取这两个变量值
    queue.getInsertTimes(&min_unprocessed_insert_time, &max_processed_insert_time);

    // Load start time, then finish time to avoid reporting false delay when start time is updated between loading of two variables.
    // 获取队列更新的开始及结束时间
    time_t queue_update_start_time = last_queue_update_start_time.load();
    time_t queue_update_finish_time = last_queue_update_finish_time.load();

// 当前时间,返回的是从纪元开始(1970-01-01)至今的秒数
    time_t current_time = time(nullptr);
    if (!queue_update_finish_time)
    {
        /// We have not updated queue even once yet (perhaps replica is readonly). As we have no info about the current state of replication log, return effectively infinite delay.

        /// 看到一篇文章的解释是:如果队列最近一次的更新一直没结束,表示正在向当前队列中加操作日志,则认为延迟时间是无穷大
return current_time;
    }
    else if (min_unprocessed_insert_time)
    {
        /// There are some unprocessed insert entries in queue. 
/// 如果队列中有尚未处理的日志,且其时间早于当前时间,则输出两者差值作为delay;否则,说明是后生成的,delay记为0.
        return (current_time > min_unprocessed_insert_time) ? (current_time - min_unprocessed_insert_time) : 0;
    }
/// 如果队列更新开始时间大于结束时间
    else if (queue_update_start_time > queue_update_finish_time)
    {
        /// Queue is empty, but there are some in-flight or failed queue update attempts (likely because of problems with connecting to ZooKeeper).
        /// Return the time passed since last attempt.
        /// 说明队列为空,但有一些正在运行或失败的队列更新尝试(可能是由于连接到ZooKeeper时出现问题)。
        // 返回自上次尝试后经过的时间。
        return (current_time > queue_update_start_time) ? (current_time - queue_update_start_time) : 0;
    }
    else
    {
        /// Everything is up-to-date. 否则,说明没有延迟
        return 0;
    }
}

4. queue_update_finish_time的计算

上面函数中只简单提到了

time_t queue_update_finish_time = last_queue_update_finish_time.load();

其定义在

    /** The queue of what needs to be done on this replica to catch up with everyone. It is taken from ZooKeeper (/replicas/me/queue/).
     * In ZK entries in chronological order. Here it is not necessary.
     */
    ReplicatedMergeTreeQueue queue;
    std::atomic<time_t> last_queue_update_start_time{0};
    std::atomic<time_t> last_queue_update_finish_time{0};

找到一张图

在这里插入图片描述

可以看到,其计算主要在queueUpdatingTask函数

void StorageReplicatedMergeTree::queueUpdatingTask()
{
    if (!queue_update_in_progress)
    {
        last_queue_update_start_time.store(time(nullptr));
        queue_update_in_progress = true;
    }
    try
    {
        queue.pullLogsToQueue(getZooKeeperAndAssertNotReadonly(), queue_updating_task->getWatchCallback(), ReplicatedMergeTreeQueue::UPDATE);
        last_queue_update_finish_time.store(time(nullptr));
        queue_update_in_progress = false;
    }
    catch (const Coordination::Exception & e)
    {
    ...
    }
    catch (...)
    {
    ...
    }
}

根据chatgpt的介绍:

queue.pullLogsToQueue(getZooKeeperAndAssertNotReadonly(), queue_updating_task->getWatchCallback(), ReplicatedMergeTreeQueue::UPDATE);

queue.pullLogsToQueue 函数用于从日志表中获取更新任务,并将其添加到队列中。具体来说,该函数会检查当前副本是否处于只读模式,如果是,则直接返回;否则,使用 ZooKeeper 客户端对象从日志表中获取更新任务,并将其添加到队列中。

在调用 queue.pullLogsToQueue 函数时可能出现以下异常:

  • 如果当前副本处于只读模式,会抛出 Exception 异常。
  • 如果无法获取到 ZooKeeper 客户端对象,会抛出 zkutil::KeeperException 异常。
  • 如果从日志表中获取更新任务失败,会抛出 Exception 异常。

此外,在使用客户端对象操作ZooKeeper时,还可能遇到网络连接问题或ZooKeeper服务器响应超时等问题,这些情况也有可能导致 zkutil::KeeperException 异常。

last_queue_update_finish_time.store(time(nullptr));

last_queue_update_finish_time 表示最近一次更新操作完成的时间。

time(nullptr) 函数返回当前系统时间(以秒为单位),用于更新 last_queue_update_finish_time 的值。通过调用 store 函数将新的时间戳写入到原子变量中,在这个过程中使用了锁,以保证多线程环境下的原子性和可见性。

这个时间戳可以用来计算当前更新操作的延迟,具体来说,可以通过与最近一次更新完成时间的比较,得到当前更新操作等待的时间长度,从而判断该更新操作是否需要加速执行。同时,由于该变量会被多个线程访问,因此需要使用原子变量来确保线程安全。

        因此可以猜测,当queue.pullLogsToQueue 函数抛异常时,会跳过last_queue_update_finish_time的计算,导致其值为0。具体可能包括:

  • 当前副本处于只读模式
  • 无法获取到 ZooKeeper 客户端对象
  • 从日志表中获取更新任务失败

三、 遗留问题

        至此,我们清楚了ReplicasMaxAbsoluteDelay返回52年的原因,以及可能导致queue_update_finish_time0的场景。但结合到实例监控

  • clickhousezookeeper的日志中目前没发现报错
  • Read-Only Replicas监控也一直显示为0

  • 队列中的任务数也很少

       没有发现符合上述问题的具体场景,且从实际来看业务没反馈过有什么影响,或许可以改为连续出现两次或多次才告警?

       希望熟悉clickhouse的朋友指点指点~

参考

AsynchronousMetrics.cpp source code [ClickHouse/src/Interpreters/AsynchronousMetrics.cpp] - Woboq Code Browser

ReplicatedMergeTreeQueue.h source code [ClickHouse/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h] - Woboq Code Browser

StorageReplicatedMergeTree.cpp source code [ClickHouse/src/Storages/StorageReplicatedMergeTree.cpp] - Woboq Code Browser

StorageReplicatedMergeTree.h source code [ClickHouse/src/Storages/StorageReplicatedMergeTree.h] - Woboq Code Browser

https://www-programmersought-com.translate.goog/article/56754023733/?_x_tr_sl=en&_x_tr_tl=zh-CN&_x_tr_hl=zh-CN&_x_tr_pto=sc

https://www.dounaite.com/article/62c907c8f4ab41be4873aadd.html

Clickhouse ReplicatedMergeTree 后台任务的工作原理-pudn.com

【ClickHouse源码】ReplicatedMergeTree之数据同步流程_一只努力的微服务的博客-CSDN博客

chatgpt的回答

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hehuyi_In

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

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

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

打赏作者

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

抵扣说明:

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

余额充值