概述
本文关于pg恢复场景下的一些想法、需求和可能得优化方向。
backfill
回顾backfill的逻辑
当Primary判断 ActingBackfill集合中,有副本需要通过backfill才能修复时,会向所有参与backfill的pg实例进行backfill的资源预留,当恢复线程不足,或者等待资源预留过程中,pg会进入backfill_wait状态。如果资源预留成功,可开始backfill也就是进入backfilling状态。如果一个backfill的pg实例,其所归属的osd空间不足,则会将当前backfill流程挂起,并释放之前申请预留的资源,同时pg进入backfill_toofull状态。primary会通过定时器中的回调函数,定期唤醒再次由primary发起资源预留申请,直到满足backfill所有条件。当pg backfill完成后,primary会发送backfilled事件,最终将状态机调整成active/clean状态,然后再删除这个pg不必要的副本。
简单来说,就是pg需要backfill,资源不够或者等待申请资源,进入backfill_wait状态,资源预留成功,osd空间足够,进入backfilling,空间不足,进入backfill_wait+toofull。主osd会定期进行重新申请,发现符合条件会再次backfill。backfill完成后,最终进入clean状态, 并回收不必要的空间
概念解释
这里重温一下之前提到的基础概念
- osd_backfillfull_ratio:如上所说,osd进行pg backfill需要空间,为了避免osd直接full掉,一般先用此值限制osd 的pg backfill行为,避免osd空间达到full,造成集群error。
- osd_full_ratio:允许单个osd所能达到的最高使用率,当达到这个值时,集群所有业务不可写入,变为只读。
- osd_max_backfills:可以理解为上面所提到的资源,单个osd所允许同时执行 recovery或者backfill的pg个数,也就是常说的恢复线程数。
- primary:pg主osd
- temp pg:临时放置组,作为一个临时过渡状态,用户在数据迁移恢复过程中,存储对象和响应请求。数据恢复后,temp就会解散。
- acting set:存有pg数据的当前osd的组合
- up set:集群恢复完成时,预测承担pg数据的所有osd的组合。
- min_size:pg响应io请求所需要的最小的存活副本数
- 数据强一致性:作为商用存储,必须保持数据强一致性,也就是所有副本全部落下返回,写成功,才返回前端。(filesotre时代用ssd做journal加速,写日志即返回。bluestore用lvm裸盘和db、wal加速,避免双写和归并加速。这里能涉及细节太多了,鉴于没人想看,就不展开赘述了)
backfill过程中存在的问题
- 恢复线程抢断,导致backfill thread 阻塞。
- 无效迁移,部分场景remap会导致osd之间互迁
- 临时pg占用大量额外空间和io
- primary注册的回调函数唤醒队列,具有盲目性和随机性,导致无法优先恢复到我们想恢复的pg
- 每个primary申请backfill时无法兼顾到全局使用率,导致在高容量集群中会出现卡在backfilltoofull
问题说明
关于pg backfill队列随机性、盲目性
为什么说pg backfill队列随机盲目问题很大。假如集群中有6个osd,以osd容灾的3副本 pool。
osd.0
osd.1
osd.2
osd.3
osd.4
osd.5
其中有三个以2为primary的pg,健康状况下,其up set 、acting set。如下
pg.id | up set | acting set |
---|---|---|
1.11c | [2,0,1] | [2,0,1] |
1.11d | [2,3,5] | [2,3,5] |
1.11f | [2,4,5] | [2,4,5] |
此时active+clean,up set和acting set一致。
假如此时发生硬件故障,osd.4和osd.5先后离线,那么基于原本离线的副本数不同,pg也会变成如下三种情况。
pg.id | up set | acting set | 状态 |
---|---|---|---|
1.11c | [2,0,1] | [2,0,1] | 不降级,但可能会remap,要进行backfill |
1.11d | [2,3,0] | [2,3] | 降级,active,仍存在两副本,要进行backfill |
1.11f | [2,1,3] | [2] | 降级为单副本,高危pg,期望最先进行backfill |
此时1.11fpg显然危险级别最高,我们希望最优先让它进入backfilling状态。因为单副本,对ceph来说有致命的风险,无论是一致性还是安全性,都需要副本的支持。
然而如果此时由于remap,或者其余pg优先进入backfill状态抢占了恢复资源,1.11f则会长时间处于backfill_wait和单副本状态,无疑是存在丢数据风险。
优化方向:
- 对恢复逻辑上来讲,应当让degarded pg的primary osd的申请,并优化pg wait队列,让降级pg处于最前端,优先恢复单副本pg
- 从运维上来讲,有两种方法,第一种是调大资源,也就是放开backfill的线程,使用force backfill提高单个pg恢复的op优先级。第二种是使用upmap,先取消掉backfilling中的pg让其处于wait,待目标pg进入backfilling后,再upmap回来。
高容量backfill_full无法保证不突破osd_full_ratio
当集群使用率非常高时,我们非常担心osd容量会突然超出osd_full_ratio,从而导致pool full,业务error。因此我们往往会设置小一点的backfillfull参数,使pg不会因为在backfill过程中,超过osd full值。
例如:
osd_full_ratio 95%,osd_backfillfull_ratio 94%,预期是当pg backfill full达到94%时,不再进行任何backfill,更不会达到95%。
然而实际情况真的是这样么?
- 业务写不可控,osd容量仍在上升
- osdmap因为集群没有clean 保留过多版本epoch,并在逐渐累积
- 在没有达到backfill_full之前申请了资源并进入backfilling状态,而单个pg过大,backfill完成前后,osd使用率上升较多。
也就是说即使设置了94%,osd仍有可能在backfill过程中,突破full的设定,从而使业务故障。
临时优化方向:
手动从高容量osd上upmap几个clean pg出去。
为什么要提前扩容
ceph利用率之所以一直给人感觉不高,正是为了满足存储一致性,安全性,健壮性等等,一个集群的最佳使用率可能只能达到物理容量的80%(集群避免full预留,和归并空洞的浪费,alloc size等等)。
假设我们等到集群所有osd使用率较高时,我们在进行扩容,我们期待的是,加入新osd时,旧osd的容量能立马下降。
然而
- backfill要全量复制,pg active clean后,才会清除原pg上的数据,释放空间。
- 为满足哈希,加入新节点后,不止是从老节点迁出pg到新节点,老节点之间的pg部分会remap。
比如osd.0 使用率最高,已达到94%,此时加入新osd扩容,可能osd.0上的pg会迁出40个,然后迁入10个。
我们当然希望的是优先先做迁出pg的backfilling,然后clean,尽快释放osd.0的空间,远离osd_full_ratio避免业务崩溃。
然而对别的primary 的osd,计算需要向osd.0迁移remap pg时,并不会关心osd.0是否有更优先级高的pg需要先迁出,直接进入backfilling,抢断了osd.0 迁出pg的机会。
也就是说,osd.0虽然因为扩容,整体负担的osd数量少了三十个,但短时间可能反而会先增加pg负担,使容量不会下降甚至还会上升、外加上条原因,会导致这种情况下集群的最高osd使用率更容易突破osd_full_ratio值。
所以,总之,要提前扩容,要给osd足够时间clean后释放,以及空间去做pg的remap。