ClickHouse问题分析删除系统表时卡住,长时间不恢复

网友投稿 2576 2022-05-29

问题现象

使用drop table system.query_log sync时,发现客户端一直卡住,长时间无法恢复。日志打印如下:

DatabaseCatalog: Waiting for table bd5888b9-a84c-41a8-bc7e-cefadf81cc43 to be finally dropped

进一步试验发现,如果我在卡住期间通过http连接后执行新的sql,卡住的命令就能正常执行了。但是,如果没有新的sql执行,会一直卡住。

问题分析

1、先了解下drop table xxx sync的流程。

# drop table system.query_log sync InterpreterDropQuery::execute executeToTable executeToTableImpl checkTableCanBeDropped StorageMergeTree::shutdown DatabaseAtomic::dropTable detachTableUnlocked tryRemoveSymlink enqueueDroppedTableCleanup if no delay tables_marked_dropped.push_front // 放在list的开头 else tables_marked_dropped.push_back // 默认8min后删除 tables_marked_dropped_ids.insert // 在set中存入需要删除的表 (*drop_task)->schedule() // dropTableDataTask 在DatabaseCatalog::loadDatabases中初始化 if no delay // 有sync或者no delay或者database_atomic_wait_for_drop_and_detach_synchronously为true waitForTableToBeActuallyDroppedOrDetached // 等待table被实际删除 waitTableFinallyDropped // wait_table_finally_dropped.wait // wait_table_finally_dropped 被通知,且 tables_marked_dropped_ids 中没有此表 # drop table的后台线程 DatabaseCatalog::dropTableDataTask // 只有不再使用且drop时间早于当前时间的table,才能被删除 if table 可以被删 tables_marked_dropped.erase dropTableFinally StorageMergeTree::drop // 删数据 shutdown // 直接就return dropAllData // 真正开始删除了 clearPartsFromFilesystem // 文件系统 disk->removeRecursive // 磁盘 remove // 删元数据 removeUUIDMappingFinally // 删uuid映射 tables_marked_dropped_ids.erase wait_table_finally_dropped.notify_all() // 通知不再wait if 还有表需要被删 dropTableDataTask // 继续删表,否则不再调度

从流程上可以知道:

ClickHouse问题分析:删除系统表时卡住,长时间不恢复

(1)如果不加sync(即no delay),则默认是至少8min后才会真正删除数据。而在添加了sync后,则将需要删除的表放在list的开头,也不用等待8min。

(2)drop的主流程,也不会真正删除表,而是存入一个tables_marked_dropped中,由后台线程dropTableDataTask来操刀。如果设置了sync,drop的主流程,会等待后台线程的通知,然后才会返回客户端删除成功。

(3)真正删除时,需要保证该table没有流程在使用,通过查看该table的shared_ptr是否是unique来确定。

(4)每次只会删除一张表。如果还有待删除的表,则会继续,不需再等待。如果没有表需要删除,则不在触发此任务。

(5)每次执行完drop table后,其实都会触发query_log的记录,进而重建该表,因此,删除query_log是没有意义的。

2、再了解下query_log的流程

# query_log的机制 # query_log建立过程 Server::main global_context->initializeSystemLogs SystemLogs::SystemLogs // 初始化各个system log,包括query log createSystemLog // 根据config中的配置生成 SystemLog::SystemLog logs.emplace_back log->startup() // 启动log的后台线程 savingThreadFunction # query log的添加 SystemLog::add // 一般用法,context.getQueryLog; query_log->add queue.push_back # query log的监控线程 SystemLog::savingThreadFunction flush_event.wait_for // 等待超时,一定时间刷新一次 flushImpl // queue中的log写到query_log表 prepareTable // 有表,则要比较列信息是否有变,变化了则需要重建,旧表重命名为query_log_N。没有表,则新建 write // 数据写入

这里比较特别的地方是:

(1)query_log在有log记录的时候,如果发现没有表的话,会走创建表的流程。如果已经有表了,则会比较列的结构是否一致,不一致的话,需要重建表,并将旧表重命名。

(2)如果在config.xml中修改表级的TTL,即使重启server,query_log也是不会变化的,因为已经存在该表,且表的列信息是一样的。如要让它生效,则需要删除query_log(会自动重建),或者采用ALTER TABLE的方式。这也是为什么第一次在config.xml中配置query_log时可以生效,而后续修改或者配置时又无法生效的原因。

3、问题分析

结合删除表时会卡住的时候报错以及正常流程时应该要有的日志(Removing metadata {} of dropped table),可知,是在dropTableDataTask中判断table是否没有地方在使用时出现了问题。

auto it = std::find_if(tables_marked_dropped.begin(), tables_marked_dropped.end(), [&](const auto & elem) { bool not_in_use = !elem.table || elem.table.unique(); bool old_enough = elem.drop_time <= current_time; min_drop_time = std::min(min_drop_time, elem.drop_time); tables_in_use_count += !not_in_use; return not_in_use && old_enough; });

即elem.table.unique()不满足。

然后,梳理整个流程和现象(在卡住期间通过http连接后执行新的sql,卡住的命令就能正常执行了)可以推理出,应该去看添加新log的流程。

template void SystemLog::prepareTable() { String description = table_id.getNameForLogs(); table = DatabaseCatalog::instance().tryGetTable(table_id, context); if (table) { ... } if (!table) { /// Create the table. ... table = DatabaseCatalog::instance().getTable(table_id, context); } }

在prepareTable中,得到了table的副本,而且一直没有释放,elem.table.unique()就一直无法满足。只有在新添加日志的时候,table变量被覆盖,原来的query_log的智能指针就被释放了,从而卡住的流程可以继续。

ClickHouse

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:有兴趣学学MAXScript-1
下一篇:UML类图常用用法
相关文章