Gitで記事を管理し、画像はCloudflare R2などのクラウドストレージに置く運用を続けていると、必ず一つの問題に直面します。それは「どこからも参照されていない画像(Orphaned Images)」の蓄積です。

記事のslug(URL)を変更したり、下書きを削除したりした際、ストレージ側の画像だけが取り残されていく。この「デジタルなゴミ」を自動で検出し、クリーンに保つための仕組みを構築しました。

なぜ「全体走査」が必要か

個別の記事をパブリッシュする際に、その記事に関連するフォルダ内を掃除する仕組み(Incremental GC)は既に導入していました。しかし、それでは不十分なケースがあります。

  • slugの変更: フォルダ名そのものが変わってしまうと、古いフォルダは掃除の対象外となり、そのまま放置されます。
  • 記事の削除: Markdownファイル自体を消してしまうと、それに対応するストレージ側の資産を追いかけるきっかけを失います。

これらを解決するには、「今ある全ての記事」が必要としている資産と、「ストレージにある全ての資産」を照らし合わせる、グローバルな照合プロセスが必要になります。

照合のアルゴリズム

実装した orphan_r2_cleaner.py のロジックは非常にシンプルかつ確実です。

  1. Markdownの全走査: src/content/blog/ 配下の全ファイルを読み込み、正規表現(Regex)を使ってR2の公開URLとして使われているパス(Key)を全て抽出。これを「生存リスト」とします。
  2. R2の全走査: Boto3(AWS SDK for Python)を使用して、R2バケット内の images/ プレフィックス配下にある全てのオブジェクトを取得。これを「現存リスト」とします。
  3. 差分の抽出: 「現存リスト」にあって「生存リスト」にないものを、削除対象の「孤立資産(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)を運用するなら、こうした「掃除の道具」もセットで設計することが、長期的な持続可能性に繋がります。