Rapatrier les tuiles manquantes depuis le serveur amont en mode cache seule

La maintenance de fond plafonne a LIDAR_TILE_BACKGROUND_MAX_Z (16 par
defaut) : les zooms natifs 17-18 de l'interface restaient transparents
sur une machine en cache seule. Une tuile absente du cache est
desormais rapatriee de LIDAR_MAPS_URL (quelques dizaines de Ko, mise en
cache, dedupliquee par verrou) sans aucun rendu local sur le chemin des
requetes.
This commit is contained in:
Antoine Jacquin
2026-09-25 20:27:46 +02:00
parent a2bf478379
commit 665ca29984
3 changed files with 58 additions and 2 deletions

View File

@ -218,7 +218,11 @@ environment:
- **`LIDAR_TILE_CACHE_ONLY=1`** — une tuile absente ou périmée est servie
transparente avec `X-Tile-Pending: 1` et `Cache-Control: no-store` (le
navigateur la redemande : dès que la maintenance l'a rendue, elle apparaît).
Plus aucun rendu, ni rapatriement amont, sur le chemin des requêtes.
Plus aucun rendu local sur le chemin des requêtes ; avec `LIDAR_MAPS_URL`,
une tuile manquante (ex. zoom au-delà de `LIDAR_TILE_BACKGROUND_MAX_Z`)
y est rapatriée — un téléchargement de quelques dizaines de Ko, mis en
cache — puis servie : la navigation couvre tous les zooms sans jamais
calculer sur la petite machine.
- **`LIDAR_TILE_BACKGROUND=1`** — un sondeur rescane les dalles à intervalle
régulier (`LIDAR_TILE_BACKGROUND_INTERVAL`, 120 s) ; chaque dalle nouvelle ou
régénérée (le worker vient de produire, le cache à la demande vient de

View File

@ -571,11 +571,30 @@ async def tile(layer: str, z: int, x: int, name: str, v: Optional[str] = None):
raise HTTPException(404, "tuile hors grille")
if _env_flag("LIDAR_TILE_CACHE_ONLY"):
# Mode cache seule : aucune navigation ne déclenche de rendu (ni amont).
# Mode cache seule : aucune navigation ne déclenche de RENDU local.
# Une tuile absente (ex. zoom > LIDAR_TILE_BACKGROUND_MAX_Z, jamais
# couverte par la maintenance) est rapatriée du serveur amont
# LIDAR_MAPS_URL — un simple téléchargement de quelques dizaines de
# Ko, mis en cache, sans décodage ni calcul sur cette machine.
data, state = await run_in_threadpool(
tiles_mod.cached_tile, OUTPUT_DIR, layer, z, x, y, scale, fmt)
pending = state == "pending"
empty = state == "empty"
if pending and MAPS_URL:
suffix = "@2x" if scale == 2 else ""
key = (layer, z, x, y, scale, fmt)
entry = _acquire_lock(key)
try:
with entry[0]:
data = _fetch_upstream(f"tiles/{layer}/{z}/{x}/{y}{suffix}.{fmt}")
if data:
tiles_mod._write_atomic(
tiles_mod.tile_cache_path(OUTPUT_DIR, layer, z, x, y,
scale, fmt), data)
pending = False
empty = False
finally:
_release_lock(key, entry)
else:
data = await run_in_threadpool(_tile_bytes, layer, z, x, y, scale, fmt)
pending = False

View File

@ -823,3 +823,36 @@ def test_ui_offers_generation_controls():
assert "all_missing: true" in _MAP_JS
# Fin de run : rafraîchissement des couches + relance de la maintenance
assert "genAfterRun" in _MAP_JS and "api/map/background" in _MAP_JS
def test_cache_only_fetches_missing_tile_upstream(tmp_path, monkeypatch):
"""Cache seule + LIDAR_MAPS_URL : une tuile absente est rapatriee du serveur
amont (aucun rendu local : les zooms au-dela de la maintenance s'affichent).
"""
import lidar_pipeline.mapserve as mapserve
from lidar_pipeline import tiles
_setup(tmp_path, monkeypatch)
monkeypatch.setenv("LIDAR_TILE_CACHE_ONLY", "1")
monkeypatch.setattr(mapserve, "MAPS_URL", "http://amont:8975")
monkeypatch.setattr(mapserve, "_UPSTREAM", {"fails": 0, "until": 0.0})
calls = []
PNG = bytes([0x89, 0x50, 0x4E, 0x47, 13, 10, 0x1A, 10]) + b'TUILE-AMONT'
def fake_fetch(rel_path):
calls.append(rel_path)
return PNG
monkeypatch.setattr(mapserve, "_fetch_upstream", fake_fetch)
try:
z = 17 # jamais couvert par la maintenance (max_z 16 par defaut)
x, y = _tile_of_cell(1054, 6882, z)
resp = asyncio.run(mapserve.tile("aspect", z, x, f"{y}@2x.webp"))
assert calls == [f"tiles/aspect/{z}/{x}/{y}@2x.webp"]
assert resp.body == PNG
assert resp.headers.get("X-Tile-Pending") != "1" # servie, plus en attente
# Mise en cache : la seconde demande ne repasse pas par l'amont
asyncio.run(mapserve.tile("aspect", z, x, f"{y}@2x.webp"))
assert len(calls) == 1
assert tiles.tile_cache_path(tmp_path, "aspect", z, x, y, 2, "webp").is_file()
finally:
monkeypatch.delenv("LIDAR_TILE_CACHE_ONLY")