今日のゴール
- タプルの有効期限(TTL)機能を理解する
- 一時データの効率的な管理方法を学ぶ
- キャッシュやセッション管理への応用を理解する
なぜ有効期限が必要か
永続的なタプルだけでは、以下の問題が発生します:
| 問題 | 説明 |
|---|---|
| メモリリーク | 不要なタプルが蓄積し続ける |
| 古いデータ | 期限切れの情報が残る |
| 手動削除の負担 | クリーンアップロジックが必要 |
有効期限(TTL: Time To Live)を設定することで、タプルは自動的に期限切れになります。
有効期限の設定
writeAsync の第2引数で有効期限を指定します:
# 現在時刻 + 5秒後に期限切れ
let expiresAt = getTime().toUnix() * 1000 + 5000
await ts.writeAsync(
toTuple(strVal("temp"), intVal(42)),
expiresAt # Unixタイムスタンプ(ミリ秒)
)
有効期限の形式
| 値 | 動作 |
|---|---|
0 |
期限切れなし(デフォルト、永久に保持) |
> 0 |
指定ミリ秒のUnixタイムスタンプで期限切れ |
let now = getTime().toUnix() * 1000
# 5秒後
let in5Seconds = now + 5000
# 1分後
let in1Minute = now + 60000
# 1時間後
let in1Hour = now + 3600000
主な用途
用途1: キャッシュ
一時的なデータを自動的にクリーンアップ。
proc setCache(key: string, value: string, ttlMs: int64) {.async.} =
let expiresAt = getTime().toUnix() * 1000 + ttlMs
await cache.writeAsync(
toTuple(strVal("cache"), strVal(key), strVal(value)),
expiresAt
)
ポイント:
- キャッシュミス時は元のソースから再取得
- TTLはデータの新鮮さ要件に応じて設定
用途2: セッション管理
ユーザーセッションの自動失効。
const SESSION_TTL = 30 * 60 * 1000 # 30分
proc createSession(userId: string, token: string) {.async.} =
let expiresAt = getTime().toUnix() * 1000 + SESSION_TTL
await sessions.writeAsync(
toTuple(strVal("session"), strVal(token), strVal(userId)),
expiresAt
)
ポイント:
- アクティブなセッションはTTLを延長(take → 再write)
- セキュリティのため短めのTTLを推奨
用途3: レートリミット
スライディングウィンドウ方式のレート制限。
const WINDOW_MS = 60000 # 1分間
const MAX_REQUESTS = 10
proc checkRateLimit(clientId: string): Future[bool] {.async.} =
let pattern = toPattern(strVal("request"), strVal(clientId), nilValue())
let requests = await rateLimit.readAllAsync(pattern)
if requests.len >= MAX_REQUESTS:
return false
# リクエストを記録(1分後に自動削除)
let expiresAt = getTime().toUnix() * 1000 + WINDOW_MS
await rateLimit.writeAsync(
toTuple(strVal("request"), strVal(clientId), intVal(getTime().toUnix())),
expiresAt
)
return true
期限切れイベントの監視
evExpire イベントを購読すると、タプルが期限切れになったときに通知を受けられます:
# 期限切れイベントを購読
let sub = await ts.subscribeAsync(
toPattern(strVal("session"), nilValue(), nilValue()),
{evExpire}
)
# 期限切れを待ち受け
let event = await sub.recvEvent()
echo "Session expired: ", event.data
用途例:
- セッション切れのログ記録
- キャッシュ更新のトリガー
- リソースのクリーンアップ
期限切れの動作詳細
| 動作 | 説明 |
|---|---|
| 遅延削除 | 期限切れタプルは即座には削除されず、アクセス時にチェック |
| 定期クリーンアップ | バックグラウンドで定期的に期限切れタプルを削除 |
| 読み取り時チェック |
read/take時に期限切れタプルはスキップ |
# 期限切れタプルはマッチしない
let result = await ts.tryReadAsync(pattern)
# → 期限切れ後は None
まとめ
学んだこと
| トピック | ポイント |
|---|---|
| 設定方法 |
writeAsync(tuple, expiresAt) ミリ秒単位のUnixタイム |
| 主な用途 | キャッシュ、セッション、レートリミット |
| イベント |
evExpire で期限切れを監視 |
| 動作 | 遅延削除、読み取り時チェック |
いつ使うか
| 場面 | TTL設定 |
|---|---|
| キャッシュ | データの新鮮さに応じて(秒〜分) |
| セッション | セキュリティ要件に応じて(分〜時間) |
| レートリミット | ウィンドウサイズと同じ |
| 一時ファイル参照 | 処理完了までの想定時間 |
演習問題
問題10-1: TTLキャッシュ
キャッシュヒット率を計測するキャッシュシステムを実装してください。
ヒント
- ヒット数とミス数をカウンタで管理
-
tryReadでキャッシュを確認 - 存在しなければミスとしてカウントし、元データを取得してキャッシュに保存
- ヒット率 = ヒット数 / (ヒット数 + ミス数)
問題10-2: セッション更新
アクティブなセッションのTTLを延長する機能を実装してください。
ヒント
-
tryTakeで既存セッションを取得 - 成功したら同じトークンで新しいTTLで再
write - 失敗したらセッションが既に切れている
- トランザクション性を考慮(take→writeは原子的ではない)
問題10-3: 期限切れ監視
期限切れタプルの統計情報(件数、最後の期限切れ時刻)を収集するモニターを実装してください。
ヒント
-
evExpireイベントを購読 -
recvEventで非同期に待ち受け - 統計情報は別のタプル
("stats", "expires", count, lastTime)で管理 - take → 更新 → write のパターンで統計を更新
前回: イベント通知 | 目次 | 次回: バルク操作