タロットカード: AI タロットカードリーディングサービスのキャッシング
OpenAI API のコストと応答速度を最適化しながら、毎回新しい体験を提供するタロットサービスのキャッシュ設計
問題意識
AIが生成するコンテンツは毎回新しいという点が長所ですが、同じ入力に対して毎回APIを呼び出すとコストがすぐに積み重なります。タロットカードサービスも同様です。カードリーディングのためにOpenAI APIを使用していますが、コストと応答速度の問題からすべてのリクエストにAPIを呼び出すのは現実的ではありません。
とはいえ、単にカードと方向のみをキーにしたキャッシュでは不十分です。ユーザーは同じカードを引いても毎回異なるリーディングを期待するからです。このギャップを縮めるために導入したのが **バケットシステムを活用したキャッシュ戦略**です。
バケット: 多様性と効率のバランス
核心アイデアは簡単です。78枚のカード、正方向/逆方向、そして10のバケットを組み合わせ **1,560個のユニークキャッシュキー**を作り、各キーに対してAIが生成したリーディングを保存しておくのです。
78枚 × 2方向 × 10バケット = 1,560個ユニーク組み合わせユーザーがリクエストするたびにこの組み合わせの1つがランダムに選ばれます。キャッシュキーは`tarot:read:{card}:{direction}:{bucket}`形態で、同一のキーで入る後続リクエストはValkeyから即時に返されます。キャッシュにないときのみOpenAI APIを呼び出します。
ここにもう1つの仕掛けを加えました。サーバはリクエストごとにキーワード4つをランダムに選んでAIにコンテキストとして一緒に渡します。そのおかげで、同じカードと方向、バケットでもキーワードによって異なるリーディングが出ることがあります。
flowchart LR
subgraph RandomSelect["ランダム選択"]
Card[78枚カード]
Dir[正方向/逆方向]
Bucket[バケット1~10]
Keywords[キーワード4個]
end
subgraph CacheKey["キャッシュキー"]
Key["tarot:read:{card}:{dir}:{bucket}"]
end
Card --> Key
Dir --> Key
Bucket --> Key
Key --> Valkey[(Valkey)]
Key -.->|キャッシュミス| OpenAI[OpenAI API]
Keywords -.->|リーディング方向| OpenAI
全体の流れをシーケンスダイアグラムで表すと次のようになります。
sequenceDiagram
autonumber
actor Client as クライアント
participant Service as TarotService
participant Cache as Valkey
participant AI as OpenAI
Client->>Service: タロットリーディングリクエスト
Note over Service: カード / 方向 / バケットランダム選択
Service->>Cache: キャッシュ確認 (`GET`)
alt キャッシュヒット (Hit)
Cache-->>Service: 保存された結果返却
else キャッシュミス (Miss)
Service->>AI: OpenAI API呼び出し
AI-->>Service: リーディング結果返却 ({advice})
Note over Service: データ結合<br/>(card.name / card.nameKR / keywords)
Service->>Cache: 結果保存 (`SET`)
end
Service-->>Client: 最終リーディング結果応答デプロイ
フロントエンドはVercel、バックエンドはホームKubernetesクラスターで運営しています。
flowchart TD
subgraph Front["Vercel"]
Vercel[タロットカード Next.js アプリ]
end
subgraph CICD["CI / CD"]
GH[GitHub]
Actions[GitHub Actions]
Harbor[(Harbor)]
ArgoCD[ArgoCD]
S3[(S3)]
end
subgraph K8s["ホームK8sクラスター"]
GW[ゲートウェイAPI]
Service[タロットカードサービス]
HPA[HPA: 2~10レプリカ]
end
%% デプロイパイプラインフロー
GH -->|Gitバージョンタグプッシュv*.*.*| Actions
Actions -->|イメージビルド/プッシュ| Harbor
Harbor -->|イメージ保存| S3
Harbor -->|イメージ参照| ArgoCD
ArgoCD -->|GitOpsデプロイ| Service
%% フロントエンドパイプラインフロー
Actions -->|フロントエンドビルド/デプロイ| Vercel
%% トラフィックフロー
Vercel --> |外部トラフィック| GW
GW -->|ルーティング| Service
Service -->|自動スケーリング| HPAデプロイパイプラインはGitHubに
**新しいバージョンタグ(**`v*.*.*`**)がプッシュされると即座に**自動的に開始されます。GitHub Actionsがそのタグを基にイメージをビルドし、インハウスコンテナレジストリであるHarborにプッシュすると、ArgoCDがGitOps設定を通じて変更を検出し、クラスター状態を自動で同期します。また、フロントエンドはGitHub ActionsからVercelへ直接ビルドおよびデプロイされます。
バックエンドはHPAを通じてトラフィックに応じて2~10のレプリカに自動スケーリングされ、外部からのユーザーのリクエストは内部ゲートウェイAPIで安全にルーティングされます。
改善の余地
現在はログインなしで誰でも利用できる単純なサービスですが、ログイン機能を追加し、ユーザーごとにリーディング履歴を保存したり、パーソナライズされた体験を提供することも構想しています。
終わりに
タロットカードサービスは小さなプロジェクトですが、AI生成とキャッシングのバランスという実質的な問題を解決する過程が含まれています。バケットシステムを活用したキャッシュ戦略はコストと多様性の間で現実的な妥協点を提供し、将来的な改善の余地も十分に残しています。