🦔

LINEリッチメニュー連打対策:DynamoDBを活用したレート制限の実装

に公開

LINEリッチメニュー連打対策:DynamoDBを活用したレート制限の実装

はじめに

はじめまして。エンジニアの田中です。

医療系予約システムを運用する中で、LINEリッチメニューの連打による問題に直面しました。ユーザーが「予約したい」ボタンを短時間に何度もタップすることで、システムに不要な負荷がかかり、場合によっては重複した予約リクエストが発生してしまうのです。

本記事では、この問題に対してDynamoDBを活用したレート制限機能を実装した事例を紹介します。

問題の背景

当初検討した「全メッセージへの一律制限」の課題

最初は、すべてのLINEメッセージに対して一律のレート制限をかけることを検討しました。しかし、以下のような問題が浮上しました:

  • 若年層ユーザーの利用パターン: 短時間で複数の短文メッセージを送信する傾向があり、後半のメッセージが制限されてしまう
  • 長文の分割送信: ユーザーが長文を複数メッセージに分けて送信した場合、後半が欠落して不完全な内容が医院側に届く
  • 運用上のリカバリーの複雑さ: 制限によりスキップされたメッセージを復旧する手段が煩雑で、医院・カスタマーサポート・エンジニアリングチームの業務に支障をきたす

対象を絞った解決策

これらの課題を踏まえ、本当に制限が必要な箇所のみに対策を施す方針に転換しました。

具体的には:

  • 対象: LINEリッチメニューの連打のみ
  • 理由: リッチメニューのテキストは固定値であり、文字列比較による判定が可能
  • 制限ルール: 同一リッチメニューの押下を60秒間に1回までに制限

実装のポイント

DynamoDBテーブル設計

# パーティションキー: rate_key (String)
# 容量モード: オンデマンド
# TTL: expires_at フィールドで2分後に自動削除

{
  rate_key: "U1234567890:message:a1b2c3d4e5f6g7h8",
  user_id: "U1234567890",
  event_type: "message",
  message: "予約したい",
  timestamp: 1625123457,
  expires_at: 1625123577  # timestamp + 120秒
}

キー設計のポイント:

  • rate_key: {user_id}:{event_type}:{message_hash}の形式
  • メッセージハッシュ: SHA256の先頭32文字を使用
  • メッセージごとに独立した制限を実現

レート制限のアルゴリズム

以下のフローで、リッチメニュー押下ごとに制限判定を行います:

  1. LINEリッチメニュー押下
    • ユーザーがリッチメニューをタップ
    • イベントがSQS経由で非同期LINEメッセージ処理に到達
  2. 初回判定: レコードが存在しない場合
    • DynamoDBに新規レコードを作成
    • timestampに現在時刻を記録
    • expires_atに現在時刻 + 120秒を設定(TTL用)
    • 処理を許可して後続処理へ
  3. 既存レコード判定: レコードが存在する場合
    • 60秒以内 & 同一メッセージスキップ(制限発動)
      • ログに制限発動を記録
      • 後続処理を実行せず、イベントを破棄
    • 60秒経過後 & 同一メッセージレコード更新して許可
      • timestampを現在時刻に更新
      • expires_atを現在時刻 + 120秒に更新
      • 処理を許可して後続処理へ
  4. 処理継続/スキップ: 判定結果に基づいて分岐
    • 許可 → 通常のLINEイベント処理フローへ
    • 拒否 → イベントを破棄し、ログに記録

アルゴリズムの工夫点:

  • メッセージハッシュによる独立制限: 「予約したい」を連打しても、「キャンセルしたい」は制限されない
  • タイムスタンプベースの判定: 複雑なカウンター管理が不要
  • 早期リターン: 制限判定を最初に行い、不要な処理を早期にスキップ

まとめ

LINEリッチメニューの連打対策として、以下のアプローチを採用しました:

  • 対象を絞る: 全メッセージではなく、リッチメニューのみに制限
  • 早期ブロック: 非同期処理の入口で制限チェック
  • 自動クリーンアップ: DynamoDBのTTL機能を活用
  • 運用の可視化: ログベースで制限発動状況を監視

結果として、システム負荷の軽減と重複リクエストの防止を実現しつつ、ユーザー体験を損なわない仕組みを構築できました。

「全体に一律の制限をかける」という安易な解決策ではなく、本当に必要な箇所にピンポイントで対策を施すことの重要性を改めて実感した事例です。

おわりに

本記事が、同様の課題に直面している方の参考になれば幸いです。

もし「通常メッセージにも制限が必要になったら?」という状況になった場合は、今回の仕組みをベースに、より洗練されたレート制限機能へと進化させていく予定です。

SCOグループ - Tech Blog

Discussion