✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。IDクリックで詳細へ)
| グループ | REQ | ARCH | SPEC | TST | IMPL | ADR |
|---|---|---|---|---|---|---|
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特に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... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特に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... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 / コンテキス... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 / コンテキスト ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特に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 / コン... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 / コンテキス... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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 / コンテキスト ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力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... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特に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% | — |
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。
抽象インターフェースにより、DBやストレージなどのバックエンド実装を差し替え可能であること。
親: —
子: ADR007, ADR010, ADR043, ADR045, ADR048, ADR050, ARCH003
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLiteの書き込みスレッドがスタックした場合でも、フェイルファストによって制御を戻せること。
親: —
子: ADR051, ADR052, ARCH003, TST007
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等に容易に差し替え可能 |
TaskDBCore, Maintenable) で定義され、利用側(core.Spotや_CacheManager)は実装に依存しない。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: ...
_WriteTask キューに投入され、専用のバックグラウンドスレッドで直列に実行される。これにより SQLite の database is locked エラーを回避する。PRAGMA query_only = ON 状態で並行して実行される。tasks テーブルの存在を確認し、必要に応じて自動的に ALTER TABLE によるカラム追加(マイグレーション)を行う。timeout 秒以内に完了する必要がある。timeout を超えた場合、タスクはキューから破棄(キャンセル)され、呼び出し元に TimeoutError を返す。timeout を超えた場合、sqlite3.Connection.interrupt() を用いて実行中のクエリを中断し、呼び出し元に TimeoutError を返す。これにより、ライタースレッドの恒久的なハングを防ぎ、システムの可用性を維持する。| 設定 | デフォルト | 説明 |
|---|---|---|
journal_mode |
WAL |
書き込みと読み取りの並行性を高める設定 |
synchronous |
NORMAL |
パフォーマンスと耐久性のバランス設定 |
timeout |
30.0 |
データベースロック待機およびクエリ実行のタイムアウト閾値 |
TimeoutError を送出する。sqlite3.Error をキャッチし、適切にラップして再送出する。:memory: 指定時は、プロセスの生存期間中のみキャッシュが保持される。親: ARCH003
@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 を満たしつつ、メンテナンスのデフォルト実装を提供
| メソッド | 役割 |
|---|---|
get_blob_refs() |
全レコードの blob_key を列挙する(GC用) |
delete_expired() |
expires_at を経過したレコードを一括削除する |
get_history() |
実行履歴(統計)を取得する |
NotImplementedError を送出する代わりに、runtime_checkable による事前チェックを推奨する。TaskDBMaintenable 構成プロトコルとして扱う。親: ARCH003
SQLiteTaskDB はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす。WALモード・専用ライタースレッドという独自アーキテクチャの信頼性を保証する。
references: tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
子: —
TaskDBCore / TaskDBBase のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL 等)の差し替えを可能にする拡張ポイントである。インターフェース契約が不明確だと、カスタム実装が実行時に予期しないエラーを起こす。
TaskDBCore の必須メソッド(save, load, delete 等)を実装したクラスが isinstance チェックをパスすることTaskDBBase がメンテナンス系メソッド(prune, stats 等)のデフォルト実装を提供し、サブクラスが必須メソッドのみの実装で動作することbs.Spot(db=CustomDB()) でプロトコル準拠のカスタムDBが正常に動作することreferences: tests/unit/test_db_writer_queue.py
親: SPEC008
子: —
SQLiteTaskDB クラスが中核。Writer Thread + Reader Threads パターンで実装。
単一の _writer_thread(デーモン)が _write_queue から書き込みタスクを取り出して直列処理し、読み取りは threading.local() のスレッドローカル接続で並行実行する。
WAL モードにより読み取りと書き込みの並行性を確保している。
_enqueue_write 内で task.event.wait(timeout=0.5) をループさせ、self.timeout を超えた場合にフェイルファスト処理を行う。task.try_cancel() でキュー内キャンセル。self._writer_conn.interrupt() を呼び出して実行中の SQLite クエリを強制終了させ、呼び出し元に TimeoutError を返す。references: src/beautyspot/db.py
親: SPEC007
子: —
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 バックエンドでもエラーにならない設計。
Flushable.flush(timeout) のデフォルトは no-op。SQLiteTaskDB のみがキュー同期を実装Shutdownable.shutdown(wait) のデフォルトは no-op。リソースを持たない軽量実装に配慮isinstance(db, Maintenable) で機能の有無を判定できるreferences: src/beautyspot/db.py
親: SPEC008
子: —
生成AIタスクの出力は、テキストだけでなく、画像、構造化データ、ダイアグラム(Mermaid, Graphviz/DOT, HTML)など多岐にわたります。
現状のデータベーススキーマ(result_type = FILE | DIRECT)は「データの保存形式」しか保持しておらず、「データの意味的種類(Semantic Type)」が不明であるため、ダッシュボードでの復元時に適切な可視化(レンダリング)ができません。
また、beautyspot はライブラリとしてユーザーの手元で動作するため、複雑なマイグレーション手順や重量級の依存関係(Alembicなど)を強制することはUXを損なうという課題があります。
content_type カラムを追加し、開発者が @task で明示的に指定できるようにする。あわせて自動マイグレーション機能を実装する。Chosen option: Option 2.
tasks テーブルに content_type (TEXT) カラムを追加する。Project 初期化時(_init_db)に PRAGMA table_info でカラムの存在を確認し、不足していれば標準ライブラリのみで ALTER TABLE を自動実行する。src/beautyspot/types.py を新設し、ContentType クラスで定数を管理する。ContentType.GRAPHVIZ (text/vnd.graphviz)ContentType.MERMAID (text/vnd.mermaid)@project.task デコレータに content_type 引数を追加し、開発者が戻り値の型を宣言できるようにする。graphviz ライブラリと st.graphviz_chart を使用する。st.components.v1.html を使用して Mermaid.js (CDN) を注入し、ブラウザ側でレンダリングする。pyproject.toml に graphviz>=0.20.1 を追加する。dot コマンド(OSレベルのパッケージ)がインストールされている必要がある。親: REQ003
子: —
ADR-0007 で MsgpackSerializer を導入しましたが、SQLiteへの保存方式について以下の課題が残っていました。
TEXT カラム(JSON)ではバイナリデータを扱えず、Numpy配列などが保存できない。TEXT カラムに保存するために Msgpack を Base64 エンコードする案がありましたが、データサイズが約33%増加し、CPUコストもかかる。TEXT カラムに保存する。BLOB カラムを追加し、Msgpack バイナリをそのまま保存する。Chosen option: Option 2.
tasks テーブルに、バイナリデータをそのまま格納するための result_data (BLOB) カラムを追加します。
result_value (TEXT) カラムは、ファイルパスやレガシーなJSONデータの保持用に継続利用する。Project 初期化時に ALTER TABLE で自動的に行われる。データの保存先に関わらず、常に MsgpackSerializer を通過させます。
save_blob=False (Direct Mode):result_data (BLOB) カラム にそのまま保存する。result_type は DIRECT_BLOB とする。save_blob=True (Blob Mode):.bin)として保存。result_value (TEXT) カラム に保存する。result_type は FILE とする。save_blob=False であっても、閾値(デフォルト: 1MB)を超えるデータが渡された場合は、警告ログ (WARNING) を出力して save_blob=True の利用を促します。
result_value カラムを維持するため、旧バージョンのキャッシュデータも読み込み可能。sqlite3 コマンドラインツール等で SELECT した際、中身がバイナリのため視認できない(ダッシュボード等のツールが必要)。親: REQ003
子: —
beautyspot は wait=False のバックグラウンド保存や async 経路で、
複数スレッドから同時に SQLite へ書き込みを行う可能性がある。
WAL モードを有効化していても SQLite の書き込みは単一ライター制約があり、
高並行時に database is locked が発生し得る。
一方で、アプリケーションの関数実行自体は失敗させたくない。 また、シャットダウン時には必ず書き込みをフラッシュしたい。
shutdown(wait=True) で書き込みを確実にフラッシュする。save/delete を直列化(単一接続は持たない)。Chosen option: Option 4.
SQLiteTaskDB に writer スレッドとキューを実装し、
書き込み系操作を単一接続で直列化する。
シャットダウン時は shutdown(wait=True) によりキューを drain し、
未処理の書き込みが残らないようにする。
さらに、保存失敗は関数実行を失敗させず、
ログと on_background_error で通知する。
SQLiteTaskDB 内部に queue.Queue と writer スレッドを保持。_enqueue_write() 経由でシリアライズ。TaskDBBase.shutdown() を追加し、SQLiteTaskDB.shutdown(wait=True) でフラッシュ。Spot.shutdown(wait=True) から db.shutdown(wait=True) を呼び出す。wait=True 保存時の失敗も例外として外に出さず、ログとコールバックで通知。database is locked の発生率が下がる。shutdown(wait=True) で書き込みを確実にフラッシュできる。保存失敗がアプリケーションの関数実行に影響しない。
Negative:
shutdown(wait=True) がブロックし続ける。本 ADR はプロセス内の直列化を対象とする。複数プロセスが同一 SQLite を共有する 運用では、依然としてロック競合が起こり得るため注意する。
親: REQ003
子: —
SQLiteTaskDB は、ライタースレッドとキューを用いた非同期書き込みを行っています。しかし、メインプロセスの終了時や Spot.flush() 実行時に、ストレージへの I/O は待機するものの、DB への書き込みキューのフラッシュ(全タスクの完了)を明示的に待機する仕組みがありませんでした。これにより、短命なスクリプト等で DB へのデータ書き込みが完了する前にプロセスが終了し、メタデータが消失するリスクがありました。
flush() 実行時に、全ての DB 書き込みタスクが完了していることを保証すること。TaskDBBase に flush(timeout) を追加する。SQLiteTaskDB では「No-op タスク」をキューに投入し、その完了を待機する方式を採用する。Chosen option: Option 2.
TaskDBBase に flush(timeout) インターフェースを追加し、SQLiteTaskDB で実装します。
SQLiteTaskDB の実装では、「何もしない (No-op) 書き込みタスク」をエンキューし、そのタスクの完了イベントを待機するというアプローチを採用します。この flush メソッドを Spot.flush() から呼び出すことで、ストレージと DB の両方の完了を確実に待機させます。
flush() 呼び出し時に、キューに積まれた既存タスクの量に応じて待機時間が発生する。timeout を超えた場合の挙動(ログ出力等)を適切に定義・管理する必要がある。親: REQ003
子: —
SQLiteTaskDB は、専用のライタースレッドを用いて書き込みを直列化しています。メインスレッドが書き込み完了を待機する際(または flush 実行時)、ライタースレッドが予期せず死亡(セグメンテーションフォールトや深刻なエラー等)した場合に、メインスレッドが永久にハングアップするのを防ぐ必要があります。
is_alive())を組み合わせる。Chosen option: Option 3.
0.5 秒というポーリング間隔を維持し、ループ内で task.event.wait(timeout=0.5) とスレッドの生存確認を行う設計を継続します。
threading.Event.wait はイベントがセットされた瞬間に即座に復帰するため、正常な書き込み時には 0.5 秒という数値はレイテンシに一切影響しません。親: REQ003
子: —
Spot クラスは TaskDBBase インスタンスを DI で受け取っているにもかかわらず、シャットダウン処理内で強制的に db.shutdown() を呼び出していました。これにより、複数の Spot インスタンスで 1 つの DB を共有する場合、一方の Spot が破棄されると他の Spot も DB にアクセスできなくなるという重大なバグが生じていました。これは DI の導入と、GC 時の強制破棄戦略が複雑に絡み合った結果の技術的負債でした。
Spot インスタンス間で単一の DB インスタンスを安全に共有できるようにすること。Spot が引き続き DB のシャットダウンを担当する。インスタンス共有時にクラッシュするリスクがある。Spot クラスから db.shutdown() の呼び出しを削除し、DB を生成・注入した側の責任とする。Chosen option: Option 2.
Spot クラス内部から db.shutdown() の呼び出しを完全に削除します。リソース(DB インスタンス)を生成して注入した側が、そのライフサイクルの全責任を負うという原則を厳格に適用します。
Spot インスタンスでの DB 共有が安全に行えるようになり、関心の分離が明確になった。親: REQ003
子: —
beautyspot の SQLiteTaskDB は、並行読み取り性能を高めるため、各スレッドごとに専用のコネクションを保持する設計となっています。シャットダウン時には WAL のチェックポイントを妨げないよう、これらの接続を一括で閉じようとしていましたが、以下の深刻な問題がありました。
1. check_same_thread の制約: 別スレッドから close() を呼ぶとエラーが発生する。
2. クラッシュの危険性: クエリ実行中に強制クローズされるとセグメンテーションフォールトを引き起こす。
3. リカバリ時のデッドロック: エラー発生時の再接続処理で、再帰的なロック取得によりハングする。
4. GC 時のブロック: ロック解放を待機する設計では、GC 時にメインスレッドがフリーズし、ADR-0045 の「GC 時は絶対にメインスレッドをブロックさせない」という原則に違反する。
RLock) とノンブロッキングクローズ (blocking=False) を組み合わせたフェイルファストなクリーンアップ。Chosen option: Option 2.
相反する要件を解決するため、以下の機構を導入します。
threading.RLock) の導入: 同一スレッド内でのリカバリ処理によるデッドロックを防ぎます。check_same_thread=False を指定し、別スレッドからのクローズを許可します。close() 呼び出しは、ロック取得を ノンブロッキング (blocking=False) で試行します。親: REQ019
子: —
db.py (SQLiteTaskDB) では、バックグラウンドでの書き込みを直列化するために専用の Writer スレッドとキューを使用しています。シャットダウン時 (shutdown(wait=True)) には、キューに積まれた全てのタスクの完了を待機しますが、タイムアウトが発生した場合の振る舞いが問題となります。特に、現在実行中 (RUNNING) の書き込みタスクを強制的にキャンセルすべきかどうかの設計判断が求められました。
Chosen option: Option 2.
実行中の書き込みタスク (RUNNING) は、タイムアウト時間を超過した場合でも強制的にキャンセルしない設計としました。
SQLite のトランザクション実行中にスレッドを強制終了させたり、非同期的に接続を閉じたりすると、データベースファイルが破損する(Corruption)、あるいはWALファイルが不適切な状態になるリスクがあります。
キューの処理ロジックにおいて、すでにキューから取り出され実行状態に入ったタスクは「不可分なアトミック操作」と見なします。シャットダウンのタイムアウトは「新たなタスクの取り出し」を停止するためには機能しますが、実行中のI/O処理を途中で引き裂くことはしません。これにより、システムの安全性とデータ一貫性を最優先しています。
親: REQ019
子: —