Redisのトランザクションは「夢」を見るか:MULTI/EXECの深淵と楽観的ロックの真実
Redisのトランザクション(`MULTI/EXEC`)について語るとき、多くのエンジニアはRDBMSのACID特性をそのまま投影しようとする。だが、それは致命的な誤解の始まりだ。Redisにおけるトランザクションは、分離レベルやロールバックの概念を捨て去ることで、単一スレッドのイベントループという「聖域」のパフォーマンスを最大限に引き出すための、極めて計算された妥協の産物である。
今日は、表層的なコマンドの使い方ではなく、Redisの内部アーキテクチャから見たこの仕組みの「真実」を解き明かそう。
—
1. アトミック性の本質:イベントループの独占
Redisの`MULTI`は、単なる「コマンドのキューイング」に過ぎない。クライアントからコマンドが送られるたび、Redisはそれを実行せず、クライアントごとの固定サイズバッファに積み上げる。`EXEC`が呼ばれた瞬間に、Redisは単一スレッドのイベントループの実行権を完全に掌握し、キューに溜まったコマンドを直列的に実行する。
ここで重要なのは、「Redisのイベントループは非同期的な並列処理を行わない」という点だ。`EXEC`ブロックの実行中に、他のクライアントからのコマンドが割り込むことは物理的に不可能である。これがRedisにおける「アトミック性」の正体だ。
アーキテクトの視点:なぜロールバックがないのか
RDBMSにあるような`ROLLBACK`が存在しないのは、Redisの設計思想が「失敗は回復ではなく、防ぐもの」という極めて現実的な哲学に基づいているからだ。コマンドのシンタックスエラーは`EXEC`前のエンキュー段階で検知できるが、実行時エラー(型不一致など)は防げない。しかし、Redisはそれを許容する。なぜなら、複雑なトランザクション状態の追跡やログ復元は、Redisの命である「メモリ内処理の高速性」を根本から破壊するからだ。
—
2. WATCHコマンド:楽観的ロックの正体と副作用
`MULTI/EXEC`だけでは、データ競合を防ぐことはできない。そこで登場するのが`WATCH`だ。これは、特定のキーに対する「変更監視」を有効にするメカニズムである。
内部的には、`client`構造体の中に「監視対象のキー」を保持するハッシュテーブルが存在する。Redisはデータベース全体で `watched_keys` というハッシュマップを持っており、あるキーが更新されるたびに、そのキーをWATCHしている全クライアントのフラグ(`CLIENT_DIRTY_CAS`)を立てる。
楽観的ロックの典型例
WATCH user:1001
この間に別のクライアントから user:1001 が更新されると、
次の EXEC は失敗する (nil を返す)
MULTI
INCR user:1001:balance
EXEC
限界を突破する知見:WATCHのコスト
`WATCH`を乱用してはいけない。`watched_keys`の更新チェックは、更新のたびに発生する。もし膨大なキーを監視すれば、更新処理のたびにO(N)(NはWATCHしているクライアント数)のオーバーヘッドが加算される。大規模な分散システムでこれを行うと、CPUのL3キャッシュを食いつぶし、イベントループのレイテンシを跳ね上げる原因となる。
—
3. パフォーマンスとスケーラビリティのトレードオフ
プロフェッショナルであれば、`MULTI/EXEC`よりも、Luaスクリプト (`EVAL`) の利用を第一選択肢にすべきだ。
なぜか?
1. ネットワークラウンドトリップ: `MULTI/EXEC`はコマンドの数だけ往復が発生する可能性がある(パイプラインを使わない場合)。Luaスクリプトは一度の送信で完結する。
2. 実行の不可分性: Luaスクリプトは実行中、他のスクリプトやコマンドの割り込みを許さない。`WATCH`のような「失敗前提の再試行ループ」を書く必要がなく、一度の実行で完了する。
3. ロジックの局所化: サーバーサイドで完結するため、クライアント側のネットワーク遅延による「ロック期間の肥大化」が起きない。
—
4. 最後に:アーキテクトとしての提言
Redisのトランザクションを設計する際、以下の3点だけは必ず守れ。
- WATCHは「最後の手段」: 競合が極めて稀なケースでしか使わないこと。高頻度で競合するなら、それはRedisの設計自体を見直すべきだ。
- Luaスクリプトへの移行: 複雑な条件分岐が必要な処理は、全てLuaに詰め込め。ただし、ブロッキングするような重い処理を書けば、Redis全体が停止することに注意せよ。
- 失敗を前提としたアプリ設計: Redisのトランザクションは「失敗する」可能性がある。`EXEC`が`nil`を返した際の再試行戦略(指数バックオフなど)をクライアント側に実装しないアーキテクチャは、プロの仕事とは呼べない。
Redisは、そのシンプルさゆえに、使う側の知性が試されるデータベースだ。内部メカニズムを理解せずツールとして使うのは、フェラーリで砂利道を走るようなものだ。限界を知り、その上で最適解を導き出すこと。それが、この極限のメモリDBを使いこなす唯一の道である。
コメント