# 0020. Cache invalidation is flush-all

- **Status:** Accepted
- **Date:** 2025-11-20

## Context

Reference data, query results, the database schema and the sitemap are all cached. Content changes come
almost exclusively through the admin, in bursts, from a handful of editors — and the site is one server with
one Redis instance.

## Decision

**There is no selective invalidation. Every write path flushes everything.**

[`CacheHelper::flush()`](../../helpers/CacheHelper.php) is one line — `Yii::$app->cache->flush()` — and is
called from:

| Trigger | Path |
|---------|------|
| Admin save / delete / sort | the generic actions' `flushCache` flag, on by default per controller |
| Dashboard "flush cache" button | `admin/dashboard/delete-cache`, behind the `DELETE_CACHE` right |
| Deploy | `./yii deploy/flush-cache`, **run twice** |

The deploy flushes before *and* after migrating: the first clears the database schema cache so migrations see
the current schema, the second clears application data so the frontend picks up the new state.

## Alternatives

- **Tag-based invalidation** (`TagDependency`). Correct, and every cached read would need to declare its
  tags, every model its dependents. A large amount of bookkeeping to avoid re-warming a cache that re-warms
  in one request.
- **Key-level invalidation.** Same problem, finer grained.
- **Short TTLs and no invalidation.** Editors would not see their own changes for up to the TTL, which is the
  one thing they will not accept.

## Consequences

- **Correctness is trivial.** There is no such thing as a stale entry after a write, and no dependency graph
  to get wrong.
- **A flush is genuinely global** — schema cache included. The first request after a deploy or a save is
  measurably slower because the schema is re-read.
- **Anything written outside the admin needs a manual flush**: raw SQL, an import, a legacy migration run, an
  AI translation run. Nothing detects it.
- **Redis has no default TTL here.** Only `FileCache` gets `defaultDuration`; an entry written without an
  explicit duration lives until the next flush. That is safe *because* invalidation is flush-all — under any
  other scheme it would be a leak.
- The widget output cache was built on this assumption
  ([`base/Widget.php`](../../base/Widget.php)) but is inert: `getCacheIdParams()` returns `null` in the base
  class and no widget overrides it.
- At a larger editing volume this becomes the wrong trade. The signal to revisit is editors complaining that
  saving is slow, or the schema re-read showing up in response times.
