Archiv: je Version quellen.tar und arbeit.tar statt einzelner Dateien – SMB schafft 25 Dateien/s, am Stück 90 MB/s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
7ba08b68e0
commit
54d1d6a8da
3 files changed
+26
-26
No files matched your search
@@ -108,5 +108,5 @@ storage/<Thema>/
|
||||
```
|
||||
|
||||
Alte Ablagen (alles direkt unter `storage/<Thema>/`) werden beim Start nach `v1/` verschoben, fehlende Slugs nachgetragen.
|
||||
Bei fertigen Versionen liegen `quellen/` (außer QUELLEN.md) und `arbeit/` im Archiv (`BUCHWERK_ARCHIV/<Thema>/vN/`), wenn eines
|
||||
konfiguriert ist; Ereignisse dazu tragen den Agenten `archiv`.
|
||||
Bei fertigen Versionen liegen `quellen/` (außer QUELLEN.md) und `arbeit/` als `quellen.tar` und `arbeit.tar` im Archiv
|
||||
(`BUCHWERK_ARCHIV/<Thema>/vN/`), wenn eines konfiguriert ist; Ereignisse dazu tragen den Agenten `archiv`.
|
||||
@@ -24,9 +24,10 @@ make logs / make stop
|
||||
`storage/` und `.claude-data/` (Sitzungen der CLI) werden als Volumes eingebunden; die Claude-Code-CLI
|
||||
liegt als Binary im SDK, Node wird im Container nicht gebraucht.
|
||||
|
||||
Archiv: ist `ARCHIV_DIR` in `.env` gesetzt (auf dem Server die Storage Box, Mount mit `uid=1000`, da der Container als
|
||||
`app` schreibt), verschiebt `backend/archiv.py` nach jedem fertigen Lauf `quellen/` (außer QUELLEN.md) und `arbeit/`
|
||||
der Version dorthin – das sind die Gigabytes an Klonen. Beim Start holt er nach, was fehlt. Ohne Variable bleibt alles in `storage/`.
|
||||
Archiv: ist `ARCHIV_DIR` in `.env` gesetzt (auf dem Server ein Ordner der Storage Box, deren Mount `gid=1000` hat, da der
|
||||
Container als `app` schreibt), packt `backend/archiv.py` nach jedem fertigen Lauf `quellen/` (außer QUELLEN.md) und `arbeit/`
|
||||
der Version als `quellen.tar` und `arbeit.tar` dorthin und löscht sie – das sind die Gigabytes an Klonen. Beim Start holt er
|
||||
nach, was fehlt. Ohne Variable bleibt alles in `storage/`.
|
||||
|
||||
Ablauf (backend/pipeline.py): Stufen seriell, innerhalb einer Stufe bis zu `PARALLEL` Agenten gleichzeitig.
|
||||
Jeder Agent arbeitet im Themenverzeichnis `storage/<Thema>/`; sein Abschlussbericht landet unter `berichte/`
|
||||
|
||||
+20
-21
@@ -1,9 +1,12 @@
|
||||
"""Quellen und Arbeitsdateien fertiger Versionen ins Archiv verschieben (config.ARCHIV, auf dem Server eine Storage Box).
|
||||
Nichts liest danach noch daraus: neue Versionen recherchieren neu, fertige Läufe setzen nie fort. QUELLEN.md bleibt im Speicher,
|
||||
die Buchansicht zeigt sie. Erst kopieren, dann löschen – ein Abbruch lässt den Speicher vollständig, der nächste Start holt nach."""
|
||||
"""Quellen und Arbeitsdateien fertiger Versionen als tar ins Archiv (config.ARCHIV, auf dem Server die Storage Box).
|
||||
Je Version quellen.tar (ohne QUELLEN.md, die bleibt im Speicher und steht in der Buchansicht) und arbeit.tar. Ein tar statt
|
||||
einzelner Dateien, weil SMB je Datei mehrere Round-Trips braucht (25 Dateien/s), am Stück aber 90 MB/s schafft.
|
||||
Nichts liest danach noch daraus: neue Versionen recherchieren neu, fertige Läufe setzen nie fort.
|
||||
Erst packen, dann löschen – ein Abbruch lässt den Speicher vollständig, der nächste Start holt nach."""
|
||||
import asyncio
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from . import config, state
|
||||
@@ -25,32 +28,28 @@ def _eintraege(d: Path) -> list[Path]:
|
||||
|
||||
|
||||
def auslagern(key: str) -> None:
|
||||
"""Verschiebt quellen/ (außer QUELLEN.md) und arbeit/ nach ARCHIV/<key>/. Blockiert – im Thread aufrufen."""
|
||||
"""Packt quellen/ (außer QUELLEN.md) und arbeit/ nach ARCHIV/<key>/<ordner>.tar und löscht sie. Blockiert – im Thread aufrufen."""
|
||||
v = state.verzeichnis(key)
|
||||
ziel = config.ARCHIV / key
|
||||
ziel.mkdir(parents=True, exist_ok=True)
|
||||
for u in ORDNER:
|
||||
for p in _eintraege(v / u):
|
||||
_kopieren(p, config.ARCHIV / key / u / p.name)
|
||||
eintraege = _eintraege(v / u)
|
||||
if not eintraege:
|
||||
continue
|
||||
tmp = ziel / f"{u}.tar.tmp"
|
||||
r = subprocess.run(["tar", "-cf", str(tmp), "-C", str(v / u), *[p.name for p in eintraege]], capture_output=True, text=True)
|
||||
if r.returncode:
|
||||
raise OSError(f"tar {u}/ von {key}: {r.stderr.strip()[-300:]}")
|
||||
tmp.replace(ziel / f"{u}.tar")
|
||||
for p in eintraege:
|
||||
if p.is_dir() and not p.is_symlink():
|
||||
shutil.rmtree(p)
|
||||
else:
|
||||
p.unlink()
|
||||
|
||||
|
||||
def _kopieren(quelle: Path, ziel: Path) -> None:
|
||||
"""Ohne Rechte, Zeiten und Symlinks: CIFS kennt beides nicht. Links in Klonen entfallen."""
|
||||
if quelle.is_symlink():
|
||||
return
|
||||
if quelle.is_dir():
|
||||
ziel.mkdir(parents=True, exist_ok=True)
|
||||
for k in quelle.iterdir():
|
||||
_kopieren(k, ziel / k.name)
|
||||
elif quelle.is_file():
|
||||
ziel.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(quelle, ziel)
|
||||
|
||||
|
||||
async def _auslagern(key: str) -> None:
|
||||
state.ereignis(key, "archiv", "start", f"Quellen und Arbeitsdateien nach {config.ARCHIV / key}")
|
||||
state.ereignis(key, "archiv", "start", f"Quellen und Arbeitsdateien als tar nach {config.ARCHIV / key}")
|
||||
try:
|
||||
await asyncio.to_thread(auslagern, key)
|
||||
state.ereignis(key, "archiv", "ende", "ausgelagert")
|
||||
@@ -69,7 +68,7 @@ def anstossen(key: str) -> None:
|
||||
|
||||
|
||||
async def nachholen() -> None:
|
||||
"""Beim Start: alle fertigen Versionen mit Restbestand, nacheinander (Bestand und abgebrochene Kopien)."""
|
||||
"""Beim Start: alle fertigen Versionen mit Restbestand, nacheinander (Bestand und abgebrochene Läufe)."""
|
||||
if not config.ARCHIV:
|
||||
return
|
||||
for t in state.themen():
|
||||
|
||||
Reference in new issue
Block a user