www no longer serves stale base data until it is redeployed
Shipped 2026-08-27
www reads its base data — faqs, skills, news, videos, materials, tunnels — from
app.locals.redisData. That object was filled once at boot and nothing ever
invalidated it, so a www process served whatever it read at startup for its
entire lifetime.
That mattered more than it sounds. Every CMS area — tunnels, news, skills,
reference-materials, notifications — rebuilds the Redis keys on save. The rebuild
was working correctly and reaching Redis. It just never reached www, which
went on serving its snapshot until the next deploy, roughly weekly. An editor
saving a news item saw the site unchanged with nothing to explain why.
getStoredData now re-reads a key once its copy is older than
BASE_DATA_TTL_MS — an hour in production — using stale-while-revalidate: the
cached copy is returned immediately and the refresh happens behind the request. Awaiting
Redis on the request path would have swapped a silent staleness bug for a latency
one, and these keys are read on nearly every public page.
Two details worth keeping in mind:
- A failed refresh still stamps the timestamp. Without that, a Redis outage would make every request re-attempt the read — turning one broken dependency into a retry storm. It backs off for a full interval instead, and the last good copy keeps being served.
- The existing recovery path is unchanged. A genuinely missing key still
triggers the immediate background re-read that
getStoredDataalready did, so awwwinstance that boots against a cold Redis recovers on the next request rather than staying empty until a restart.
Found while updating the FAQs
TUN-850 rewrote 69 FAQ rows across English, French and Spanish. MySQL was
committed and the API rebuilt the Redis keys correctly, but the live French page
kept rendering the old content — voleur still appearing ten times — until
iba-www was redeployed by hand.
The FAQ runbook and the reasons it is easy to get wrong are now documented on the
Redis infrastructure page: commit before touching
anything else, delete the keys before restarting the API because
loadRedisBaseData() skips ones that already exist, and use JSON.GET rather
than GET when verifying, since these are RedisJSON values and a plain GET
returns nil on them.
- Jira: TUN-855
- Author: @alexhobday