CephFS IOPS QoS 限速
# CephFS IOPS QoS 限速
# 项目简介
这个功能面向多租户共享 CephFS 的场景。典型问题是多个租户、项目或业务目录挂载到同一个 CephFS 集群后,某个热点元数据请求可能抢占 MDS 处理能力,影响其他业务目录的稳定性。MDS 侧 QoS 的目标是在文件系统元数据服务入口统一排队和调度请求,在存储系统内部提供更可控的资源隔离。
当前实现是 CephFS MDS metadata request QoS,不是完整的数据面 read/write IOPS 限速。
核心目标:
- 在 MDS active 状态下,对来自 CephFS client 的 metadata request 接入 dmClock 调度。
- 以客户端 session 的
root元数据识别业务根路径,并归一化到 subvolume root 维度。 - 让同一个 subvolume 下的多个 client session 共享同一组 reservation / weight / limit。
- 支持默认 QoS 参数和运行时按路径覆盖 QoS 参数。
- 保持未启用、配置不完整、非 client 请求、非 active MDS 等场景的原有处理路径。
需求拆解:
- 目录级 QoS 策略配置:当前支持
enabled、reservation、weight、limit。默认值来自 MDS 配置项,单个 subvolume 可通过 admin socket 覆盖。暂不支持独立 read IOPS、write IOPS、metadata IOPS 三类字段,也没有独立 burst 字段。 - 策略匹配与继承:当前按 client session 的
root元数据匹配,并将深度超过 3 的路径归一化为/volumes/<group>/<subvolume>。这是 subvolume root 维度的匹配,不是任意层级目录继承模型。 - 请求路径限速:接入
CEPH_MSG_CLIENT_REQUEST,覆盖 MDS 处理的客户端元数据请求,例如 lookup、getattr、readdir、open、create、mkdir、unlink、rename、setattr 等。 - 限速算法:复用 Ceph dmClock
PushPriorityQueue,每个 volume/subvolume 作为 dmClock client,使用 reservation / weight / limit 三元组进行调度。 - 多客户端 / 多 MDS 协同:同一 MDS rank 内,同一 subvolume 的多个 session 共享一个
VolumeInfo。多 MDS 场景下是各 rank 本地调度,不提供跨 rank 的全局强一致计数。 - 可观测性与运维:提供
dump qos、qos set、qos get、qos rmadmin socket 命令,可查看默认状态、队列长度、inflight 请求数、session 列表、平均调度延迟和 throttle 计数。
# 项目团队分工
存储系统侧(1人):
- 需求拆解:将“IOPS QoS”收敛为 MDS metadata request QoS,优先解决元数据热点目录或 subvolume 对 MDS 的冲击。
- 方案设计:选择 MDS 侧集中调度而不是 client 侧自限速;选择 dmClock 而不是手写令牌桶,复用已有 reservation / weight / limit 语义。
- 核心开发:新增
MDSDmclockScheduler,在Server::dispatch()的 client request 入口接入调度器,在 session open/reconnect/close/kill 路径维护 volume 与 session 关系。 - 运维接口:提供
qos set、qos get、qos rm等libcpehfs-jni 进行参数配置,以及类似dump qos的观测命令。
管理平台侧(1人):如果需要对接统一通过新增的libcpehfs-jni接口:展示当前 MDS rank 的 subvolume QoS 状态,在管理界面进行桌面操作时调用 qos set 调整 reservation / weight / limit,调用 qos rm 恢复默认参数。
# 详细方案设计
# 1. QoS 策略模型设计
当前 QoS 策略绑定 inode_t中,在inode加载时直接读取,维护到内存中的 MDSDmclockScheduler::volume_info_map。key 是 VolumeId,也就是从 client session metadata 中取到的 root 路径,经过 convert_subvol_root() 归一化后的 subvolume root。
路径归一化规则:
- 如果 root 路径深度大于 3,截断为前三层。
- 典型 subvolume 路径
/volumes/_nogroup/subvolume/dir/a会归一化为/volumes/_nogroup/subvolume。 - 如果 root 路径深度不超过 3,则使用原路径。
核心数据结构:
mds_dmclock_conf default_conf:MDS rank 的默认 QoS 配置,包含enabled、reservation、weight、limit。VolumeInfo:单个 volume/subvolume 的运行时状态,继承QoSInfo,字段包括reservation、weight、limit、use_default、updated、session_list、inflight_requests、throttle、latency_sum、latency_cnt。ClientRequest:进入 QoS 调度队列的客户端请求,保存原始MClientRequest、volume id、到达时间和 cost。UpdateRequest:用于异步更新某个 volume 的 dmClock client info。
# 2. 策略匹配与继承规则
请求到达 MDS 后,调度器通过 mds->get_session(mds_req) 获取对应 session,再从 session->info.client_metadata["root"] 读取 root 路径。该路径代表客户端被授权或挂载的根目录。调度器随后调用 convert_subvol_root() 把路径归一化为 subvolume root。
例如:
/volumes/_nogroup/subvolume
/volumes/_nogroup/subvolume/dir-a
/volumes/_nogroup/subvolume/dir-a/file-b
2
3
都会归并到:
/volumes/_nogroup/subvolume
这意味着当前实现是 subvolume 级别 QoS。它可以覆盖很多“目录级”业务诉求,因为 CephFS 多租户通常通过 subvolume 暴露租户根目录;但它不是对任意深层目录逐级查找最近祖先策略的通用目录树 QoS。
多级目录策略当前不支持:
- 不会从请求目标 inode 向上查找父目录 QoS。
- 不存在“最近祖先目录优先”或“最严格策略优先”。
- 不存在父子目录限速叠加。
qos set path=...只能对已有VolumeInfo生效,也就是该路径必须已经有活跃 session 被调度器识别。
当前这种设计的优点是匹配成本低:请求路径不需要额外解析目标 inode,也不需要每次沿目录树向上查找策略;调度 key 直接来自 session metadata 和内存 map。
# 3. 对外接口
libcephfs_jni 系列接口中新增一批用于增删改查 子卷QoS参数的接口,用于提供到管理平台在界面做设置调用
MDS admin socket 新增一批用于查询qos状态的观测命令,用于底层观测调试该功能的运行状态
# 4. 请求路径限速
server的 op 分发函数会判断以下条件:
- 是否开启Qos配置功能。
- 请求来源是 client。
- 当前 MDS rank 处于 active 状态。
- 对应 volume 的 QoS 信息已经被更新到 dmClock。
- reservation / weight / limit 都大于 0。
满足条件时,请求进入调度器的 request_queue,再由调度线程提交到 dmClock,不满足条件时,直接调用原有路径不受影响。
当前覆盖的是客户端元数据请求。代码中按操作类型设置 cost。限速后的行为不是立即返回错误,而是排队调度。因此业务侧看到的是请求延迟增加,而不是直接收到 EAGAIN 或其他限速错误。
# 5. 限速算法与运行状态
当前实现使用 Ceph 已有 dmClock 调度库:
每个 VolumeId 是一个 dmClock client。
运行时流程:
- MDSRank 构造时创建
MDSDmclockScheduler,调度器启动独立线程处理request_queue。 - MDS 进入 active 时,调度器设置 QoS功能,如果配置启用则扫描
OPEN/STALEsession 建立VolumeInfo。 - 新 session open、force open、reconnect 成功后调用
add_session(),将 session 加入对应 volume。 - session close / kill 时调用
remove_session(),session 数归零且无 inflight 请求后删除VolumeInfo。 - client request 入队后,调度线程把它加入 dmClock queue,并增加 volume 和全局 inflight 计数。
- dmClock 回调
submit_request_to_mds()时,记录排队延迟,调用 MDS 原始处理函数,并减少 inflight 计数。
为了避免 QoS 模块自身成为瓶颈,当前实现做了几件事:
- 策略匹配基于 session root,不对每个请求做目录树向上查找。
- 调度处理放在独立线程,主 dispatch 路径只做轻量判断和入队。
- volume 状态使用内存 map 和 mutex 保护,避免持久化读写进入热路径。
- 只有启用 QoS 且 volume 信息有效时才进入 dmClock;否则走原路径。
# 6. 多客户端 / 多 MDS 场景
多客户端场景:
- 同一 subvolume 下的多个 client session 会共享同一个
VolumeInfo和 dmClock client。 - 例如四个 session 都挂载
/volumes/_nogroup/subvolume,如果该 subvolume 的limit=100,那么这四个 session 在该 MDS rank 上共享约 100 metadata IOPS 的上限。 dump qos中可以通过session_cnt和session_list看到当前共享同一 QoS 的 session。
多 MDS 场景:
- 当前 QoS 队列状态维护在每个 MDS rank 本地。
- 没有跨 MDS rank 的全局令牌桶、全局计数器和中心化调度器。
- 当同一个 subvolume 的请求被多个 active MDS rank 处理时,每个 rank 分别执行本地 dmClock 调度;因此它是 rank-local QoS,不是全局精确 QoS。
这里做了一个工程取舍,降低了实现复杂度和热路径开销,但牺牲了全局精确限速能力。对于多 MDS 下需要强全局 QoS 的业务,后续需要引入跨 rank 策略同步与聚合计数机制。
# 7. 持久化与恢复
MDS故障后恢复时,重新加载OMAP元数据到内存中时,会主动识别 inode_t 的 iops_qos 字段,如果配置有相关参数,会更新到 subvolume_iops_qos_map 中,当新的客户端会话访问对应子卷时,可将其op放入qos队列中进行限速。MDS故障、节点重启、子树迁移等都不会导致qos策略失效。
# 8. 可观测性与运维能力
dump输出分为两部分:
qos_state:全局状态,包括qos_enabled、state、default_reservation、default_weight、default_limit、mds_dmclock_queue_size、inflight_requests。volume_infos:每个 volume 的状态,包括volume_id、use_default、reservation、weight、limit、throttle、latency_avg、inflight_requests、session_cnt、session_list。
# 项目踩过的一些坑
# 目录策略匹配成本问题
原始“目录级 QoS”很容易设计成每个请求都解析目标路径或目标 inode,然后向上查找父目录策略。这在 MDS 热路径上成本较高,也会引入缓存一致性和目录迁移问题。
当前实现规避了这个问题:不按请求目标路径查找策略,而按 client session 的 root 元数据确定业务根路径,再归一化到 subvolume root。这样请求到达时只需要做 session 查找、内存 map 匹配,开销可控。
代价是能力边界更窄:它适合 subvolume / 租户根目录 QoS,不适合任意深层目录独立限速。
# 限速精度与性能开销取舍
当前实现没有追求跨 MDS rank 的全局强一致 IOPS 计数。原因是 MDS metadata request 本身是高频路径,如果每次请求都参与分布式计数或远程协调,会把 QoS 模块变成新的性能瓶颈。
dmClock 的 reservation / weight / limit 可以在单 MDS rank 内提供公平调度和上限控制。多 MDS 下则采用本地近似限速,适合先解决热点 subvolume 对单个 MDS rank 的冲击。
# 客户端预判与 MDS 裁决边界
当前没有在客户端做快速限速或提前拒绝。所有裁决在 MDS 侧完成:
- 客户端无需感知 QoS 策略。
- MDS 能看到实际进入 metadata server 的请求流。
- 被限速请求通过排队体现,不改变客户端协议语义。
这个设计降低了客户端改造成本,但无法在请求到达 MDS 前削峰。如果未来需要更早削峰,可以考虑把策略下发给客户端做预判,MDS 仍保留最终裁决。
# 用户体验与错误语义
当前限速不会直接返回错误码,而是让请求在 MDS 侧排队。业务侧表现为 metadata 操作延迟增加。这比直接拒绝更接近传统 IO throttle 语义,也能避免应用把限速误判为存储故障。
但运维侧需要把 latency_avg、throttle、MDS slow request 等指标关联起来看,避免把预期内的 QoS 排队误判为 MDS 异常。
# 当前该功能的约束条件
- 限速范围:仅覆盖 MDS metadata request,不覆盖 OSD 数据读写路径。
- 粒度:实际是 subvolume root 粒度,不是任意目录树粒度。
- 参数:支持 reservation / weight / limit,不支持独立 read/write IOPS、burst、继承策略。
- 生效条件:需要显式开启QoS配置,MDS 处于 active 状态,volume 信息已创建且 QoS 参数有效。
- 配置方式:新增控制面系列命令,通过inode_t的新加字段元数据变更确保持久化。
- 多客户端:同一 subvolume 下多个 session 在同一 MDS rank 内共享 QoS。
- 多 MDS:每个 active MDS rank 本地调度,不提供全局精确限速。