本ドキュメントは、『beautyspot』のアーキテクチャ設計を定義する。
システムを構成する主要コンポーネントとその責務、およびコンポーネント間の相互作用を定義する。
graph TD
A[bs.Spot Factory] -->|DI/Composition| B[core.Spot]
B --> C[TaskDBBase]
B --> D[SerializerProtocol]
B --> E[BlobStorageBase]
B --> F[StoragePolicyProtocol]
B --> G[LimiterProtocol]
B --> H[LifecyclePolicy]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| core.Spot | キャッシュエンジン。キー生成→検索→実行→保存の制御 | mark(), cached_run() |
| TaskDBBase | メタデータ永続化。キャッシュキーによる検索と保存 | find_by_key(), insert() |
| SerializerProtocol | データのシリアライズ/デシリアライズ | pack(), unpack() |
| BlobStorageBase | 大規模データの外部ストレージ保存 | save(), load(), delete() |
| StoragePolicyProtocol | Blob保存の判定ポリシー | should_save_as_blob() |
sequenceDiagram
participant User as ユーザーコード
participant Spot as core.Spot
participant DB as TaskDB
participant Ser as Serializer
participant Blob as BlobStorage
User->>Spot: fn(args)
Spot->>DB: find_by_key(cache_key)
alt キャッシュヒット
DB-->>Spot: cached_data
Spot->>Ser: unpack(data)
else キャッシュミス
Spot->>Spot: fn(args) 実行
Spot->>Ser: pack(result)
Spot->>DB: insert(metadata)
opt Blob保存
Spot->>Blob: save(key, data)
end
end
Spot-->>User: result
| 技術領域 | 選定 | 理由 |
|---|---|---|
| メタデータDB | SQLite | ゼロ設定、組み込み可能、十分な性能 |
| シリアライズ | MessagePack | JSONより高速・コンパクト、バイナリ対応 |
| ストレージ | ローカルファイル / クラウド | 小規模はローカル、大規模は外部ストレージ等で拡張可能 |
graph TD
A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy]
B -->|バインド| C[bound_keygen]
C -->|引数別戦略| D[Strategy]
C -->|正規化| E[canonicalize]
C -->|ハッシュ計算| F[KeyGen.hash_items]
E --> F
| コンポーネント | 責務 | インターフェース |
|---|---|---|
KeyGenPolicy |
関数の引数に対するハッシュ化戦略(無視、ファイル内容等)の宣言的定義と保持。 | bind(func) |
Strategy |
引数ごとの処理方法(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT)の定義。 | - |
canonicalize |
Pythonオブジェクトを再帰的に正規化し、Msgpackで安定してシリアライズ可能な状態に変換。 | @singledispatch canonicalize(obj) |
KeyGen |
正規化されたリストからSHA-256ハッシュを生成。レガシーデフォルトのハッシュ生成も担う。 | hash_items(items), from_file_content(path) |
sequenceDiagram
participant Spot as core.Spot
participant KP as KeyGenPolicy
participant Sig as inspect.signature
participant Can as canonicalize
participant KG as KeyGen
Spot->>KP: bind(func)
KP->>Sig: signature(func)
Sig-->>KP: bound_keygen 生成
KP-->>Spot: bound_keygen
Spot->>Spot: bound_keygen(*args, **kwargs)
loop 各引数
alt Strategy.IGNORE
Spot->>Spot: スキップ
else Strategy.FILE_CONTENT
Spot->>KG: from_file_content(path)
else Strategy.PATH_STAT
Spot->>KG: from_path_stat(path)
else Strategy.DEFAULT
Spot->>Can: canonicalize(val)
Can-->>Spot: 正規化済みデータ
end
end
Spot->>KG: hash_items(items_to_hash)
KG-->>Spot: SHA-256 ハッシュ文字列 (キャッシュキー)
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 引数のバインド | inspect.signature |
argsとkwargsを定義順に正確にマッピングし、デフォルト値も適用するため |
| 正規化 | functools.singledispatch |
型ごとの正規化ロジックを拡張可能かつクリーンに実装するため |
| シリアライズ | msgpack |
高速でバイナリセーフであり、一貫したバイト列表現を得るため |
| ハッシュ関数 | SHA-256 | 衝突確率が極めて低く、暗号学的に安全かつ広くサポートされているため |
KeyGenPolicy を定義可能にし、柔軟なキャッシュ戦略をサポートする。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)は実装に依存しない。graph TD
A[core.Spot] -->|判定| B[StoragePolicyProtocol];
A -->|保存/読込| C[BlobStorageBase];
D[LocalStorage] --> C;
E[S3Storage] --> C;
A -->|メタデータ| F[TaskDBBase];
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| StoragePolicyProtocol | データのサイズ等に基づき、DB保存かBlob保存かを判定する | should_save_as_blob() |
| BlobStorageBase | 大規模データの外部ストレージ保存を抽象化する | save(), load(), delete() |
| LocalStorage | ローカルファイルシステムへのアトミックな保存と検証 | base_dir, os.replace |
| S3Storage | AWS S3互換オブジェクトストレージへの保存 | s3:// URI, boto3 |
sequenceDiagram
participant Spot as core.Spot
participant Policy as StoragePolicy
participant DB as TaskDB
participant Blob as BlobStorage
Spot->>Policy: should_save_as_blob(data)
alt Policy: True (Blob保存)
Spot->>Blob: save(key, data)
Blob-->>Spot: location (path/URI)
Spot->>DB: insert(metadata, storage_type=FILE, blob_key=location)
else Policy: False (Inline保存)
Spot->>DB: insert(metadata, storage_type=DIRECT_BLOB, result_data=data)
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 保存判定 | 閾値ベース (Threshold) | DBの肥大化を防ぎつつ、小容量データのI/Oを最適化 |
| アトミック性 | 一時ファイル + Rename | 保存中のクラッシュや並行書き込みによる破損を完全に防止 |
| クラウド対応 | Boto3 (S3) | 業界標準のプロトコルによる高い互換性とスケーラビリティ |
安全性: LocalStorage では is_relative_to によるパストラバーサル防止を徹底
信頼性: 保存時は fsync を実行し、OSレベルでのデータ到達を確認
効率性: S3 保存時は upload_fileobj を使用し、5GB超のマルチパートアップロードに自動対応
graph TD
A[MsgpackSerializer] -->|Registry| B[TypeRegistryProtocol]
A -->|LRU Cache| C[Thread-Local Storage]
A -->|Core| D[msgpack-python]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| MsgpackSerializer | MessagePackベースの高速・コンパクトなバイナリ変換 | dumps(), loads() |
| TypeRegistryProtocol | ユーザー定義型(ExtType)のエンコーダ・デコーダ登録 | register() |
| Thread-Local Cache | free-threading環境でのロック競合を回避するMRO解決キャッシュ | OrderedDict (LRU) |
sequenceDiagram
participant User as ユーザーコード
participant Ser as MsgpackSerializer
participant Reg as TypeRegistry
participant MP as msgpack
User->>Ser: dumps(obj)
Ser->>Ser: 型チェック & MRO解決
opt カスタム型
Ser->>Reg: エンコーダ取得
Reg-->>Ser: encoder_fn
Ser->>MP: ExtType(code, encoder_fn(obj))
end
MP-->>Ser: binary
Ser-->>User: bytes
| 技術領域 | 選定 | 理由 |
|---|---|---|
| フォーマット | MessagePack | JSONより高速・コンパクト。バイナリをネイティブにサポート |
| スレッド安全 | Copy-on-Write (CoW) | 読み取りパスからロックを排除し、マルチスレッド性能を最大化 |
| 型拡張 | ExtType (0-127) | カスタム型を1バイトのオーバーヘッドで表現可能 |
ExtCode 未登録エラーを検知し、互換性問題を明示的に報告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 | 強制終了時も、可能な限り保留中の書き込みを完了させる |
透過性: 保存失敗時もユーザーコードの例外にはせず、コールバック経由で通知
一貫性: flush() により、クリティカルな処理の直後での保存完了を保証
安全性: シャットダウンシーケンス中に新規タスクを受け付けない排他制御を導入
graph TD
A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]
B -->|ルールリスト| C[Rule]
C -->|パターン照合| D[fnmatch]
B -->|パース| E[Retention]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| LifecyclePolicy | 複数のルールを管理し、関数名に最適な有効期限を決定する | resolve_with_fallback() |
| Rule | 1つのパターンとそれに対応する保持期間のペア | match(func_name) |
| Retention | 多様な時間表現(文字列/数値)を秒数に正規化する | parse_retention() |
sequenceDiagram
participant Spot as core.Spot
participant Policy as LifecyclePolicy
participant DB as TaskDB
Spot->>Policy: resolve_with_fallback("module.func")
Policy->>Policy: ルール順次照合 (fnmatch)
Policy-->>Spot: expires_at (timestamp)
Spot->>DB: save(..., expires_at)
Note over DB: get() 時に現在時刻と比較判定
| 技術領域 | 選定 | 理由 |
|---|---|---|
| パターンマッチ | fnmatch (Glob) | 正規表現よりも直感的で、開発者にとって馴染みのある指定方法 |
| 保持期間指定 | 文字列パーサー ("30d"等) | 設定ファイルやデコレータでの可読性を向上 |
| 判定タイミング | 遅延判定 (Lazy) | 書き込み・読み込み時のオーバーヘッドを最小化 |
Retention.FOREVER 等の定数により、グローバルポリシーの個別上書きに対応graph TD
A["@spot.consume"] -->|宣言的適用| B[LimiterProtocol]
C[core.Spot] -->|DI| B
D[TokenBucket] --> B
D -->|アルゴリズム| E[GCRA]
D -->|時刻同期| F[monotonic clock]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| TokenBucket | GCRAアルゴリズムによるスムースなトラフィック制御 | consume(), consume_async() |
| @spot.consume | 関数実行前のトークン消費を透過的に行うデコレータ | cost (int or callable) |
| LimiterProtocol | レートリミッターの差し替えを可能にするインターフェース | LimiterProtocol |
sequenceDiagram
participant User as ユーザーコード
participant Dec as @spot.consume
participant Bucket as TokenBucket
User->>Dec: call func()
Dec->>Bucket: consume(cost)
Bucket->>Bucket: 次回実行許可時刻の計算 (GCRA)
alt 許可
Bucket-->>Dec: OK
Dec->>User: execute func()
else 拒否 (Over rate)
Bucket-->>Dec: raise ValueError/RateLimitError
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| アルゴリズム | GCRA (Generic Cell Rate Algorithm) | 固定ウィンドウと違い「スムースな」流量制御が可能 |
| 待機制御 | asyncio.sleep / time.sleep | 同期・非同期の両方のコンテキストで正確なスロットリングを実現 |
| コスト計算 | ダイナミックコスト | 引数に応じて消費トークン数を動的に変更可能 |
正確性: time.monotonic() を使用し、システム時刻の変更(NTP同期等)の影響を受けない
バースト制御: max_burst 設定により、アイドル後の過度なバーストを防止
スレッド安全: threading.Lock により、マルチスレッド環境での二重消費を防止
graph TD
A[core.Spot] -->|イベント通知| B[HookBase]
C[ThreadSafeHookBase] --> B
B -->|コンテキスト| D[Context Objects]
E[PreExecuteContext] --> D
F[CacheHitContext] --> D
G[CacheMissContext] --> D
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| HookBase | 実行ライフサイクルの各段階で呼び出される抽象基底クラス | pre_execute, on_cache_hit, on_cache_miss |
| ThreadSafeHookBase | RLockを用いて各フックメソッドの実行を直列化する | __init_subclass__ による自動ラップ |
| Context Objects | フックに渡される実行時の引数、結果、例外、メタデータのコンテナ | ctx.args, ctx.result 等 |
sequenceDiagram
participant Spot as core.Spot
participant Hook as HookBase
participant Fn as Target Function
Spot->>Hook: pre_execute(ctx)
alt キャッシュヒット
Spot->>Hook: on_cache_hit(ctx)
else キャッシュミス
Spot->>Fn: execute()
Fn-->>Spot: result
Spot->>Hook: on_cache_miss(ctx)
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| パターン | オブザーバー | コアロジックを汚さずに、ログ出力や統計取得等の横断的関心を分離 |
| スレッド安全 | 自動装飾 (Decorator) | ユーザーが意識せずに、サブクラス化するだけで安全なフックを実装可能 |
| 実行制御 | 準同期実行 | フック内の例外をキャッチしログ出力に留めることで、主処理を保護 |
透過性: フックの実行失敗(例外)は主処理の結果に影響を与えない
柔軟性: 関数ごとに異なるフックリストを hooks=[...] で指定可能
パフォーマンス: フック未登録時のオーバーヘッドを最小化するガード条件を実装
graph TD
A[MaintenanceService] -->|操作集約| B[TaskDBBase]
A -->|操作集約| C[BlobStorageBase]
A -->|自動実行| D[Probabilistic Eviction]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| MaintenanceService | DBとストレージを跨ぐクリーンアップ操作のファサード | clean_garbage(), prune() |
| TaskDBBase | 参照カウントや期限切れレコードの提供 | get_blob_refs(), delete_expired() |
| BlobStorageBase | 物理ファイルの列挙と削除 | list_keys(), delete() |
sequenceDiagram
participant Service as MaintenanceService
participant DB as TaskDB
participant Storage as BlobStorage
Service->>DB: get_blob_refs()
DB-->>Service: 有効な参照リスト (Whitelist)
Service->>Storage: list_keys()
Storage-->>Service: 全ファイルリスト
Service->>Service: 差分抽出 (孤立ファイルの特定)
Service->>Storage: delete(orphan_keys)
Note over Service: 猶予期間 (grace_period) を考慮して削除
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 削除ポリシー | ホワイトリスト方式 | DBに記録がないBlobを削除対象とすることで、データの整合性を担保 |
| 競合防止 | 更新時刻確認 (mtime) | 保存中のファイルを誤って消さないよう、一定時間経過した孤立のみ削除 |
| 実行頻度 | 確率的オートエビクション | 書き込み時に低確率でGCをキックし、手動メンテなしでの健康度を維持 |
graph TD
A[beautyspot CLI] -->|Command| B[Typer App]
B -->|Business Logic| C[MaintenanceService]
B -->|Output| D[Rich Console]
B -->|Dashboard| E[Dashboard App]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| Typer App | コマンドライン引数のパースとサブコマンドのディスパッチ | main(), gc(), list() |
| MaintenanceService | 実際のキャッシュ管理ロジックの実行 | clean_garbage(), clear() |
| Rich Console | ターミナルでの視覚的に分かりやすいテーブルやパネルの描画 | Console, Table |
| Dashboard App | キャッシュ状態をインタラクティブに閲覧するためのTUI | dashboard.py |
sequenceDiagram
participant User as ユーザー
participant CLI as CLI App
participant Service as MaintenanceService
participant Output as Rich Console
User->>CLI: beautyspot gc --name my-project
CLI->>Service: clean_garbage()
Service-->>CLI: result (removed count, etc.)
CLI->>Output: print summary table
Output-->>User: formatted output
| 技術領域 | 選定 | 理由 |
|---|---|---|
| フレームワーク | Typer | 型ヒントに基づいた堅牢なコマンドラインインターフェースを迅速に構築 |
| 出力装飾 | Rich | プログレスバーやステータス表示により、長時間処理の進捗を可視化 |
| 連携 | Factory DI | カレントディレクトリや設定から自動的に Spot インスタンスを組み立て |
clear等)には対話的な確認プロンプトを表示graph TD
A[bs.Spot Factory] -->|構築| B[core.Spot]
A -->|DI| C[TaskDBBase]
A -->|DI| D[BlobStorageBase]
A -->|DI| E[SerializerProtocol]
A -->|DI| F[StoragePolicyProtocol]
A -->|DI| G[LimiterProtocol]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| bs.Spot (Factory) | 環境(名前、パス等)に応じたデフォルトコンポーネントの選定と組み立て | bs.Spot() |
| core.Spot | 注入された依存関係を使用して、キャッシュロジックをオーケストレーションする | mark(), cached_run() |
| Protocols / ABCs | 各コンポーネントが満たすべき契約(インターフェース)の定義 | SerializerProtocol 等 |
| 技術領域 | 選定 | 理由 |
|---|---|---|
| パターン | Constructor Injection | 依存関係を明示的に渡し、テスト時のMock差し替えを容易にする |
| インターフェース | Protocol (Duck Typing) | 厳密な継承を強制せず、構造的部分型によりサードパーティ実装を受け入れ |
| デフォルト設定 | 規約より構成 (CoC) | .beautyspot/ ディレクトリを基点とした標準パスを自動生成 |
core.Spot は具体的なクラス名(SQLite等)を知らず、Protocolのみに依存する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による消失を防止