Skip to content

Cache and performance

CPalius's performance strategy is layered; each layer can be switched on and off independently and none is mandatory for the core to work (the site opens without Redis and without Varnish).

browser ─▶ [CDN] ─▶ [Varnish] ─▶ web server ─▶ [origin HTML cache] ─▶ PHP/Symfony ─▶ [Redis/application cache] ─▶ database

Anonymous traffic is sessionless

The precondition of caching: if the visitor is anonymous, no session cookie is set. A response with a cookie cannot be put into a cache shared by everyone. For this reason counters, votes and notification states must not be tied to the session (see the Checklist).

Origin HTML cache

This is the core's own page cache; it works without installing Varnish or a CDN. It caches only anonymous GET requests; never admin, authentication and form endpoints.

Setting Default
performance.cpalius.ttl 86400 Snapshot lifetime (s)
performance.cpalius.excludes /aacp /admin /login /logout /hesap /api /_fragment /_profiler /_wdt Path prefixes that will not be cached
performance.cpalius.minify on HTML/CSS/JS minification
performance.cpalius.compress_assets on Static asset compression
performance.cpalius.compress_images on Compressing local images
performance.cpalius.shield off Source code shield (compresses HTML and makes it unreadable)

Snapshots are written after the response has been sent (kernel.terminate); the visitor does not wait for the cost of writing. Expired ones are cleaned by the cpalius.origin_cache.purge task (every 15 min).

Tagged invalidation (cache tags)

Deleting the whole cache on every change is both slow and unnecessary. The core uses the cache tag idea known from Drupal, but without a rebuild step: tags are plain strings that are not registered in advance.

A controller says what its response depends on:

$this->tags->addEntityTag('node', $node->getId());   // "node:42"
$this->tags->addListTag('node', 'post');             // "node_list:post"
$this->tags->addConfigTag('blog.settings');          // "config:blog.settings"
$this->tags->setMaxAge(3600);                        // optional upper limit

At the end of the request OriginCacheWriter reads these tags and indexes the snapshot with them. When a Node, User or Term is saved/deleted, EntityCacheTagInvalidator automatically deletes only the snapshots that carry that entity and the list tag. So if your own entity implements FieldableInterface, you do not need to call purgeAreas() by hand in the controller.

When manual clearing is needed:

$purger->purgeAll();                     // everything (e.g. after an import)
$purger->purgeTags('node:42');          // by tag
$purger->purgeAreas('blog');            // by area name
The owner of the deletion is not disrupted

Cache invalidation is a hook listener; if it fails it is isolated and does not block saving the entity.

Varnish

If Varnish is in front of you, CPalius gives it the correct Cache-Control headers. Admin and authentication paths are always private; the operator's performance.varnish.excludes list is an additional prefix.

Setting Default
performance.varnish.backend_url http://127.0.0.1/
performance.varnish.port 6081
performance.varnish.ttl 120
performance.varnish.timeout 2

Redis, Memcached, Nginx PageSpeed: backend management

The AACP → Maintenance → Performance screen (PerformanceBackendRegistry) manages each backend with a live connection test. An important rule: a backend can only be enabled if the last recorded test succeeded. This prevents a wrong Redis setting from bringing the site down.

Backend Settings
Redis host, port (6379), password, timeout
Memcached host, port (11211), timeout
Nginx PageSpeed status check address
Varnish above

The dashboard carries system widgets that show the status of these services.

If there is no Redis

If the Redis client (phpredis) is not installed or the connection cannot be opened, the service returns null and consumers treat it as "Redis offline". The reason: an ordinary dependency edge would bring every route (including the recovery console) down to 500 when the extension is missing. That is why OptionalRedis exists. If Redis is used for sessions, a failover session handler takes over.

Application cache and rebuilding

Frequently read data such as settings, field definitions and the role map are kept in cache.app and invalidated with clearCache() / invalidate() on write.

CacheRebuildManager does separate jobs from the panel; they are independent:

  • Clear the Symfony cache (clearSymfonyCache)
  • Reset OPcache (resetOpcache)
  • Rebuild assets (rebuildAssets, compileMappedAssets)
  • After a code update all in sequence (afterCodeUpdate)

If clearing the cache fails, a marker file (cache-purge-failed.json) is left and the panel shows it; there is no silent failure.

Counter rebuild (Rebuilder)

A module does expensive counts like COUNT(*) not on the write path but as a separate job. A class implementing RebuilderInterface defines a named "recount" job (id, name, total, batch size, priority, rebuild($offset, $limit)). The AJAX runner in Studio calls the job in batches: each batch returns quickly and is called again with the next offset. Modules register, and the core does not import them.

N+1 guard

In the development (and test) environment, if more than 10 queries go to the same table in a single request, MaxQueriesExceededException is thrown (a limit per table). This forces you to write a fetch join or bulk loading. Helpers:

  • cp_fields_preload(items) (Twig) for field references
  • QueryScopeApplier (turned into SQL) for the permission filter
  • node_field_index for the field index
Off in production

The N+1 guard is active only in the dev (and test) environment; it is off in production. There is a reason for this: if it were active in production it would silently break origin cache writing. Catch the problem while developing.

A practical recipe

  1. Small site: install nothing. The origin HTML cache + cache.app is enough.
  2. Medium site: add Redis (session and application cache).
  3. Large/high-traffic site: put Varnish or a CDN in front, copy media to remote storage (Media and storage).
  4. After every change, run the test in AACP → Performance.
Last updated: 13.10.2026