Page cache is the most impactful layer. When PAGE_CACHE_ENABLED is true, the complete HTML response is stored in cache/page_[hash].cache after the first request. The cache key is derived from the full URL, the active language, and the device type (mobile or desktop). Subsequent requests for the same URL read directly from the file and bypass PHP entirely. Logged-in users always bypass page cache.

Object Cache: Request-Scoped by Default

Object cache stores individual data structures — posts, categories, menus, settings — in memory for the duration of a single request. Its purpose is to avoid redundant database queries when the same data is needed multiple times in one page render. Object cache is request-scoped by default; it does not persist between requests unless you configure an external store (Redis or Memcached) via the CACHE_DRIVER constant.

Query Cache and the Stale Data Problem

Query cache stores the results of database queries keyed by a SHA-256 hash of the SQL string and bound parameters. Unlike object cache, query cache persists between requests as files in cache/query_[hash].cache, with a default TTL of 300 seconds. This is the layer most likely to cause "stale data" issues — if you update a post via direct SQL without going through the JekCMS post-save mechanism, the query cache will serve the old data until the TTL expires.

Manual Cache Invalidation

Cache invalidation for page and query caches happens automatically through action hooks that fire on post save, update, and delete. If you need to force invalidation — for example, after a bulk database operation — call Cache::flush('query') or Cache::flush('page') from a PHP script. The admin panel's "Clear All Caches" button covers all three layers including the image proxy cache.

Cache Configuration and Tuning

Each cache layer has configurable parameters in config/config.php:

// Page cache - full HTML responses
define('PAGE_CACHE_ENABLED', true);
define('PAGE_CACHE_TTL', 300); // 5 minutes
define('PAGE_CACHE_EXCLUDE', [
 '/admin/*',
 '/api/*',
 '/search*'
]);

// Query cache - database results
define('QUERY_CACHE_ENABLED', true);
define('QUERY_CACHE_TTL', 300); // 5 minutes

// Object cache driver
define('CACHE_DRIVER', 'file'); // file, redis, memcached

The default TTL of 300 seconds works well for most sites. News sites that publish frequently should reduce it to 60 seconds. Sites that update content rarely can safely increase it to 3600 seconds (one hour) to reduce server load significantly.

Redis as an Object Cache Backend

For sites handling more than 50 concurrent users, file-based object cache becomes a bottleneck. Switching to Redis eliminates filesystem I/O for cached reads:

// config/config.php
define('CACHE_DRIVER', 'redis');
define('REDIS_HOST', '127.0.0.1');
define('REDIS_PORT', 6379);
define('REDIS_PREFIX', 'jek_');

Redis-backed object cache persists between requests automatically, giving you the performance of persistent caching without the stale-data risk of file-based query cache. In benchmarks on a standard VPS (2 vCPUs, 4GB RAM), Redis reduced average page generation time from 145ms to 38ms on a site with 400 posts and 50 categories.

Cache Warming Strategy

After clearing all caches — during deployment or after major content changes — the first visitor to each URL pays the performance cost of a cold cache. Cache warming pre-generates cached responses for your most visited pages:

# Warm cache for top 50 URLs from sitemap
php tools/cache-warm.php --sitemap --limit=50

# Or warm specific pages
php tools/cache-warm.php --urls="/,/about,/contact"

Run cache warming immediately after deployment. The script makes internal HTTP requests to each URL, triggering the page and query cache generation. Warming 50 pages takes approximately 15 seconds on a typical server.

Debugging Cache Issues

Common Symptoms and Solutions

  • Admin changes not appearing on frontend: Page cache is serving stale content. Click "Clear Page Cache" in the admin panel or wait for the TTL to expire.
  • Direct SQL changes not reflected: Query cache stores results keyed by SQL hash. Clear query cache after direct database modifications.
  • Same data loaded multiple times in one page: Object cache is not enabled or the same query uses different parameter formatting. Ensure consistent query construction.
  • Cache files consuming too much disk space: Set up the cache cleanup cron job (includes/cache-cleanup.php) to run daily. A site with 1,000 posts generates approximately 50MB of cache files at default TTL.
  • Logged-in users seeing cached content: This should not happen — verify that session_start() is called before the page cache check in bootstrap.php.

Cache Hit Rate Monitoring

Enable cache statistics in the admin dashboard by setting CACHE_STATS to true. The dashboard widget shows hit rates for each cache layer over the past 24 hours. A healthy page cache hit rate is above 85%. If it drops below 70%, investigate whether too many unique URLs (query parameters, pagination) are fragmenting the cache. Query cache hit rates above 60% indicate the TTL is appropriate for your publishing frequency.

When in doubt, start with the default TTL values and monitor cache hit rates for two weeks before making adjustments. The combination of all three layers working together is what delivers sub-50ms response times on modest hardware, and disabling any single layer without understanding its role can degrade performance significantly.