← 全体レポートに戻る

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

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

対象アイテム

17

REQ

2

ARCH

1

SPEC

2

TST

2

IMPL

2

ADR

8

レビュー済

17/17

Suspect

0

グループ

DB
アイテム: REQ003 REQ019 ARCH003 SPEC007 SPEC008 TST007 TST008 IMPL007 IMPL008 ADR007 ADR010 ADR043 ADR045 ADR048 ADR050 ADR051 ADR052

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

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

グループREQARCHSPECTSTIMPLADR
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR007
# Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR051
# Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR007
# Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR051
# Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR010
# Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR043
# Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR045
# Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR048
# SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR050
# Delegate Database Lifecycle Management to Caller ## Context and Problem State...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
ADR052
# SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR010
# Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR043
# Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR045
# Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR048
# SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR050
# Delegate Database Lifecycle Management to Caller ## Context and Problem State...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
ADR052
# SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン...

カバレッジ(局所)

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

アイテム詳細

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

キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。

抽象インターフェースにより、DBやストレージなどのバックエンド実装を差し替え可能であること。

親:

子: ADR007, ADR010, ADR043, ADR045, ADR048, ADR050, ARCH003

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

データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLiteの書き込みスレッドがスタックした場合でも、フェイルファストによって制御を戻せること。

親:

子: ADR051, ADR052, ARCH003, TST007

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

コンポーネント構成

classDiagram
  class TaskDBCore {
    <<Protocol>>
    +init_schema() void
    +get(cache_key) TaskRecord
    +save(cache_key, ...) void
    +delete(cache_key) bool
  }
  class Maintenable {
    <<Protocol>>
    +delete_expired() int
    +prune(older_than) int
    +delete_all() int
  }
  class TaskDBMaintenable {
    <<Protocol>>
  }
  TaskDBCore <|-- TaskDBMaintenable
  Maintenable <|-- TaskDBMaintenable

  class SQLiteTaskDB {
    -db_path: Path
    -_write_queue: Queue
    +init_schema() void
    +get(cache_key) TaskRecord
    +save(cache_key, ...) void
  }
  TaskDBMaintenable <|-- SQLiteTaskDB

コンポーネント責務

コンポーネント 責務 インターフェース
TaskDBCore キャッシュ実行時に必要な最小限のメタデータDBアクセス init_schema(), get(), save(), delete()
Maintenable GCやCLI等、運用・保守に必要な拡張操作 delete_expired(), prune(), delete_all()
TaskDBMaintenable 実行用と保守用の両方を備えた上位Protocol TaskDBCore + Maintenable
SQLiteTaskDB SQLiteを用いたデフォルト実装。非同期書き込みキューを内包 TaskDBMaintenable 実装

データフロー

sequenceDiagram
  participant Spot as core.Spot
  participant DB as SQLiteTaskDB
  participant Q as WriteQueue
  participant Thread as WriterThread
  participant SQLite as SQLiteDB

  Spot->>DB: get(cache_key)
  DB->>SQLite: SELECT
  SQLite-->>DB: TaskRecord
  DB-->>Spot: TaskRecord

  Spot->>DB: save(metadata)
  DB->>Q: put(WriteTask)
  DB-->>Spot: void (Non-blocking)

  Thread->>Q: get()
  Thread->>SQLite: INSERT / UPDATE

技術選定

技術領域 選定 理由
デフォルトメタデータDB SQLite ゼロ設定、組み込み可能、ローカルキャッシュとして十分な性能
バックグラウンド書き込み キュー + 専用スレッド SQLiteの排他制御によるメインスレッドのブロックを防ぐため
抽象化 ProtocolベースのDI TaskDBCoreを満たせばPostgreSQL等に容易に差し替え可能

非機能要件方針

親: REQ003, REQ019

子: SPEC007, SPEC008

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

インターフェース

class SQLiteTaskDB(TaskDBBase):
    def __init__(self, db_path: str | Path, timeout: float = 30.0): ...
    def save(self, task: TaskRecord) -> None: ...
    def get(self, cache_key: str, *, include_expired: bool = False) -> TaskRecord | None: ...

振る舞い

ライタースレッド方式

読み取り並行性

スキーマ管理

タイムアウトとフェイルファスト

パラメータ詳細

設定 デフォルト 説明
journal_mode WAL 書き込みと読み取りの並行性を高める設定
synchronous NORMAL パフォーマンスと耐久性のバランス設定
timeout 30.0 データベースロック待機およびクエリ実行のタイムアウト閾値

エラーハンドリング

エッジケース

親: ARCH003

子: IMPL007, TST007

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

インターフェース

@runtime_checkable
class TaskDBCore(Protocol):
    def get(self, cache_key: str, *, include_expired: bool = False) -> TaskRecord | None: ...
    def save(self, record: TaskRecord) -> None: ...

class TaskDBBase(ABC):
    # TaskDBCore を満たしつつ、メンテナンスのデフォルト実装を提供

振る舞い

階層構造

  1. TaskDBCore: キャッシュの実行に必要な最小限のメソッド定義
  2. Maintenable: GCや統計取得に必要な管理用メソッドの定義
  3. TaskDBBase: 共通のバリデーションや例外処理を実装する基底クラス

パラメータ詳細 (主要メソッド)

メソッド 役割
get_blob_refs() 全レコードの blob_key を列挙する(GC用)
delete_expired() expires_at を経過したレコードを一括削除する
get_history() 実行履歴(統計)を取得する

エラーハンドリング

エッジケース

親: ARCH003

子: IMPL008, TST008

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

目的

SQLiteTaskDB はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす。WALモード・専用ライタースレッドという独自アーキテクチャの信頼性を保証する。

検証観点

references: tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py

親: REQ019, SPEC007

子:

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

目的

TaskDBCore / TaskDBBase のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL 等)の差し替えを可能にする拡張ポイントである。インターフェース契約が不明確だと、カスタム実装が実行時に予期しないエラーを起こす。

検証観点

references: tests/unit/test_db_writer_queue.py

親: SPEC008

子:

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

実装概要

SQLiteTaskDB クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の _writer_thread(デーモン)が _write_queue から書き込みタスクを取り出して直列処理し、読み取りは threading.local() のスレッドローカル接続で並行実行する。 WAL モードにより読み取りと書き込みの並行性を確保している。

タイムアウトの実装

references: src/beautyspot/db.py

親: SPEC007

子:

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

実装概要

DB 層のプロトコル階層を定義。TaskDBCore(ランタイム必須の CRUD)、 Flushable(書き込み同期)、Shutdownable(安全な停止)、 Maintenable(GC・統計等の管理操作)の4つのプロトコルと、 それらを統合した TaskDBMaintenable を提供する。 TaskDBBase は ABC として Maintenable のデフォルト実装(安全な no-op)を提供する。

設計判断

プロトコルの細分化

単一の巨大インターフェースではなく、責務ごとにプロトコルを分割した。 これにより、カスタム DB 実装は TaskDBCore のみ実装すればランタイムで動作し、 メンテナンス機能は段階的にオプトインできる。

デフォルト実装の安全性

TaskDBBase のメンテナンスメソッド(delete_expired(), prune() 等)は デフォルトで空リスト返却や no-op を行い、サブクラスが未実装でも安全に動作する。 CLI の gc コマンドが未対応の DB バックエンドでもエラーにならない設計。

実装メモ

references: src/beautyspot/db.py

親: SPEC008

子:

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

Semantic Content Type Support

Context and Problem Statement / コンテキスト

生成AIタスクの出力は、テキストだけでなく、画像、構造化データ、ダイアグラム(Mermaid, Graphviz/DOT, HTML)など多岐にわたります。 現状のデータベーススキーマ(result_type = FILE | DIRECT)は「データの保存形式」しか保持しておらず、「データの意味的種類(Semantic Type)」が不明であるため、ダッシュボードでの復元時に適切な可視化(レンダリング)ができません。

また、beautyspot はライブラリとしてユーザーの手元で動作するため、複雑なマイグレーション手順や重量級の依存関係(Alembicなど)を強制することはUXを損なうという課題があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

1. Database Schema & Migration

2. Type Definition

3. Interface

4. Rendering Strategy (Dashboard)

5. Dependencies

Consequences / 決定

親: REQ003

子:

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

Msgpack Everywhere with Native BLOB Support

Context and Problem Statement / コンテキスト

ADR-0007 で MsgpackSerializer を導入しましたが、SQLiteへの保存方式について以下の課題が残っていました。

  1. JSONの限界: 従来の TEXT カラム(JSON)ではバイナリデータを扱えず、Numpy配列などが保存できない。
  2. Base64の非効率性: TEXT カラムに保存するために Msgpack を Base64 エンコードする案がありましたが、データサイズが約33%増加し、CPUコストもかかる。
  3. 一貫性: 画像や動画などのバイナリデータも、可能な限り変換なしで「そのまま」扱いたい。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

1. Schema Change: Add BLOB Column

tasks テーブルに、バイナリデータをそのまま格納するための result_data (BLOB) カラムを追加します。

2. Msgpack Everywhere Strategy

データの保存先に関わらず、常に MsgpackSerializer を通過させます。

3. Size Guardrails (Unchanged)

save_blob=False であっても、閾値(デフォルト: 1MB)を超えるデータが渡された場合は、警告ログ (WARNING) を出力して save_blob=True の利用を促します。

Consequences / 決定

親: REQ003

子:

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

Serialize SQLite Writes with a Writer Queue

Context and Problem Statement / コンテキスト

beautyspotwait=False のバックグラウンド保存や async 経路で、 複数スレッドから同時に SQLite へ書き込みを行う可能性がある。 WAL モードを有効化していても SQLite の書き込みは単一ライター制約があり、 高並行時に database is locked が発生し得る。

一方で、アプリケーションの関数実行自体は失敗させたくない。 また、シャットダウン時には必ず書き込みをフラッシュしたい。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

SQLiteTaskDB に writer スレッドとキューを実装し、 書き込み系操作を単一接続で直列化する。 シャットダウン時は shutdown(wait=True) によりキューを drain し、 未処理の書き込みが残らないようにする。

さらに、保存失敗は関数実行を失敗させず、 ログと on_background_error で通知する。

Implementation Sketch / 実装概要

Consequences / 決定

Notes / 補足

本 ADR はプロセス内の直列化を対象とする。複数プロセスが同一 SQLite を共有する 運用では、依然としてロック競合が起こり得るため注意する。

親: REQ003

子:

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

Introduce Explicit DB Queue Flushing

Context and Problem Statement / コンテキスト

SQLiteTaskDB は、ライタースレッドとキューを用いた非同期書き込みを行っています。しかし、メインプロセスの終了時や Spot.flush() 実行時に、ストレージへの I/O は待機するものの、DB への書き込みキューのフラッシュ(全タスクの完了)を明示的に待機する仕組みがありませんでした。これにより、短命なスクリプト等で DB へのデータ書き込みが完了する前にプロセスが終了し、メタデータが消失するリスクがありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

TaskDBBaseflush(timeout) インターフェースを追加し、SQLiteTaskDB で実装します。

SQLiteTaskDB の実装では、「何もしない (No-op) 書き込みタスク」をエンキューし、そのタスクの完了イベントを待機するというアプローチを採用します。この flush メソッドを Spot.flush() から呼び出すことで、ストレージと DB の両方の完了を確実に待機させます。

Consequences / 決定

親: REQ003

子:

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

SQLite ライタースレッドの死活監視におけるポーリング間隔の維持

Context and Problem Statement / コンテキスト

SQLiteTaskDB は、専用のライタースレッドを用いて書き込みを直列化しています。メインスレッドが書き込み完了を待機する際(または flush 実行時)、ライタースレッドが予期せず死亡(セグメンテーションフォールトや深刻なエラー等)した場合に、メインスレッドが永久にハングアップするのを防ぐ必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

0.5 秒というポーリング間隔を維持し、ループ内で task.event.wait(timeout=0.5) とスレッドの生存確認を行う設計を継続します。

  1. 安全装置: ライタースレッドが異常終了しても、最大 0.5 秒以内に異常を検知してメインスレッドを解放できます。
  2. 正常系パフォーマンス: threading.Event.wait はイベントがセットされた瞬間に即座に復帰するため、正常な書き込み時には 0.5 秒という数値はレイテンシに一切影響しません。
  3. リソース効率: 間隔を適切に保つことで、不要なコンテキストスイッチや CPU 浪費を防ぎます。

Consequences / 決定

親: REQ003

子:

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

Delegate Database Lifecycle Management to Caller

Context and Problem Statement / コンテキスト

Spot クラスは TaskDBBase インスタンスを DI で受け取っているにもかかわらず、シャットダウン処理内で強制的に db.shutdown() を呼び出していました。これにより、複数の Spot インスタンスで 1 つの DB を共有する場合、一方の Spot が破棄されると他の Spot も DB にアクセスできなくなるという重大なバグが生じていました。これは DI の導入と、GC 時の強制破棄戦略が複雑に絡み合った結果の技術的負債でした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

Spot クラス内部から db.shutdown() の呼び出しを完全に削除します。リソース(DB インスタンス)を生成して注入した側が、そのライフサイクルの全責任を負うという原則を厳格に適用します。

Consequences / 決定

親: REQ003

子:

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

Thread-Safe SQLite Connection Closing and Lock Management

Context and Problem Statement / コンテキスト

beautyspotSQLiteTaskDB は、並行読み取り性能を高めるため、各スレッドごとに専用のコネクションを保持する設計となっています。シャットダウン時には WAL のチェックポイントを妨げないよう、これらの接続を一括で閉じようとしていましたが、以下の深刻な問題がありました。 1. check_same_thread の制約: 別スレッドから close() を呼ぶとエラーが発生する。 2. クラッシュの危険性: クエリ実行中に強制クローズされるとセグメンテーションフォールトを引き起こす。 3. リカバリ時のデッドロック: エラー発生時の再接続処理で、再帰的なロック取得によりハングする。 4. GC 時のブロック: ロック解放を待機する設計では、GC 時にメインスレッドがフリーズし、ADR-0045 の「GC 時は絶対にメインスレッドをブロックさせない」という原則に違反する。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

相反する要件を解決するため、以下の機構を導入します。

  1. リエントラントロック (threading.RLock) の導入: 同一スレッド内でのリカバリ処理によるデッドロックを防ぎます。
  2. 安全なクローズの許可: check_same_thread=False を指定し、別スレッドからのクローズを許可します。
  3. ノンブロッキング・クローズ機構 (Fail-fast Close):
  4. シャットダウンや GC 時の close() 呼び出しは、ロック取得を ノンブロッキング (blocking=False) で試行します。
  5. 他のスレッドがクエリを実行中でロックを取得できなかった場合、安全を最優先してクローズを諦め、Python の自然な GC 管理に委ねます。

Consequences / 決定

親: REQ019

子:

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

SQLite Write Task Cancellation Strategy

Context and Problem Statement / コンテキスト

db.py (SQLiteTaskDB) では、バックグラウンドでの書き込みを直列化するために専用の Writer スレッドとキューを使用しています。シャットダウン時 (shutdown(wait=True)) には、キューに積まれた全てのタスクの完了を待機しますが、タイムアウトが発生した場合の振る舞いが問題となります。特に、現在実行中 (RUNNING) の書き込みタスクを強制的にキャンセルすべきかどうかの設計判断が求められました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

実行中の書き込みタスク (RUNNING) は、タイムアウト時間を超過した場合でも強制的にキャンセルしない設計としました。

Rationale / 理由

SQLite のトランザクション実行中にスレッドを強制終了させたり、非同期的に接続を閉じたりすると、データベースファイルが破損する(Corruption)、あるいはWALファイルが不適切な状態になるリスクがあります。

キューの処理ロジックにおいて、すでにキューから取り出され実行状態に入ったタスクは「不可分なアトミック操作」と見なします。シャットダウンのタイムアウトは「新たなタスクの取り出し」を停止するためには機能しますが、実行中のI/O処理を途中で引き裂くことはしません。これにより、システムの安全性とデータ一貫性を最優先しています。

Consequences / 決定

親: REQ019

子: