fix: satellite-download chunk-basiert (max 500 Futures im RAM), restart: on-failure
This commit is contained in:
parent
62494438ef
commit
aaf499352b
5 changed files with 43 additions and 33 deletions
|
|
@ -32,7 +32,7 @@ services:
|
|||
- SAT_THREADS=16
|
||||
- PYTHONUNBUFFERED=1
|
||||
command: ["python3", "-u", "/scripts/download-satellite.py", "/data/satellite.mbtiles"]
|
||||
restart: "no"
|
||||
restart: on-failure
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ Sources-Cache unter `./build/sources/` – bei Rebuild nicht löschen (spart Re-
|
|||
Der Download läuft in einem eigenen Container – vollständig unabhängig von
|
||||
SSH-Sessions und VS Code. Resume-fähig: bei Unterbrechung einfach neu starten.
|
||||
|
||||
**Ressourcen:** 16 Threads, 1 GB RAM-Limit (IO-bound), 4 CPUs.
|
||||
**Robustheit:** Download schreibt in `satellite.mbtiles.part` → Martin ignoriert diese Datei während des Downloads. Nach Abschluss automatische Umbenennung zu `satellite.mbtiles`.
|
||||
**Ressourcen:** 16 Threads, 1 GB RAM-Limit (effektiv ~40 MB, chunk-basiertes Processing), 4 CPUs.
|
||||
**Robustheit:** Download schreibt in `satellite.mbtiles.part` → Martin ignoriert diese Datei während des Downloads. Nach Abschluss automatische Umbenennung zu `satellite.mbtiles`. Bei OOM startet der Container automatisch neu (`restart: on-failure`).
|
||||
|
||||
```bash
|
||||
cd /mnt/services-data/rd13_tile_server
|
||||
|
|
|
|||
|
|
@ -92,14 +92,21 @@ Fix 3: YAML-Duplikat `reservations` in docker-compose.download.yml bereinigt.
|
|||
|
||||
Martin gestoppt bis Download fertig.
|
||||
|
||||
### Prompt 15
|
||||
Sicher, es macht nichts wenn wir ihn einfach auslassen bis wir fertig sind.
|
||||
### Prompt 16
|
||||
Was machen wir wenn wieder das mem limit vom download gerissen wird?
|
||||
|
||||
### Antwort 15
|
||||
Martin bleibt gestoppt. Download läuft mit .part-Datei, 1GB Limit, 16 Threads.
|
||||
Status:
|
||||
- `satellite.mbtiles.part` wächst (neuer aktiver Download)
|
||||
- `satellite.mbtiles` (3.5 GB, alter Resume-Stand) bleibt als Backup
|
||||
### Antwort 16
|
||||
Eigentlicher Bug identifiziert: `futs = [submit_tile(...) for ...]` legte trotz Semaphore alle 5,5 Mio Future-Objekte gleichzeitig in eine Python-Liste → ~500 MB allein für die Liste.
|
||||
|
||||
Fix: Chunk-basiertes Processing mit Generator:
|
||||
- `tile_coords()` Generator – liefert (z,x,y) lazy ohne alles im RAM
|
||||
- Jeweils 500 Coords zu einem Chunk zusammenfassen
|
||||
- Nur 500 Futures gleichzeitig im Speicher statt 5,5 Mio
|
||||
- Ergebnis: ~37 MB RAM statt 500+ MB
|
||||
|
||||
Zusätzlich: `restart: on-failure` in docker-compose.download.yml – bei OOM automatischer Neustart mit Resume.
|
||||
|
||||
Verifiziert: `CPU: 10% RAM: 36.92MiB / 1GiB`, Fortschritts-Logs erscheinen korrekt.
|
||||
|
||||
|
||||
### Antwort 10
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# PROJECT_CONTEXT – rd13_tile_server
|
||||
|
||||
**Letzte Aktualisierung:** 2026-06-13 – Martin gestoppt (wartend). Satellit-Download läuft in `.part`-Datei (3.5 GB bereits heruntergeladen, resume aktiv). Fixes: .part-Pattern, OOM-Limit 1GB, YAML-Duplikat.
|
||||
**Letzte Aktualisierung:** 2026-06-13 – Satellit-Download läuft stabil: chunk-basiert (max 500 Futures, ~37MB RAM), restart:on-failure, .part-Datei. Martin gestoppt bis Fertigstellung.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,14 @@ def log(msg):
|
|||
print(msg, flush=True)
|
||||
|
||||
|
||||
def tile_coords():
|
||||
"""Generator: liefert alle (z,x,y)-Koordinaten ohne sie alle im RAM zu halten."""
|
||||
for z in range(MAX_ZOOM + 1):
|
||||
for x in range(1 << z):
|
||||
for y in range(1 << z):
|
||||
yield z, x, y
|
||||
|
||||
|
||||
def main():
|
||||
total = sum(4 ** z for z in range(MAX_ZOOM + 1))
|
||||
log("[Sat] Output: %s" % OUTPUT)
|
||||
|
|
@ -89,31 +97,26 @@ def main():
|
|||
conn = init_db(OUTPUT)
|
||||
t0 = time.time()
|
||||
n = 0
|
||||
# Bounded queue: max THREADS*4 Futures gleichzeitig im Speicher
|
||||
# verhindert OOM bei 5.5 Mio Tasks
|
||||
sem = threading.Semaphore(THREADS * 4)
|
||||
CHUNK = 500 # max Futures gleichzeitig im Speicher (nicht 5.5 Mio)
|
||||
|
||||
def submit_tile(ex, z, x, y):
|
||||
sem.acquire()
|
||||
fut = ex.submit(do_tile, conn, z, x, y)
|
||||
fut.add_done_callback(lambda _: sem.release())
|
||||
return fut
|
||||
coords = tile_coords()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=THREADS) as ex:
|
||||
futs = []
|
||||
for z in range(MAX_ZOOM + 1):
|
||||
for x in range(1 << z):
|
||||
for y in range(1 << z):
|
||||
futs.append(submit_tile(ex, z, x, y))
|
||||
for n, f in enumerate(as_completed(futs), 1):
|
||||
f.result()
|
||||
if n % 1000 == 0 or n == total:
|
||||
dt = time.time() - t0 or 0.001
|
||||
eta = (total - n) / n * dt / 3600
|
||||
log("[Sat] %5.1f%% %d/%d ok=%d skip=%d err=%d %.0ft/s ETA=%.1fh" % (
|
||||
n / total * 100, n, total,
|
||||
stats["ok"], stats["skip"], stats["err"],
|
||||
n / dt, eta))
|
||||
while True:
|
||||
chunk = [(z, x, y) for z, x, y in (next(coords, None) for _ in range(CHUNK)) if (z, x, y) != (None, None, None)]
|
||||
if not chunk:
|
||||
break
|
||||
futs = {ex.submit(do_tile, conn, z, x, y): 1 for z, x, y in chunk}
|
||||
for f in as_completed(futs):
|
||||
f.result()
|
||||
n += 1
|
||||
if n % 1000 == 0 or n == total:
|
||||
dt = time.time() - t0 or 0.001
|
||||
eta = (total - n) / n * dt / 3600
|
||||
log("[Sat] %5.1f%% %d/%d ok=%d skip=%d err=%d %.0ft/s ETA=%.1fh" % (
|
||||
n / total * 100, n, total,
|
||||
stats["ok"], stats["skip"], stats["err"],
|
||||
n / dt, eta))
|
||||
with _lock:
|
||||
if pending:
|
||||
conn.executemany("INSERT OR REPLACE INTO tiles VALUES(?,?,?,?)", pending)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue