注意
本文档适用于 Ceph 的开发版本。
部分对象恢复
部分对象恢复(Partial Object Recovery)提高了基于日志的恢复(相对于回填)的效率。原始的基于日志的恢复根据 pg_log 差异计算 missing_set。
如果对象被 pg_log 标记为已修改,那么整个对象就应该从一个 OSD 恢复到另一个 OSD,无论对象中真正修改了多少内容。这意味着一个 4M 的对象,即使只修改了其中的 4k,也应该恢复整个 4M 的对象,而不是只恢复修改的 4k 内容。此外,即使对象映射(object map)没有被修改,也应该恢复。
部分对象恢复旨在解决上述问题。为了实现这些目标,需要做两件事:
记录对象被修改的位置是必要的
记录对象的 object_map 是否被修改也是必要的
引入了 class ObjectCleanRegion 来实现我们的目标。clean_offsets 是一个 interval_set<uint64_t> 类型的变量,用于指示对象中未修改的内容。clean_omap 是一个布尔变量,指示 object_map 是否被修改。new_object 意味着 OSD 不存在该对象。max_num_intervals 是 clean_offsets 中间隔数量的上限,以确保 clean_offsets 的内存成本始终有界。
如果 clean_offsets 中的间隔数量超过边界,最短的干净间隔将被修剪。
例如:max_num_intervals=2, clean_offsets:{[5~10], [20~5]}
那么新的间隔 [30~10] 将会驱逐最短的间隔 [20~5]
最终 clean_offsets 变为 {[5~10], [30~10]}
部分对象恢复的流程
首先,OpContext 和 pg_log_entry_t 应该包含 ObjectCleanRegion。在 do_osd_ops()、finish_copyfrom()、finish_promote() 中,ObjectCleanRegion 中的相应内容应该标记为脏,以便跟踪对象的修改。同时将 OpContext 中的 ObjectCleanRegion 更新到其 pg_log_entry_t 中。
其次,pg_missing_set 可以正确地构建和重建。在 peering 过程中计算 pg_missing_set 时,也要合并每个 pg_log_entry_t 中的 ObjectCleanRegion。
- 例如:对象 aa 有 pg_log
26’101 {[0~4096, 8192~MAX], false}
26’104 {0~8192, 12288~MAX, false}
28’108 {[0~12288, 16384~MAX], true}
对象 aa 的 missing_set:合并上述 pg_log --> {[0~4096, 16384~MAX], true}。这意味着 4096~16384 被修改,并且 object_map 也在版本 28’108 上被修改。
此外,OSD 可能会在合并日志后崩溃。因此,我们需要 read_log 并重建 pg_missing_set。例如,pg_log 是:
对象 aa: 26’101 {[0~4096, 8192~MAX], false}
对象 bb: 26’102 {[0~4096, 8192~MAX], false}
对象 cc: 26’103 {[0~4096, 8192~MAX], false}
对象 aa: 26’104 {0~8192, 12288~MAX, false}
对象 dd: 26’105 {[0~4096, 8192~MAX], false}
对象 aa: 28’108 {[0~12288, 16384~MAX], true}
最初,如果 bb, cc, dd 被恢复而 aa 没有。因此我们需要重建对象 aa 的 pg_missing_set,并发现 aa 在版本 28’108 上被修改。如果 object_info 中的版本是 26’96 < 28’108,我们不需要考虑 26’104 和 26’101,因为整个对象都会被恢复。然而,部分对象恢复也要求我们重建 ObjectCleanRegion。
仅仅知道对象是否被修改是不够的。
因此,我们还需要遍历之前的 pg_log,即 26’104 和 26’101 也 > object_info(26’96),并基于这三个日志:28’108、26’104、26’101 重建对象 aa 的 pg_missing_set。合并日志的方式与上面提到的一样。
最后,根据 pg_missing_set 完成 push 和 pull 过程。根据 pg_missing_set 中的 ObjectCleanRegion 更新 recovery_info 中的 copy_subset。copy_subset 指示需要 pull 和 push 的内容的间隔。
这里复杂的部分是 submit_push_data,需要分别考虑几种情况。我们需要考虑如何处理对象数据,对象数据由 omap_header、xattrs、omap 和 data 组成。
情况 1:first && complete:由于对象恢复在单个 PushOp 中完成,我们希望保留原始对象并直接在对象上覆盖。对象不会被移除,也不会创建一个新的。
问题 1:由于对象未被移除,旧的 xattrs 仍保留在旧对象中,但可能在新对象中已更新。覆盖相同的键或添加新的键是正确的,但删除键将是错误的。为了解决这个问题,我们需要移除对象中所有的原始 xattrs,然后更新新的 xattrs。
问题 2:由于对象未被移除,object_map 可能会根据 clean_omap 进行恢复。因此,如果恢复 clean_omap,我们需要移除对象的旧 omap,原因与 xattrs 相同,因为 omap 更新也可能涉及删除。因此,在这种情况下,我们应该执行以下操作:
清除对象的 xattrs
如果需要恢复 omap,清除对象的 omap
将对象截断到 recovery_info.size
恢复 omap_header
恢复 xattrs,如果需要则恢复 omap
如果 fiemap 显示没有内容,则对原始对象打零(punch zeros)
覆盖已修改的对象内容
完成恢复
情况 2:first && !complete:对象恢复需要多次完成。这里,target_oid 将指示 pgid_TEMP 中的一个新 temp_object,因此问题有所不同。
问题 1:由于对象是新创建的,无需处理 xattrs
问题 2:由于对象是新创建的,并且 object_map 可能未传输,具体取决于 clean_omap。因此,如果 clean_omap 为 true,我们需要从原始对象克隆 object_map。问题 3:由于对象是新创建的,并且未修改的数据不会传输。因此,我们需要从原始对象克隆未修改的数据。因此,在这种情况下,我们应该执行以下操作:
移除临时对象
创建一个新的临时对象
为新的临时对象设置 alloc_hint
将新的临时对象截断到 recovery_info.size
恢复 omap_header
如果 omap 是干净的,则从原始对象克隆 object_map
从原始对象克隆未修改的 object_data
对新的临时对象打零(punch zeros)
恢复 xattrs,如果需要则恢复 omap
覆盖已修改的对象内容
移除原始对象
移动并重命名新的临时对象以替换原始对象
完成恢复