セッションストレージとリーダーボードのパターン
ElastiCacheを使用してアプリケーションサーバーからHTTPセッション状態を切り離し、Redisのソート済みセットでリアルタイムのリーダーボードを実装します。
「セッションストレージとリーダーボードのパターン」はCoddyKit上の無料Cloud & IT Cert Prepレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはCloud & IT Cert Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Cloud & IT Cert Prepコースには全4レッスンが含まれています。
サーバー側セッションの問題
従来のWebアプリケーションでは、セッションデータをサーバーのメモリに保存します。単一サーバーでは問題なく動作しますが、水平スケーリングすると問題が発生します。ユーザーの次のリクエストが別のEC2インスタンスにルーティングされた場合、そのインスタンスにはユーザーのセッションに関する情報がないため、ユーザーはログアウトされます。スティッキーセッション(ロードバランサーでのセッションアフィニティ)を使用すれば部分的に解決できますが、ロードバランシングの効果が低下します。スケーラブルな解決策は、すべてのインスタンスからアクセスできる共有された低レイテンシーのストアにセッション状態を移すことです。これを実現するのがElastiCache Redisです。
セッションストレージとしてのRedis
セッションをRedisに保存すると、すべてのアプリケーションサーバーからサブミリ秒でセッションを読み取れるほか、自動的にセッションを期限切れにする組み込みTTL、競合状態を防ぐアトミックなセッション更新、キーを削除してセッションを即座に無効化する機能を利用できます。アプリケーションはセッションIDをCookieに保存し、各リクエストでRedisからセッションIDを検索してセッションデータを取得します。すべてのアプリケーションサーバーが同じRedisを共有するため、どのサーバーでも任意のユーザーのリクエストを処理できます。
# Session storage with Redis (Python Flask example)
import redis, json, uuid
from datetime import timedelta
redis_client = redis.Redis(host='prod-redis-primary', port=6379)
SESSION_TTL = int(timedelta(hours=8).total_seconds())
def create_session(user_id):
session_id = str(uuid.uuid4())
session_data = {'user_id': user_id, 'logged_in': True}
redis_client.setex(f'session:{session_id}', SESSION_TTL, json.dumps(session_data))
return session_id
def get_session(session_id):
data = redis_client.get(f'session:{session_id}')
return json.loads(data) if data else NoneセッションTTLとスライディング有効期限
固定TTLでは、アクティビティに関係なく、作成からN秒後にセッションが期限切れになります。スライディングTTL(アクセスごとに有効期限を延長する方式)は、よりユーザーフレンドリーです。セッションは最後のアクセスからN秒後に期限切れになります。Redisでは、セッションの読み取りが成功するたびにセッションキーに対してEXPIRE(またはEXPIREAT)を呼び出して有効期限のカウントダウンをリセットすることで、スライディングTTLを実装します。これにより、アクティブなユーザーが予期せずログアウトされることを防ぎながら、非アクティブなセッションは自動的に期限切れとなり、メモリを解放できます。
# Sliding TTL session implementation
def get_session_with_sliding_ttl(session_id, redis_client, ttl_seconds=1800):
session_key = f'session:{session_id}'
# Pipeline: GET + EXPIRE in one round trip
pipe = redis_client.pipeline()
pipe.get(session_key)
pipe.expire(session_key, ttl_seconds) # Reset TTL on access
results = pipe.execute()
data = results[0]
if data:
return json.loads(data)
return None # Session expired or not foundRedisでのショッピングカート
ECサイトのショッピングカートは、Redisに適した用途です。各カートはRedis Hashとして保存し、フィールドに商品SKU、値に数量を設定します。HINCRBYやHDELなどのHash操作を使用すると、カート全体を取得して書き直すことなく、アトミックに更新できます。24時間後に放棄されたカートを期限切れにするTTLと組み合わせることで、Redisは、カートへの追加イベントごとにリレーショナルデータベースを使用するオーバーヘッドなしに、高速で永続的なカートストアを提供します。
# Shopping cart operations using Redis Hash
cart_key = f'cart:{user_id}'
# Add item (or increase quantity)
# HINCRBY cart:user42 SKU-001 2
redis_client.hincrby(cart_key, 'SKU-001', 2)
# Remove item
# HDEL cart:user42 SKU-001
redis_client.hdel(cart_key, 'SKU-001')
# Get all items in cart
# HGETALL cart:user42
cart = redis_client.hgetall(cart_key) # {b'SKU-001': b'2', b'SKU-002': b'1'}
# Set TTL for cart abandonment (24 hours)
redis_client.expire(cart_key, 86400)リーダーボードのアーキテクチャ
リアルタイムリーダーボードは、Sorted Sets(ZSETs)によって実現できる、Redisの代表的なユースケースです。すべてのプレイヤーエントリにはスコアがあり、sorted setは常にメンバーをスコアの昇順で維持します。リーダーボードのクエリ(上位N人のプレイヤー、プレイヤーの順位、特定のスコア範囲にいるプレイヤー)の計算量はO(log n)またはO(log n + m)で、数百万人のプレイヤーがいても非常に高速です。Redisのsorted setsは、複雑なデータベースクエリやページ表示のたびの順位再計算を必要とせず、多くのゲーム、フィットネス、ソーシャル機能におけるランキングの基盤となります。
# Real-time leaderboard with Redis Sorted Set
# Add or update a player's score
# ZADD game:weekly:leaderboard 15750 'player:alice'
redis_client.zadd('game:weekly:leaderboard', {'player:alice': 15750})
# Increment score (atomic)
# ZINCRBY game:weekly:leaderboard 500 'player:alice'
redis_client.zincrby('game:weekly:leaderboard', 500, 'player:alice')
# Get top 10 players (highest scores first)
# ZREVRANGE game:weekly:leaderboard 0 9 WITHSCORES
top_10 = redis_client.zrevrange('game:weekly:leaderboard', 0, 9, withscores=True)プレイヤーの順位と周辺のプレイヤー
「上位10人を表示する」以外に、リーダーボードでよく使われる機能が2つあります。それはプレイヤーの順位を表示することと、指定したプレイヤーの周辺にいるプレイヤーを表示することです。どちらもRedisのsorted setsを使えば簡単に実現できます。ZREVRANKは、スコアの降順に並べたプレイヤーの0始まりの順位を返します。あるプレイヤーの上下5人を表示するには、まずそのプレイヤーの順位を取得し、次にZREVRANGEで順位-5から順位+5までを取得します。これにより、Redisの2つのコマンドだけでパーソナライズされたリーダーボードを表示できます。複雑なSQLのウィンドウ関数は必要ありません。
# Get Alice's rank (0-indexed, so add 1 for display)
# ZREVRANK game:weekly:leaderboard 'player:alice'
rank = redis_client.zrevrank('game:weekly:leaderboard', 'player:alice')
print(f'Alice is rank #{rank + 1}')
# Get 5 players above and below Alice
start = max(0, rank - 5)
end = rank + 5
nearby = redis_client.zrevrange(
'game:weekly:leaderboard', start, end, withscores=True
)
print('Players near Alice:', nearby)Redisによるレート制限
レート制限(クライアントが一定の時間枠内に実行できるリクエスト数を制限すること)も、Redisの重要なユースケースです。スライディングウィンドウアルゴリズムでは、各メンバーがリクエストのタイムスタンプであるsorted setを使用します。各リクエストで、時間枠より古いメンバーを削除し、残りのメンバー数を数え、その数が上限を超えていれば拒否し、新しいタイムスタンプを追加します。これにより、ミリ秒単位の精度で正確なスライディングウィンドウ方式のレート制限を実装できます。固定ウィンドウのカウンターよりもはるかに正確で、データベースのオーバーヘッドもありません。
# Sliding window rate limiter (100 requests per 60 seconds)
import time
def is_rate_limited(user_id, redis_client, limit=100, window_seconds=60):
key = f'ratelimit:{user_id}'
now = time.time()
window_start = now - window_seconds
pipe = redis_client.pipeline()
pipe.zremrangebyscore(key, '-inf', window_start) # Remove old
pipe.zcard(key) # Count current
pipe.zadd(key, {str(now): now}) # Add this request
pipe.expire(key, window_seconds)
results = pipe.execute()
request_count = results[1]
return request_count >= limit # True = rate limitedRedisによる分散ロック
分散ロックは、複数のアプリケーションサーバー間で共有リソースへの排他的なアクセスを調整します。RedisのSET key value NX EX ttlコマンドは、アトミックなロック取得を提供します。キーが存在しない場合にのみキーを設定し(NX = Not eXists)、ロック保持側がクラッシュした場合のデッドロックを防ぐためにTTLを設定します。処理が完了すると、ロック保持側がキーを削除します。Redlockアルゴリズム(クォーラムのために複数のRedisノードを使用)は、より堅牢な分散ロックを提供しますが、複雑さが増します。ほとんどのユースケースでは、単一のRedisノードによるロックで十分です。
# Distributed lock with Redis SET NX EX
import uuid
def acquire_lock(redis_client, resource, ttl_seconds=30):
lock_id = str(uuid.uuid4()) # Unique ID to identify this lock holder
key = f'lock:{resource}'
acquired = redis_client.set(key, lock_id, nx=True, ex=ttl_seconds)
return lock_id if acquired else None
def release_lock(redis_client, resource, lock_id):
key = f'lock:{resource}'
# Only delete if we still own the lock (Lua script for atomicity)
lua = 'if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end'
redis_client.eval(lua, 1, key, lock_id)セッションストレージ:ElastiCacheとDynamoDBの比較
ElastiCache RedisとDynamoDBはいずれもセッションデータを保存できますが、トレードオフが異なります。ElastiCache Redis:マイクロ秒単位のレイテンシ、インメモリ(永続化を有効にしない限り揮発性)、よりシンプルなデータモデル、VPCが必要です。DynamoDB:1桁ミリ秒のレイテンシ(DAXを使えばRedisと同等にできます)、維持するクラスターが不要なフルマネージドサービス、デフォルトで耐久性があり、Global Tablesによってグローバルにアクセスでき、オンデマンドキャパシティによるサーバーレス運用が可能です。SAA-C03試験では、問題がマイクロ秒単位のレイテンシや複雑なインメモリ操作を重視している場合はRedisを選びます。耐久性、サーバーレス、またはグローバルなスケールを重視している場合は、DynamoDBを検討します。
Redisによる地理空間インデックス
Redisには、緯度・経度の座標を保存し、近接検索を可能にする組み込みの地理空間データ型(GEOコマンド)があります。GEOADD、GEODIST、GEORADIUS(Redis 6.2ではGEOSEARCH)を使うと、ある地点から指定した半径内にあるすべての場所をO(n + log n)の時間で検索できます。ユースケースには、近くのドライバーを探す(ライドシェア)、5 km以内のレストランを探す、検索結果を距離順に並べる、といったものがあります。これにより、別の地理空間データベースが不要になり、位置情報のクエリをインメモリ速度で処理できます。
# Store driver locations
# GEOADD drivers 13.361389 38.115556 'driver:001'
# GEOADD drivers 15.087269 37.502669 'driver:002'
# Find all drivers within 10 km of a point
# GEOSEARCH drivers FROMLONLAT 13.5 38.1 BYRADIUS 10 km ASC COUNT 5 WITHCOORD
# Result: sorted list of driver IDs within 10 km with coordinates一意な訪問者数のカウントに使うHyperLogLog
HyperLogLogは、追加された一意なアイテム数に関係なく、一定量のメモリ(Redisでは12 KB)で集合内の一意な要素の数を推定する確率的データ構造です。標準誤差は約0.81%です。PFADDで要素を追加し、PFCOUNTで推定値を取得します。正確なカウントが不要で、メモリ効率が重要な場合に、日次アクティブユーザー数、一意なページビュー数、一意なIPアドレス数のカウントに最適です。数百万人分の一意なユーザーIDをRedis Setに保存すると数GBを消費しますが、HyperLogLogなら12 KBで済みます。
# Count unique daily visitors using HyperLogLog
date = '2024-01-15'
hll_key = f'unique_visitors:{date}'
# Track a visitor (PFADD is idempotent for the same user)
# PFADD unique_visitors:2024-01-15 'user:12345'
redis_client.pfadd(hll_key, 'user:12345')
redis_client.pfadd(hll_key, 'user:67890')
redis_client.pfadd(hll_key, 'user:12345') # Duplicate — not counted again
# Get estimated unique visitor count
# PFCOUNT unique_visitors:2024-01-15
count = redis_client.pfcount(hll_key)
print(f'Unique visitors today (estimate): {count}')クイックチェック
このレッスンで学んだAWS Solutions Architect(SAA-C03)の概念を確認しましょう。
レッスンのまとめ
このレッスンでは、Redisのセッションストレージによって、すべてのインスタンスがサブミリ秒のレイテンシで共有セッション状態にアクセスでき、ステートレスな水平スケーリングを実現できること、Redis sorted setsによってO(log n)の順位クエリを備えたリアルタイムリーダーボードを構築できること、そしてRedisの特殊なデータ型(一意な数のカウントにはHyperLogLog、近接検索にはGEO、排他制御には分散ロック)が一般的なアーキテクチャ上の問題を効率的に解決することを学びました。これでElastiCacheによるキャッシュコースは完了です。次は高可用性とフォールトトレラントアーキテクチャについて学びます。
よくある質問
「セッションストレージとリーダーボードのパターン」レッスンは無料ですか?
はい。「セッションストレージとリーダーボードのパターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Cloud & IT Cert Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Cloud & IT Cert Prepコースには全4レッスンが含まれています。
「セッションストレージとリーダーボードのパターン」で何を学びますか?
ElastiCacheを使用してアプリケーションサーバーからHTTPセッション状態を切り離し、Redisのソート済みセットでリアルタイムのリーダーボードを実装します。 ブラウザで直接実行するハンズオンコードでCloud & IT Cert Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Cloud & IT Cert Prepを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのCloud & IT Cert Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「セッションストレージとリーダーボードのパターン」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このCloud & IT Cert Prepレッスンでコードを書いて実行できますか?
はい。すべてのCloud & IT Cert Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- RedisとMemcached:適切なエンジンの選択
- ElastiCache Redisのレプリケーショングループとクラスターモード
- キャッシュ戦略:レイジーローディングとライトスルー
- セッションストレージとリーダーボードのパターン