MinIO 存储池下线(Pool Decommission)全流程操作与源码原理解析
技术指南定位:本文基于
docs/distributed/DECOMMISSION.md,系统讲解 MinIO 分布式部署中“存储池(Server Pool)下线 / 退役”机制的完整操作链路——包括启动、状态查询、取消、失败恢复、完成后的清理步骤,以及驱动该机制的核心数据结构与后台搬迁逻辑。读完本文,你将能够安全地把旧硬件池上的数据在线迁移至新池,并正确地从启动参数中摘除退役池。
什么是 Pool Decommissioning:为什么需要下线一个存储池
MinIO 支持把一组独立启动的驱动器组合(通常由 MINIO_VOLUMES 环境变量或命令行参数里的一组端点表示,如 http://minio{1...4}/data{1...4})组织成一个 Server Pool(存储池)。集群可以横向扩展:新增一个 pool 即扩容,但反过来——当旧硬件需要更换、集群需要收缩或整体迁移到性能更好的硬件上时,就需要一种安全、在线、可恢复的机制把旧 pool 中的数据搬出去,这就是 decommissioning(下线/退役)。
其工作方式(与同目录 docs/distributed/DECOMMISSION.md 描述一致)是:
- 下线是“排空并扩散”的过程:把被下线 pool(如
pool1)中的数据均匀扩散到剩余的所有 pool(如pool2、pool3)上,而不是整体搬去某一个池; - 全程 在线进行,读写不中断,由管理员在后台编排执行。
三个核心特性
- 退役期仍可读、新写入自动绕开:处于 decommission 状态的 pool 仍然允许对其全部内容进行 READ 访问;同时,新产生的 WRITE 请求会被自动调度到“非 decommission 状态”的 pool 上,避免把新数据写入即将退役的池(docs/distributed/DECOMMISSION.md)。
- 版本化顺序保持:对于所有开启了版本化的 bucket,对象在被迁移到其他 pool 后,各“版本”的相对顺序保持不变——这对依赖 GetBucketVersions / 版本链语义的应用至关重要。
- 断点续跑:下线过程中若被打断(例如集群重启),任务会从上次的进度处自动恢复,而非从头再来。
前置概念:集群中的 Pool 状态机
从源码结构看,每个 pool 的下线状态被持久化在名为 pool.bin 的元数据文件中(格式常量见 cmd/erasure-server-pool-decom.go#L478-L483),并由以下三层结构描述(cmd/erasure-server-pool-decom.go#L46-L163):
poolMeta:集群级的 pool 元数据清单(版本号 + 各 pool 状态数组),save()会把该文件写入所有 pool,以保证对第一块盘退役操作本身的高可用;PoolStatus:单个 pool 的登记信息,含ID、命令行标识CmdLine、最后更新时间LastUpdate和可选的Decommission详情;PoolDecommissionInfo:真正承载下线进度,包括StartTime、StartSize/TotalSize/CurrentSize、Complete/Failed/Canceled状态位,以及逐 bucket、逐前缀、逐对象的游标(QueuedBuckets/DecommissionedBuckets/Bucket/Prefix/Object)和统计计数(已迁移/失败的对象数与字节数)。
因此 mc admin decommission 显示的状态位与上面结构一一对应,我们可以归纳出完整的生命周期:
| 状态 | 含义 | 对应结构位 |
|---|---|---|
Active |
正常服役中,未参与下线 | Decommission == nil |
Draining |
正在排空数据(下线进行中) | Decommission 存在,三个结束位均为 false |
Draining(Canceled) |
下线被取消 | Canceled = true |
Draining(Failed) |
下线失败 | Failed = true |
Complete |
下线完成,可安全摘除 pool | Complete = true |
状态位互斥切换由 poolMeta.DecommissionComplete / DecommissionFailed / DecommissionCancel 方法保证(cmd/erasure-server-pool-decom.go#L184-L217)。
开始下线:mc admin decommission start
使用 mc admin decommission start 把目标 pool(用与启动参数一致的通配端点表达式标识)标记为排空。这里的 alias 指 mc 中已配置的 MinIO 集群别名:
λ mc admin decommission start alias/ http://minio{1...2}/data{1...4}
从管理侧 API 的实现看(cmd/admin-handlers-pools.go#L39-L116),该命令最终会命中 REST 接口 POST /minio/admin/v3/pools/decommission(注册于 cmd/admin-router.go#L184)。服务端会做几层合法性校验:
- 仅支持新式(ellipses 风格)启动参数,
globalEndpoints.Legacy()的旧式集群返回ErrNotImplemented; - 集群必须多于一个 pool,且对象层必须是
*erasureServerPools; - 若已有下线任务在跑(
IsDecommissionRunning()),返回errDecommissionAlreadyRunning; - 若 rebalance 已启动,则拒绝并发执行;
- 请求还会被代理到目标 pool 的首个端点所在节点执行(
proxyDecommissionRequest),以就近操作。
请求需要具备 admin 权限 DecommissionAdminAction(对应 mc 中的 mc admin decommission)。
查看下线状态:mc admin decommission status
不带参数:列出所有 pool 的概况
λ mc admin decommission status alias/
┌─────┬─────────────────────────────────┬──────────────────────────────────┬────────┐
│ ID │ Pools │ Capacity │ Status │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Active │
│ 2nd │ http://minio{3...4}/data{1...4} │ 329 GiB (used) / 421 GiB (total) │ Active │
└─────┴─────────────────────────────────┴──────────────────────────────────┴────────┘
表内的 Capacity 一列来自对 pool 容量(已用 / 总量)的探测,Status 列则对应当前状态机中的状态。服务端对应 GET /minio/admin/v3/pools/status(cmd/admin-router.go#L182)。
指定 pool:查看迁移进度
λ mc admin decommission status alias/ http://minio{1...2}/data{1...4}
Decommissioning rate at 36 MiB/sec [4 TiB/50 TiB]
Started: 1 minute ago
进度以“迁移速率 + 累计搬迁量/总量”的方式呈现,底层计数即 PoolDecommissionInfo.BytesDone、TotalSize 等字段。需要说明的是,文档(docs/distributed/DECOMMISSION.md 的 TODO 一节)指出:目前还没有更丰富的进度 UI,mc 只展示数据传输速率与用量增长;更精细的进度可视化将在后续版本中完善。
下线完成时
当所有数据搬移完毕后,状态查询会提示可以安全地从启动参数中移除该 pool:
λ mc admin decommission status alias/ http://minio{1...2}/data{1...4}
Decommission of pool http://minio{1...2}/data{1...4} is complete, you may now remove it from server command line
未参与下线的 pool
对未处于 decommission 状态的 pool 执行 status 会得到明确的错误提示:
λ mc admin decommission status alias/ http://minio{1...2}/data{1...4}
ERROR: This pool is not scheduled for decommissioning currently.
取消下线:mc admin decommission cancel
当系统负载过高、希望把下线调度到更合适的时间点时,可以停止一个正在进行的下线任务。
不带参数时列出所有正在进行的下线任务:
λ mc admin decommission cancel alias/
┌─────┬─────────────────────────────────┬──────────────────────────────────┬──────────┐
│ ID │ Pools │ Capacity │ Status │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Draining │
└─────┴─────────────────────────────────┴──────────────────────────────────┴──────────┘
指定 pool 后真正执行取消:
λ mc admin decommission cancel alias/ http://minio{1...2}/data{1...4}
┌─────┬─────────────────────────────────┬──────────────────────────────────┬────────────────────┐
│ ID │ Pools │ Capacity │ Status │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Draining(Canceled) │
└─────┴─────────────────────────────────┴──────────────────────────────────┴────────────────────┘
重要警告:取消不会让 pool 回到 Active
NOTE: Canceled decommission will not make the pool active again, since we might have potentially partial namespace on the other pools, to avoid this scenario be absolutely sure to make decommissioning a planned well thought activity. This is not to be run on a daily basis.
这是一条关键约束(见 docs/distributed/DECOMMISSION.md):由于迁移过程中数据可能已经被部分地写入其他 pool(产生 partial namespace),取消下线后该 pool 无法回退为 Active,只能处于 Draining(Canceled) 这一“静止排空”态。因此,decommission 应当被当作一次经过充分规划的迁移活动,而不是日常运维动作。
在管理 API 的实现中,取消会校验请求权限 DecommissionAdminAction、拒绝旧式集群,并通过 pools.DecommissionCancel(ctx, idx) 把状态位置为 Canceled 并清空 StartTime(cmd/admin-handlers-pools.go#L118-L158、cmd/erasure-server-pool-decom.go#L207-L217)。
失败状态
若下线过程因任何原因失败,状态列会显示 Draining(Failed):
λ mc admin decommission status alias/
┌─────┬─────────────────────────────────┬──────────────────────────────────┬──────────────────┐
│ ID │ Pools │ Capacity │ Status │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Draining(Failed) │
│ 2nd │ http://minio{3...4}/data{1...4} │ 329 GiB (used) / 421 GiB (total) │ Active │
└─────┴─────────────────────────────────┴──────────────────────────────────┴──────────────────┘
重启被取消或失败的下线
对于 Canceled 或 Failed 的 pool,可以直接再次执行 start 来恢复迁移任务:
λ mc admin decommission start alias/ http://minio{1...2}/data{1...4}
对应的服务端逻辑允许在 Complete/Failed/Canceled 三个终态之上重新初始化一个新的 PoolDecommissionInfo(重新记录 StartTime 与空间基线);只有“正在排空中”的任务(三者皆非)才会拒绝二次 start(errDecommissionAlreadyRunning,见 cmd/erasure-server-pool-decom.go#L289-L308)。
断点续跑:集群重启后的自动恢复机制
“被打断后从上次位置恢复”在源码中有清晰实现,这也是 Decommission 最值得信赖的特性之一:
- 当集群启动、
erasureServerPools.Init()初始化时,会先从盘上读取pool.bin(cmd/erasure-server-pool-decom.go#L377-L416),随后通过poolMeta.validate()与当前命令行指定的 pools 做交叉比对,若发现仍有残留的未完成 pool,就据此重建元数据; returnResumablePools()只挑出既非Complete也非Canceled的 pool——也就是说 Complete 与 Canceled 不会被续跑,其余任何中间态都会在重启后被列入恢复清单(cmd/erasure-server-pool-decom.go#L167-L182);- 恢复动作并非立刻执行:只有 local 端点上的 leader 节点会启动后台 goroutine,先
sleep 3 * time.Minute等待集群稳定,再调用z.Decommission();若发现任务其实已经在跑(errDecommissionAlreadyRunning),则直接以doDecommissionInRoutine重新接管各 pool 的迁移例程(cmd/erasure-server-pool-decom.go#L519-L558); - 续跑所依赖的游标正是
PoolDecommissionInfo中持久化的Bucket/Prefix/Object与QueuedBuckets/DecommissionedBuckets(见ResumeBucketObject、bucketPop等实现),从而做到逐对象级别的精确续传。
数据是怎么“搬”过去的:对象级迁移原理
深入 cmd/erasure-server-pool-decom.go 的排空循环,可以还原一条数据迁移的完整链路:
- 逐 bucket 排空:
decommissionPool针对decomBucketInfo{Name, Prefix},逐 erasure set 启动并发 worker。worker 数默认等于该 pool 的 set 数,每额外增加一个 set 还会再加一个 List worker(合计2 * len(pool.sets)规模),并可通过内部环境变量_MINIO_DECOMMISSION_WORKERS覆盖(cmd/erasure-server-pool-decom.go#L744-L761)。 - 以版本为单位处理:读取对象的全部版本(
fileInfoVersions),然后用versionsSorter.reverse()做时间逆序排序,以保证目标 pool 上重建出与源一致、正确的版本栈顺序(cmd/erasure-server-pool-decom.go#L824-L826)。 - 不同对象类型走不同路径:
- 普通/版本化对象:以
GetObjectNInfo读回,再用PutObject写入其他 pool,迁移时显式设置DataMovement: true、SrcPoolIdx、MTime、UserDefined,并通过PreserveETag与IndexCB保留原始 ETag 与分片索引,保证压缩/校验和等元数据语义一致(cmd/erasure-server-pool-decom.go#L675-L695); - 多段上传(multipart)对象:重建为一个新的 multipart 会话,逐 part 写入并
CompleteMultipartUpload,保留每个 part 的 ETag、Index 及各类 checksum(cmd/erasure-server-pool-decom.go#L618-L668); - 删除标记(delete marker):以
DeleteObject+Versioned: true+DeleteMarker: true+SkipDecommissioned: true的方式在目标池重建一个等价的删除标记,从而保住版本链的完整性(cmd/erasure-server-pool-decom.go#L855-L896)。
- 普通/版本化对象:以
- 迁移前先消化生命周期规则:每个对象在搬迁前都会按 bucket 的版本化配置、生命周期策略、对象锁与复制配置做一次评估,已过期(expired)的对象/版本会被跳过并由生命周期系统按原计划清理,不再搬去新池(
filterLifecycle,见 cmd/erasure-server-pool-decom.go#L791-L810)。 - 成功与失败都记账:
CountItem精确累计成功/失败的对象数与字节数,并周期性把进度写回所有 pool 的pool.bin(updateAfter按距上次落盘超过阈值才写,见 cmd/erasure-server-pool-decom.go#L433-L476)。
当状态变为 Complete:如何安全摘除 pool
一旦所有数据排空,状态列为 Complete,就表示现在可以安全地把第一个 pool 参数从 MinIO 启动命令行中移除了。三种典型部署形态的操作如下(docs/distributed/DECOMMISSION.md):
- 裸机(baremetal):假设当前环境变量为
MINIO_VOLUMES="http://minio{1...2}/data{1...4} http://minio{3...4}/data{1...4}", 则删除第一段http://minio{1...2}/data{1...4}更新MINIO_VOLUMES,然后并行重启所有 server:systemctl restart minio。 - Kubernetes:修改 StatefulSet 中 MinIO 容器的命令行输入参数,应用变更:
kubectl apply -f statefulset.yaml。 - MinIO Operator:修改
tenant.yaml的pools:段,把两个条目缩减为单个条目,再执行kubectl apply -f tenant.yaml。
硬性约束:没有
Complete状态标记的Active或Drainingpool 一律不允许从配置中移除。这一点在源码侧同样被强约束:poolMeta.validate()在发现命令行中仍包含一个“已标记为 complete”的 pool 时会持续输出形如 “pool(1st) = ... is decommissioned, please remove from server command line” 的警告(cmd/erasure-server-pool-decom.go#L356-L358),提醒你完成摘除动作。
管理 API 一览
mc 客户端命令与服务端 REST 端点的对应关系(注册见 cmd/admin-router.go#L182-L185):
| mc 命令 | 服务端端点 | 说明 |
|---|---|---|
mc admin decommission status alias/ |
GET /minio/admin/v3/pools/status |
查询全部/指定 pool 下线状态 |
mc admin decommission start alias/ <pool> |
POST /minio/admin/v3/pools/decommission |
启动/恢复下线 |
mc admin decommission cancel alias/ <pool> |
POST /minio/admin/v3/pools/cancel |
取消进行中的下线 |
三个端点均要求具备 policy.DecommissionAdminAction 权限,并且都会在“对象层不是 *erasureServerPools”或“pool 数不足 2”等前提下返回 ErrNotImplemented。
当前限制与 Roadmap
根据文档(docs/distributed/DECOMMISSION.md),该机制尚有以下边界与规划中的能力:
- 空删除标记不迁移:只有纯 delete marker、且对象没有其他后继版本的“空删除标记”不会迁移到新池,以避免在目标池产生空的元数据。如果确有迁移这类空删除标记的需求,官方建议在 GitHub 上提交 issue 讨论。
- 进度 UI 仍显简陋:目前只有数据传输速率与已用空间增长的展示,更丰富的进度界面将在后续版本补齐。
- 热分层(Hot Tier)与 ILM Transition 兼容性:文档指出 pooled setup 下“过渡的热分层尚不被支持”,试图对带 ILM Transition 的 bucket 执行下线会被服务端拒绝,规划在未来版本支持。作为佐证,当前仓库的迁移循环里已经为远端(remote/tiered)版本预留了独立分支
DecomTieredObject(见 cmd/erasure-server-pool-decom.go#L899-L919),说明远端对象处理是持续演进的关注点。实际使用前请务必结合你所部署版本的 Release Notes 确认 ILM Transition 桶的兼容性。 - Console UI 暂未开放:嵌入式的 MinIO Console 目前还不提供通过界面触发 decommission 的能力,该能力规划在后续版本中支持;现阶段一律通过
mc完成。
在本地验证整个下线流程
仓库提供了可复现的端到端验证脚本 docs/distributed/decom.sh,它完整覆盖了“构建版本化数据 → 创建温层(warm tier)→ 触发下线 → 校验”的场景,适合作为学习与演练素材。其关键步骤包括:
- 用
minio server http://localhost:9000/tmp/xl/{1...10}/disk{0...1}拉起多盘本地集群,设置CI=true、MINIO_SCANNER_SPEED=fastest加速测试; - 通过
mc创建用户、策略并建立开启版本化的 bucket(mc mb -l myminio/versioned); - 用
mc mirror internal myminio/versioned/灌入真实源码目录作为对象数据,随后执行软删除(mc rm -r --force)制造 delete marker,再二次 mirror 生成新版本,以构造多版本、含删除标记的复杂命名空间; - 额外拉起一个
:9002端口的小集群作为温层,创建 bucket 与 lifecycle 策略,验证分层对象参与数据排空的行为; - 配合同目录下的多份变体脚本(如
decom-compressed-sse-s3.sh、decom-encrypted.sh、decom-encrypted-kes.sh等)还可覆盖压缩、SSE-S3/SSE-KMS 加密等组合场景,并最终校验对象 checksum 与用户/策略计数以确认迁移无损。
仓库内的单元测试 cmd/erasure-server-pool-decom_test.go 与 *_gen.go(msgp 序列化生成)亦可作为进一步研究状态机与持久化格式的参考。关于多池集群的容量规划与扩展模型,还可结合 docs/distributed/DESIGN.md 与 docs/distributed/SIZING.md 阅读。
结语
Pool Decommissioning 是 MinIO 多池架构里“可进可退”的关键一环:它让集群在硬件换代、容量收缩时无需整体重建即可把旧池数据在线扩散到新池,并以 pool.bin 持久化 + 启动续跑的方式保证了过程的可靠与可审计。正确使用它只需记住三条铁律:把下线当作规划性变更、不随意取消、未出现 Complete 之前绝不摘除 pool。配合 mc admin decommission 的三条命令与本文梳理的源码链路,你就可以在裸机、Kubernetes 与 Operator 三种部署形态下安全地完成存储池的平滑退役。
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 StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00