|
| 1 | +# RDMA BLOB レプリケーション修正 TODO |
| 2 | + |
| 3 | +## 目的 |
| 4 | + |
| 5 | +RDMA BLOB レプリケーションで、大きな BLOB を sender / receiver のどちらでも |
| 6 | +全量メモリに載せず、streaming で転送できるように修正する。 |
| 7 | + |
| 8 | +現在失敗している regression test は次のとおり。 |
| 9 | + |
| 10 | +```sh |
| 11 | +./build-rdma-rep-tests/test/limestone-test \ |
| 12 | + --gtest_filter=log_channel_handler_test.handle_rdma_data_event_accepts_blob_split_by_rdma_socket_io |
| 13 | +``` |
| 14 | + |
| 15 | +## 方針検討 |
| 16 | + |
| 17 | +### 受信処理方式と性能 |
| 18 | + |
| 19 | +RDMA 版を TCP 版のような stream 処理に寄せる案も検討した。 |
| 20 | +設計としてはきれいで、既存の `socket_io` / `blob_socket_io` に近づけやすい。 |
| 21 | +しかし RDMA write size は 4 KB から 64 KB 程度と小さいため、callback ごとに |
| 22 | +worker thread を起こす素朴な stream 実装では context switch の回数が多くなり、 |
| 23 | +性能面で不利になる可能性が高い。 |
| 24 | + |
| 25 | +一方、callback 内で BLOB chunk を file に直接 `write()` する方式は、 |
| 26 | +余分な user-space buffer copy と payload ごとの thread wakeup を避けられる。 |
| 27 | +通常の buffered file write は多くの場合 page cache への copy なので、 |
| 28 | +「callback 内で I/O するから必ず遅い」とは言えない。 |
| 29 | + |
| 30 | +ただし、callback 内 write には次のリスクがある。 |
| 31 | + |
| 32 | +- page cache / filesystem / memory pressure によって `write()` が詰まる可能性がある。 |
| 33 | +- callback が RDMA receive progress thread 上で長く止まると、RDMA 側の進行に影響する可能性がある。 |
| 34 | +- 小さい chunk が大量に来るため、syscall 回数は多くなる。 |
| 35 | + |
| 36 | +現時点の第一候補は次の方針とする。 |
| 37 | + |
| 38 | +```text |
| 39 | +RDMA BLOB 専用 protocol |
| 40 | ++ callback 内 state machine |
| 41 | ++ callback 内 buffered file write |
| 42 | +``` |
| 43 | + |
| 44 | +callback 内では `fsync()` / `fdatasync()` のような同期 I/O は行わない。 |
| 45 | +将来 callback blocking が問題になった場合に備えて、BLOB 受信処理は |
| 46 | +`rdma_blob_receiver` のような独立したクラス境界に閉じ込め、 |
| 47 | +内部実装を batched writer thread に差し替えられるようにする。 |
| 48 | + |
| 49 | +### Flush と ACK の分離 |
| 50 | + |
| 51 | +TCP 版と RDMA 版では ACK の見え方が異なる。 |
| 52 | + |
| 53 | +TCP 版では、transport ACK は TCP protocol ACK として OS の TCP stack が処理するため、 |
| 54 | +limestone からは見えない。limestone が明示的に扱う ACK は `COMMON_ACK` だけである。 |
| 55 | + |
| 56 | +RDMA 版では、ACK は rdma-comm-lib の内部メッセージとして明示的に扱われる。 |
| 57 | +この ACK は RDMA frame の `sequence_number` 単位で返され、送信側 `flush()` は |
| 58 | +pending ACK が 0 になるまで待つ。 |
| 59 | + |
| 60 | +重要なのは、rdma-comm-lib の現行 ACK は単なる「sender buffer 解放通知」ではなく、 |
| 61 | +「受信側ユーザハンドラの処理完了通知」として定義されていること。 |
| 62 | +受信側は RDMA 通知をデコードし、正常イベントまたはエラーイベントをユーザハンドラへ渡し、 |
| 63 | +そのハンドラが return した後に ACK を返す。 |
| 64 | +したがって `flush()` の成功は、 |
| 65 | + |
| 66 | +- 送信済み RDMA frame すべてについて ACK が返ったこと |
| 67 | +- その ACK は、受信側ユーザハンドラの処理完了後に送られたこと |
| 68 | + |
| 69 | +を意味する。 |
| 70 | + |
| 71 | +一方で、この ACK は durable ACK ではない。 |
| 72 | +`fsync()` / `fdatasync()` の完了やアプリケーションレベルの成功は `flush()` だけでは保証されない。 |
| 73 | +アプリケーションレベルの成否は、必要に応じて ACK body で通知し、 |
| 74 | +`flush()` 後に `take_ack_body()` で回収する設計である。 |
| 75 | + |
| 76 | +BLOB 対応では、巨大 BLOB を多数の RDMA frame に分割するため、 |
| 77 | +どの単位で「受信側ユーザハンドラの処理完了」とみなすかが問題になる。 |
| 78 | +`BLOB_DATA` frame ごとに file write が終わればすぐ ACK を返すのか、 |
| 79 | +あるいは BLOB 全体や WAL apply 完了まで ACK を遅延させるのかで、`flush()` の意味が変わる。 |
| 80 | + |
| 81 | +現時点では、次の順序で扱う前提で検討する。 |
| 82 | + |
| 83 | +```text |
| 84 | +RDMA frame callback |
| 85 | +-> protocol / sequence / state check |
| 86 | +-> BLOB chunk を buffered file write |
| 87 | +-> write 成功 |
| 88 | +-> callback return |
| 89 | +-> rdma-comm-lib が transport ACK を送信 |
| 90 | +-> sender 側 rdma-comm-lib が pending ACK を完了し、send buffer slot を解放 |
| 91 | +``` |
| 92 | + |
| 93 | +この方針でよいか、rdma-comm-lib の仕様変更が必要かを先に整理する。 |
| 94 | + |
| 95 | +## 作業方針 |
| 96 | + |
| 97 | +### 1. rdma-comm-lib で ACK / flush の責務を整理する |
| 98 | + |
| 99 | +- 現行の `flush()` / ACK 仕様を維持するか、変更するかを決める。 |
| 100 | +- ACK を「受信側ユーザハンドラの処理完了通知」として維持するのか、 |
| 101 | + buffer 解放用 ACK と application completion を分離するのかを決める。 |
| 102 | +- BLOB_DATA frame ごとに ACK を返すのか、BLOB 全体または transaction 単位で ACK を返すのかを決める。 |
| 103 | +- 仕様変更する場合、`flush()` が待つ対象を transport ACK のままにするのか、 |
| 104 | + application completion を待つ API に変えるのかを決める。 |
| 105 | +- `ack_body` を application result / error 通知に使う場合の保持方式を決める。 |
| 106 | + - 現状は stream あたり 1 件だけ保持し、未取得の body があると後続 body が drop される。 |
| 107 | + - logical ACK に使うなら queue 化や sequence number との対応付けが必要か検討する。 |
| 108 | +- limestone 側の RDMA wrapper に必要な API を expose する。 |
| 109 | + - receiver 側: `receive_handler_with_ack` 相当。 |
| 110 | + - sender 側: `take_ack_body()` または logical ACK 取得 API。 |
| 111 | +- この整理が終わるまで、limestone 側で ACK の意味を独自に増やさない。 |
| 112 | + |
| 113 | +### 2. RDMA BLOB sub-protocol を設計する |
| 114 | + |
| 115 | +- 最小限必要な RDMA BLOB frame / message 種別を決める。 |
| 116 | +- 各種別の payload format を決める。 |
| 117 | +- protocol 定義を `src/limestone/rdma/` に置くか、 |
| 118 | + `src/limestone/replication/` に置くか決める。 |
| 119 | +- `message_log_entries` の metadata と BLOB data をどう分離するか決める。 |
| 120 | +- receiver がどの時点で WAL entry を apply するか決める。 |
| 121 | +- BLOB 転送が中断または失敗したときに、partial file をどう cleanup するか決める。 |
| 122 | + |
| 123 | +初期案: |
| 124 | + |
| 125 | +```text |
| 126 | +RDMA_STREAM_BEGIN |
| 127 | +RDMA_STREAM_BYTES |
| 128 | +RDMA_BLOB_BEGIN |
| 129 | +RDMA_BLOB_DATA |
| 130 | +RDMA_BLOB_END |
| 131 | +RDMA_STREAM_END |
| 132 | +``` |
| 133 | + |
| 134 | +receiver は `RDMA_BLOB_DATA` を replica 側の BLOB file に直接書き込む。 |
| 135 | +BLOB 全体を `std::vector` や `std::string` に集約してはいけない。 |
| 136 | + |
| 137 | +### 3. Protocol 型とテストを追加する |
| 138 | + |
| 139 | +- frame / message type 定義を追加する。 |
| 140 | +- encode / decode helper を追加する。 |
| 141 | +- 各 frame 種別に対する focused unit test を追加する。 |
| 142 | +- 不正な frame type と不正な payload の test を追加する。 |
| 143 | + |
| 144 | +### 4. Sender 側を実装する |
| 145 | + |
| 146 | +- `rdma_socket_io::send_blob()` の暗黙的な byte-stream 分割をやめる。 |
| 147 | +- BLOB metadata と BLOB chunk を、明示的な RDMA BLOB protocol frame として送る。 |
| 148 | +- BLOB を含まない RDMA replication path は変えない。 |
| 149 | +- sender 側で frame の順序と chunk 境界を確認する test を追加する。 |
| 150 | +- 100 MB から 1 GB 超の BLOB でも streaming で動作することを前提にする。 |
| 151 | + |
| 152 | +### 5. Receiver 側を実装する |
| 153 | + |
| 154 | +- `log_channel_handler` が RDMA BLOB sub-protocol を処理できるようにする。 |
| 155 | +- BLOB chunk を replica datastore の BLOB file に直接書き込む。 |
| 156 | +- 必要な BLOB がすべて揃うまで `message_log_entries` を apply しない。 |
| 157 | +- BLOB 全体を `std::vector` や `std::string` に集約しない。 |
| 158 | +- sequence number、payload size、channel validation の既存チェックを維持する。 |
| 159 | + |
| 160 | +### 6. End-to-end regression test を通す |
| 161 | + |
| 162 | +- 必要なら、新 protocol に合わせて failing regression test を調整する。 |
| 163 | +- 分割された RDMA BLOB payload が正常に処理されることを確認する。 |
| 164 | +- replica 側の BLOB file が作成され、内容が sender 側と一致することを確認する。 |
| 165 | +- BLOB 転送完了前に WAL entry が書かれないことを確認する。 |
| 166 | + |
| 167 | +### 7. 異常系 test を追加する |
| 168 | + |
| 169 | +- BLOB chunk 欠落。 |
| 170 | +- BLOB size 不一致。 |
| 171 | +- 予期しない `RDMA_BLOB_END`。 |
| 172 | +- duplicate frame または out-of-order frame。 |
| 173 | +- BLOB 受信中の transfer abort または connection close。 |
| 174 | +- partial BLOB file の cleanup。 |
| 175 | +- 1 つの log entry に複数 BLOB が含まれるケース。 |
| 176 | +- 1 session に複数の BLOB log entry が含まれるケース。 |
0 commit comments