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
+28 -6
View File
@@ -17,12 +17,22 @@ class QueryExecutor:
source_conn: sqlite3.Connection,
stats: StatsCollector,
delta: dict[str, ResolvedDelta] | None = None,
ttl: dict[str, int] | None = None,
) -> None:
self._cache = cache
self._registry = registry
self._source_conn = source_conn
self._stats = stats
self._delta = delta or {}
self._ttl = ttl or {}
def _ttl_expired(self, table: str) -> bool:
"""True if *table* has a TTL and its cached copy is older than that TTL."""
ttl = self._ttl.get(table)
if ttl is None:
return False
age = self._cache.seconds_since_refresh(table)
return age is not None and age > ttl
def execute(self, parsed: ParsedQuery) -> list[dict]:
for table in parsed.tables:
@@ -37,12 +47,18 @@ class QueryExecutor:
def _ensure_full(self, table: str) -> None:
"""Load every column of *table* (SELECT * / t.*), refetching unless already full."""
if self._cache.is_table_cached(table) and self._cache.is_table_full(table):
cached = self._cache.is_table_cached(table)
stale = cached and self._ttl_expired(table)
if cached and self._cache.is_table_full(table) and not stale:
logger.debug(f"Cache hit (full): {table!r}")
self._stats.record_hit()
return
if self._cache.is_table_cached(table):
if cached and stale:
logger.info(f"Cache expired (ttl) — reloading {table!r} in full.")
self._stats.record_refetch()
elif cached:
logger.warning(f"Re-fetching {table!r} in full — SELECT * requested.")
self._stats.record_refetch()
else:
@@ -52,16 +68,20 @@ class QueryExecutor:
self._load(table, columns, full=True)
def _ensure_columns(self, table: str, columns: list[str]) -> None:
"""Load *table* with at least *columns*, refetching only when columns are missing."""
"""Load *table* with at least *columns*, refetching on new columns or TTL expiry."""
missing = self._registry.needs_refetch(table, columns)
table_cached = self._cache.is_table_cached(table)
stale = table_cached and self._ttl_expired(table)
if not missing and table_cached:
if table_cached and not missing and not stale:
logger.debug(f"Cache hit: {table!r} columns={columns}")
self._stats.record_hit()
return
if table_cached and missing:
if stale:
logger.info(f"Cache expired (ttl) — reloading {table!r}.")
self._stats.record_refetch()
elif table_cached and missing:
logger.warning(
f"Re-fetching {table!r} — new columns requested: {missing}. "
f"Expanding cache from {self._registry.get_columns(table)} + {missing}"
@@ -71,7 +91,9 @@ class QueryExecutor:
self._stats.record_miss()
all_columns = list(self._registry.get_columns(table)) + missing
self._load(table, all_columns, full=False)
# Preserve a fully-cached table's status across a TTL reload.
full = table_cached and self._cache.is_table_full(table)
self._load(table, all_columns, full=full)
def _load(self, table: str, columns: list[str], full: bool) -> None:
"""Fetch *table* into cache, adding delta key/timestamp columns when tracked."""