← 全体レポートに戻る

局所トレーサビリティビュー グループ: BGIO

生成日時: 2026-03-13 23:59:50

対象アイテム

18

REQ

1

ARCH

1

SPEC

2

TST

2

IMPL

2

ADR

10

レビュー済

18/18

Suspect

0

グループ

BGIO
アイテム: REQ006 ARCH006 SPEC014 SPEC015 TST014 TST015 IMPL014 IMPL015 ADR005 ADR024 ADR033 ADR036 ADR037 ADR040 ADR047 ADR049 ADR053 ADR001

トレーサビリティマトリクス

✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。IDクリックで詳細へ)

グループREQARCHSPECTSTIMPLADR
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR005
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR005
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR024
# Non-blocking Cache Persistence and Task Tracking ## Context and Problem State...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR033
# 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR036
# バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR037
# GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR040
# Background Loop Shutdown Strategy and Threading Model ## Context and Problem ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR047
# Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR049
# 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR053
# Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
ADR001
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR024
# Non-blocking Cache Persistence and Task Tracking ## Context and Problem State...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR033
# 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR036
# バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR037
# GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR040
# Background Loop Shutdown Strategy and Threading Model ## Context and Problem ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR047
# Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR049
# 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR053
# Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
ADR001
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...

カバレッジ(局所)

リンク方向カバー数カバー率未カバー
ARCH → REQ 1 / 1 100.0%
SPEC → ARCH 1 / 1 100.0%
TST → SPEC 2 / 2 100.0%
IMPL → SPEC 2 / 2 100.0%
ADR → REQ 1 / 1 100.0%

アイテム詳細

REQ006 REQ {h(g)} ✓ レビュー済

キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。

親:

子: ADR001, ADR005, ADR024, ADR033, ADR036, ADR037, ADR040, ADR047, ADR049, ADR053, ARCH006

ARCH006 ARCH {h(g)} ✓ レビュー済

コンポーネント構成

graph TD
  A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop]
  B -->|Daemon Thread| C[asyncio.AbstractEventLoop]
  C -->|I/O| D[TaskDB / BlobStorage]
  A -->|ライフサイクル制御| E[atexit / ContextManager]

コンポーネント責務

コンポーネント 責務 インターフェース
_BackgroundLoop デーモンスレッドでのイベントループ管理とタスク投入 submit(), run_forever()
core.Spot 非同期保存のトリガーとエラーハンドリング _save_metadata_async()
ContextManager 処理終了時のタスク完了待機(Flush) __exit__, flush()

データフロー

sequenceDiagram
  participant Main as メインスレッド
  participant BGLoop as _BackgroundLoop
  participant Store as ストレージ

  Main->>BGLoop: submit(save_coro)
  Note over Main: 関数は即座に結果を返す (save_sync=False)
  BGLoop->>Store: 書き込み実行 (I/O)
  alt 成功
      Store-->>BGLoop: OK
  else 失敗
      BGLoop->>Main: on_background_error(error)
  end

技術選定

技術領域 選定 理由
並行処理 asyncio in Thread GILを解放しつつ、多数のI/Oタスクを効率的に多重化
スレッド種類 Daemon Thread アプリケーション終了時にプロセスをブロックしない
終了制御 atexit / drain 強制終了時も、可能な限り保留中の書き込みを完了させる

非機能要件方針

親: REQ006

子: SPEC014, SPEC015

SPEC014 SPEC {h(g)} ✓ レビュー済

インターフェース

class _BackgroundLoop:
    def submit(self, coro: Coroutine) -> None: ...
    def stop(self) -> None: ...
    def is_running(self) -> bool: ...

振る舞い

  1. 起動: 初回の submit 時にデーモンスレッドを作成し、asyncio.run() を開始する
  2. 投入: run_coroutine_threadsafe を使用して、メインスレッドからコルーチンをイベントループへ投入
  3. 待機: 投入されたタスクの完了は待たず、制御を即座に呼び出し元へ返す

パラメータ詳細

内部属性 説明
_loop AbstractEventLoop スレッド内で稼働するループ本体
_thread Thread daemon=True 設定のスレッド

エラーハンドリング

エッジケース

親: ARCH006

子: IMPL014, TST014

SPEC015 SPEC {h(g)} ✓ レビュー済

インターフェース

def flush(timeout: float | None = None) -> bool: ...

@spot.mark(save_sync=False)
def my_func(): ...

振る舞い

同期保存 (save_sync=True)

非同期保存 (save_sync=False)

フラッシュ (flush)

パラメータ詳細

パラメータ デフォルト 説明
timeout None flush の最大待機時間(秒)。None は無限待機
on_background_error logger.error 非同期保存失敗時のエラーハンドラ

エラーハンドリング

エッジケース

親: ARCH006

子: IMPL015, TST015

TST014 TST {h(g)} ✓ レビュー済

目的

_BackgroundLoop はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコルーチン受け渡しは競合状態やデッドロックが発生しやすく、シャットダウン時のタスク消失はデータロスに直結する。

検証観点

references: tests/integration/core/test_background_loop.py

親: SPEC014

子:

TST015 TST {h(g)} ✓ レビュー済

目的

save_sync パラメータと flush / drain メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユーザーが制御する手段である。save_sync=False 使用時にデータが失われないことと、コンテキストマネージャによる確実なフラッシュを保証する。

検証観点

references: tests/integration/core/test_exit_drain.py

親: SPEC015

子:

IMPL014 IMPL {h(g)} ✓ レビュー済

実装概要

_BackgroundLoop クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に asyncio.new_event_loop() で新しいイベントループを作成し、 _thread(デーモンスレッド)上で loop.run_forever() を実行する。 外部からは submit() を通じてコルーチンを投入でき、run_coroutine_threadsafe で スレッドセーフにループへ渡される。

設計判断

デーモンスレッドと明示的なシャットダウン

スレッドは daemon=True とし、メインスレッド終了時にプロセスがブロックされないようにしている。 しかし、安全なリソース解放のために drain() メソッドを提供し、 投入された全タスクの完了を drain_timeout の範囲で待機する。

独立したイベントループ

メインスレッドのイベントループと競合しないよう、専用のイベントループを 作成してバックグラウンドスレッドで駆動する。これにより save_sync=False 時の 保存処理などが、呼び出し元の asyncio 環境から完全に隔離される。

実装メモ

references: src/beautyspot/core.py

親: SPEC014

子:

IMPL015 IMPL {h(g)} ✓ レビュー済

実装概要

Spot クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 save_sync=False の場合、キャッシュへの保存処理は _bg_loop.submit() で バックグラウンドに投入される。これら未完了のタスクやDBキューを同期するため、 flush() およびコンテキストマネージャによる drain が実装されている。

設計判断

flush と drain の使い分け

バックグラウンドエラーの隔離

バックグラウンド保存時のエラーは呼び出し元のメインフローを妨げないよう、 on_background_error コールバックに通知され、ログ出力のみ行う。 例外の再送出は行わない。

実装メモ

references: src/beautyspot/core.py

親: SPEC015

子:

ADR005 ADR {h(g)} ✓ レビュー済

Manage Executor Lifecycle via Instance Ownership and Weak References

Context and Problem Statement / コンテキスト

src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。

この実装には以下の課題がある:

  1. リソース制御の欠如: スレッド数(max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。
  2. ゾンビプロセス: プロセス終了時に明示的なシャットダウンが行われないため、環境によってはゾンビプロセス化するリスクがある。
  3. テスト困難性: グローバル変数はモック化が難しく、ユニットテストの分離を妨げる。

代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

Project クラスの設計を以下のように変更する:

  1. Executorのインスタンス化: グローバル変数を廃止し、Project インスタンスごとに ThreadPoolExecutor を保持する。
  2. Dependency Injection (DI): コンストラクタで外部 Executor の注入を許可する。
  3. 所有権と責任の分離:
    • 外部注入 (executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。
    • 内部生成 (executor 引数なし): Project がライフサイクルを管理する責任を持つ。
  4. 自動クリーンアップ: 内部生成した場合に限り、weakref.finalize を使用してシャットダウンを自動化する。

Technical Details / 技術詳細: Why weakref.finalize instead of atexit?

単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。

# Implementation Sketch
class Project:
    def __init__(self, ...):
        # ...
        self.executor = ThreadPoolExecutor(...)
        # self を参照しないよう、executor オブジェクトだけを渡す
        self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)

    @staticmethod
    def _shutdown_executor(executor):
        executor.shutdown(wait=True)

Consequences / 決定

親: REQ006

子:

ADR024 ADR {h(g)} ✓ レビュー済

Non-blocking Cache Persistence and Task Tracking

Context and Problem Statement / コンテキスト

大規模なデータのシリアライズや、S3 等のリモートストレージへの保存処理は、ユーザーのメインロジックの実行を妨げる(ブロッキング)要因となっていました。「インフラとしてのキャッシュ処理がユーザーを待たせない」という設計思想を強化する必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

キャッシュの保存処理をバックグラウンドで実行し、かつリソースの整合性を保つためのライフサイクル管理を導入します。

  1. wait=False モードの導入: @spot.markcached_runwait オプションを追加し、保存完了を待たずにメインロジックに結果を返すことを可能にします。
  2. タスク追跡メカニズム: 実行中のバックグラウンドタスク(Future)を内部のセットで追跡します。
  3. Flush (非破壊的待機) の導入: with spot: ブロックを抜ける際に、全てのバックグラウンドタスクの完了を待機する(Flush)処理を実行します。
  4. 再利用性の確保: コンテキストを抜けても Executor は即座にシャットダウンせず、Spot インスタンスを繰り返し安全に利用可能にします。

Consequences / 決定

親: REQ006

子:

ADR033 ADR {h(g)} ✓ レビュー済

非同期保存処理におけるエラー可視化とコンテキストの導入

Context and Problem Statement / コンテキスト

beautyspot では、関数の実行結果をキャッシュに保存する際、wait=False を指定することでバックグラウンドスレッドでの非同期保存を行うことができます。しかし、バックグラウンドでの保存処理中にエラーが発生した場合、これまではログが出力されるのみ(サイレントフェイル)でした。

メインスレッドをクラッシュさせないという点では安全ですが、ユーザーからすると「なぜかキャッシュが効かない」という原因究明が困難な状態に陥るリスクがありました。また、監視ツールへのエラー通知や、カスタムのリカバリ処理を行うためのフックが存在しませんでした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

以下の設計方針で実装します。

  1. on_background_error 引数の追加: Spot.__init__ にコールバックを受け付ける引数を追加します。
  2. 型安全なコンテキストオブジェクト (SaveErrorContext) の導入: ハンドラーには、エラー時の詳細情報(対象の関数名、キャッシュキー、戻り値等)を含む専用のデータクラスを渡します。
  3. result (評価済みオブジェクト) の包含: デバッグを容易にするため、キャッシュしようとした実際のオブジェクトを含めます。ただしメモリリークのリスクがあるため、Docstring にて警告を記載します。
  4. コールバック内の例外保護: コールバック関数自体が失敗した場合でも、バックグラウンドスレッドをクラッシュさせないよう try-except で保護します。

Consequences / 決定

親: REQ006

子:

ADR036 ADR {h(g)} ✓ レビュー済

バックグラウンドループのライフサイクル管理とGC時のデータロスト対策

Context and Problem Statement / コンテキスト

Spot クラスは非同期のキャッシュ保存を構造的に直列化するため、内部で専用の asyncio ループを実行するスレッド (_BackgroundLoop) を保持しています。インスタンスがガベージコレクション (GC) によって破棄される際、未保存のキャッシュデータを厳密に待機してしまうと、メインスレッドを予測不能なタイミングでフリーズさせたり、デッドロックを引き起こす危険性があります。

そのため、これまでは安全を優先し、GC 時にはタスクをキャンセルしてリソースを解放する設計となっていました。しかし、この設計では一時的なスコープで Spot を使用した場合、GC のタイミングによってキャッシュデータが失われるという課題がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

  1. atexit によるグローバルな安全網の追加: weakref.WeakSet を用いて起動中のバックグラウンドループを追跡し、プロセス終了時 (atexit) に wait=True でドレイン(残存タスクの処理)を行うフックを実装します。プロセス終了時であれば、メインスレッドをブロックしてもデッドロックの危険性が低いためです。
  2. 明示的なリソース管理の推奨: Spot インスタンスは長寿命なオブジェクトとして扱うか、コンテキストマネージャ (with) または shutdown(wait=True) による明示的な管理を行うべきであることを周知します。

Consequences / 決定

親: REQ006

子:

ADR037 ADR {h(g)} ✓ レビュー済

GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略

Context and Problem Statement / コンテキスト

beautyspotSpot クラスは、非同期でのキャッシュ保存タスクを構造的に直列化するため、内部で専用の asyncio ループを実行するバックグラウンドスレッド (_BackgroundLoop) を保持しています。インスタンスのライフサイクル終了時、これらのバックグラウンドタスクを安全に終了させる必要がありますが、シャットダウンのトリガー(明示的呼び出し、アプリ終了、GC)によって、メインスレッドへの影響とプロセスの振る舞いが大きく異なるという課題がありました。

特に、GC は予測不能なタイミングで発生します。ここでタスク完了を待機(スレッドを join)してしまうと、メインスレッドがフリーズしたりデッドロックを引き起こす危険性があります。一方で、待機せずにタスクを強制キャンセルすると、保存待ちだったキャッシュデータが失われてしまいます。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

以下の3つの方式を実装し、状況に応じて使い分けます。

  1. stop(wait=True) (完全同期シャットダウン)
    • 用途: 明示的な shutdown()with ブロックの終了、atexit
    • 理由: データの確定を保証するため。プロセス終了時はメインスレッドをブロックしないと OS に強制終了されるため。
  2. stop_gracefully_no_wait() (非同期ドレイン / スレッド切り離し)
    • 用途: weakref.finalize による GC 時。
    • 理由: メインスレッドのフリーズを回避しつつ、既存タスクを完了させてから自発的に終了させるため。
  3. stop(wait=False) (強制停止)
    • 用途: エラー発生時の緊急停止など。

Consequences / 決定

親: REQ006

子:

ADR040 ADR {h(g)} ✓ レビュー済

Background Loop Shutdown Strategy and Threading Model

Context and Problem Statement / コンテキスト

バックグラウンドでの非同期 IO タスクを処理するイベントループにおいて、単純に daemon=True スレッドを使用すると、メインスレッド終了時にプロセスが即座に強制終了され、データ破損やキャッシュのロストが発生する危険がありました。一方で、daemon=False にして atexit で待機を試みるアプローチでは、タスクがハングした場合にプロセス全体が永遠に終了しなくなるデッドロックの罠が存在していました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

真の耐障害性を持つ Graceful Shutdown を実現するため、以下のアーキテクチャを採用します。

  1. デーモンスレッドの維持: スレッドは daemon=True で起動し、プロセス終了時の無限ハングを OS の力で防ぎます。
  2. 明示的なタスク追跡とロック: ロックとアクティブタスクカウンタを用いたステートマシンを導入し、submit 時と atexit 時の競合状態を完全に排除します。
  3. Grace Period(猶予期間)の導入: atexit フック内でメインスレッドをブロックし、タイムアウト付き(デフォルト5秒)で _thread.join() を実行することで、IO タスクが完了するための猶予を与えます。

Consequences / 決定

親: REQ006

子:

ADR047 ADR {h(g)} ✓ レビュー済

Background Loop Shutdown Strategy on Garbage Collection

Context and Problem Statement / コンテキスト

beautyspot はキャッシュの永続化をバックグラウンドスレッドにオフロードしています。コンテキストマネージャを使用した場合は安全にタスクがドレインされますが、インスタンスをグローバルに生成して使い捨てた場合など、ガベージコレクション (GC) によってインスタンスが破棄される際の振る舞いが課題となっていました。GC のタイミングで未完了の I/O タスクを待機すべきか、それとも破棄すべきかを決定する必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

ガベージコレクション (weakref.finalize) によるインスタンス破棄時には、未完了のタスクを待機せず、即座に破棄する(Fail-fast & Non-blocking) ことを決定しました。

同時に、タスクが破棄された場合は logger.warning および ResourceWarning を発行し、ユーザーに対してコンテキストマネージャや明示的な shutdown() の使用を強く促します。

Consequences / 決定

親: REQ006

子:

ADR049 ADR {h(g)} ✓ レビュー済

実行中タスクの強参照セットによる追跡

Context and Problem Statement / コンテキスト

Spot インスタンスは、バックグラウンドで実行中の保存タスク(Future)を内部のセットで管理しています。これはシャットダウン時や flush() 呼び出し時に、すべてのタスクが完了したかを確認するために必要です。この追跡において、強参照を使用すべきか、それとも GC を妨げない弱参照を使用すべきかを決定する必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

タスクの追跡には通常の set を使用し、強参照によってタスクの生存を保証します。

  1. 生存保証: Spot が強参照を保持することで、タスクが確実に完了まで実行されることを保証します。
  2. 待機可能性: WeakSet で発生しうる「待機すべきタスクが勝手に消える」という不透明な挙動を排除します。
  3. 決定論的な挙動: タスク削除のタイミングが完了コールバックに紐付くため、挙動が予測可能になり、デバッグやテストが容易になります。

Consequences / 決定

親: REQ006

子:

ADR053 ADR {h(g)} ✓ レビュー済

Background Loop and Executor Shutdown Sequence

Context and Problem Statement / コンテキスト

beautyspot では、バックグラウンドでのI/Oタスク処理に専用のスレッド(_BackgroundLoop)と、ブロッキング操作を委譲する ThreadPoolExecutor の2つを組み合わせて使用しています。 Spot.shutdown(save_sync=False) またはプロセスのシャットダウン時に、これら2つのリソースを破棄する順序やタイミングの不整合が問題となりました。

具体的には、バックグラウンドループ内の非同期タスクが run_in_executor に依存している状態(I/O待ち)で、先に Executorcancel_futures=True で強制終了されると、タスク側で予期せぬ CancelledErrorRuntimeError が発生し、クラッシュやログのノイズ、正常な後処理(一時ファイルの削除やDB切断など)の阻害を引き起こすリスクがありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

シャットダウン手順の順序自体(Loopの停止 -> Executorの停止)は論理的に正しいものの、強制停止(save_sync=False または Loop のドレインタイムアウト)時には、Executor内のタスクがキャンセルされることを前提とした防御的プログラミングを採用します。

具体的には、_save_result_async 内で run_in_executor を呼び出す際に try...except (asyncio.CancelledError, RuntimeError) ブロックを設け、シャットダウンによる強制キャンセルを安全に捕捉します。捕捉した場合は、on_background_error コールバックを呼び出すか、適切に警告を記録することで、クラッシュを防ぎつつ通知を行います。

Consequences / 決定

親: REQ006

子:

ADR001 ADR {h(g)} ✓ レビュー済

Manage Executor Lifecycle via Instance Ownership and Weak References

Context and Problem Statement / コンテキスト

src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。

この実装には以下の課題がある:

  1. リソース制御の欠如: スレッド数(max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。
  2. ゾンビプロセス: プロセス終了時に明示的なシャットダウンが行われないため、環境によってはゾンビプロセス化するリスクがある。
  3. テスト困難性: グローバル変数はモック化が難しく、ユニットテストの分離を妨げる。

代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

Project クラスの設計を以下のように変更する:

  1. Executorのインスタンス化: グローバル変数を廃止し、Project インスタンスごとに ThreadPoolExecutor を保持する。
  2. Dependency Injection (DI): コンストラクタで外部 Executor の注入を許可する。
  3. 所有権と責任の分離:
    • 外部注入 (executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。
    • 内部生成 (executor 引数なし): Project がライフサイクルを管理する責任を持つ。
  4. 自動クリーンアップ: 内部生成した場合に限り、weakref.finalize を使用してシャットダウンを自動化する。

Technical Details / 技術詳細: Why weakref.finalize instead of atexit?

単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。

# Implementation Sketch
class Project:
    def __init__(self, ...):
        # ...
        self.executor = ThreadPoolExecutor(...)
        # self を参照しないよう、executor オブジェクトだけを渡す
        self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)

    @staticmethod
    def _shutdown_executor(executor):
        executor.shutdown(wait=True)

Consequences / 決定

親: REQ006

子: