Gitで記事を管理し、画像はCloudflare R2などのクラウドストレージに置く運用を続けていると、必ず一つの問題に直面します。それは「どこからも参照されていない画像(Orphaned Images)」の蓄積です。
記事のslug(URL)を変更したり、下書きを削除したりした際、ストレージ側の画像だけが取り残されていく。この「デジタルなゴミ」を自動で検出し、クリーンに保つための仕組みを構築しました。
なぜ「全体走査」が必要か
個別の記事をパブリッシュする際に、その記事に関連するフォルダ内を掃除する仕組み(Incremental GC)は既に導入していました。しかし、それでは不十分なケースがあります。
- slugの変更: フォルダ名そのものが変わってしまうと、古いフォルダは掃除の対象外となり、そのまま放置されます。
- 記事の削除: Markdownファイル自体を消してしまうと、それに対応するストレージ側の資産を追いかけるきっかけを失います。
これらを解決するには、「今ある全ての記事」が必要としている資産と、「ストレージにある全ての資産」を照らし合わせる、グローバルな照合プロセスが必要になります。
照合のアルゴリズム
実装した orphan_r2_cleaner.py のロジックは非常にシンプルかつ確実です。
- Markdownの全走査:
src/content/blog/配下の全ファイルを読み込み、正規表現(Regex)を使ってR2の公開URLとして使われているパス(Key)を全て抽出。これを「生存リスト」とします。 - R2の全走査: Boto3(AWS SDK for Python)を使用して、R2バケット内の
images/プレフィックス配下にある全てのオブジェクトを取得。これを「現存リスト」とします。 - 差分の抽出: 「現存リスト」にあって「生存リスト」にないものを、削除対象の「孤立資産(Orphans)」として特定します。
安全性の担保:y/N プロンプト
自動化は強力ですが、誤検知による資産喪失は避けなければなりません。
このスクリプトでは、削除を実行する前に検知された孤立資産の名前を一覧表示し、ユーザーに y/N での確認を求めるように設計しました。
[ALERT] Found 3 orphaned images in R2:
- images/2026/01/old-slug/old-hero.png
- images/garbage/test.txt
Do you want to delete these 3 objects? (y/N):
この仕組みによって、手動での確信を持ってから「ゴミ箱を空にする」ことができます。
結論:クリーンな基盤が自由な執筆を支える
「環境を汚したくない」という心理的なブレーキは、執筆やリファクタリングのスピードを鈍らせます。
「いつでも一発で掃除できる」という道具があるだけで、slugの変更や構成のやり直しを躊躇なく行えるようになります。自作のCMS(Content Management System)を運用するなら、こうした「掃除の道具」もセットで設計することが、長期的な持続可能性に繋がります。