何を見つけたか
アカウントグループの違うプロジェクトを行き来すると、起動のたびに新しい世代が作られ、そのたびに全体のバックアップ(full.tar.zst)を取り直す。 保持は 3 世代なので、2 つのグループを交互に使うと、同じグループの履歴が 3 世代の枠から押し出される。
世代を分ける判定は、直前の 1 世代のボリュームの組との比較だけで行っている。
# lib/devbase/snapshot/manager.py:504-508
snap_dir = self.backups_dir / latest.get('name', '')
if snap_dir.is_dir() and self.snapshot_volumes(snap_dir) != self.volumes:
logger.info("対象ボリュームの構成が変わったため新しい世代を作成します ...")
self.volumes は devbase_home_ubuntu と devbase_home_<group> の組で、後者は起動するプロジェクトのグループで決まる。グループが違えば組が変わるため、必ず新世代になる。
実測(2026-09-23、この端末)
backups/snapshot.yml と du の実測。
| 世代 |
作成 |
対象ボリューム |
差分数 |
サイズ |
20260915-231738 |
09-15 23:18 |
ubuntu, default |
9 |
37 GB |
20260920-212546 |
09-20 21:26 |
ubuntu, with |
0 |
1.8 GB |
20260923-081407 |
09-23 08:15 |
ubuntu, default |
0 |
3.9 GB |
backups/ の合計は 42 GB。
- 09-20 21:26 は
with-ai-dev の起動(コンテナ with-ai-dev-dev-1 の作成が 21:26:02)
- 09-23 08:15 は
ai-plugins の起動(コンテナ ai-plugins-dev-1 の作成が 08:16:33)。ai-plugins 自身は default で、グループの指定はどこにも無い
利用者から見えるのは次の 1 行だけで、なぜ with が出てくるのかが読み取れない。直前の世代を作ったのが別のグループのプロジェクトだった、という文脈が文言に無い。
対象ボリュームの構成が変わったため新しい世代を作成します (旧: devbase_home_ubuntu, devbase_home_with / 新: devbase_home_ubuntu, devbase_home_default)
なぜ起きるか
世代の系列が 1 本しかない。 should_start_new_generation は「最新の 1 世代」とだけ比べ、rotate は世代の一覧の末尾から DEFAULT_MAX_GENERATIONS = 3 件を残して古い順に消す(manager.py:36, 451-479)。ボリュームの組ごとに系列を持つ仕組みが無いため、
- グループを跨ぐたびに
full.tar.zst を取り直す(差分が積めない。09-23 の世代は 3.9 GB)
- 3 世代の枠を、グループの違う世代が奪い合う
2 が本体の実害である。 default → with → default → with と 4 回切り替えると、保持される 3 世代のうち default の履歴は 1〜2 世代しか残らない。devbase snapshot restore で戻せる範囲が、切り替えの回数だけ短くなる。
世代を分けること自体は正しい(コメントのとおり、別のレイアウトの snapshot.snar へ差分を積むと全ファイルが移動した扱いになって差分が壊れる)。問題は分けた後の保持の単位である。
直さないと何が起きるか
- 複数のアカウントグループを使う端末(この端末は
default / with / kkg の 3 つ)で、バックアップの履歴が黙って短くなる。切り替えの頻度が上がるほど短くなり、気づく手段が無い
- 切り替えのたびに全体のバックアップを取り直すため、起動が遅くなりディスクを食う
- 文言から原因が読み取れないため、今回のように「関係ないはずのグループ名が出る」ことの説明に調査が要る
修正レイヤー
現象レイヤー: lib/devbase/snapshot/manager.py の should_start_new_generation(:482-512)と rotate(:451-479)。
修正レイヤー: 世代の保持の単位。いまは「全世代を 1 本の列として新しい順に 3 件」だが、ボリュームの組(= グループ)ごとに系列を持ち、系列ごとに保持数を数えるのが責務の位置である。backups/snapshot.yml は既に世代ごとに volumes を持っているので、読み取りの側を変えれば足りる見込み。
採る手: 分割(系列の導入)。should_start_new_generation は「最新の 1 世代」ではなく「同じボリュームの組を持つ最新の世代」と比べ、差分を積めるならそこへ積む。rotate は組ごとに keep 件を残す。
決めること
- 保持の数をグループごとに数えるか、全体の上限も併せて持つか(3 グループ × 3 世代 = 最大 9 世代がディスクに載る。この端末では 37 GB の世代があるため、全体の上限も要るかもしれない)
- 同じ組の古い世代へ差分を積み直せるか(
snapshot.snar は世代ごとに持っているので積めるはずだが、間に別のグループの起動を挟んだ場合にボリュームの中身が変わっている可能性をどう扱うか)
- 文言に「直前の世代を作ったプロジェクト / グループ」を添えるか(今回の混乱はこれが無いことによる)
由来
v3.7.0 の作業中、ai-plugins(default)を起動したときに devbase_home_with が出てきた理由の調査。
進行
モード: standard / 作業ツリー: .worktrees/feature/plan68-snapshot-series / 計画: issues/PLAN68_snapshot-series.md
振り返り: #262 (comment)
何を見つけたか
アカウントグループの違うプロジェクトを行き来すると、起動のたびに新しい世代が作られ、そのたびに全体のバックアップ(
full.tar.zst)を取り直す。 保持は 3 世代なので、2 つのグループを交互に使うと、同じグループの履歴が 3 世代の枠から押し出される。世代を分ける判定は、直前の 1 世代のボリュームの組との比較だけで行っている。
self.volumesはdevbase_home_ubuntuとdevbase_home_<group>の組で、後者は起動するプロジェクトのグループで決まる。グループが違えば組が変わるため、必ず新世代になる。実測(2026-09-23、この端末)
backups/snapshot.ymlとduの実測。20260915-23173820260920-21254620260923-081407backups/の合計は 42 GB。with-ai-devの起動(コンテナwith-ai-dev-dev-1の作成が 21:26:02)ai-pluginsの起動(コンテナai-plugins-dev-1の作成が 08:16:33)。ai-plugins 自身はdefaultで、グループの指定はどこにも無い利用者から見えるのは次の 1 行だけで、なぜ
withが出てくるのかが読み取れない。直前の世代を作ったのが別のグループのプロジェクトだった、という文脈が文言に無い。なぜ起きるか
世代の系列が 1 本しかない。
should_start_new_generationは「最新の 1 世代」とだけ比べ、rotateは世代の一覧の末尾からDEFAULT_MAX_GENERATIONS = 3件を残して古い順に消す(manager.py:36, 451-479)。ボリュームの組ごとに系列を持つ仕組みが無いため、full.tar.zstを取り直す(差分が積めない。09-23 の世代は 3.9 GB)2 が本体の実害である。 default → with → default → with と 4 回切り替えると、保持される 3 世代のうち default の履歴は 1〜2 世代しか残らない。
devbase snapshot restoreで戻せる範囲が、切り替えの回数だけ短くなる。世代を分けること自体は正しい(コメントのとおり、別のレイアウトの
snapshot.snarへ差分を積むと全ファイルが移動した扱いになって差分が壊れる)。問題は分けた後の保持の単位である。直さないと何が起きるか
default/with/kkgの 3 つ)で、バックアップの履歴が黙って短くなる。切り替えの頻度が上がるほど短くなり、気づく手段が無い修正レイヤー
現象レイヤー:
lib/devbase/snapshot/manager.pyのshould_start_new_generation(:482-512)とrotate(:451-479)。修正レイヤー: 世代の保持の単位。いまは「全世代を 1 本の列として新しい順に 3 件」だが、ボリュームの組(= グループ)ごとに系列を持ち、系列ごとに保持数を数えるのが責務の位置である。
backups/snapshot.ymlは既に世代ごとにvolumesを持っているので、読み取りの側を変えれば足りる見込み。採る手: 分割(系列の導入)。
should_start_new_generationは「最新の 1 世代」ではなく「同じボリュームの組を持つ最新の世代」と比べ、差分を積めるならそこへ積む。rotateは組ごとにkeep件を残す。決めること
snapshot.snarは世代ごとに持っているので積めるはずだが、間に別のグループの起動を挟んだ場合にボリュームの中身が変わっている可能性をどう扱うか)由来
v3.7.0 の作業中、
ai-plugins(default)を起動したときにdevbase_home_withが出てきた理由の調査。進行
モード: standard / 作業ツリー:
.worktrees/feature/plan68-snapshot-series/ 計画:issues/PLAN68_snapshot-series.md振り返り: #262 (comment)