業務システムにログインセッションのテーブルがあり、期限切れレコードを定期的に消す処理を持っている方に向けた内容です。夜間バッチでDELETE FROM sessions WHERE expires_at < now()のようなSQLを叩いているシステムは珍しくありません。ただしユーザー数が増え、削除対象が数十万件規模になった段階で、この単純な仕組みが思わぬ障害を招くことがあります。
典型的な事故は、cron(Linuxの定期実行の仕組みで、分・時・日・月・曜日を指定してコマンドを起動する)が一定時間ごとに公開URLを叩き、そのURLがリクエスト内でそのまま大量削除を実行するパターンです。cronの呼び出しが重複したり遅延したりすると、同じ削除処理が二重に走ったり、ワーカー(実際の削除作業を担うプロセス)のプールが溢れたりします。ヨーロッパ圏のSaaS事例では、この「呼び出しの信頼性」と「削除の正しさ」を分けて設計する考え方が整理されています。日本の業務システムでも、会員数が伸びた後発規模拡大フェーズで同じ壁にぶつかるケースは想定できます。
本稿では、期限切れセッション削除の仕組みをどう設計するか、判断軸ごとに整理します。今のバッチ処理が該当するかどうかを確認しながら読んでみてください。
どんな場面でこの判断が必要になるか
削除処理を「単純なcronジョブ」から「安全な仕組み」に見直す必要が出るのは、次のような兆候が見えたときです。
- 削除バッチの実行時間がじわじわ伸びている
- 同じバッチが二重起動してエラーログが増えている
- 削除処理がDB接続やAPIのレート制限(一定時間あたりのリクエスト数上限)に引っかかるようになった
- ユーザーがセッションを更新した直後に、そのセッションが消えてしまう不具合報告がある
- 監視担当者が「バッチが本当に完走したか」をログから確認するのに時間がかかっている
これらに1つでも心当たりがあれば、削除処理の設計を見直す価値があります。逆にセッション数が数千件程度で、削除処理が数秒で終わっているなら、今の仕組みのままで十分なことも多いです。
判断軸1: 冪等性は担保されているか
冪等性(べきとうせい、同じ操作を何度実行しても結果が変わらない性質)は、この種の設計で最初に確認すべき軸です。
cronからの呼び出しは、遅延・重複・再試行のいずれが起きても不思議ではありません。ネットワーク越しの呼び出しである以上、これは前提条件として扱う必要があります。
確認すべきは、削除処理が「同じリクエストが2回来ても、最終的なセッションの生存状態が変わらないか」という点です。これを担保する具体的な仕組みが、削除対象の範囲(cutoffとなるタイムスタンプ)を1回のバッチ実行内で固定し、ワーカー側のローカル時刻を使わせないという設計です。あわせてバッチごとに、実行IDとカーソル範囲から導出される決定的な冪等性キーを持たせ、同じキーの処理は二重実行させない仕組みも有効です。
加えて重要なのが「削除は無条件実行ではなく条件付きで行う」という点です。ワーカーが削除を確定する瞬間に、対象セッションが「まだ期限切れのままか」を再確認してから削除します。スキャン時点と削除実行時点の間にユーザーがセッションを更新していれば、削除はスキップされ、実害のない空振りになります。これがなければ、いわゆるTOCTOU(time-of-check/time-of-use、確認時と使用時のタイミングのずれによる競合)の典型例になり、ログイン中のユーザーが突然セッション切れになる不具合につながります。
判断軸2: レート制限とワーカー容量に耐えられるか
削除対象が数万〜数十万件になると、キューやワーカープールへの負荷が問題になります。
HTTP 429(Too Many Requests、リクエストが多すぎるという意味のステータスコード)が返ってきたときの挙動を確認してください。レスポンスに含まれるRetry-Afterヘッダーを尊重し、同じバッチキーで再試行する設計になっているかが分かれ目です。
逆に、429を受け取るたびに新しいジョブIDを発行して即座に再送する実装は要注意です。この作り方は冪等性の仕組みを台無しにし、レート制限を守っているつもりが実際には突破しようとする動きになります。結果として1回の削除ウィンドウで処理しきれなかった分が積み重なり、バックログが雪だるま式に増える事態を招きます。
確認方法として、キューやジョブランナー(Celery、AWS SQS、あるいは社内製のジョブキューなど)の再試行ポリシー設定を開き、バックオフ(再試行間隔を段階的に伸ばす仕組み)が有効になっているか、ジョブIDが再試行時に維持されるかをコードレビューで見ておくと安心です。
判断軸3: 実行履歴を検証できる仕組みがあるか
cronのログや実行履歴だけでは「削除が完了した」ことの証明にはなりません。
cronの役割は「起動条件(分・時・日・月・曜日)に従ってコマンドを呼ぶこと」までで、アプリケーションレベルでの完了保証は持っていません。「cronが起動した」と「削除処理が完了した」は別の状態として扱う必要があります。
そこで有効なのが、実行記録を永続化する仕組み(run ledger、実行台帳)です。トリガーが実行を受け付けた時点でHTTP 202 Accepted(受理はしたが処理は非同期という意味のステータス)を返し、実際の削除進捗は台帳側で追跡します。これにより、リクエストがタイムアウトしても「受理されたが結果が不明」という状態を後から検証できます。
判断基準としては、削除バッチのステータス(成功・失敗・部分完了)が、バッチ単位の成功フラグだけでなく、アイテム単位の結果として記録されているかを確認してください。バッチ全体が「成功」と記録されても、一部レコードだけ処理漏れしているケースは珍しくありません。
判断軸4: 削除範囲の走査方法は安全か
大量データを1回のSQLで走査するOFFSETベースのページネーションは、件数が増えるほど遅くなり、途中で件数が変動すると重複やスキップが発生しやすくなります。
これに対してキーセットページネーション(前回取得した最後のキー値を基準に次のページを取得する方式)は、削除中にテーブルの状態が変化しても走査位置がずれにくい設計です。バッチサイズの最適値は、テーブルのインデックス構造、平均セッション寿命、キューの遅延、削除コストによって変わるため、一律の正解はありません。本番相当のデータ分布で負荷試験を行い、1バッチあたりの行数と同時実行数の両方に上限を設けて検証するのが確実な確認方法です。
選択肢の比較
| 方式 | 実装コスト | 大量データ耐性 | 向いている規模 |
|---|---|---|---|
| 単純cron+同期削除 | 低い | 低い | 数千件・単一インスタンス |
| 認証付きトリガー+同期削除 | 中程度 | 中程度 | 数万件・小規模チーム |
| トリガー+キュー+冪等ワーカー | 高い | 高い | 数十万件・複数リージョン |
ケース別の推奨
削除対象が常に数千件以内で、削除処理が数秒以内に終わっているなら、認証付きの単純なcron呼び出しで十分です。過剰な設計はメンテナンスコストを増やすだけになります。
ユーザー数が数万〜数十万規模で、削除処理が秒単位から分単位にかかっているなら、認証付きトリガーとキュー導入を検討する段階です。まずは冪等性キーの導入と条件付き削除への切り替えから着手すると、既存コードへの影響を抑えられます。
複数リージョンで運用しており、ネットワーク遅延や再試行が日常的に起きる環境であれば、実行台帳とキーセットページネーションまで含めたフル装備の設計が適しています。監視・アラートの仕組みとセットで導入する価値があります。
あえて見送るべき条件
- セッションテーブルの行数が少なく、削除処理が本番のピーク時間帯にも影響していない
- 削除処理を実行するチームが1〜2名で、キューやワーカー基盤の運用ノウハウがまだない
- 既存の監視体制がバッチの成否をSlack通知程度で十分カバーできている
これらに当てはまる場合、冪等性やキュー化への投資は後回しにして構いません。過剰な仕組みは、かえって障害点を増やすことがあります。
まとめ
期限切れセッション削除は地味な処理に見えますが、規模が増えると冪等性・レート制限・実行検証・走査方式という4つの判断軸が効いてきます。
まず着手しやすいのは、削除処理を「無条件削除」から「条件付き削除(削除確定時に期限切れを再確認)」に変えることです。次に、バッチ単位の成功フラグだけでなくアイテム単位の結果を記録する仕組みを足すと、障害調査が格段に楽になります。
自分のシステムがどの規模に当てはまるか、上の比較表と照らし合わせて確認してみてください。