RRC / src /racing_reports /data_cache.py
pablogrois's picture
Cachear preprocessed en RAM + cartel dinámico de cold/warm
5708b1b
Raw
History Blame Contribute Delete
2.22 kB
"""Cache en memoria del DataFrame del preprocessed.
``pd.read_csv`` de un archivo de 2.47 GB tarda ~60 s en CPU básico. Si lo
guardamos en RAM, la primera corrida sigue tardando lo mismo, pero las que
vienen después son instantáneas (parte CPU del pipeline).
La key del cache incluye ``mtime`` para que, si el archivo se actualiza en
disco (sync semanal del preprocessed), la entrada se invalide sola.
Single-container HF Space, sin multiproceso → un dict global con lock alcanza.
"""
from __future__ import annotations
import threading
from collections import OrderedDict
from pathlib import Path
import pandas as pd
_CACHE: "OrderedDict[tuple[str, float, bool], pd.DataFrame]" = OrderedDict()
_LOCK = threading.Lock()
_MAX_ENTRIES = 2 # preprocessed base + (eventual) labeled distinto
def get_cached(path: Path, *, low_memory: bool = False) -> pd.DataFrame:
"""Devuelve un DataFrame del CSV, leyendo del cache si está disponible.
Importante: el DataFrame es **compartido**. Los callers que vayan a mutar
columnas deben hacer ``.copy()`` antes. Mutaciones tipo ``df[col] = ...``
sobre el devuelto afectan al cache.
"""
p = Path(path).resolve()
key = (str(p), p.stat().st_mtime, low_memory)
with _LOCK:
hit = _CACHE.get(key)
if hit is not None:
_CACHE.move_to_end(key)
return hit
# Leer fuera del lock para no bloquear otros lookups durante 60 s.
df = pd.read_csv(p, low_memory=low_memory)
with _LOCK:
# Si otro thread cargó la misma key en el medio, preferimos su copia.
existing = _CACHE.get(key)
if existing is not None:
return existing
_CACHE[key] = df
_CACHE.move_to_end(key)
while len(_CACHE) > _MAX_ENTRIES:
_CACHE.popitem(last=False)
return df
def is_cached(path: Path, *, low_memory: bool = False) -> bool:
"""Si el archivo ya está cargado en RAM (sin tocar disco más allá del stat)."""
p = Path(path).resolve()
if not p.exists():
return False
key = (str(p), p.stat().st_mtime, low_memory)
with _LOCK:
return key in _CACHE
def clear() -> None:
with _LOCK:
_CACHE.clear()