Skip to content

Instantly share code, notes, and snippets.

@ryogrid
Last active October 2, 2021 17:01
Show Gist options
  • Save ryogrid/55cc5a5f79edbadb1406c3b128d07f87 to your computer and use it in GitHub Desktop.
Save ryogrid/55cc5a5f79edbadb1406c3b128d07f87 to your computer and use it in GitHub Desktop.
Rust製分散KVSの設計および実装のメモ

Rustでの実システムを想定したシミュレータでは何のロックを取ればよいのか

  • リエントラントロックを使っている点には注意が必要
    • 各API呼び出しで起動されるスレッドは呼び出しごとに別々である。したがって、別ノードを中継して同じノードのAPIを叩いた場合は、実システムに合わせるならば2回目のロック取得はブロックされなければならない
    • ただし、同一ノード内ではAPI呼び出しという形はとれないはずなので、そういったケースでは同じスレッドが複数のAPIに対応する処理を行う場合もあるはずで、その際は同一のロックを複数回とれても問題ない  
  • ノードオブジェクト自体のロックを各オペレーション担当スレッドが持ち続ける必要はなく、持ち続けてはいけない
    • 他のスレッドは、そのオブジェクトをラップしたArRmRs型のオブジェクトをglobal_datasから得ることができるが、ロックをとってラップをはがすことができなくなる  
  • 現状、複数スレッドで触れるように排他できるのは、ChordNodeにArRmRs型のフィールドとして持たせている3つのクラス単位でしかないような気がする。。。しかし、それでいいような気もする。
    • ★ただ、クリティカルセクション区間を設けようとした場合は別の仕組みの導入が必要なような★  
  • どのクラスとどのクラスが、同じタイミングで排他されている必要があるか整理が必要そう(実はそんなシチュエーションはないという可能性もありそう)
    • とはいえ、どこでもNodeInfoには触っているな・・・。cloneすることでどうにかなるか?
    • 別に、異なるノードのものであれば並列に処理できる時間は多いはずなので、いいのかな?  
  • 今のいちいち借用を開放してどうこうという方法ではいずれにせよクリティカルセクションは構成できないはず
    • ロックはとったまま、借用だけ無効にするようなコーディングは可能?いつものマクロをいじるところからやって、Arc型の中身だけを引き回すようにすればいける?(修正量ハンパないことになりそう。。。)
    • ★適当な意味のない値をMutexでラップしたものをオブジェクトのフィールドに用意して、それのロックをクリティカルセクションの間は保持しておくようにすればクリティカルセクションを構成することはできそう★  
  • リエントラントでないロックで動作するようにしたほうが、書き換えは大変かもしれないが、実システム化するときはギャップが少ないというか、実システムになったからといって変更しないといけない部分というのが発生しなくて良いかもしれない
    • (リエントラントであっても、結局、借用のルールの制約によって、リエントラントでなくても動作するようなコードを書かざるを得なくなっている気がする。というか、そのようなコードに既になっているのでないかという気がする)  
  • NodeInfoの更新を行わない処理は、最初に同クラスのオブジェクトをcloneして(successor_info_list[0]やpredesessor_info[0]、finger_tableなども必要であればcloneしておく必要ありか)、すぐにロック解放するようにすればよい?
    • NodeInfoの更新系と、それ以外の操作が、一部の例外を除いて並行に動作できるようになる、はず  
  • finger_tableは単独で排他されているだけで大丈夫な気がするが、NodeInfoのフィールドになっているので単独でロックできないのはいまいち都合が悪い気がする  

整合性が必ず維持されなければならないデータ(群)は何があるか?(KVSの保持データのレプリケーションを行わないとして)

  • 各コレクションの単体での内容(同時に複数スレッドが触ってもよいが、writeアクセスするものが混ざってはダメ)
  • NodeInfoオブジェクトのフィールド一式(これを維持するためには、同オブジェクトのロックをとったまま、同オブジェクトの内容を変更する一連の処理を完了させる必要あり)
  • KVS上データについて見た時に、ネットワーク上にはpredecessor_info[0].node_idから、node_id(自身のID)の区間に当該データのIDが位置するという条件を満たしたノードが1つだけ存在し、当該データがそのノードに保持されていること
    • この制約は後述の方法で弱めてもワークはしそう
  • joinした前後のsuccessorおよび自身のNodeInfoの内容と、各々のdatastoreに保持されているデータ
    • join完了前は完了前として整合性がとれていなければならず、join完了後は完了後として整合性がとれていなければならない
      • これを満たすためには、いずれかのデータの更新が始まってから、委譲が完了するまでは、いずれのデータもロックがとられた状態であり続けなければならないはず。
        • つまり、それらの処理はクリティカルセクションとして行われなければならない?(Pythonコードでは委譲処理だけ後で行うようになっているが・・・)
        • joinしたノードの各々をロックしておくことは容易だが、successorの各々をロックしたままにするのは難しい予感
          • 他ノードをロックしたままの状態にしておくAPIを用意するという手はあるが、ノード故障の発生を想定した場合にはあり得ない設計と言える、と思う
            • タイムアウトを設定できるようにすればいいのかもしれないが
      • ブレークダウンして、制約を弱めることを考えるべき?
        • getのリクエストはどちらのノードもエラーとすれば良く、putのリクエストはまだ委譲は受けていないが担当範囲を引き継いだjoinしたノードの方がのみ受け付けるようになっていれば、整合性は崩れない。ただし、受け付けたputのリクエストによって保持したデータが★委譲の際に上書きされないように注意する必要がある。★
    • putとgetのリクエストを処理している間は、自身のNodeInfoはロックしたままである必要がある(更新さえされなければ良い)

以前検討したデッドロックが起こるパターン(レプリカの管理をする場合のみ発生するものも含む)

  • 検出済み1: joinで、P <- N (P=Predecessor, N=joinしたNode) とアクセスしているタイミングで、stabilize_successorの延長の処理で N -> some S (N=stabilizeしているNode, some S = NのsuccessorList内のノードのいずれか)という処理が走ると、お互い呼び出し元のロックを持った状態で、そのノードは他方の呼び出し先のノードである場合があるため、その条件が揃うと双方ともに呼び出し先のロックがとれずにデッドロックする
    • ↑★故障ノードの発生を想定しない実装では考慮不要
  • 検出済み2: 2つのstabilize処理が別ノードで走った際に、stabilize処理を開始したノードが互いのsuccessorListに入っていた場合。ノードA, B がいた時に簡易的には A -> B , B -> A とロックを取得しようとしてデッドロックする
    • ネットワーク上のノード数が少なく、2者のsuccessorListだけで全周をカバーしてしまうようなケースに生じうる
    • ↑★故障ノードの発生を想定しない実装では、successorは1ノードしか保持しないため、ネットワークに2ノードのみしか存在する場合しか発生し得ない。で、そのケースの考慮はstabilize_successorに実装済み★
  • 検出済み3: check_replication_redunduncyでsuccessor_info_list内のノードの余剰ノードにメソッド呼び出しを行うが、それがstabilizeメソッドを呼び出して、stabilize処理を開始したノードであった場合にデッドロックする
    • 2と同じようなシチュエーションで発生
    • ↑★故障ノードの発生を想定しない実装では考慮不要
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment