0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ninda 10日目: 有効期限(TTL) 〜自動的に消えるタプル〜

Posted at

今日のゴール

  • タプルの有効期限(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 のパターンで統計を更新

前回: イベント通知 | 目次 | 次回: バルク操作

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?