Operations

Markdown

Performance tuning

Operator guide for keeping Public CMS API and admin paths fast: response cache, lean includes, queue workers, and Pulse/Health signals.

Public API response cache

Externa caches successful Public CMS API reads (REST and GraphQL) via PublicApiResponseCache.

FactDetail
FreshnessSynchronous collection version bump on write — cache misses immediately after publish/edit
TTLSafety net only — env PUBLIC_API_CACHE_TTL (seconds, default 120, minimum 1) via config/api.php response_cache_ttl (core#115). The class constant TTL_SECONDS = 120 is deprecated; prefer ttlSeconds() / config. Global only (no per-collection override yet).
Global flushSettings → Performance → flush Public API cache (bumps a global epoch folded into every collection version)
StatusSame Performance page shows cache store, Redis reachability, queue driver, public API epoch, TTL seconds, per-epoch hits/misses, and last flushed at / by
PUBLIC_API_CACHE_TTL=120

When debugging stale public reads: confirm the write path bumped the collection, then flush Public API cache from Performance if you force-edited data outside normal writers. Hit/miss counters reset on flush (new epoch).

include costs

Public item list/show responses are lean by default. Expanding files, users, or relations costs extra queries and payload size.

PracticeWhy
Prefer no include for list cardsKeep IDs; hydrate detail pages only
Add include=files only when the UI needs file objectsWithout it, image/file fields stay as stored IDs
Limit relation field names in includeDepth 1 only; nested items do not re-apply relation includes
Avoid spraying CSV tokens “just in case”Unknown tokens are ignored, but known ones still hydrate

See Public CMS API — Expand with include.

RelationInclude list prewarm (1.3.0)

Shipped in externa-core 1.3.0 (core#112): Public REST item lists call RelationIncludeHydrator::prewarm once per response so depth-1 relation targets load in a flat query count as parent N grows. Lean includes still matter for payload size and GraphQL/detail paths — prefer fewer tokens on large list pages when clients do not need the expanded objects.

Admin / Public list N+1 (1.3.0)

core#117 memoizes ProjectSettings::raw() for the request (singleton binding) so admin and Public item lists stop re-querying the settings table once per parent row. No operator config — upgrade to 1.3.0+ is enough. If Pulse still shows slow_queries_24h spikes on lists, look at filters and over-broad includes first.

Queues & Horizon

Async work (webhook delivery, zips, imports, AI attachments, Meilisearch / git export jobs) needs a real queue worker. Without it, HTTP stays snappy while background features stall and storage grows.

StackWorker
Full (Redis)php artisan horizon + Horizon
MinimalQUEUE_CONNECTION=database + queue:listen / queue:work — Minimal vs full

Do not use QUEUE_CONNECTION=sync in production — large zips/imports block requests. Scheduler must run every minute for scheduled publish and cleanup (Operations).

Reading Pulse for CMS hotspots

Dashboard → Health shows Pulse aggregates without embedding Livewire Pulse:

SignalWhat to check
exceptions_24hSpikes after deploys or bad public clients
slow_jobs_24hWebhook / import / zip jobs timing out
slow_queries_24hHeavy list filters, missing indexes, over-broad includes

Deep dive: /pulse (super-admin). Settings → Performance links Jobs / Pulse / Horizon when permitted, and shows Public API cache TTL / hits / misses / last flush (core#115). Full config: Pulse & Health.

Quick checklist

  1. Redis (or database queue) + Horizon/worker running in prod
  2. Scheduler every minute
  3. Public list endpoints without unnecessary include (prewarm helps query count; payload still grows)
  4. Tune PUBLIC_API_CACHE_TTL only if orphaned keys need a different safety-net window
  5. Performance flush after unusual cache suspicion; check hits/misses on the same page
  6. Watch Health tab after traffic or content spikes