nano-cms ドキュメントは株式会社ケイズ・ソフトウェアが運営しています。
nano-cms技術ドキュメント
← ドキュメント一覧へ

なぜ 10 年運用しても重くならないのか ― 全サブシステムを自己剪定にする設計

ソフトウェアは、放っておくと太ります。DB はレコードを貯め、git はコミットを貯め、キャッシュは古いファイルを貯め、ログはディスクを埋めていく。多くのシステムはこれに「掃除機能」で後から対抗します ―― あとで足す、運用が手で回す、そして忘れたころに深夜にディスクが満杯になる。@nano-cms/core は、これを別のやり方で扱っています。古いものを捨てる仕事を、あとづけの機能ではなく、各サブシステムの既定の力学にしたのです。

結果として何が起きるか。システムの大きさが「これまで何回操作したか(累積)」ではなく、「いま何個の仕事が生きているか(活動)」だけで決まります。開いている下書き、動いている予約、直近の版 ―― それだけが土台の大きさを決め、過去の堆積物は各サブシステムが自分で剪定する。だから 1 年運用しても 10 年運用しても、土台のオーバーヘッドはだいたい平らなままです。数えてみると、こうした自己剪定の仕組みが独立に 12 個ありました。ここでは、その設計を見ていきます。

先に正直なことを書いておきます。「何も増えない」わけではありません。増えないのは堆積物(オーバーヘッド)のほうで、あなたが公開し続けるコンテンツそのものは、当然それに比例して増えます。これから話すのは「本体」ではなく、「本体のまわりに溜まる副産物」を平らに保つ設計です。本体は残し、まわりのしっぽだけを刈る ―― そこを混同すると誇大広告になるので、最初に切り分けておきます。

図1: 素朴なやり方では履歴・キャッシュ・ログ・死んだ作業が累積で増え続け、nano-cms はオーバーヘッドを平らに保ち、増えるのは公開を選んだ本体だけ、という比較。
図1: 素朴なやり方では履歴・キャッシュ・ログ・死んだ作業が累積で増え続ける(左)。nano-cms はオーバーヘッドを平らに保ち、増えるのは「公開し続けることを選んだ本体」だけにする(右)。

剪定の対象は性質で 3 つに分けられます。履歴(古い版・古いコミット)、派生物(作り直せるキャッシュや一時ファイル)、ライフサイクル(使われなくなった作業そのもの)。順に見ていきます。

履歴 ―― 最新だけ残して、古い版とコミットは捨てる

編集を重ねれば版が増えます。記事を 200 回直せば、素朴には版が 200 個 DB に積まれる。これを 記事・カテゴリ・共通パーツのそれぞれについて、公開済みの最新 N 件だけ残して、それより古い版を物理削除します。このサイトでは N = 30。31 個目から先の古い版は毎日の片付けで消えます。

大事なのは、消えるのが古い「版」だけで、いま公開中の版は必ず残ることです。残す順は公開日時の新しい順なので、現在公開している版は常に先頭に入り、削除対象になりません。失うのは「30 個より前まで巻き戻す」能力だけ。日々の編集に 30 段のやり直しは要るが、200 段は要らない ―― そこに線を引いています。

もう一段下、git のコミット履歴も同じ考えで畳みます。公開はブランチを main にマージして進むので、放っておけばコミットが延々と伸びます。月に一度のデータ最適化のとき、main の履歴を「いまアクティブな予約が必要とする、いちばん古い分岐点」まで切り詰めます。切る基準は日数でも件数でもなく、生きている予約ブランチです。予約が 1 本も無ければ、main の先頭まで切り詰める(過去コミットは全部不要)。

ここで効いているのは、git replace --graftコミットのつながりの見え方を変えるだけで、いまのファイルには指一本触れない、という性質です。現在の全ファイルの中身(main の先頭ツリー)は完全に保持され、到達できなくなった古いコミットだけが、次回の git gc --prune=now でまとめて回収されます。失うのは「ずっと昔のコミットまで遡って差分を見る」能力で、現在のサイトの中身ではありません。

派生物 ―― 作り直せるものは、ためずに捨てる

キャッシュや一時ファイルは、いつでも作り直せるのが本質です。だから、ためらわず捨てられます。別ページで扱った予約プレビューのキャッシュが、この考え方の代表例です。キャッシュの鍵を「計算結果を決める材料そのもの」から作るので、予約が変われば鍵も変わり、古い結果は誰からも参照されなくなって自然に外れます。「古いキャッシュを探して消す」無効化処理を、そもそも持っていません(→ 予約プレビューのキャッシュ設計)。

では参照されなくなった鍵のファイルが永遠に溜まるのかというと、溜まりません。月次のデータ最適化の直後に、「最後に書き出してから 30 日たったもの」と「合計が 100 MiB を超えたぶんを古いものから」まとめて回収します。万一まだ使う結果をうっかり消しても、次の表示で作り直すだけ(計測では約 130 ミリ秒)。作り直せる前提が、後始末を軽くしています

同じ発想が、ほかの一時物にも一律に効いています。中断したアップロードの断片、日付つきのファイルログ、イベント・ログイン・操作の記録 ―― いずれも経過時間や保持日数で自動的に痩せます(たとえばイベントログは既定 60 日)。どれも「あれば便利だが、古くなれば価値が消える」もので、価値の賞味期限を過ぎたら捨てる、という同じ規則を当てています。

ライフサイクル ―― 使われない作業は、ひとりでに崩れて消える

いちばん溜まりやすいのは、実は「やりかけて放置された作業」です。書き始めて公開しなかった下書き、確認用に切ったプレビュー。これらは放っておくと、ブランチも作業ディレクトリも DB の行も残り続けます。nano-cms はここを多段の崩壊として設計しました。

  • 90 日触られなかった下書きは、自動でアーカイブ状態に落ちる(中身はまだ残る)。
  • アーカイブされてから 200 日たつと、git の作業ブランチとプレビュー作業ディレクトリを削除する。
  • ブランチが消えてから 30 日たつと、DB の行そのものを物理削除する。
図2: 放置された作業が90日でアーカイブ、200日でブランチ削除、30日でDB行削除、と多段で消える図。公開された本体はこの崩壊の対象外。
図2: 放置された作業の多段崩壊(このサイトでは 90 日 → 200 日 → 30 日)。復元の余地を残しつつ最終的にはゼロにする。公開された本体はこの崩壊の対象外。

段を分けてあるのは、復元の余地を残しながら、最終的にはゼロにするためです。アーカイブ直後なら戻せる。ブランチが消えても、しばらくは記録が残る。だが誰も手を出さないまま時間が過ぎれば、最後はきれいに消える。「公開して役目を終えた下書き」も「書きかけて忘れられた下書き」も、放っておけば自分で土に還る、というわけです。公開された本体(main に取り込まれた中身)はこの崩壊の対象ではありません ―― 崩れるのは作業の「入れ物」であって、成果物ではない。

同じく、検証用の preprod 出力は日次のスキャンで、保持件数(このサイトでは最新 5 件)を超えた古いものを物理削除します(公開予定がまだ来ていない未来分や、予約一覧から参照中のものは残す)。どこからも参照されなくなったメディアファイルは月次でスキャンしますが、これはレポートを出すだけで自動削除はしません。「使われていないなら、いつまでも置いておかない」を、ここでも徹底しています。

共通しているのは「消す処理を書かない」こと

12 個の仕組みを並べてみると、設計の癖がはっきり見えてきます。どれも「古いものを名指しで探して消すコード」を持っていません。代わりに、消えることが自然に起きる状態を作っています。

3 つの共通パターン

  • 無効化を書かない。 鍵を中身から作る/残す基準を「活動」(生きている予約・最新の版)から導く。だから「消し忘れ」というバグが構造的に起きえない。
  • 剪定はベストエフォートで、本流を止めない。 片付けが失敗しても配信や公開は last-known-good のまま続く。キャッシュを消しすぎても再計算で回復する。掃除の失敗が事故にならない。
  • 「特別な状態」を持たない。 競合状態の一覧も、無効化対象のリストも、手動で消すべきゴミの台帳も持たない。持たないものは、壊れようがない。

「消す処理」はたいていバグの温床です。消し忘れれば溜まり、消しすぎれば壊れ、参照を取り違えれば事故になる。その処理をそもそも書かずに済む状態を作っておけば、10 年動かしても腐りようがありません。派手なアルゴリズムではなく、「あとで掃除する」を「最初から散らからない」に置き換える、という地味な判断の積み重ねです。

正直な境界 ―― できることと、入れていること

誇張にならないよう、2 つだけ線を引いておきます。

① 平らになるのはオーバーヘッドだけ

繰り返しになりますが、剪定されるのは履歴・キャッシュ・ログ・放置作業といった副産物です。公開し続けるコンテンツの本体は、選んで残しているぶんだけ増えます。「何を残すか」はあなたの編集判断で、システムが勝手に本体を捨てることはありません。

② 能力は共通、発動はデプロイごとの設定

これらの剪定は @nano-cms/core という共通の土台に組み込まれていますが、汎用の土台としての既定値は多くが「無効(=永久保持)」です。各サイトが運用方針に合わせて閾値を入れて初めて働きます。このサイトでは、版は最新 30、下書きは 90 日でアーカイブ、ブランチは 200 日、行は 30 日、preprod は 5 件、と入れています。「設計上すべてを有界化できる」土台があり、運用側がそれを入れている、というのが正確な言い方です。

グループ 捨てるもの 残す基準(このサイトの設定)
履歴記事・カテゴリ・共通パーツの古い版各最新 30 件
main の古いコミット生きている予約が要る範囲
派生物予約プレビューのキャッシュ30 日 + 100 MiB 上限
一時アップロード・各種ログ経過時間・保持日数(ログ 60 日 ほか)
ライフサイクル放置下書き → アーカイブ → ブランチ → DB 行90 日 → 200 日 → 30 日 の多段
preprod 出力日次・最新 5 件超の過去分を削除(未来/参照中は保持)
孤児メディア月次スキャン(レポートのみ・自動削除なし)

予約プレビューを数百ミリ秒に収めた話も、この「散らからない」設計も、根は同じ考え方です。あとで何とかするのではなく、最初から何とかしなくていい形にしておく。古いキャッシュを消す処理を書かない、競合状態を持たない、放置された作業はひとりでに崩れる ―― そうやって「手入れが要る場所」を一つずつ減らしていくと、10 年動かしても土台はだいたい平らなまま保てます。なお、このドキュメントサイト自体も @nano-cms/core で動いており、各ページも予約公開で「当日の見え方」を確認してから出しています。