PGInfo存在于PG的整个生命周期中,其在对象数据的写入、数据恢复、PG Peering过程中均发挥重要的作用。本章试图研究pg info在整个PG生命周期中的变化过程,从而对PG及PGInfo有一个更深入的理解。
从上面的代码可知,ReplicatedPG以及PGBackend中使用到的PGInfo均为PG::info。
1. pginfo相关数据结构
1.1 pg_info_t数据结构
pg_info_t数据结构定义在osd/osd_types.h头文件中,如下:
下面我们分别介绍一下各字段:
1.1.1 pgid
pgid用于保存当前PG的pgid信息。
1.1.2 last_update
last_update表示PG内最近一次更新的对象版本,还没有在所有OSD上更新完成。在last_update与last_complete之间的操作表示该操作已经在部分OSD上完成,但是还没有全部完成。
下面我们来看一下pginfo.last_update在ceph整个运行过程中的更新操作:
1) PG数据写入阶段增加log entry
从上面可以看到,在PG数据写入阶段,将pg_log_entry_t添加进pg_log时,会将info.last_update更新为ctx->at_version。
Question: pg_log.add(e)是将该pg_log_entry添加到内存中的,万一系统重启,内存中的pg_log_entry丢失怎么办?
Answer: 即使系统重启,内存中的pg_log_entry丢失其实也是没有问题的。系统重启之后,首先会读取日志,然后再进行peering操作,从而使3个副本重新达成一致。
2) 进入activate阶段更新本地保存的peer.last_update
从上面的代码可以,当PG primary进入activate阶段,表示副本之间已经达成一致,此时对于PG primary来说,可以更新本地保存的peer.last_update为权威的last_update。
3)PG分裂时设置info.last_update
在PG分裂时,肯定已经完成了peering操作,此时info.last_update必定等于pg_log.get_head,既然如此为何代码中还要在重新设置一遍呢?这是因为在PG分裂时,pg_log也要进行分裂,原来的head有可能被分裂到了child中了,因此这里需要重新设置当前PG的last_update。如下图所示:
4)恢复丢失的pglog时,更新info.last_update
从上面可以看到,在调用完成pg_log.append_new_log_entries()之后,会对info.last_update进行更新。下面我们来看一下在什么情况下会调用该函数:
- merge_new_log_entries()日志合并
- do_update_log_missing()更新丢失日志
5) peering过程中处理副本日志,形成权威日志的过程中更新oinfo的last_update
6) 处理有分歧日志时
从上面可以看到,对于PG的非primary副本在进行peering过程中,会调用rewind_divergent_log()来回退分歧的日志,从而更新pginfo.last_update;
7)合并权威日志过程中
8) issue_repop()更新本地保存的peerinfo
上面代码在PG写入流程中调用,直接更新本地保存的peerinfo的last_update;
9) 标记object丢失时,更新当前pginfo的last_update
总结
last_update永远指向当前PG副本的最新日志版本。只要日志发生了改变(如对象数据修改、pglog合并等)都可能会引起last_update的修改。
1.1.2 last_complete
表示从[0, last_complete]之间的所有object均在本PG副本
上存在。
ps: 在写PGLog时也会写pg_info,参看PG::write_if_dirty()中调用prepare_write_info()部分
下面我们来看一下pginfo.last_complete在ceph整个运行过程中的更新操作:
1) PG初始化时,更新info.last_complete
从上面代码可知,当需要进行backfill时,会直接将info.last_complete更新为info.last_update。
2)PG数据写入过程中,如果上次已经更新到最新,则将info.last_complete更新为e.version
3) peering完成,调用activate()激活PG时更新last_complete
从上面我们可以看到,peering完成调用activate()激活PG时,如果missing为空,那么可以将info.last_complete直接更新为info.last_update。
4) 进入activate阶段更新本地保存的peer.last_complete
从上面的代码可以,当PG primary进入activate阶段,表示副本之间已经达成一致,此时对于PG primary来说,如果确定peer并没有missing对象,则可以更新本地保存的peer.last_complete为peer.last_update。
5) PG进行分裂时,更新对应的last_complete
6) peering过程中,当缺失的日志补齐,更新info.last_complete
7) peering过程中,形成权威日志上更新oinfo.last_complete
从上面我们可以看到,在处理副本日志时,如果pg missing不为空,则从权威pg log中找出该PG副本第一个丢失的日志,那么oinfo.last_complete就是对应前一条日志的版本; 否则(即pg missing为空),oinfo.last_complete就是oinfo.last_update。
8) peering过程中,rewind分歧日志时,可能需要回滚last_complete
9) recover恢复阶段,提升info.last_complete
10) 进入uncomplete状态时,更新info.last_complete
11) issue_repop()更新本地保存的peerinfo
上面代码在PG写入流程中调用,直接更新本地保存的peerinfo的last_complete;
12) recovery阶段,当没有Object missing,更新info.last_complete
总结
通过上面,我们发现pginfo.last_complete的确切含义似乎不是很好理解,下面我们再来看一下代码中对该字段的注释:
“last version pg was complete through”翻译成中文为:上一版PG已经完成。那么是否可以理解为:在last_complete之前的所有对象均在本PG副本上已经存在了,而(last_complete, last_update]有可能还是缺失的。
1.1.4 last_epoch_started
last_epoch_started表示指定PG在本OSD上启动时的epoch值。我们先来看一下doc/dev/osd_internals/last_epoch_started对该字段的描述:
从上面的描述中我们知道,info.last_epoch_started记录的是在一个interval内该PG activation时候的epoch值,即该interval的第一个epoch值。由于在一个interval内,PG的所有副本OSD将不会发生任何变动,因此在该interval及之前所提交的写操作均会反应到local info/log中。因为所有已提交的写操作均不会出现分歧,因此即使我们获取到last_epoch_started值更为老旧的权威info/log信息,我们也仍然可以保持info.last_epoch_started的独立性(即:当我们获取到的权威info/log的last_epoch_started小于当前info.last_epoch_started时,我们不必对当前info.last_epoch_started进行修改)。
info.history.last_epoch_started记录的是PG最近一次整体进入active状态并开始接受写操作时的下边界epoch值。对于一个特定的OSD,info.history.last_epoch_started记录的intervals
中PG activation时epoch的上边界值。因为所有committed writes都被提交到了acting set中的所有OSD副本,任何非歧义性的写操作都会确保info.history.last_epoch_started被acting set中的所有副本所记录。因此,在peering中向一个OSD查询info.history.last_epoch_started到某一个interval之间的信息时,并不会将max(info.history.last_epoch_started)之后interval的写操作报告为committed。因此,在PG各副本info.last_epoch_started >= MAX(history.last_epoch_started)的info中,last_update的最小值
即为已成功提交的写操作的上边界。 如下图所示:
我们会在首次接收到activation消息的时候就更新info.last_epoch_started,但是只有在新的info.last_epoch_started被持久化之后我们才会更新history.last_epoch_started。这就确保了在acting set中的所有OSD都成功记录info.last_epoch_started之前,我们并不需要获取OSD上最新的info.last_epoch_started。
下面我们来看一下其在PG整个生命周期中的更新操作:
1) 处理权威日志时,更新pginfo.last_epoch_started
可以看到,这里是从权威pginfo以及本地pginfo中选出一个较大的last_epoch_started作为info.last_epoch_started。
2)PG::activate()激活时更新
从上面可以看出,对于PG Primary来说,直接将激活时的activation_epoch设置为info.last_epoch_started,即在activate完成新一轮的last_epoch_started的设置;对于PG replicas而言,则当收到primary发送的activation_epoch较大时,更新其last_epoch_started值。
3) PG::activate()更新本地保存的peerinfo.last_epoch_started
4) Replicas激活完成,调用_activate_committed()通知primary
上面发送pg_notify_t消息,将info.history.last_epoch_started设置为了activation_epoch。
5)PG分裂设置child的last_epoch_started
6) PG replica写数据时,更新history.last_epoch_started
7) share pginfo时,更新本地保存的peerinfo.last_epoch_started
上面的代码中,当scrub完成时,PG Primary就会更新本地保存的peer_info信息,并将其发送到对应的副本以更新对应副本上的pginfo信息。
8)当所有副本被激活时,更新info.history.last_epoch_started
从上面的代码中,我们看到当peering完成,所有的副本完成激活时,会将info.last_epoch_started赋值给history.last_epoch_started。
9)pginfo初始化时,将last_epoch_started置为0
1.1.4 last_user_version
用于记录用户所更新对象的最大版本号。
下面我们来看一下其在PG整个生命周期中的更新操作:
1) last_user_version初始化
在PGInfo初始化时,将last_user_version均初始化为0值。
2) PG分裂时,从父PG中拷贝info.last_user_version的值
3) 数据更新操作,添加log entry时更新last_user_version
4)merge_log时更新info.last_user_version
5) finish_ctx()更新last_user_version
1.1.5 log_tail
log_tail指向pg log最老的那条记录。下面我们来看其在整个PG生命周期中的变化:
1) PG分裂时,生成新的log_tail
PG分裂时,日志也会进行分裂。
2) trim日志时,log_tail移动
3) merge_log时,更新info.log_tail
4) 读取PGlog时,更新log.tail
1.1.6 last_backfill
last_backfill用于记录上一次backfill到的位置,处于[last_backfill, last_complete)之间的对象可能处于missing状态。下面我们来看一下本字段在PG整个生命周期里的变化。
1) PGinfo初始化时设置last_backfill
初始化时,还不知道有哪些对象缺失,因此直接设置为hobject_t::get_max()。
2)activate()时根据权威日志计算出所要backfill的对象列表
3) PG分裂时,计算出child的last_backfill
如果父PG没有需要backfill的对象,那么自然child pg也是没有,直接设置为hobject_t::get_max()即可;如果父PG有需要backfill的对象,那么则将父PG及子PG的last_backfill均设置为hobject_t(),重新开始进行backfill操作。
4) PG初始化时,根据backfill标志设置last_backfill
5) 收到MOSDPGBackfill消息时设置last_backfill
6) on_removal时设置last_backfill
7) recover_backfill()时设置last_backfill
1.2 pg_stat_t数据结构
pg_stat_t数据结构用于保存当前PG的状态信息,其定义在osd/osd_types.h头文件中:
3) pg_history_t数据结构
pg_history_t用于保存PG最近的peering/mapping历史记录,其定义在osd/osd_types.h头文件中:
2. PG info信息的初始化
1)PG构造函数中初始化pginfo
从上面我们可以看到,在PG的构造函数中设置了pg_info_t.pgid;将last_epoch_started初始化为0;将last_user_version初始化为0;将last_backfill设置为hobject_t::get_max(),表示没有需要backfill的对象;将last_backfill_bitwise设置为false。
[参看]
- ceph中PGLog处理流程