Operations
MarkdownPerformance tuning
Operator guide for keeping Public CMS API and admin paths fast: response cache, lean includes, queue workers, and Pulse/Health signals.
Related
Public API response cache
Externa caches successful Public CMS API reads (REST and GraphQL) via PublicApiResponseCache.
| Fact | Detail |
|---|---|
| Freshness | Synchronous collection version bump on write — cache misses immediately after publish/edit |
| TTL | Safety 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 flush | Settings → Performance → flush Public API cache (bumps a global epoch folded into every collection version) |
| Status | Same 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.
| Practice | Why |
|---|---|
Prefer no include for list cards | Keep IDs; hydrate detail pages only |
Add include=files only when the UI needs file objects | Without it, image/file fields stay as stored IDs |
Limit relation field names in include | Depth 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.
| Stack | Worker |
|---|---|
| Full (Redis) | php artisan horizon + Horizon |
| Minimal | QUEUE_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:
| Signal | What to check |
|---|---|
exceptions_24h | Spikes after deploys or bad public clients |
slow_jobs_24h | Webhook / import / zip jobs timing out |
slow_queries_24h | Heavy 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
- Redis (or database queue) + Horizon/worker running in prod
- Scheduler every minute
- Public list endpoints without unnecessary
include(prewarm helps query count; payload still grows) - Tune
PUBLIC_API_CACHE_TTLonly if orphaned keys need a different safety-net window - Performance flush after unusual cache suspicion; check hits/misses on the same page
- Watch Health tab after traffic or content spikes