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.
|
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
|
Bei fertigen Versionen liegen `quellen/` (außer QUELLEN.md) und `arbeit/` als `quellen.tar` und `arbeit.tar` im Archiv
|
||||||
konfiguriert ist; Ereignisse dazu tragen den Agenten `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
|
`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.
|
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
|
Archiv: ist `ARCHIV_DIR` in `.env` gesetzt (auf dem Server ein Ordner der Storage Box, deren Mount `gid=1000` hat, da der
|
||||||
`app` schreibt), verschiebt `backend/archiv.py` nach jedem fertigen Lauf `quellen/` (außer QUELLEN.md) und `arbeit/`
|
Container als `app` schreibt), packt `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/`.
|
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.
|
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/`
|
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).
|
"""Quellen und Arbeitsdateien fertiger Versionen als tar ins Archiv (config.ARCHIV, auf dem Server die Storage Box).
|
||||||
Nichts liest danach noch daraus: neue Versionen recherchieren neu, fertige Läufe setzen nie fort. QUELLEN.md bleibt im Speicher,
|
Je Version quellen.tar (ohne QUELLEN.md, die bleibt im Speicher und steht in der Buchansicht) und arbeit.tar. Ein tar statt
|
||||||
die Buchansicht zeigt sie. Erst kopieren, dann löschen – ein Abbruch lässt den Speicher vollständig, der nächste Start holt nach."""
|
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 asyncio
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from . import config, state
|
from . import config, state
|
||||||
@@ -25,32 +28,28 @@ def _eintraege(d: Path) -> list[Path]:
|
|||||||
|
|
||||||
|
|
||||||
def auslagern(key: str) -> None:
|
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)
|
v = state.verzeichnis(key)
|
||||||
|
ziel = config.ARCHIV / key
|
||||||
|
ziel.mkdir(parents=True, exist_ok=True)
|
||||||
for u in ORDNER:
|
for u in ORDNER:
|
||||||
for p in _eintraege(v / u):
|
eintraege = _eintraege(v / u)
|
||||||
_kopieren(p, config.ARCHIV / key / u / p.name)
|
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():
|
if p.is_dir() and not p.is_symlink():
|
||||||
shutil.rmtree(p)
|
shutil.rmtree(p)
|
||||||
else:
|
else:
|
||||||
p.unlink()
|
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:
|
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:
|
try:
|
||||||
await asyncio.to_thread(auslagern, key)
|
await asyncio.to_thread(auslagern, key)
|
||||||
state.ereignis(key, "archiv", "ende", "ausgelagert")
|
state.ereignis(key, "archiv", "ende", "ausgelagert")
|
||||||
@@ -69,7 +68,7 @@ def anstossen(key: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
async def nachholen() -> 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:
|
if not config.ARCHIV:
|
||||||
return
|
return
|
||||||
for t in state.themen():
|
for t in state.themen():
|
||||||
|
|||||||
Reference in new issue
Block a user