Add per-table TTL refresh for tables without a change column

This commit is contained in:
Jan Doubravský
2026-06-05 12:12:57 +02:00
parent 33aa126ff6
commit 85bb84a1a6
8 changed files with 240 additions and 19 deletions
+30 -3
View File
@@ -22,6 +22,7 @@ class CachingEngine:
self,
source_engine: Engine,
delta: dict[str, DeltaConfig] | None = None,
ttl: dict[str, int] | None = None,
) -> None:
self._source_engine = source_engine
self._cache = CacheManager(CACHE_DB_PATH, BACKUP_INTERVAL_SECONDS)
@@ -29,9 +30,18 @@ class CachingEngine:
self._stats = StatsCollector()
self._refresh_interval = REFRESH_INTERVAL_SECONDS
self._delta = self._resolve_delta(delta or {})
self._ttl = dict(ttl or {})
self._refresher = DeltaRefresher(self._cache, self._delta)
if self._delta:
overlap = set(self._delta) & set(self._ttl)
if overlap:
raise ValueError(
f"Tables {sorted(overlap)} are in both delta and ttl — a table is "
"either delta-refreshed (has a change column) or TTL-refreshed (full "
"reload), not both."
)
if self._delta or self._ttl:
self._run_refresh() # catch up tables restored from disk
self._start_refresh_thread()
@@ -66,7 +76,7 @@ class CachingEngine:
with self._source_engine.connect() as sa_conn:
raw_conn = cast(sqlite3.Connection, sa_conn.connection.dbapi_connection)
executor = QueryExecutor(
self._cache, self._registry, raw_conn, self._stats, self._delta
self._cache, self._registry, raw_conn, self._stats, self._delta, self._ttl
)
return executor.execute(parsed)
@@ -79,8 +89,25 @@ class CachingEngine:
with self._source_engine.connect() as sa_conn:
raw_conn = cast(sqlite3.Connection, sa_conn.connection.dbapi_connection)
self._refresher.refresh(raw_conn)
self._refresh_ttl(raw_conn)
except Exception as e:
logger.error(f"Delta refresh cycle failed: {e}")
logger.error(f"Refresh cycle failed: {e}")
def _refresh_ttl(self, source_conn: sqlite3.Connection) -> None:
"""Proactively full-reload TTL-tracked tables whose cache has expired."""
for table, ttl in self._ttl.items():
if not self._cache.is_table_cached(table):
continue
age = self._cache.seconds_since_refresh(table)
if age is None or age <= ttl:
continue
try:
columns = self._cache.get_table_columns(table)
full = self._cache.is_table_full(table)
self._cache.load_table(table, columns, source_conn, full=full)
logger.info(f"TTL refresh {table!r}: reloaded (age {age:.0f}s > {ttl}s)")
except Exception as e:
logger.error(f"TTL refresh failed for {table!r}: {e}")
def _start_refresh_thread(self) -> None:
def loop() -> None: