ScyllaDB Task Manager 深度指南:用 REST API 与 Nodetool 追踪、诊断和终止后台任务
导读
ScyllaDB 的 Task Manager 是一套统一的长时运行后台任务追踪框架:无论 compaction(压缩)、repair(修复)、还是 decommission / bootstrap / replace / rebuild 等节点运维操作,都被抽象为"任务"(task),以树状结构组织,并通过 REST API 与 nodetool 命令对外暴露。阅读完本文,你将掌握 Task Manager 的核心概念(模块、任务树、节点任务与集群任务、内部任务、TTL 生命周期),熟悉 task_stats / task_status 两种数据结构,能够熟练使用 /task_manager/* 系列 REST 端点查询任务状态、等待任务完成、中止可中止任务并调整 TTL,还能通过 nodetool tasks 系列命令把同步操作转成可异步追踪的版本。
一、Task Manager 是什么:模块与任务树
Task Manager 追踪 ScyllaDB 中的长时运行后台操作。它被划分为若干模块(module),每个模块负责追踪一类用途相近的操作(例如 compaction)。操作及其组成部分用任务表示:
- 任务形成一棵树:根任务(root task)覆盖整个操作(如一次 compaction),其子任务覆盖子操作(如单个表的 compaction),以此类推。
从源码结构看,任务树的父子关系由 tasks/task_manager.hh 中的 children 类承载:_children 保存仍在运行的子任务(foreign_task_map),_finished_children 保存已结束子任务的摘要(task_essentials),并用 rwlock 保护并发访问;父任务的进度由 get_progress() 递归聚合所有子任务的进度得出(tasks/task_manager.cc 第 69-85 行)。
模块的注册与查找由 task_manager::make_module() / find_module() 实现(tasks/task_manager.cc 第 721-733 行),每个模块维护自己的本地任务表 _local_tasks 和虚拟任务表 _virtual_tasks。从 compaction/task_manager_module.cc 可以看到 compaction 模块用 make_and_start_task<..._task_impl>(parent_info, ...) 创建并立即启动任务——父任务信息(parent_info)会决定新任务挂在任务树的哪个位置,并继承父任务的 sequence number(tasks/task_manager.cc 第 617-642 行的 make_task)。
任务类型:node 与 cluster
任务分为两类:
- Node 任务:只在单个节点上执行的操作。查看其统计信息时,需要向特定节点请求状态。
- Cluster 任务:横跨多个节点的操作,从集群中所有节点上都可见。
两类任务的重要规则:
- Cluster 任务不能有父任务(即不能成为任务树中的子节点);
- Cluster 任务可以在多个节点上拥有子任务;
- 即使集群操作仍在运行,某个 cluster 任务的子任务状态也可能暂时无法访问(例如子任务所在的节点与当前查询节点通信受限时)。
在代码层面,任务种类由 tasks/task_manager.hh 中的 enum class task_kind { cluster, node } 表示;cluster 任务通常以"虚拟任务(virtual_task)"的形式实现——例如 node_ops 模块的虚拟任务通过 RPC(ser::tasks_rpc_verbs::send_tasks_get_children)向集群各节点收集子任务(tasks/task_manager.cc 第 399-433 行)。
内部任务(internal task)
任务可能是内部任务,意味着:
- 它有父任务,或
- 由某个内部进程启动。
默认情况下,API 调用会跳过内部任务(只有显式传入 internal=true 参数时才列出)。Cluster 任务不能是内部任务。
内部任务的特殊生命周期:
- 结束后立即从 task manager 注销;
- 如果它有非 cluster 的父任务,其状态会被折叠(fold)进父任务,只能通过父任务访问;
- 已结束任务的间接后代(如子任务的子任务)的状态不可见,除非它们失败了——失败子任务会保留下来以便诊断。
折叠逻辑见 tasks/task_manager.cc 第 225-245 行的 maybe_fold_into_parent():当任务是内部任务、父任务为本节点任务且所有子任务均结束时,会把自身的 task_essentials(含状态、进度、失败子任务链)合并进父任务,然后注销。get_failed_children()(第 186-195 行)则递归收集失败的子任务,保证"失败信息不丢失"。
二、任务的 TTL 与保留时间
非内部、非 cluster 的任务在结束后会在 task manager 中保留一段时间:
- 内部启动的任务保留
task_ttl秒; - 用户启动的任务保留
user_task_ttl秒; - Cluster 任务在 task manager 中的可见时长未作规定。
task_ttl 的取值方式有两种:
- 通过配置参数
task_ttl_in_seconds; - 通过 REST API
/task_manager/ttl。
user_task_ttl 对应 user_task_ttl_in_seconds 参数与 /task_manager/user_ttl API。
默认值与动态更新:在 db/config.cc 中定义:
task_ttl_in_seconds:默认 0(liveness::LiveUpdate,支持热更新),注释为"内部启动的已结束任务信息在内存中保留的时间";user_task_ttl_in_seconds:默认 3600 秒(1 小时),同样支持热更新。
从实现看,task manager 构造函数用这两个配置值初始化 _task_ttl / _user_task_ttl(tasks/task_manager.cc 第 644-653 行);而任务结束后的注销时机在 task::start() 中处理(第 310-337 行):任务完成后,会 sleep_abortable(user_task ? user_task_ttl : task_ttl) 等待 TTL 到期再调用 unregister_task 注销任务——用户任务默认保留 1 小时,内部任务默认(TTL=0)完成后立即注销。
TTL 的运行时更新通过 api/task_manager.cc 第 218-246 行实现:cfg.task_ttl_seconds.set_value_on_all_shards(ttl, config_source::API) 将新值同步到所有 shard,实现无重启生效。
三、Task Manager REST API 详解
Task Manager 提供一组 REST 端点,Swagger 规范见 api/api-doc/task_manager.json,实现见 api/task_manager.cc。先介绍返回的数据结构,再逐一讲解端点。
3.1 数据结构
API 返回的任务数据存放在两种结构中:task_stats 和 task_status。
task_stats(轻量统计)
字段定义于 tasks/types.hh 的 task_stats:
| 字段 | 说明 |
|---|---|
task_id |
唯一任务 ID(UUID) |
type |
任务类型,如 offstrategy compaction |
kind |
任务种类:per node(节点级)或 cluster(集群级) |
scope |
操作范围,如 keyspace、range |
state |
任务状态之一:created、running、done、failed |
sequence_number |
操作序号(按模块递增),同一棵树中的所有任务共享;对 cluster 任务无意义 |
keyspace |
可选,任务作用的 keyspace 名称 |
table |
可选,任务作用的表名称 |
entity |
可选,任务相关的附加信息 |
shard |
可选,任务作用的 shard ID |
start_time |
仅当 state != created 时有意义 |
end_time |
仅当任务已结束(state 为 done 或 failed)时有意义 |
注意:任务状态枚举在源码中实际上还有第五个取值 suspended(tasks/task_manager.hh 第 105-111 行 enum class task_state),Swagger 规范同样列出 created / running / done / failed / suspended 五种,文档正文列出的四种是 API 常用状态。
task_status(完整状态)
包含 task_stats 的全部字段,并额外增加:
| 字段 | 说明 |
|---|---|
is_abortable |
该任务是否可通过 API 中止 |
error |
仅当任务失败时有意义,存放错误信息 |
parent_id |
仅当任务有父任务时有意义 |
progress_units |
进度单位描述 |
progress_total |
以 progress_units 计的总工作量 |
progress_completed |
以 progress_units 计的当前完成量 |
children_ids |
子任务 ID 及所在节点的配对列表 |
task_status 的 JSON 序列化在 api/task_manager.cc 第 38-73 行 make_status() 中完成:子任务列表会通过 gossiper 的地址映射把 host_id 翻译成节点地址(ident.node),parent_id 为空时输出字符串 "none"。进度数据(progress_total / progress_completed)来自 tasks/task_manager.hh 中的 task::progress 结构(completed / total 两个 double 值)。
3.2 API 端点速查
| 端点 | 方法 | 说明 |
|---|---|---|
/task_manager/list_modules |
GET | 列出 task manager 支持的所有模块 |
/task_manager/list_module_tasks/{module} |
GET | 列出某模块下的任务 |
/task_manager/task_status/{task_id} |
GET | 获取单个任务的状态 |
/task_manager/abort_task/{task_id} |
POST | 中止任务(不可中止时返回 403) |
/task_manager/wait_task/{task_id} |
GET | 等待任务结束并返回其状态 |
/task_manager/task_status_recursive/{task_id} |
GET | 以 BFS 顺序返回该任务及其所有后代的状态 |
/task_manager/ttl |
GET/POST | 读取或设置新的 task ttl |
/task_manager/user_ttl |
GET/POST | 读取或设置新的 user ttl |
/task_manager/drain/{module} |
POST | 注销模块内所有已结束的本地任务 |
注意:cluster 任务不会通过 API 调用从 task manager 注销(其生命周期由集群拓扑状态机管理)。
下面逐个说明关键端点的参数与行为。
/task_manager/list_modules
无参数,返回模块名称数组。实现上直接取 tm.local().get_modules() 的所有 key(api/task_manager.cc 第 93-96 行)。
/task_manager/list_module_tasks/{module}
查询参数:
internal:布尔值,设为 true 时列出内部任务,默认 false;keyspace:若设置,仅返回作用于该 keyspace 的任务;table:若设置,仅返回作用于该表的任务。
对应实现(第 98-148 行):先通过 find_module(module) 找到模块(模块不存在时抛出 bad_param_exception),再调用 module->get_stats(internal, filter) 聚合所有 shard 上的任务;过滤逻辑同时匹配 keyspace 与 table。注意 tasks/task_manager.cc 第 543-575 行 get_stats 的实现细节:内部任务默认被跳过,且模块的虚拟任务(如 node_ops、repair 的集群级任务)只在 shard 0 上聚合。
/task_manager/task_status/{task_id}
路径参数 task_id 为任务 UUID。任务不存在时返回参数错误(bad_param_exception)。实现经由 task_handler::get_status()(见 tasks/task_handler.hh 中的 task_handler 类)完成。
/task_manager/abort_task/{task_id}
POST 中止任务。若任务不可中止,返回 403 Forbidden(api/task_manager.cc 第 162-173 行捕获 tasks::task_not_abortable 并映射为 http::reply::status_type::forbidden)。中止是可递归的:task::impl::abort()(tasks/task_manager.cc 第 170-176 行)在请求自身 abort source 后,会调用 abort_children() 在所有 shard 上查找并中止所有直接子任务(第 151-168 行),形成级联中止。
/task_manager/wait_task/{task_id}
等待任务完成并返回其最终状态。查询参数:
timeout:等待超时秒数;若设置了且等待超时,返回 408 Request Timeout。
实现(第 175-191 行):未设置 timeout 时无限等待;超时异常 timed_out_error 被映射为 408。源码中 task::impl::done() 通过 shared_promise<> _done 的 future 让调用方等待任务终结(tasks/task_manager.hh 第 212-214 行)。
/task_manager/task_status_recursive/{task_id}
以 BFS 顺序返回任务本身及所有后代(含跨节点子任务)的状态数组。实现调用 task_handler::get_status_recursively(true)(api/task_manager.cc 第 193-216 行),输出流式 JSON 数组。对于 cluster 虚拟任务,后代收集会通过 RPC 向集群中相关节点递归查询(见 tasks/task_manager.cc 第 403-433 行)。
/task_manager/ttl 与 /task_manager/user_ttl
- GET:返回当前 TTL 值;
- POST:设置新 TTL(分别通过查询参数
ttl/user_ttl传入秒数),并返回旧值。
两个端点都调用 cfg.task_ttl_seconds / cfg.user_task_ttl_seconds 的 set_value_on_all_shards() 将新值广播到所有 shard(api/task_manager.cc 第 218-246 行)。POST 成功即表示新 TTL 已全局生效,无需重启节点。
/task_manager/drain/{module}
POST 注销指定模块内所有已结束的本地任务(is_complete() 为 true 的任务),相当于手动清理内存中的历史记录;对仍在运行的任务无影响。实现见第 248-272 行:在 invoke_on_all 中遍历模块本地任务表,只注销 done / failed 状态的任务,并 maybe_yield() 避免阻塞事件循环。该端点不作用于 cluster 任务。
四、node_ops 模块:追踪节点运维操作
Task Manager 中有一个名为 node_ops 的模块,专门追踪节点级运维操作:
- decommission(节点下线)
- removenode(移除节点)
- bootstrap(节点引导/加入集群)
- replace(替换节点)
- rebuild(重建数据)
关于该模块的关键行为:
type字段标识具体操作,取值为decommission、remove node、bootstrap、replace、rebuild之一;scope和kind字段均设置为cluster(即 node_ops 任务是集群级任务);entity字段在操作未结束前保存被操作节点的 host id;对replace操作,保存的是替换节点的 host id;decommission和remove node任务是可中止的,但仅限在它们完成 tablet 迁移之前。
源码印证(node_ops/task_manager_module.cc):
request_type_to_task_type()(第 24-45 行)把拓扑请求映射为任务类型:join → "bootstrap"、remove → "remove node"、leave → "decommission";get_task_stats()(第 63-79 行)明确把kind置为task_kind::cluster、scope置为"cluster",并把entity设为 hint 中的 node_id;is_abortable()(第 148-150 行)返回is_abortable(hint.node_id.has_value())——即只有携带了明确节点标识(尚未完成 tablet 迁移)的请求才可中止,与文档描述一致。
node_ops 以虚拟任务(virtual_task)形式实现:它不把任务常驻在单节点内存中,而是每次查询时从系统 keyspace 的拓扑请求记录(system_keyspace::topology_requests_entry)动态还原任务状态(第 47-57 行的 get_state()),从而保证集群内所有节点都能看到同一份 cluster 任务视图。这也解释了为什么"cluster 任务不会被 API 注销"——它们的生命周期与集群拓扑状态机绑定,而非单节点内存。
五、Tasks API:把同步命令变成异步任务
借助 task manager,ScyllaDB 还提供了同步调用的异步版本,其中一部分通过 nodetool tasks 命令暴露。这些调用与对应的同步版本工作方式类似,区别在于:它们不等待操作完成,而是立即返回关联任务的 ID;之后你可以用 task manager API 查询该操作的状态、进度,甚至中止它。
相关的 nodetool 子命令文档位于 docs/operating-scylla/nodetool-commands/tasks/index.rst,包含:
nodetool tasks list—— 列出任务(对应/task_manager/list_module_tasks/{module});nodetool tasks status—— 查看任务状态(对应/task_manager/task_status/{task_id});nodetool tasks wait—— 等待任务完成(对应/task_manager/wait_task/{task_id});nodetool tasks tree—— 查看任务及其后代状态(对应/task_manager/task_status_recursive/{task_id});nodetool tasks abort—— 中止任务(对应/task_manager/abort_task/{task_id});nodetool tasks drain—— 清理已结束任务(对应/task_manager/drain/{module});nodetool tasks modules—— 列出模块(对应/task_manager/list_modules);nodetool tasks ttl/nodetool tasks user-ttl—— 读写 TTL(对应/task_manager/ttl//task_manager/user_ttl)。
使用建议:对耗时长、适合后台化的操作(例如大规模 compaction、repair、节点运维),优先使用这些异步命令拿到 task_id,再配合 nodetool tasks status / tree 观察进度,必要时用 abort 中止——避免长时间阻塞客户端连接。如果希望直接以 JSON 方式与 Task Manager 交互,可参考 REST API 的使用方式(见 docs/operating-scylla/rest.rst 对 REST API 的整体介绍)。
六、一次完整的排查实践
综合上述内容,一个典型的长任务追踪流程如下:
-
列出模块,确认要查询的功能模块名:
curl -X GET http://<node>:10000/task_manager/list_modules -
列出任务(默认不含内部任务):
# 列出 compaction 模块全部任务 curl -X GET "http://<node>:10000/task_manager/list_module_tasks/compaction" # 过滤指定 keyspace/table,并包含内部任务 curl -X GET "http://<node>:10000/task_manager/list_module_tasks/compaction?keyspace=ks1&table=t1&internal=true" -
查看任务完整状态与进度:
curl -X GET "http://<node>:10000/task_manager/task_status/<task_id>" -
递归查看任务树(含所有跨节点后代,BFS 顺序):
curl -X GET "http://<node>:10000/task_manager/task_status_recursive/<task_id>" -
等待任务结束(可带超时,超时返回 408):
curl -X GET "http://<node>:10000/task_manager/wait_task/<task_id>?timeout=60" -
中止任务(仅当
is_abortable=true,否则 403):curl -X POST "http://<node>:10000/task_manager/abort_task/<task_id>" -
调整保留时长并清理历史:
# 用户任务保留 2 小时 curl -X POST "http://<node>:10000/task_manager/user_ttl?user_ttl=7200" # 立即清理 compaction 模块所有已结束任务 curl -X POST "http://<node>:10000/task_manager/drain/compaction"
需要注意的边界行为(来自文档与源码双重确认):
- 默认 API 响应不含内部任务,需要显式传
internal=true; - 已结束的内部任务状态会被折叠进父任务,间接后代只有失败时才可见;
- cluster 任务(如 node_ops)不会被
drain或 TTL 机制注销,其可见时长未作规定; abort是递归的:中止父任务会级联中止所有后代,但只有is_abortable为 true 的任务才允许发起。
总结
Task Manager 是 ScyllaDB 运维体系的中枢神经系统:它以"模块 + 任务树"的模型统一了 compaction、repair、节点运维等所有长时后台操作的可观测性与控制面。通过 task_stats / task_status 两种数据结构,配合 /task_manager/* REST 端点与 nodetool tasks 命令,运维人员可以精确地查看任务进度、等待完成、中止失控操作,并按需调整任务保留时长——而这一切背后,是 tasks/task_manager.cc 中任务注册/注销、子任务折叠、跨节点 RPC 收集与递归中止等机制的精密协作。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.24 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python670
SlideSCIPPT插件,支持素材库、AI助手、一键添加图片标题,复制粘贴位置、一键图片对齐、一键插入Markdown(加粗、超链接等行内样式、代码块、LaTeX等块级样式)、便捷导出图片!C#230
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python52874
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go22545
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java36351