https://tracker.ceph.com/projects/ceph/wiki/Code_Walkthroughs
2021-11-30 @ 10am PT: OSD Read Lease - Sage Weil - https://www.youtube.com/watch?v=9hxPwQvWN4s
- 動画の残り15分程(残り37%程)の所から始まるPRを例に挙げた解説は未確認
- https://github.com/ceph/ceph/blob/d7cb2dd2e4a8dffe6a03810d4932fb6de93c24ae/doc/dev/osd_internals/stale_read.rst
- 実際に動画で参照していたものとバージョンは違うかも
- 技術的には「プライマリから読み取る」というだけ
- プライマリが依然としてプライマリのままかOSDMapを見ているため通常は問題無い
- プライマリでない者が読み取り要求を受けても、自身がプライマリでない事を認識しているため要求は無視される
- ただし、ネットワークパーティションに陥っている等でプライマリの移動が起き、現在は他の誰かがプライマリだが、各OSDは「他の事をしていた」等でそれを認識しておらず、クライアント側は依然として移動する前のプライマリから読み取ってしまう可能性がある
- このようなケースをトリガーするのはかなり難しい
- ネットワークパーティションで分断された残りの部分とOSDを並べ替えて(?)、クライアントが上手いこと古い方から読み取る、といった事をテストとして発生させる必要がある
- 基本的にブラックリストに登録することによって、それを行うユニットテストができる(?)
- (引き起こし方について説明していたがよく分からず)
- 最終的には修正された
- 読み取りに関してリース間隔を設定するプロパティがある(
read_lease_interval
)- これはプール毎に設定できる
- デフォルトは
osd_pool_default_read_lease_ratio
*osd_heartbeat_grace
が設定されるosd_pool_default_read_lease_ratio
のデフォルト値は.8
なので、リース間隔はosd_heartbeat_grace
より短い- なお、
osd_heartbeat_grace
のデフォルト値は20 - 上記リンク先のドキュメントからは単位が分からなかったが、ソースコードも見てみた所、単位は秒である模様
- なお、
- readable until(読み取り可能である時間)は、以下の2つの値を追跡することで実現される
readable_until
- リースが切れるまでに、どれくらいの時間リクエストを処理(読み取り)することができるかを示す値
readable_until_ub
- acting set内の任意のOSDに対するreadable_untilの上限値
- これは他の全てのレプリカで値が読み取れるまでの上限であるため、将来にどのくらいの期間かかる可能性があるかを示している
- エポックでPGのプライマリが決定されるわけだが、
readable_until_ub
は、そのプライマリが読める上限
- これらの値の更新は
pg_lease_t
メッセージを通じて行われる- 例えばリースを延長する際、プライマリが各レプリカにこのメッセージを送信することで全員の
readable_until
とreadable_until_ub
を更新する - 流れとしては以下の通り
- プライマリ自身の
readable_until_ub
を更新 - 更新した
readable_until_ub
を各レプリカへメッセージで共有する - 全てのレプリカで新しい
readable_until_ub
を確認できたら、プライマリ自身のreadable_until
を更新する - 更新した
readable_until
を各レプリカへメッセージで共有する
- プライマリ自身の
- (「シーケンスを正確に思い出せないが」との前置きで)、プライマリとレプリカの通信の流れは、基本的にレプリカにメッセージを送信すると、レプリカはプライマリにack応答し、プライマリはレプリカに別のメッセージを送信する
- つまり、基本的に、全ての動作しているOSDが新しい
readable_until_ub
を見たことを確認すると、プライマリは自身のreadable_until
を増加させる、という流れ - その結果、動作中のOSDの
readable_until
は常に動作中のOSDのreadable_until_ub
以下であるという不変性が得られる
- 例えばリースを延長する際、プライマリが各レプリカにこのメッセージを送信することで全員の
- 質疑
- Q.
- acting setに登場するもののいずれかがプライマリになる可能性がどうのこうの・・
- 回答から逆に推測すると、どうやらレプリカ側を読んでしまう事を危惧している模様
- A.
- 全てのレプリカはすべて、acting setのすべてが権利を得るまで読み取り可能(
readable_until
)以上のupper bandを持ち、レプリカも読み取り可能になるまで維持するため、通常、レプリカから読み取ることはない - 「
readable_until
<=readable_until_ub
」が維持されている事により、レプリカから読み取る事は起きない、とのこと - (質問者が具体的にどういうケースを想定しているのか分からなかったので、この不変性が維持されている事でどのように「レプリカ側を読んでしまう」事を防げるのか不明)
- レプリカが自身をプライマリと誤認しない、という事だと思われる
- 全てのレプリカはすべて、acting setのすべてが権利を得るまで読み取り可能(
- Q.
- 各OSDでのクロックのずれ(クロックスキュー)を回避するため、NTP等ではなく、単調クロックによるモノトニッククロックを使用している
- 理由としては、「NTPが何か迷惑な事をしている可能性がある」とのこと
- (当然だが)この単調クロックは戻ることは無い
- OSDの時計(時間設定)を変更しても、この単調クロックは変更されない
- 単調なローカル時間のようなものだが、各OSDに独立して存在する
- https://github.com/ceph/ceph/blob/9ef2b8d2ddbb4770c6f421fbfcdb0cc54b349f79/src/osd/OSD.cc#L5533
OSD::handle_osd_ping()
の中でHeartbeatStamps
構造体のgot_ping()
を呼び出している部分HeartbeatStampsRef stamps
が、2つのOSD間でpingをやり取りする際に使用するステートフルオブジェクトのようなもの- ハートビートでやり取りするメッセージのタイプが
MSG_OSD_PING
である場合、それはMOSDPing
という型 MOSDPing *m
(handle_osd_ping()
の引数)のm->get_connection()
で取得したコネクション情報から取り出したセッション情報の中にHeartbeatStampsRef stamps
がある
- ハートビートでやり取りするメッセージのタイプが
got_ping()
はping(MOSDPing::PING
)を受信した場合の関数で、ローカル時間の同期のための処理を行っている- 細かくは以下の通り
- 受信したメッセージに含まれる上限のエポック秒(
m->up_from
)と自身(HeartbeatStampsRef
構造体)の上限のエポック秒(up_from
)を比較し、受信した方が新しければ(大きな値であれば)、受信したエポック秒で自身の上限のエポック秒を更新する- Cephの世界での「エポック」は「A (monotonically increasing) OSD map version number」
- 「メッセージに含まれるピア側の時刻(
m->mono_send_stamp
) - ローカル時刻」を自身(HeartbeatStamps
構造体)のpeer_clock_delta_lb
へ設定 - メッセージに含まれるピア側が持っている時刻差分の上限情報(
m->delta_ub
)のポインタを自身(HeartbeatStamps
構造体)のpeer_clock_delta_ub
へ設定 peer_clock_delta_ub
の値に-1を掛けた値を、今度はこちらから送信する時刻差分の上限情報(sender_delta_ub
)とする- この
sender_delta_ub
を含めてMOSDPing::PING_REPLY
というオペレーションとしてメッセージを送信している - この箇所は
OSD::handle_osd_ping()
の中でMOSDPing::PING
である場合の処理だが、MOSDPing::PING_REPLY
の場合の処理もここ以降にある
- この
- 受信したメッセージに含まれる上限のエポック秒(
- https://github.com/ceph/ceph/blob/9ef2b8d2ddbb4770c6f421fbfcdb0cc54b349f79/src/osd/OSD.cc#L5792
got_ping_reply()
はpingの返信(MOSDPing::PING_REPLY
)を受信した場合の関数でこちらもまた時刻同期の為の処理- 細かくは以下の通り
- https://github.com/ceph/ceph/blob/9ef2b8d2ddbb4770c6f421fbfcdb0cc54b349f79/src/osd/PeeringState.h#L114
HeartbeatStamps
は、peer_clock_delta_lb
とpeer_clock_delta_ub
でローカルの時間とピアOSDの時間の差(デルタ)の下限と上限を保持する- このデルタは「ピアOSDの時間 - ローカルの時間」であり、「ローカルの時間 + デルタ = ピアOSDの時間」、「ピアOSDの時間 - デルタ = ローカルの時間」という関係
- このデルタには上限値(
peer_clock_delta_ub
)と下限値(peer_clock_delta_lb
)があり、ピアOSDの時間が[ローカルの時間 + 下限値, ローカルの時間 + 上限値]のどこかであるように維持される - 逆にピアOSDの時間に対してローカルの時間は[ピアOSDの時間 - 上限値, ピアOSDの時間 - 下限値]のどこかとなる
- https://github.com/ceph/ceph/blob/9ef2b8d2ddbb4770c6f421fbfcdb0cc54b349f79/src/osd/PeeringState.h#L143-L155
- この箇所で
peer_clock_delta_ub
とpeer_clock_delta_lb
をログ出力している- ただ、実際のログを
"hbstamp"
というキーワードで検索してみても何もヒットせず・・
- ただ、実際のログを
- この箇所で
- (前置き)
- 「Prior Interval」というのは、そういう名前のパラメータがある、というよりは、そういう「期間」の話をしている模様
- そして、ここで言う「Prior」は、OSD同士のピアリングで
readable_until
が確定する前、という意味である模様 - (元のドキュメントにも"The prior interval is ..."というような直接的な説明が無い・・)
- 全てのOSDは
readable_until
の値の上限(readable_until_ub
)を保持する必要がある - OSDマップの変更等でまだpingを行っていないOSDについては、そのピアリング中に
readable_until_ub
がpg_history_t
の一部として共有される pg_history_t
はこの辺りに定義されているpg_history_t
はこの辺りの変数を見ればわかる通り、「何かを行ったエポック秒」といった歴史的な情報を持っている- また、
prior_readable_until_ub
というフィールドがある- これは、読み取り可能になるまでのインターバルの上限
- 一般的には、インターバルのリースが切れるまで待ってから書き込み処理を開始することになる
- また、書き込みが完了した後に、古いOSDの1つが読み出し処理を行い、読み書きの順序を乱すことがないようにしている、とのこと
- そして、その下の方にはいくつかのヘルパー関数がある
- (前置き)
- 「Prior Readable」(先行読み出し)という考え方がある様で、ここではその読み出し期間についての関数が定義されている模様
refresh_prior_readable_until_ub()
- ローカルOSDの現在の時間とその上限を持つメッセージ(おそらく最初に送信する正式な(?)メッセージの事?)を送信する前に呼び出される
- そして、新しいdurationを計算するか、あるいは、既に上限を過ぎている場合は、古いリースが切れるまで読めるので(?)、ゼロを返す
- それにより、前のインターバルが読めなくなったことがわかる(?)
- (Prior Interval中であっても読み出し可能な期間というものをリフレッシュする関数である模様)
get_prior_readable_until_ub()
- この値を読み取る場合は、値を現在の時刻に加算する必要がある
- (何らかの前処理が必要とのことみたい)
- この値を読み取る場合は、値を現在の時刻に加算する必要がある
- (前置き)
- この様に、Prior Intervalsは、実時間の代わりに、このduration時間で表現される
- (発表者曰く、)それらはほとんど迷惑なタイムスタンプ(?)で、2・3箇所くらいしか出てこないのでほとんど無視して構わない
- pgはアクティブなのに、何らかの理由で最低限のリースが十分に速く更新されないと、ラグが発生する。その状態の話
- この状態に陥る流れとしては以下の通り
- PGが読み出しと書き込みをサービスしている間に何らかの理由で読み出しが入ってくる
- それによって読み出し可能な期間の値が過去のものになる
- 「書き込みを行っている間に読み出しが入ってきた」という話なので、書き込みを行う前に読み出した値については「過去のもの」ということを言っている?
- それによってその読み出しをサービスすることができなくなった場合、PGは遅延(laggy)状態になる
- 一度「過去のもの」を読んでしまった事に気づいた時点で「ちょっと待とう」という事になる、ということかな
- この状態になると、リースが延長されるか、何か他のことが起こって遅延状態が解除されるまで、すべての読み出しがキューに入れられる
- この時、読み出しをサービスしないが、メッセージがネットワークを通過することでリースが成立するのであれば、その理由を検討し、サービスを提供することにしている
- リース間隔が短すぎたり、遅いネットワークや高負荷のOSDを使用していない限り、ほとんどの場合、この現象は起こらないはず
- 他にも、クラスタが高ストレスである、あるいはパーティション化されたOSDがプライマリとの通信を停止していることに気づかず、リースが削除されなかったり、プライマリがレプリカから応答リースを取得できなかったりしない限り、ほとんどの場合、この現象は起こらない
- なお、その場合、パーティションが切れているので、いずれはラグが発生し、読み出しのサービスができなくなる
- Q. この遅延したOSDにキューイングされていたリクエストはどうなるのか?どのように並べ替えられるのか?
- A.
- キューイングされたリクエストがどうなるのか?について
- ラグ状態が解消されるか、OSDやPGリピーター(?)がすべてのキューを完全にドロップするまで、それらは永遠にそこでブロックされることになってしまう
- そのため、一般的にインターバルが変更されると、そのPGのすべてのリクエストがドロップされ、OSDまたはクライアントがそれらを再送信する
- したがって、このような、例えば実際にパーティション分割が起きてしまったような場合、最終的にOSDがそのOSDマップの変更を発見するまで、基本的にキューに入り、それらはドロップされることになる(?)
- あるいは、一時的/短期的なパーティションの場合、例えばネットワークケーブルが5秒間ダウンし、その後復旧するというようなことはある
- その場合、ネットワークが再接続され、すべてのメッセージが通過し、リースが更新され、そしてすべての読み取り要求が再キューされる
- リクエストがどのように並べ替えられるのか?について
- PGのコードのどこにブロックされたリクエストを再キューイングする関数があるのか、正確には忘れた
- リクエストの並べ替えには非常に繊細な順序がある
- リクエストのブロックには、リカバリ待ち、リリース待ち、出現待ち(?)、などなど、10個程の異なる理由がある
- 加えて、リクエストをリリースする優先順位というのも、最も低いものから、最も高いものまである
- そのような訳で、リクエストの順序付けは複雑
- どこに書かれていたか忘れたが、それを行うその同じ関数の中で、すべて同じように処理される
- キューイングされたリクエストがどうなるのか?について
- Prior Intervalの上限がまだ未来にあるような(Prior Intervalがまだまだ続くような)「待機状態(wait state)」の場合、少し異なる
- そのような場合は、ピアリングが完了するのを待つしかない
- だから「wait state」という事か
- そして、これの待機時間をユーザーに気づかれないように隠す基本的なトリックがある(?)
- 基本的にラグがあると、リースを(送り元=プライマリ?)へ返送し、PGがアイドルになったらプライマリはリースメッセージを送信する
- なお、既存のメッセージに少なくともメッセージをピギーバック(※)させることができる
- ※ ピギーバック:背中に乗る(乗せる)、背負って運ぶ、おんぶ(する)、肩車(する)、便乗(する)、などの意味を持つ英単語
- 既存のメッセージに、メッセージを相乗りさせることができる(?)
- ※ ピギーバック:背中に乗る(乗せる)、背負って運ぶ、おんぶ(する)、肩車(する)、便乗(する)、などの意味を持つ英単語
- (OSDダウンに至るケースと、OSDダウンの扱いの話)
- ピアリング中などで良く問題になるのがOSDダウン
- OSDダウンの際の扱いとしては以下の通り
- まず、OSDが本当にダウンしたと判断する前に、10秒間、その状態のままpingを打ち続ける
- そうすると、リースは最短時間(8秒)でタイムアウトする
- そしてOSDダウンという判定になる、という事を言っている、という理解
- OSDダウンという判定になると、リース間隔が、ハートビートタイムアウトと同様に常に短くなる、とのこと
- なお、OSDはタイムアウトして失敗することもあれば、プロセスが終了したり停止したりすることもよくあること
- その場合、8秒といったタイムアウト時間の間、ハートビートを待つ必要はない
- そのため、OSDマップには新しいフィールドが追加されている
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/OSDMap.h#L96
- この
dead_epoch
フィールドはOctopusで追加されたもの - 基本的に、これは完全に死んだと確認された最後のエポック
- そこに至るには2つの方法がある
- 一つは、OSDがモニターにMarkMeDeadメッセージを送ることができ、明示的に「私がリクエストに応答しなくなった」と言うように、MarkMeDeadに遷移することができること
- もうひとつの方法は、相手にメッセージを送っているときに切断され、再接続しようとする場合
- その際、ECONNREFUSEDを取得する
- この
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/OSD.cc#L6486
- これは、handle_refusedというメッセンジャーからのコールバックで、ECONNREFUSEDを取得した場合に発動される
- 基本的にconnection refusedに陥った場合、それはネットワークが切断されたのではなく、プロセスが停止しているということが推測できるらしい
- そうなると、タイムアウトになるなどして、反応しなくなる
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/OSD.cc#L6511
- その場合、モニターに送信する失敗メッセージには、「このOSDを直ちに停止して失敗とマークする」というフラグ(
MOSDFailure::FLAG_IMMEDIATE
)が付く - そして、これが
dead_epoch
を設定するトリガーとなる - そうすると、OSDは本当にダウンしたと判断できる
- その場合、モニターに送信する失敗メッセージには、「このOSDを直ちに停止して失敗とマークする」というフラグ(
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/osd_types.h#L2927
- このような略語はよく使われる
- これは Prior Readable Until Upper Bound の略
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.h#L1442-L1443
readable_until
はここに定義されている- OSDが読み取りをサービスすることが許可されている期間
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.h#L1445-L1446
readable_until_ub
はここ
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L1294
- いつピアリングが始まっても良いようにこれを使用してprior setをビルドする
- (どうやら、過去の情報(PastIntervals)を用いてprior setというものを作っているようだが、よく分かっていない)
- (少なくとも、「prior setのビルド」という処理が、Priorの処理すなわちピアリングの前に行われる模様)
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L749
- これは、prior setのfuture bitをすべて計算した直後に来る関数とのこと
- 初期化処理やピアリング期間の開始時で呼ばれている
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L788L791
- Prior Intervalにおけるreadable until(読み取り可能期間)の更新処理
refresh_prior_readable_until_ub()
の実装によると、既にupper boundを超えていたら0、そうで無ければ「upper bound - 現在」の差分を新たなprior_readable_until_ub
として設定している模様
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L795
- そして、
prior_readable_down_osds
をリセットする- 変数名からは「ダウンしたOSD」を保持しているのかと思われるが、
is_dead
判定されたOSDをこのリストから消す処理(後述)があるので、そうでも無さそう
- 変数名からは「ダウンしたOSD」を保持しているのかと思われるが、
- そして、
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L6711
- ただ、その前にピアリングを行うとのことで、以下の説明をしているが、よく分かっていない
- いつから始めるかというと、それは最初に登場するとき
- そして、事前に情報のスキャンを行い、ビルドを行う
- そして、先行してダウンしているOSDを、現在ダウンしているprior setに入っていたOSDすべてに設定した
- そして、prior setは、クエリに必要な現在生きているOSDのリストと両方を持っている
- また、その前のインターバルにあった、有用な情報を持っている可能性のあるダウンOSDのリストも持っている
- しかしそれらは現在はダウンしている
- これらは我々が気にかけているものであり、以前はレプリカだった可能性があるか、プライマリがまだ読み取りを処理している可能性があるため、それらが期限切れになるのを確実に待つ必要がある
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L1257
- これはOSDマップが進行しているとき(言っている意味がよく分からないが、「作成中」とかそういうニュアンス?)に発生する
- そしてこれはダウンしたOSDを探すところ
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L1261-L1262
- ここで、リリースの期限切れを待っているものをすべて調べる
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L1263
- そして、もしマップに「死んだ」と書かれていたら、
prior_readable_down_osds
から消す- (なぜ?むしろ
prior_readable_down_osds
へ追加すべきでは?)
- (なぜ?むしろ
- そして、もしマップに「死んだ」と書かれていたら、
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L1273
- そして、もし
prior_readable_down_osds
が空になったなら、prior_readable_until_ub
もクリアするとのこと
- そして、もし
- (前置き)
- これはPeering Stateの再アクティベート時等で呼び出される関数
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PrimaryLogPG.cc#L864
- 待ち状態の遅延状態(waiter laggy)でなければ、何も目新しい事は無いので大丈夫
- しかし、もしこの2つの状態のどちらかになってしまったら、re-queueしなければならない
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PrimaryLogPG.cc#L881
- この時、PG状態をクリアしたり、
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PrimaryLogPG.cc#L896
- laggy状態をクリアしたり
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PrimaryLogPG.cc#L904
- そして、読み出し待ちを再度キューに入れる
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L2729
- プライマリをアクティブにすると、このPG情報(prior_readable_until_ub)がすべてのレプリカに送信されることになる
pg_history_t
というデータ構造で存在する履歴情報を管理する仕組みらしいが、よく分かっていない- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L209
- これが登場するときはいつでも、リモートノードから履歴を取得する
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L3008
- そしてこれ(
update_history()
)は色々な所から呼ばれている - プライマリが増えたことでレプリカから、あるいはレプリカのために呼び出される
- また、プライマリから入力を受けたときに呼び出される
- そしてこれ(
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L3150
- あるいは新しいマスターなど、さまざまな場所でヒストリーを更新し、ヒストリーの上限を常に一定に保っている
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L212
- 自身のhistoryを更新することで、正確な時間的相対的なタイムスタンプを持つようにする
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L213
- そして、他の履歴と自身の履歴をマージする
- 新しいインターバルを見て、有効期限が切れていたりしない限りは、基本的に上限を取る
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L214
- しかし、時にはそのhistoryを進めなければならないこともある
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L221
- しかし、ここでは基本的に、履歴を更新した場合、その値を読み出すことで、上限まで前の読み取り可能な値を更新する
- https://github.com/ceph/ceph/blob/89bad98ff59da867142408ba268c9b8bafd1a911/src/osd/PeeringState.cc#L223-L226
- そして、念のため期限切れの場合は、デバッグ用のメッセージをプリントアウトしている