Skip to content

Caching

Included free on every install, with one cache provider of any kind and up to 10 response cache rules per tenant. Several providers with failover, moving entries between providers, the provider benchmark and more rules need a license with the cache-pro feature. See pricing.

Caching keeps the answers to frequent reads, such as content items, content lists, searches, the dashboard and the schema list, so repeated requests skip the database. A content change clears the affected entries, so readers do not see stale content after a write.

SetupWhere entries liveUse it when
DefaultEach replica's memory.You run one replica.
CACHE_DRIVER=redisOne Redis, Valkey or KeyDB.You run more than one replica.
Listed providersSeveral servers, tried in order. More than one needs cache-pro.One cache server going down must not slow every read.

A content write clears that tenant's cached reads by default. Flushing the cache never signs anyone out, because sessions are kept apart from it.

You need an admin token in TOKEN. The quickstart shows how to get one.

  1. List the cache providers and their state:

    Terminal window
    curl http://localhost:3001/api/admin/cache/providers \
    -H "Authorization: Bearer $TOKEN"
    {
    "data": [
    { "name": "memory-primary", "kind": "inmemory", "hits": 0, "misses": 0, "entries": 0, "max_size": 10000, "circuit_state": "closed", "connected": true, "active": true }
    ],
    "limit": 50,
    "offset": 0,
    "total_count": 1
    }
  2. As a super admin, see hits and misses per kind of read:

    Terminal window
    curl http://localhost:3001/api/admin/cache/metrics \
    -H "Authorization: Bearer $TOKEN"

    The answer has query_caches, keyed by kind of read such as content:item, content:list and schema:list, and the overall stats.

  3. Cache the answers of a public route for five minutes:

    Terminal window
    curl -X POST http://localhost:3001/api/admin/cache/rules \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"pattern": "/api/v1/search/{schema}", "ttl_seconds": 300, "tags": ["posts"]}'

    The answer is 201 with the rule. An anonymous search that reaches your tenant, such as one on a domain mapped to it, now answers X-Cache: MISS the first time and X-Cache: HIT after that, until a posts entry changes.

  4. Flush your tenant's cache:

    Terminal window
    curl -X POST http://localhost:3001/api/admin/cache/flush \
    -H "Authorization: Bearer $TOKEN"
    { "message": "cache flushed" }
  5. In the admin console, open Insight > Observability > Cache for hit rates, providers and flush controls.

Every install runs one cache provider of any kind: in-memory, Redis, Valkey or KeyDB, with every caching feature on it. With several providers listed and no cache-pro, the instance starts the first one that works, logs each one it left out by name, and lists them on GET /api/admin/cache/providers under inactive with the reason provider_limit. The answer also carries licensed.

A license with cache-pro starts the rest without a restart. If it lapses, every running provider keeps running until the next restart, which starts the first one only. The cache that holds sign-in tokens is separate and never counts as a provider.

Terminal window
CACHE_DRIVER=redis
REDIS_URL=redis://redis.internal:6379/0

If CACHE_DRIVER=redis is set without REDIS_URL, the instance connects to redis://localhost:6379/0. The same Redis also shares sessions and sign-in lockouts between replicas. See scale and tune.

This needs cache-pro. List providers by number, starting at 0. The lowest number is tried first. When a provider stops answering, requests move to the next one, and the provider returns to service once it answers again.

Terminal window
CACHE_PROVIDER_0_KIND=redis
CACHE_PROVIDER_0_NAME=primary
CACHE_PROVIDER_0_URL=redis://redis-a.internal:6379/0
CACHE_PROVIDER_0_ENABLED=true
CACHE_PROVIDER_1_KIND=inmemory
CACHE_PROVIDER_1_NAME=local
CACHE_PROVIDER_1_ENABLED=true
VariableMeaning
CACHE_PROVIDER_N_KINDredis, valkey, keydb or inmemory. The list ends at the first number with no kind.
CACHE_PROVIDER_N_NAMEA name for the admin console and the API. Defaults to <kind>-<N>.
CACHE_PROVIDER_N_URLThe server address.
CACHE_PROVIDER_N_ENABLEDMust be true, or the provider is skipped.
CACHE_PROVIDER_N_DEFAULT_TTLEntry lifetime for this provider, such as 5m.
CACHE_PROVIDER_N_MAX_ENTRIESEntry ceiling for an inmemory provider.

When CACHE_PROVIDER_0_KIND is set, CACHE_DRIVER and REDIS_URL are not used for the cache. A provider that was skipped can be put back at once with POST /api/admin/cache/providers/{name}/reset-circuit.

VariableWhat it doesDefault
CACHE_CIRCUIT_ENABLED, or CACHE_PROVIDER_N_CIRCUIT_ENABLEDSkip a provider after repeated failures.false
CACHE_CIRCUIT_MAX_FAILURESFailures in a row before it is skipped.5
CACHE_CIRCUIT_FAILURE_WINDOWThe window those failures are counted in.120s
CACHE_CIRCUIT_OPEN_TIMEOUTHow long it is skipped before a retry.30s
CACHE_CIRCUIT_HALF_OPEN_SUCCESSESSuccessful retries before it is used again.2

The CACHE_CIRCUIT_* names apply to CACHE_DRIVER=redis. With listed providers, use the same names with the CACHE_PROVIDER_N_ prefix, such as CACHE_PROVIDER_0_CIRCUIT_MAX_FAILURES.

Copy entries from one provider to another before you switch. This needs a super admin and cache-pro:

Terminal window
curl -X POST http://localhost:3001/api/admin/cache/migrate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"source": "primary", "destination": "replacement", "pattern": "*", "dry_run": true}'

pattern is a key glob and defaults to *. overwrite replaces keys the destination already has. dry_run reports what would be copied. GET /api/admin/cache/benchmark?n=5 times each provider, also with cache-pro. Both answer 400 for a malformed request and 404 for an unknown provider before the license is read, so a 402 means only that the request needs cache-pro.

A response cache rule names a public read route and keeps its answers in the cache. Each tenant holds 10 rules on every install, and cache-pro lifts the limit. There is no limit on how long an answer may be kept.

FieldMeaning
patternA path under /api/v1/, at most 255 characters. A segment is literal, a glob within the segment such as post-*, a parameter such as {schema} that matches any one segment, or a final ** that matches whatever follows. One rule per pattern per tenant.
ttl_secondsHow long an answer is kept. Any positive number of seconds.
tagsUp to 32 content type names whose changes clear what the rule cached. A rule with no tags is cleared by every content change in its tenant.
enabledA rule that is off caches nothing and keeps its place. Default true.

Rules serve anonymous public reads and nothing else: a GET under /api/v1/ that carries no credential and that the instance resolved to a tenant. A request with an Authorization header, an API key or a cookie is never served from the cache and never stored, because a signed-in caller may be shown a different answer. The Content API's own content reads need a credential, so in practice rules serve the public routes that answer without one, such as search, localization and media files.

  • On a single-tenant install every anonymous read belongs to that tenant, so any public route a rule names can be served.
  • On a multi-tenant install an anonymous request reaches a tenant only through a domain mapped to it. The tenant's public reads are served from the cache when they arrive on its own domain, and the same read on the shared hostname passes through.

The cache does not warm itself. After a clear, the next anonymous read of a path fills it again.

The first enabled rule, oldest first, whose pattern matches decides. The cache key is the tenant, the path, the query, Accept-Language and Accept. An answer is stored only when it is a 200 that sets no cookie, is not marked no-store or private, and is at most 1 MiB. A hit answers with X-Cache: HIT, and a matching If-None-Match gets 304.

Whatever a rule names, these requests are never served from the cache or stored in it, and a pattern that can name only them is refused when you save it:

  • Sign-in, sessions and second factors: /api/v1/ followed by auth, oauth, sso, saml, mfa, passkey, passkeys, webauthn, magic-link, password-reset, session, sessions, me, csrf or captcha.
  • Signed and expiring links: local storage downloads under /api/v1/storage/local/, and any request whose query carries sig, exp, expiry or token, or a name containing signature, expires, token or credential.
  • Flow endpoints and incoming webhooks: /api/v1/flows/ and /api/v1/webhooks/.
  • Experiments and answers about the caller: /api/v1/ab/, the A/B test, feed and behavior routes of recommendations, /api/v1/regions/nearest, /api/v1/quotas/, /api/v1/quota-requests, /api/v1/gdpr/ and /api/v1/scim/.
  • Streams: /api/v1/realtime/ and /api/v1/ws/.
  • A content create, update or delete clears, in its tenant, the answers of every rule tagged with that content type and of every rule with no tags.
  • Changing or deleting a rule clears what it cached.
  • POST /api/admin/cache/rules/{id}/purge clears one rule's answers. POST /api/admin/cache/rules/purge with {"tag": "posts"} clears every rule carrying the tag, and with no tag every answer the tenant has cached.

Publishing or unpublishing through the Content API's publish routes does not clear cached answers, which expire with their ttl_seconds. Each replica reads the rules again at most every 10 seconds. With the in-memory provider each replica keeps its own cached answers, so a clear reaches only the replica that saw the change. A shared Redis gives every replica one cache.

Rate limits, the firewall and quotas count a cache hit like any other request. GET /api/admin/cache/rules answers the rules with licensed and limits, as {"rules": {"limit": 10, "current": 3}}, where limit is null with cache-pro. If the license lapses, every rule keeps serving, and only a new rule past 10 is refused.

VariableWhat it doesDefault
CACHE_DRIVERmemory or redis, when no providers are listed.memory
REDIS_URLThe Redis address for CACHE_DRIVER=redis.redis://localhost:6379/0
CACHE_TTLHow long an entry is kept when it was stored without a lifetime of its own. Content, search, dashboard and schema reads carry their own lifetimes.60s
CACHE_MAX_ENTRIESEntry ceiling for the in-memory backend.10000
CACHE_INVALIDATION_STRATEGYWhat a content write clears. tenant clears that tenant's cached reads. keyspace clears the affected kind of read for everyone.tenant
CACHE_PROBE_INTERVALHow often providers are checked, such as 10s.15s

Caching runs on every install. If you set LYEVE_PLUGINS to choose which features start, include cache in it. See licensing and tiers.

Cache routes
MethodPathWhoResult
GET/api/admin/cache/statsSuper adminhits, misses, sets, deletes, flushes, evictions, entries, max_size.
GET/api/admin/cache/metricsSuper adminCounts per kind of read, and the overall stats.
GET/api/admin/cache/entriesSuper adminCurrent entries with key, size, expires_at and tags. Where the backend cannot list them, the answer is an empty list with a note.
GET/api/admin/cache/tags/{tag}Super adminKeys carrying a tag.
POST/api/admin/cache/warmAdminFills the cache ahead of traffic.
POST/api/admin/cache/flushAdminClears your tenant's entries, or the whole cache on a single-tenant install.
POST/api/admin/cache/flush/{tag}AdminClears every entry with a tag.
DELETE/api/admin/cache/entries/{key}AdminClears one entry.
GET/api/admin/cache/providersAdminProviders with their state, with limit and offset, plus licensed and the inactive providers.
GET/api/admin/cache/providers/{name}AdminOne provider.
POST/api/admin/cache/providers/{name}/reset-circuitSuper adminPuts a skipped provider back in service now.
POST/api/admin/cache/migrateSuper adminCopies entries from one provider to another. Needs cache-pro.
GET/api/admin/cache/benchmarkSuper adminTimes each provider. Query: provider, n (at most 100). Needs cache-pro.
GET/api/admin/cache/rulesAdminYour tenant's response cache rules, with licensed and limits.
POST/api/admin/cache/rulesAdminCreate a rule: pattern, ttl_seconds, tags, enabled. 201.
GET/api/admin/cache/rules/{id}AdminOne rule.
PUT/api/admin/cache/rules/{id}AdminReplace a rule and clear what it cached.
DELETE/api/admin/cache/rules/{id}AdminDelete a rule and clear what it cached.
POST/api/admin/cache/rules/{id}/purgeAdminClear one rule's cached answers.
POST/api/admin/cache/rules/purgeAdminClear the answers of every rule with tag, or all of your tenant's.
StatusMessage
400tag is required, key is required, provider name is required
400source and destination are required
400?n= must be a positive integer, ?n= must not exceed 100
402payment_required, naming feature:cache-pro, on migrate or the benchmark
402cap_exceeded with "cap": "cache.rules", an 11th rule without cache-pro
403cannot query another tenant's tags, or a key that belongs to another tenant.
404provider "<name>" not found
409the tenant already has a rule for this pattern
422pattern must be a path under /api/v1/, pattern names routes the cache never serves: sign-in, signed downloads, flows, experiments or answers about the caller, ttl_seconds must be a positive number of seconds, a rule carries at most 32 tags, each tag is 1 to 64 letters, digits, dots, dashes or underscores
429a benchmark is already in progress: try again later