ceph deep-scrub卡住导致业务中断问题分析

时间:2024-04-01 19:33:29

1. 问题背景

一个pg scrub了14天,前端虚机挂掉大半

2. 分析过程

2.1 查看scrub pg的主OSD日志

ceph deep-scrub卡住导致业务中断问题分析
从日志里可以看出pg[13.832]:

  • 有slow op,该oldest slow op等待时间1199828s==14天
  • 这些op 都处于waiting for scrub

什么场景会导致op处于waiting for scrub?
首先从字面上可以理解这些op是等待scrub,那为什么scrub过程中需要阻塞io?
因为在scrub过程中一旦选择了参与scrub的oid,这些oid所有的io都需要等待scrub完成,否则就会出现scrub校验不一致

2.2 分析scrub过程那些场景会导致scrub阻塞,首先看下scrub状态机

ceph deep-scrub卡住导致业务中断问题分析

从上面的scrub状态变迁过程可以看出,有4种情况会导致scrub阻塞,分别是

  • WAIT_PUSHES: wait for pushes to apply
  • WAIT_LAST_UPDATE: wait for writes to flush
  • WAIT_REPLICAS: wait for replicas to build scrub map
  • WAIT_DIGEST_UPDATES: waiting on digest updates

从主osd日志并不能看出当前pg scruber处于那个状态,接下来分析其他从副本日志看看有什么线索。

2.3 分析从副本osd.227日志,过滤13.832的日志

ceph deep-scrub卡住导致业务中断问题分析
从上面过滤出的日志,发现13.832这个pg

  • 出现了slow op,该slow op 等待时间为983040s==11天
  • 这些op处于currently reached_pg,说明已经开始交给pg层处理了
  • 这些op类型是replica scrub即主副本发来scrub请求,请看下图
PrimaryABCINACTIVENEWCHUNKBUILD_MAP_request_scrub_map_request_scrub_mapWAIT_REPLICASCEPH_OSD_OP_SCRUB_MAPCEPH_OSD_OP_SCRUB_MAPCOMPARE_MAPSFINISHINACTIVEPrimaryABC

从上面的流程可以知道主PG当前的状态是为WAIT_REPLICAS即主副本上所有的io请求都是因为等待从副本osd.227 scrub完成而阻塞

既然都已经reached_pg,那接下来分析那些场景会阻塞replica scrub op。

2.4 分析那些场景会导致replica scrub阻塞

分析replica scrub调用过程,有3种情况会阻塞replica scrub op,分别为

  • sharedwq共享线程池被阻塞了
  • 当前的replica scrub op中scrub_to记录的版本号大于当前osd记录的last_update_applied,即这些scrub对象数据还没更新到和主OSD一致
  • 当前osd正在做数据恢复即active_pushes不为0

首先排除sharedwq共享线程池被阻塞了,因为sharedwq的线程会被加入到HeartbeatMap监控中心,一旦线程被阻塞了HeartbeatMap就会检查到并且会在日志中打印timeout的告警严重的话会造成线程自杀,而日志并无这些异常打印,所以这种情况排除掉

其次排除掉因为数据恢复导致的这种情况,因为当前集群运行正常并未做任何的数据恢复,并且恢复的过程中是不会进行scrub校验的

剩下的只有一种情况即scrub_to记录的版本号大于当前osd记录的last_update_applied这一种情况了,什么场景会出现scrub_to大于last_update_applied情况呢?

首先明确一点scrub_to是主osd封装的,记录是本次选中参与scrub的所有scrub对象最新的pglog版本

其次明确last_update_applied本地副本自己on_apply到filestore后更新的,因此就会存在同一时刻不同副本的last_update_applied的版本号不一致,如下图

PrimaryABClast_update_applied = 1748'25607client write opissue_opissue_opqueue_transactionsub_op_modify_implsub_op_modify_implqueue_transactionqueue_transactionon_applied(last_update_applied = 1748'25609)sched_scrubchunk_scrub(scrub_to = 1748'25609)_request_scrub_map_request_scrub_mapWAIT_REPLICASreplica_scrub(last_update_applied = 1748'25607 < scrub_to = 1748'25609)将srub op挂起 scrubber.active_rep_scrub = opon_appled(last_update_applied = 1748'25609)scrub_to != last_update_applied, scrub op一直被挂起CEPH_OSD_OP_SCRUB_MAPPrimaryABC

从上图可以看出当从副本B在收到scrub op请求时如果此时op还未on_applied到filestore就会把scrub op挂起等待op applied完成后重新调度该op,而重新调度的前提是last_update_applied等于scrub_to否则scrub op就会永远被挂起,就会出现这个问题。所以什么场景会导致last_upate_applied不是一个一个版本递增的呢?

2.5 分析什么场景会导致last_update_applied版本不是按照一个版本一个版本方式增加的?

分析代码有一种场景会导致op on_applied成功后last_update_applied是增加了两个版本,即clone场景

当打了快照或者克隆触发clone操作时候版本就会增加两次,如果此时恰好这些对象又被选中参与scrub过程并按照上面假设的时序进行就会复现该问题了

3. 原因分析

某些场景会导致last_update_applied版本跳变,如果此时有scrub op因为版本原因被挂起的话,当这些op applied完成后因为last_update_applied不等于scrub_to,所以scrub op一直被挂起,主副本的PG上的请求一直处于waiting for scrub进而阻塞业务,导致业务中断
注:该问题在新的版本已经得到解决了