セッションをRDBで管理する理由
セッション管理というとRedisを思い浮かべることが多いですが、RDBで管理する設計もあります。
RDBセッションは、端末管理、全端末ログアウト、監査、管理画面との相性がよい方式です。
向いている場面:
- 管理画面でログイン端末一覧を出したい
- 全端末ログアウトを確実にしたい
- 誰がいつログインしたかを追いたい
- セッション失効理由を残したい
- Redisを増やしたくない小規模構成
高速性だけを見るとRedisが有利ですが、管理性ではRDBが扱いやすい場面があります。
基本テーブル
例としてPostgreSQLで考えます。
CREATE TABLE sessions (
id UUID PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
session_hash TEXT NOT NULL UNIQUE,
device_label TEXT,
ip_address INET,
user_agent_hash TEXT,
auth_level TEXT NOT NULL DEFAULT 'password',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
revoked_reason TEXT
);
Cookieに入れるセッションIDをそのままDBに保存せず、ハッシュ化した値を保存する設計もあります。
Cookie: session_id=raw_random_value
DB: session_hash=hash(raw_random_value)
DB漏洩時に、そのままCookieとして使われるリスクを下げられます。
有効なセッションの条件
ログイン済み判定では、少なくとも次を確認します。
SELECT *
FROM sessions
WHERE session_hash = $1
AND revoked_at IS NULL
AND expires_at > now();
sessions テーブルに行があるだけでは有効とは限りません。
| 列 | 意味 |
|---|---|
expires_at | 期限切れか |
revoked_at | 明示的に失効されたか |
auth_level | どの認証レベルか |
last_seen_at | 最近使われたか |
論理削除のように revoked_at を残すと、監査や不正調査に使えます。
インデックス
認証チェックでは、session_hash で1件を引くことが多いです。
CREATE UNIQUE INDEX idx_sessions_session_hash
ON sessions(session_hash);
ユーザーごとのセッション一覧には、次を使います。
CREATE INDEX idx_sessions_user_active
ON sessions(user_id, revoked_at, expires_at);
期限切れ掃除には、expires_at が役立ちます。
CREATE INDEX idx_sessions_expires_at
ON sessions(expires_at);
ただし、インデックスを貼りすぎると書き込みが重くなります。セッションはログイン、アクセス更新、ログアウトで更新されるため、必要なクエリから逆算します。
last_seen_at の更新頻度
すべてのリクエストで last_seen_at を更新すると、DB書き込みが増えます。
避けたい実装:
全リクエストで UPDATE sessions SET last_seen_at = now()
実務では、次のように間引きます。
最終更新から5分以上経っていたら更新する
また、画像やCSSでは更新しない、認証APIだけで更新する、といった制御もできます。
全端末ログアウト
全端末ログアウトは、RDBだと分かりやすく書けます。
UPDATE sessions
SET revoked_at = now(),
revoked_reason = 'logout_all'
WHERE user_id = $1
AND revoked_at IS NULL;
この端末だけログアウト:
UPDATE sessions
SET revoked_at = now(),
revoked_reason = 'user_logout'
WHERE session_hash = $1
AND revoked_at IS NULL;
パスワード変更後に全失効する場合:
UPDATE sessions
SET revoked_at = now(),
revoked_reason = 'password_changed'
WHERE user_id = $1
AND revoked_at IS NULL;
revoked_reason があると、後から「なぜログアウトされたのか」を追いやすくなります。
device管理
端末一覧を出す場合、セッション行に端末情報を持たせます。
Chrome on macOS
Safari on iPhone
Edge on Windows
ただし、User-Agent文字列をそのまま信用しすぎないようにします。User-Agentは偽装できますし、長くて扱いにくい値です。
保存例:
- 表示用の短い
device_label - 調査用の
user_agent_hash - 最終利用時刻
- おおまかなIP
IPアドレスは個人情報に近い扱いになるため、保存期間や利用目的を明確にします。
期限切れデータの掃除
期限切れや失効済みセッションは、無限に残さないようにします。
DELETE FROM sessions
WHERE expires_at < now() - interval '30 days'
OR revoked_at < now() - interval '30 days';
ただし、監査要件がある場合は、セッション本体を削除し、別の監査ログに必要最小限を残す方がよいこともあります。
Redisとの使い分け
| 観点 | Redis | RDB |
|---|---|---|
| 読み書き速度 | 高速 | DB負荷に注意 |
| TTL | 得意 | 掃除処理が必要 |
| 管理画面 | 別設計が必要 | 得意 |
| 監査 | 弱め | 得意 |
| 全端末ログアウト | Set管理などが必要 | SQLで書きやすい |
大規模で高頻度ならRedis、管理性や監査を重視するならRDB、という判断になります。両方を組み合わせる設計もあります。
まとめ
RDBでセッションを管理すると、端末管理や監査、全端末ログアウトを設計しやすくなります。
sessionsテーブルに期限と失効状態を持たせる- Cookieの生値ではなくハッシュ保存を検討する
expires_atとrevoked_atを分けるlast_seen_atの更新は間引く- ユーザー単位の全端末ログアウトをSQLで実装できる
- 古いセッションの掃除方針を決める
セッションは認証の土台なので、ただ保存するだけでなく、失効・監査・端末管理まで含めて設計します。