大規模ユーザー環境では、データベースへのアクセス集中による負荷増大とシステム性能低下が課題となります。この問題に対処するため、Redisを用いたデータキャッシュ戦略を実装し、データベース負荷を軽減しながらアクセス速度の向上を図ります。
一主二従+センチネル構成の採用理由
Redisの並行処理能力をさらに高めるため、主従クラスタを構築し読み取り/書き込み分離を実現します。本プロジェクトでは一主二従のクラスタ構成を採用し、マスターノードが書き込み、スレーブノードが読み取りを担当します。マスターがデータを書き込んだ後、スレーブにデータを同期することで、読み取り/書き込み分離を実現し、高並行処理問題に対応します。
センチネルは独立したプロセスとして動作し、pingコマンドの送信によるRedisサーバーの応答監視を通じて、複数のRedisインスタンスの状態を監視します。この仕組みにより以下の機能を実現します:
- 自動障害復旧:マスターノードのダウンを検出すると、自動的にスレーブノードを新しいマスターに昇格させ、他のスレーブサーバーに設定変更を通知
- 旧マスターの復旧時:自動的に新しいスレーブノードとしてクラスタに再参加
高並行処理と高可用性の両立
主従クラスタ構成により高並行処理を保証し、センチネル機構により高可用性を確保します。この二つの仕組みを組み合わせることで、システム全体の安定性と性能を両立しています。
キャッシュとデータベースの一貫性保証
データ更新時には、データベースとキャッシュの内容を一致させる必要があります。本システムではキャッシュアサイドパターンを採用し、以下の手順で一貫性を維持します:
- データベースを先に更新:変更をデータベースにコミット
- キャッシュを削除:該当するキャッシュエントリを削除
理論上は不整合が発生する可能性がありますが、実際にはキャッシュの書き込み速度がデータベースよりはるかに速いため、その確率は極めて低くなります。
更新と削除の順序比較
アプローチA:先にキャッシュ削除、後にデータベース更新
スレッドA: キャッシュ削除
スレッドB: キャッシュミス → DB読み取り → キャッシュ更新(古い値)
スレッドA: DB更新(新しい値)
結果:DB = 新しい値、キャッシュ = 古い値 → 不整合発生
アプローチB:先にデータベース更新、後にキャッシュ削除(採用)
スレッドA: DB更新
スレッドB: キャッシュ読み取り(古い値、ただしこの瞬間のみ)
スレッドA: キャッシュ削除
次回アクセス時:キャッシュミス → DBから最新値を取得 → キャッシュ更新
結果:一貫性が保証される
キャッシュ削除方式を採用することで、次回のクエリは必ずデータベースから最新データを読み込むことになり、結果的にデータの一貫性が確保されます。キャッシュがクリアされた後、スレッドはデータベースにアクセスし、その結果を元にキャッシュを更新します。重要なのは、データベースとキャッシュの最終的な一致状態と、その一致が達成されるまでの時間です。