Read-after-write přes TTL cache — nová hodnota není vidět
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Vícekrokový self-service flow „ulož hodnotu → ověř a aktivuj”: POST uloží
rozpracovanou hodnotu do DB a vrátí ok: true, ale okamžitě navazující
POST na ověřovací endpoint odpoví „Nejdřív zadejte hodnotu” a DELETE
téže hodnoty „Žádný záznam”. Po ~30 sekundách začne vše fungovat samo —
klasický příznak TTL cache.
Root cause
Sekce “Root cause”Request-scoped kontext entity (zde tenant v multi-tenant aplikaci) se resolvuje přes sdílenou in-memory TTL cache (30 s), aby se nešahalo do DB při každém requestu:
// resolver s TTL cache 30 sconst cached = peekEntityByKey(key);if (cached !== undefined) return cached;const fresh = await db.entity.findUnique({ where: { key } });cache.set(key, { entity: fresh, expiresAt: Date.now() + 30_000 });Zapisující endpoint aktualizoval DB, ale cache neinvalidoval. Navazující endpointy četly entitu z kontextu (tedy z cache) a rozhodovaly podle staré hodnoty — read-after-write inkonzistence uvnitř jednoho flow.
Fix
Sekce “Fix”Dvě opatření zároveň:
// 1) zapisující endpoint po commitu invaliduje cacheawait db.entity.update({ where: { id }, data: { pendingValue } });invalidateEntityCache(key);
// 2) rozhodovací kroky flow čtou čerstvě z DB, ne z cache kontextuconst fresh = await db.entity.findUnique({ where: { id: ctx.entity.id } });if (!fresh?.pendingValue) return error("Nejdřív zadejte hodnotu.");Cache kontext zůstává pro běžné čtení (zobrazování), ale nikdy není zdrojem pravdy pro krok, který bezprostředně navazuje na zápis.
Jak to příště odhalit dřív
Sekce “Jak to příště odhalit dřív”- Smoke test flow spouštět bez pauzy mezi kroky (ulož → hned ověř) — právě rychlá návaznost stale cache odhalí.
- Při zavádění jakékoli TTL cache si vypsat všechna místa zápisu do cachované entity a ke každému doplnit invalidaci.