1. 问题背景
一个pg scrub了14天,前端虚机挂掉大半
2. 分析过程
2.1 查看scrub pg的主OSD日志
从日志里可以看出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状态机
从上面的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的日志
从上面过滤出的日志,发现13.832这个pg
- 出现了slow op,该slow op 等待时间为983040s==11天
- 这些op处于currently reached_pg,说明已经开始交给pg层处理了
- 这些op类型是replica scrub即主副本发来scrub请求,请看下图
从上面的流程可以知道主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的版本号不一致,如下图
从上图可以看出当从副本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进而阻塞业务,导致业务中断
注:该问题在新的版本已经得到解决了