BlueStore 磁盘静默数据损坏分析
实际场景
1.腾讯场景问题确认
2.rbd mirror
3.rbd export import
4.集群扩容
Bluestore的checksum算法默认采用的是crc32, 算法的逻辑很简单,即以固定大小的数据块为单位进行计算checksum,数据块的大小一般为4K,同设备的块大小。
Checksum的结果作为bluestore_blob_t结构体序列化的一部分存放在数据库中,假设一个blob的长度为64KB,进行checksum的数据块大小为4KB, 则初始化该blob时,对于crc32,每个数据块的checksum用4字节描述,将分配(64KB/4KB)* 4 = 64字节用于存储该blob数据的checksum。
写数据会进行checksum的计算,对于对齐写和非对齐写,都会以数据所在的4KB对齐的数据块为单位进行计算。比如0-256区间的数据更新,会将257-4095区间的数据读出来,然后以4KB为单位进行计算,如果257-4095原来不存在数据,则用0进行填充后进行计算,计算完将checksum值存储在该blob中。
读数据时,bluestore中会首先查找所读取数据区间所对应的checksum数组,然后用读取的数据计算checksum,判断两者是否一致。举例如下:假设blob的大小为64KB,
本次读取的数据区间为8192~16383, 则分别计算8192~12287和12288-16383两个数据块的checksum,然后读取blob中之前写数据时计算的checksum,这两块数据对应的checksum值为checksum数组偏移为2和3的两个4字节变量。
BlueStore中如下五种操作会读取对象数据,其中目前只有对象数据read操作能主动触发对象修复操作,且对上层用户不感知。
结论:读数据的时候依靠checksum机制可以发现数据不一致的情况,其中read可以自动修复,其他的读取操作如果发现存在静默错误,会及时报错,所以可以取消定期deep-scrub操作。
==>> int BlueStore::_mount(bool kv_only, bool open_db)
====>> fsck(cct->_conf->bluestore_fsck_on_mount_deep)
如果bluestore_fsck_on_mount_deep为真,bluestore的fsck过程中将会读取整个文件,以此将会检查该文件对应的磁盘块是否存在数据损坏现象,如果文件损坏,会导致mount失败,从而导致该osd启动失败。
//bluestore_fsck_on_mount_deep 默认为true
Option("bluestore_fsck_on_mount_deep", Option::TYPE_BOOL, Option::LEVEL_DEV)
.set_default(true)
.set_description("Run deep fsck at mount"),
Clone操作是否从磁盘读数据取决于bluestore_clone_cow配置,如果bluestore_clone_cow为真,则直接进行extent的共享,不会有实际的数据读写操作。如果为假,读取被clone对象的数据,写入新的对象,如果此时读取过程中,发生磁盘静默错误,则直接assert,osd进程down掉。
//bluestore_clone_cow 默认为true
Option("bluestore_clone_cow", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
.set_default(true)
.set_safe()
.set_description("Use copy-on-write when cloning objects (versus reading and rewriting them at clone time)"),
读操作过程中,如果发现磁盘静默数据错误,是会主动对该对象进行修复的,代码调用流程如下:
==>> do_osd_ops
====>> do_read
======>> objects_read_sync
======>> rep_repair_primary_object
将对象加入待修复列表,进行修复,读操作 会重新发起,用户不感知。
Write操作的rmw写,需要首先从磁盘读取数据进行覆盖写,如果此时发生磁盘静默错误,代码中直接assert,代码调用流程如下:
==>> _do_write_small
====>> _do_read 触发( assert(r >= 0 && r <= (int)tail_read);)
通常ext4类的本地文件系统会直接abort事务,将文件系统挂载为只读,因此此处直接assert osd,导致osd进程down也可以理解。
==>> _do_write
====>> _do_gc
======>> _do_read 触发assert(r == (int)it->length);
此处也会导致osd down,策略我理解同write操作。
1.1 cache?1.2 no crc? 1.3 migration?
ceph社区邮件”Copying without crc check when peering may lack reliability”提到的磁盘静默数据损坏没有及时发现的问题,根据发件人的测试步骤没有复现,数据都能自动修复,目前正在请求发件人提供完整复现脚本。现阶段调查结果如下:
1, ceph-objectstore-tool修改对象数据时,如果checksum使能,会同步用新的数据计算checksum,如果修改对象主副本,则新数据迁移到新的crush root下,此处是合理的,因为checksum检查通过,osd会认为数据是正确的,如果修改对象的从副本,由于迁移是根据主副本的数据,新的crush root下的数据仍然是主副本数据。
如果对该osd做deep-scrub, 如果对象所属的pg在该osd上是主pg,是能检测到数据变动的,因为会和从pg上的副本做对比,如果对象所属的pg在该osd上为从pg, deep-scrub将不会针对该pg做检查的。
2,如果对象数据的主副本存在cache,即使对应的底层硬件存在静默错误,正常的读操作和deep-scrub都不会检测到磁盘静默错误(我已经发送patch让deep-scrub能检测出此种损坏,patch已经被merge)。
3,数据迁移在ceph master分支上验证,在三副本测试中,破坏一个副本的数据,然后让数据迁移到另一个crush root下,ceph会自动进行数据修复的,用户不感知。
测试步骤:
破坏journal对象的主副本数据,然后进行replay,待replay完成后检查两个rbd设备的数据是否一致。
测试结果:
暂时没有找到测试方法来控制上述流程,但理论分析是可以修复的,用户不感知道。Rbd-mirror回放日志数据,也就是读取日志对象主副本数据,具体流程同”3. read操作”。
测试步骤:
创建一个rbd设备,向其偏移0处写入4MB随机数据,然后通过dd破坏其中一段数据,注意破坏是主副本的数据。破坏完成后,将主副本所在的osd重启以消除cache的影响,然后进行rbd的export 和import测试,读取两个rbd设备的前4MB数据MD5值与初始的写入rbd设备的随机数据文件md5值是否一致。
测试结果:
损坏的rbd对象数据会自动进行修复,用户不感知,具体原理参见”3. read操作”。
测试步骤:
创建3 osd集群,用rados创建一个对象,损坏对象主副本数据,注意同样需要重启osd以清除bluestore层cache,加入3个新的osd,待集群稳定后,用rados读取对象数据,验证数据md5值是否一致。
测试结果:
集群rebalance成功,损坏的数据会自动进行修复,用户不感知,具体原理参见”3. read操作”。
原文:https://blog.51cto.com/wendashuai/2778521