✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。IDクリックで詳細へ)
| グループ | REQ | ARCH | SPEC | TST | IMPL |
|---|---|---|---|---|---|
| HERD | REQ013 ✓ 同一キャッシュキーに対する並行リクエストを直列化し、1つのリクエストのみが関数を実行し、他のリクエストはその結果を共有することで重複実行を防止できること。 | ARCH013 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|管理要求| B[CacheManager] B -... | SPEC023 ✓ ## インターフェース ```python class CacheManager: def get_or_create_inflight( ... | TST023 ✓ ## 目的 Thundering Herd Protection は、同一キーへの並行リクエストが一斉に関数を実行する「Thundering Herd」問題を... | IMPL023 ✓ ## 実装概要 `CacheManager` クラス内で、Thundering Herd(キャッシュミス時に同一キーへの 大量アクセスが同時に発生する問題)を... |
| リンク方向 | カバー数 | カバー率 | 未カバー |
|---|---|---|---|
| ARCH → REQ | 1 / 1 | 100.0% | — |
| SPEC → ARCH | 1 / 1 | 100.0% | — |
| TST → SPEC | 1 / 1 | 100.0% | — |
| IMPL → SPEC | 1 / 1 | 100.0% | — |
同一キャッシュキーに対する並行リクエストを直列化し、1つのリクエストのみが関数を実行し、他のリクエストはその結果を共有することで重複実行を防止できること。
親: —
子: ARCH013
graph TD
A[core.Spot] -->|管理要求| B[CacheManager]
B -->|In-flight追跡| C[_inflight Dict]
C -->|同期待機| D[threading.Event]
C -->|非同期待機| E[asyncio.Future]
コンポーネント責務インターフェースCacheManagerキャッシュキーごとの実行状態管理と実行のシリアライズget_or_create_inflight()_inflight実行中のタスクを保持し、後続リクエストをイベントで待機させるExecutionStatecore.Spot待機結果の受け取りとエラーの伝播cached_run()
sequenceDiagram
participant T1 as Thread 1 (First)
participant T2 as Thread 2 (Second)
participant CM as CacheManager
participant Fn as Target Function
T1->>CM: get_or_create(key)
Note over CM: 新規作成 (Executor)
T2->>CM: get_or_create(key)
Note over CM: 既存あり (Waiter)
T1->>Fn: execute()
Fn-->>T1: result
T1->>CM: set_result(key, result)
Note over CM: Eventをセット
CM-->>T2: notify result
T1-->>T1: return result
T2-->>T2: return result
技術領域選定理由待機機構Event / FutureOS/ランタイムレベルの待機を使用し、CPU負荷(ビジーループ)を回避スコープキャッシュキー単位異なる関数の実行は妨げず、同一入力のみを直列化安全策タイムアウト & リトライ実行者がハングした場合に、待機側がデッドロックしないよう保護
一貫性: 実行者が成功すれば全員に結果を、失敗すれば全員に同じ例外を伝播
効率性: メモリリークを防ぐため、結果配布後に _inflight エントリを即座に削除
信頼性: 強参照 (StrongReference) で実行中のタスクを保持し、GCによる消失を防止
親: REQ013
子: SPEC023
class CacheManager:
def get_or_create_inflight(
self, key: str, is_async: bool
) -> ExecutionState: ...
_inflight 辞書をチェックするExecutionState を新規作成し、自分が「実行者 (Executor)」となるExecutionState を返し、自分は「待機者 (Waiter)」となるExecutionState の Event (または Future) をセットし、結果を共有する実行中のタスク状態(イベント・結果・Future)が GC によって消失しないよう、
_inflight 辞書を通じて強参照で保持する。WeakRef は使用しない。
_inflight 辞書はキャッシュキーをキーとし、以下の3要素タプルを値として保持する:
_inflight: dict[str, tuple[threading.Event, list[asyncio.Future], list]]
| 要素 | 型 | 役割 |
|---|---|---|
event |
threading.Event |
同期待機者への完了通知シグナル |
futures |
list[asyncio.Future] |
非同期待機者への結果配信チャネル |
result_box |
list |
結果の共有ボックス。[(success: bool, value: Any)] 形式 |
wait_herd_sync / wait_herd_async で _inflight にキーが存在しない場合、
_inflight_lock を保持した状態で新規タプルを挿入する_inflight 辞書がタプルへの唯一の管理参照を保持する。
待機者はロック取得時にタプル要素への参照を取得するが、_inflight エントリが正規の所有者であるnotify_and_cleanup_inflight で以下の順序で解放する:
a. _inflight_lock を取得し、エントリの同一性を event の identity (is) で確認する
b. 辞書からエントリを削除する(del _inflight[cache_key])
c. ロックを解放した後、event.set() で同期待機者に通知する
d. futures リスト内の各 asyncio.Future に結果またはエラーを伝播する_inflight 辞書への挿入・削除は常に _inflight_lock の保護下で行う_inflight 内には高々1つのエントリしか存在しないevent と同一オブジェクト(is 比較)を持つスレッドのみが行えるresult_box への書き込みは実行者スレッドのみが行い、待機者は読み取り専用とするfutures リストへの追加は _inflight_lock の保護下でのみ行う_inflight 辞書は CacheManager インスタンスの属性であり、CacheManager が
生存している限り、全てのインフライトエントリは GC の対象にならない。
CacheManager 自体は Spot インスタンスが所有するため、with spot: ブロック内での
安全性が保証される。
| 内部定数 | 値 | 説明 |
|---|---|---|
HERD_TIMEOUT |
300.0 |
待機者が実行者の完了を待つ最大秒数 |
HERD_MAX_RETRIES |
3 |
実行者が失敗またはタイムアウトした際のリトライ回数 |
親: ARCH013
Thundering Herd Protection は、同一キーへの並行リクエストが一斉に関数を実行する「Thundering Herd」問題を防ぐ。この保護が不完全だと、重い計算やAPI呼び出しが不要に多重実行され、リソース浪費やレートリミット超過を招く。並行性バグはテスト困難であるため、複数の観点から網羅的に検証する。
asyncio.Task が同一キーで同時にキャッシュミスした場合も、関数が1回のみ実行されること_inflight エントリがクリーンアップされることHERD_TIMEOUT(300秒)を超過した場合にタイムアウト処理が発動し、無限待機にならないことHERD_MAX_RETRIES(3回)の範囲でリトライが行われ、上限超過で最終エラーが返ること_inflight 辞書の状態遷移(エントリ追加→結果セット→エントリ削除)が正しい順序で行われることreferences: tests/integration/core/test_thundering_herd.py
親: SPEC023
子: —
CacheManager クラス内で、Thundering Herd(キャッシュミス時に同一キーへの
大量アクセスが同時に発生する問題)を防止する直列化機構を実装。
_inflight 辞書で実行中のキーを管理し、最初の1スレッド/タスクだけが関数を実行し、
後続の呼び出しは wait_herd_sync / wait_herd_async でその完了を待機する。
_inflight 辞書の値として (threading.Event, list[asyncio.Future], list[result]) の
タプルを保持し、スレッドベースの待機 (Event.wait) と asyncio ベースの待機 (Future) の
両方を単一の管理下で混在できるようにしている。
関数の実行がハングアップした場合に待機側が永遠にブロックされるのを防ぐため、
HERD_TIMEOUT(300秒)と HERD_MAX_RETRIES(3回)を設定。
タイムアウト時には警告ログを出しつつ再試行し、上限を超えれば TimeoutError で
フェイルファストする堅牢な設計とした。
notify_and_cleanup_inflight() で待機タスクへの結果伝播と _inflight からの削除を
_inflight_lock によりアトミックに行うresult_box(要素数1のリスト)をリファレンス渡しで共有することで、
Event が set された際に安全に結果を取得できるようにしている_notify_future() は call_soon_threadsafe を使い、非同期Futureに対して
別スレッドから安全に結果(または例外)をセットするreferences: src/beautyspot/cache.py
親: SPEC023
子: —