Redisトランザクションの深淵:MULTI/EXECと楽観的ロックの「真の制約」を解剖する
Redisのトランザクションは、しばしばRDBMSのACID特性と混同される。しかし、アーキテクトとして断言しよう。Redisの`MULTI/EXEC`は、純粋なステートマシンとしての「コマンドキューイング」に過ぎない。
今日論じるのは、ドキュメントに書かれた表面的な使い方ではない。メモリ管理、イベントループ、そして楽観的ロックの背後にある「真のコスト」についてだ。
—
1. MULTI/EXECの内部メカニズム:キューイングの代償
Redisはシングルスレッドで動作する。これは周知の事実だが、`MULTI`を叩いた瞬間、何が起きているかを正確に理解しているだろうか。
`MULTI`以降、クライアントは接続ごとに保持される「コマンドキュー」にポインタを積むことになる。この間、サーバー側ではそのクライアント専用の`multiState`構造体がメモリを消費し続ける。
// Redis内部構造の概念図(簡略化)
typedef struct multiState {
multiCmd commands; // キューイングされたコマンド配列
int count; // コマンド数
// …
} multiState;
極限の知見:
このキューイング期間中、Redisは単にコマンドを蓄積しているだけではない。`EXEC`が呼ばれるまで、サーバーは他のクライアントからのコマンドを順次処理するが、トランザクション内のコマンドは「実行の予約」としてメモリに滞留する。
もしトランザクションが肥大化すれば、メモリ圧迫だけでなく、`EXEC`実行時のアトミックな処理フェーズで、シングルスレッドのイベントループを長時間ブロックすることになる。「数千コマンドを詰め込むような設計」は、Redisにおいてはアーキテクチャの敗北だ。
—
2. RDBMSとの決定的差異:ロールバックの不在
Redisのトランザクションは、実行時のエラー(型不一致など)に対してロールバックを提供しない。
- 構文エラー: キューイング時に検知(`EXEC`が失敗)。
- 実行時エラー: `EXEC`実行中に発生しても、他のコマンドは止まらず処理される。
これは「コマンドの順序保証」と「アトミックな実行」のみを目的としているからだ。RDBMSのような「全か無か」のロールバックを期待する設計は、Redisの哲学から外れている。もし厳密な整合性が必須なら、それはRedisの責務ではなく、アプリケーション層の楽観的ロックか、Luaスクリプトで解決すべきだ。
—
3. WATCHの正体:楽観的ロックの「コスト」
`WATCH`による楽観的ロックは、クライアントが監視しているキーに対する「変更フラグ」を追跡する仕組みだ。
内部的には、Redisの`db->watched_keys`というハッシュテーブルに、キーとクライアントのリストが保持されている。`dirty`フラグが立った瞬間、該当するクライアントの`REDIS_DIRTY_CAS`フラグがセットされる。
楽観的ロックの典型例
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey val
EXEC # ここでmykeyが他から書き換えられていれば、nilが返る
アーキテクチャの急所:
この監視コストは、監視対象のキー数に比例する。高頻度で更新されるキーを`WATCH`すると、`dirty`フラグのチェック頻度が爆発し、CPUキャッシュヒット率を低下させる要因となる。
数万のキーを並行して`WATCH`するような設計は、Redisのパフォーマンスをスケーラビリティの谷底へ突き落とす。
—
4. 伝説的アーキテクトからの提言:Luaスクリプトへの回帰
もしあなたが大規模システムで`MULTI/EXEC`と`WATCH`を多用しようとしているなら、一度立ち止まるべきだ。
Redisにおいて、Luaスクリプトはトランザクションの完全なる上位互換である。
1. ネットワーク往復の削減: `MULTI/EXEC`はコマンドごとに往復が必要だが、Luaは一度の送出で完結する。
2. アトミック性の保証: Luaスクリプト実行中は、Redisは他のコマンドを受け付けない(ブロッキング)。これは`MULTI/EXEC`よりも強力なアトミック性だ。
3. 条件分岐のサーバーサイド処理: `WATCH`の再試行ループをクライアント側で書く必要はない。Lua内で完結する。
結論
`MULTI/EXEC`は、あくまで「コマンドをまとめて投げるためのパイプライン」として捉えるべきだ。複雑な状態遷移や、条件付き更新をRedisに求めるなら、`WATCH`の再試行ループではなく、Luaスクリプトをマスターせよ。
Redisは、メモリという極めて高価で高速なリソースを、いかに「待たせずに回すか」に全てが懸かっている。この本質を見失わない限り、あなたのシステムは限界を突破し続けるだろう。
魂を込めたコードを書け。それがアーキテクトの唯一の責務だ。
コメント