Redisはキャッシュだけじゃない:データ構造、Pub/Sub、そしてリアルタイムアプリケーション
Redisをキャッシュ以上の用途で活用する方法を学びましょう。データ構造、Pub/Subメッセージング、ストリーム、そして高速でスケーラブルなアプリケーション構築のパターンを解説します。
多くの開発者はRedisを「あの速いキャッシュ」として認識しています。しかしRedisは、セッション管理、リアルタイムランキング、レート制限、メッセージキューなどをサブミリ秒のレイテンシですべて処理できる、フル機能のインメモリデータ構造サーバーです。
Redisがこれほど速い理由
Redisはすべてをメモリ上に保存し、シングルスレッドのイベントループでコマンドを処理します。読み取り時のディスクI/Oなし、ロック競合なし、コンテキストスイッチなし。その結果、一般的なハードウェアで1秒間に10万回以上の操作が可能です。
レイテンシの一般的な比較:
┌─────────────────┬──────────────┐
│ 操作 │ レイテンシ │
├─────────────────┼──────────────┤
│ Redis GET │ 0.1 ms │
│ PostgreSQL SEL │ 1-5 ms │
│ REST API call │ 50-200 ms │
│ ディスク読み取り │ 5-10 ms │
└─────────────────┴──────────────┘
コアデータ構造
Redisは単なるキーバリューストアではありません。アプリケーションの一般的なニーズに直接対応できるリッチなデータ構造をサポートしています。
Strings — 基本の構成要素
Stringsはテキスト、数値、またはバイナリデータを最大512 MBまで保持します。
SET user:1001:name "Alice"
GET user:1001:name # "Alice"
# アトミックカウンター
INCR page:views # 1, 2, 3, ...
INCRBY cart:total 2500 # 25.00を加算(センt単位で保存)
# 有効期限付きキー(TTL)
SET session:abc123 "{...}" EX 3600 # 1時間後に期限切れ
TTL session:abc123 # 残り秒数
Hashes — 軽量オブジェクト
Hashesはシリアライズのオーバーヘッドなしにオブジェクトを保存するのに最適です。
HSET user:1001 name "Alice" email "alice@example.com" plan "pro"
HGET user:1001 name # "Alice"
HGETALL user:1001 # すべてのフィールドと値
HINCRBY user:1001 logins 1 # フィールドのアトミックインクリメント
Hashesは、各フィールドを個別のキーとして保存するより10倍少ないメモリを使用します。
Lists — キューとフィード
両端からのプッシュ/ポップをサポートする順序付きコレクション:
LPUSH notifications:alice "New comment on your post"
LPUSH notifications:alice "You have a new follower"
LRANGE notifications:alice 0 9 # 最新10件の通知
# キューとして使用(プロデューサー/コンシューマー)
RPUSH queue:emails "send-welcome"
LPOP queue:emails # 次のジョブを処理
Sets — 一意のコレクション
ユニークな文字列の順序なしコレクション:
SADD tags:post:42 "redis" "database" "nosql"
SMEMBERS tags:post:42 # すべてのタグ
SISMEMBER tags:post:42 "redis" # true
# Set操作
SINTER tags:post:42 tags:post:99 # 共通タグ
SUNION tags:post:42 tags:post:99 # 全タグの合算
Sorted Sets — ランキングと順位表
各メンバーにスコアを持ち、自動的にソートされるSet:
ZADD leaderboard 1500 "alice" 2300 "bob" 1800 "charlie"
ZREVRANGE leaderboard 0 2 WITHSCORES # 上位3プレイヤー
ZRANK leaderboard "alice" # 順位(0始まり)
ZINCRBY leaderboard 100 "alice" # Aliceが100ポイント獲得
これが、ゲームのランキング、トレンド投稿、優先度キューを大規模に構築する方法です。
実践的なパターン
パターン1: セッションストレージ
データベースベースのセッションの代わりに、即時ルックアップのためにRedisを使用する:
import redis
r = redis.Redis()
# セッションを保存
r.setex(f"session:{token}", 3600, json.dumps(user_data))
# セッションを取得
data = r.get(f"session:{token}")
なぜ? データベースセッションはリクエストごとに1〜5msを追加します。Redisセッションは0.1msです。毎秒1,000リクエストでは、1秒ごとに5秒分の時間を節約できます。
パターン2: レート制限
3つのコマンドによるスライディングウィンドウ方式のレートリミッター:
# ユーザーごとに1分間に100リクエストまで許可
MULTI
INCR rate:user:1001
EXPIRE rate:user:1001 60
EXEC
# 確認:INCRの結果が100を超えたらリクエストを拒否
パターン3: キャッシュアサイドによるキャッシング
最も一般的なキャッシングパターン:
def get_user(user_id):
# 1. キャッシュを確認
cached = redis.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# 2. キャッシュミス → データベースを照会
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# 3. キャッシュに保存(5分で期限切れ)
redis.setex(f"user:{user_id}", 300, json.dumps(user))
return user
パターン4: リアルタイムイベントのためのPub/Sub
Redis Pub/Subはサービス間のリアルタイムメッセージングを可能にします:
# サブスクライバー(メッセージをリッスン)
SUBSCRIBE chat:room:42
# パブリッシャー(メッセージを送信)
PUBLISH chat:room:42 "Hello everyone!"
アプリケーションコードでは:
# パブリッシャー
redis.publish("notifications", json.dumps({
"type": "new_order",
"order_id": 12345
}))
# サブスクライバー
pubsub = redis.pubsub()
pubsub.subscribe("notifications")
for message in pubsub.listen():
handle_notification(message)
パターン5: 分散ロック
複数のサーバー間での競合状態を防止する:
# ロックを取得(NX = 存在しない場合のみ、EX = 期限切れ)
SET lock:invoice:gen "worker-1" NX EX 30
# ロックを解放(所有者の場合のみ — Luaスクリプトを使用)
Redis Streams — イベントソーシングとメッセージキュー
StreamsはKafkaスタイルのイベントログに対するRedisの回答です:
# イベントを追加
XADD orders * user_id 1001 product "Widget" qty 3
XADD orders * user_id 1002 product "Gadget" qty 1
# 最新のイベントを読み取る
XRANGE orders - + COUNT 10
# コンシューマーグループ(複数のワーカー)
XGROUP CREATE orders processors $ MKSTREAM
XREADGROUP GROUP processors worker-1 COUNT 5 BLOCK 2000 STREAMS orders >
Streamsはコンシューマーグループを持つ永続的で再生可能なイベントログを提供します。マイクロサービス間の通信に最適です。
パフォーマンスのヒント
1. 大量操作にはパイプラインを使用する
100回のラウンドトリップの代わりに、100個のコマンドを一度に送信:
pipe = redis.pipeline()
for i in range(100):
pipe.set(f"key:{i}", f"value:{i}")
pipe.execute() # シングルラウンドトリップ
2. 適切なデータ構造を選ぶ
- カウンターが必要? →
INCR(GET + SETではなく) - オブジェクトが必要? → Hash(シリアライズされたJSON文字列ではなく)
- ユニークなアイテムが必要? → Set(List + 重複排除ロジックではなく)
- ランキングが必要? → Sorted Set(クエリ後にSortするのではなく)
3. すべてにTTLを設定する
メモリは有限です。キャッシュキーには常に有効期限を設定しましょう:
SET cache:api:result "{...}" EX 300 # 5分のTTL
4. キーの命名規則を使用する
resource:id:field
user:1001:profile
cache:api:v2:products
session:abc123
queue:emails:pending
永続化:RDB vs AOF
Redisは耐久性のためにデータをディスクに保存できます:
| モード | 仕組み | トレードオフ |
|---|---|---|
| RDB | ポイントインタイムスナップショット | 高速リカバリ、データ損失の可能性あり |
| AOF | すべての書き込み操作をログに記録 | 低速、データ損失は最小限 |
| 両方 | RDB + AOFの組み合わせ | 最高の耐久性 |
# redis.confの設定
save 900 1 # 900秒以内に1キーが変更された場合にスナップショット
appendonly yes # AOFを有効化
appendfsync everysec # 毎秒Fsync
クイックコマンドリファレンス
特定のRedisコマンドを調べたいときは、Redis Cheat Sheet をご利用ください。200以上のコマンドをデータ構造別に整理し、構文、使用例、ワンクリックコピー機能付きで掲載しています。
まとめ
Redisはキャッシュをはるかに超えた存在です。そのデータ構造は実際のアプリケーションのニーズに明確に対応しています:
- Strings → セッション、カウンター、シンプルなキャッシュ
- Hashes → ユーザープロファイル、設定オブジェクト
- Lists → キュー、アクティビティフィード、最近のアイテム
- Sets → タグ、ユニーク訪問者、リレーションシップ
- Sorted Sets → ランキング、順位表、優先度キュー
- Streams → イベントソーシング、メッセージキュー
- Pub/Sub → リアルタイム通知、チャット
まずはキャッシングから始めて、そこで止まらないでください。Redisはスタック内のいくつかのインフラコンポーネントを置き換えることができます。そしてすべてをサブミリ秒のスピードでこなします。