Ava CMS is designed to be fast by default. To achieve this, it uses a two-layer performance strategy:
- Content Indexing: A pre-built index of your content metadata to avoid parsing Markdown files on every request.
- Webpage Caching: A static HTML cache that serves fully rendered webpages instantly.
Together, these systems mean most visitors get pre-rendered HTML served directly from disk with minimal overhead.
With webpage caching enabled (the default), cached pages serve in approximately 0.02-0.04ms. Uncached pages render in approximately 1.5-5ms, depending on content and hardware. The content index ensures even uncached pages are fast.
Quick Guide
For most users:
- The defaults work great (Array backend with
igbinaryinstalled and webpage caching enabled) - Run
./ava rebuildafter content changes in production - Check
./ava statusto see your cache status - Prefer servers with high I/O performance (SSD highly recommended)
Upgrading from another CMS or have lots of content?
- Up to roughly 1,000 items: Array + igbinary remains the simple default
- 1,000-10,000 items: Benchmark both; favour SQLite for filtering, sorting, deep pagination, or constrained memory
- 10,000+ items: SQLite is strongly recommended (
'backend' => 'sqlite'inapp/config/ava.php) - Without igbinary: Consider SQLite much earlier
Having performance issues?
- Run
./ava statusto check index freshness - Run
./ava cache:statsto check webpage cache - Run
./ava benchmark --compareto test different backends on your server
Content Indexing
The Content Index is the foundation of Ava CMS's performance. Instead of reading and parsing Markdown files on every request, Ava CMS builds a binary index of your content metadata (titles, dates, slugs, custom fields, taxonomies).
Think of it like a library catalog: Rather than opening every book to find what you need, you look it up in the catalog first.
How It Works
When you run ./ava rebuild (or when changes are auto-detected in 'auto' mode), Ava CMS:
- Scans all your Markdown files recursively
- Parses frontmatter and extracts metadata
- Validates content for common issues (YAML syntax, required fields, duplicate slugs/IDs)
- Builds optimized indexes for fast lookups
- Stores the index in your chosen backend format
Index Files
Ava CMS generates several files in storage/cache/ to optimise different types of queries:
| File | Contents | Purpose |
|---|---|---|
recent_cache.bin |
Top 200 items per type, pre-sorted | Instant Archives: Homepage and first ~20 archive pages (at the default 10 items/page). |
slug_lookup.bin |
Slug → File Path map with minimal metadata | Fast Single Posts: Find one item without loading the full index. |
content_index.bin |
Full content metadata | Deep Queries: Search, filtering, deep pagination (page 21+). |
tax_index.bin |
Taxonomy terms with item counts | Taxonomies: Category/tag lists, term pages. |
routes.bin |
URL → Content map | Routing: Maps incoming URLs to content and redirects. |
html_cache.bin |
Pre-rendered Markdown → HTML (published only) | Faster Uncached Renders: Skips Markdown conversion work when enabled. |
fingerprint.json |
Hash of content and config files | Change Detection: Determines when to rebuild in auto mode. |
Tiered Caching Strategy
Ava CMS uses a "tiered" approach to ensure common requests are ultra-fast, even on huge sites:
| Tier | Cache Used | Operations | Typical Response |
|---|---|---|---|
| Tier 1 | Recent Cache | Homepage and archive pages 1-20 | ~0.2ms |
| Tier 2 | Slug Lookup | Viewing a single post or page | ~1-15ms |
| Tier 3 | Full Index | Search, complex filtering, deep pagination (page 21+) | ~15-300ms |
Why this matters: ~90% of real-world traffic hits Tier 1 or Tier 2 operations. The full index is only loaded for things like search results or browsing beyond the first ~20 archive pages.
Backend Options
Ava CMS supports two index storage backends, plus a compression option. The best choice depends on your content size and server resources.
Array Backend (Default)
Stores the index as serialized PHP arrays in .bin files. On each request, the relevant cache file is loaded into memory and queried.
// app/config/ava.php
'content_index' => [
'backend' => 'array', // Default
'use_igbinary' => true, // Recommended if available
],
Compression Options:
| Option | Extension Required | Benefits |
|---|---|---|
| igbinary (recommended) | igbinary |
~2× faster reads, much smaller cache files |
| serialize (fallback) | None | Works everywhere, slower and larger files |
Ava CMS automatically uses igbinary if installed and enabled. Most quality hosts include it by default.
Pros:
- Fastest for small sites and recent-content queries
- Zero external dependencies
- Simple to understand and debug
Cons:
- Memory usage scales with content size
- Each concurrent request loads the index into memory
SQLite Backend
Stores the index in a single SQLite database file (storage/cache/content_index.sqlite). Queries are executed directly against the database without loading everything into memory.
// app/config/ava.php
'content_index' => [
'backend' => 'sqlite',
],
Pros:
- Minimal per-query memory overhead (doesn't load full index into RAM)
- Near-instant counts (uses database indexes)
- Strongly recommended for very large sites (10k+ items)
- Faster for most indexed operations as content approaches 10,000 items
Cons:
- Slower for the small recent-items fast path
- Requires
pdo_sqlitePHP extension
Benchmark Comparison
We tested all backends with realistic content. You can run these tests on your own server using ./ava benchmark --compare and ./ava stress:generate post <count> to create test posts.
Key metrics explained:
- Homepage (Recent): How fast your homepage and recent posts load (uses Tier 1 cache)
- Get by slug: How fast a single post/page loads (Tier 2)
- Deep Archive (Page 50): Complex queries beyond the recent cache (Tier 3)
- Memory per query: RAM used for each uncached request
- Cache Size: Disk space used by the index
Note: We're comparing Array with igbinary (the recommended default if available) vs SQLite. Plain serialize() is much slower and larger—use igbinary if possible.
Headline: Array + igbinary is ideal for small sites and recent-content queries. SQLite is recommended for memory-constrained installations and becomes decisively faster for most indexed operations as content approaches 10,000 items.
1,000 Posts
| Operation | Array + igbinary | SQLite | Winner |
|---|---|---|---|
| Count | 62.4ms | 0.86ms | SQLite ✓ |
| Get by slug | 2.1ms | 0.88ms | SQLite ✓ |
| Recent (Page 1) | 0.72ms | 2.0ms | Array ✓ |
| Deep Archive (Page 50) | 68.3ms | 31.3ms | SQLite ✓ |
| Sort by date | 63.7ms | 30.9ms | SQLite ✓ |
| Sort by title | 64.5ms | 35.2ms | SQLite ✓ |
| Search | 63.4ms | 29.7ms | SQLite ✓* |
| Build index | 1.6s | 1.3s | SQLite ✓ |
| Memory per query | 7.5 MB | 2.6 KB PHP allocation | SQLite ✓ |
| Cache Size | 4.3 MB | 1.1 MB | SQLite ✓ |
Verdict: Array + igbinary remains the simple default at this scale, especially for recent-content pages. SQLite is already attractive if you rely on filtering, sorting, search-like indexed queries, deep pagination, or tighter memory budgets.
10,000 Posts
| Operation | Array + igbinary | SQLite | Winner |
|---|---|---|---|
| Count | 488.0ms | 2.8ms | SQLite ✓ |
| Get by slug | 19.2ms | 1.7ms | SQLite ✓ |
| Recent (Page 1) | 0.66ms | 13.7ms | Array ✓ |
| Deep Archive (Page 50) | 718.3ms | 450.3ms | SQLite ✓ |
| Sort by date | 696.2ms | 522.7ms | SQLite ✓ |
| Sort by title | 790.1ms | 490.9ms | SQLite ✓ |
| Search | 613.8ms | 334.0ms | SQLite ✓* |
| Build index | 9.8s | 9.5s | SQLite ✓ |
| Memory per query | 70.7 MB | 2.6 KB PHP allocation | SQLite ✓ |
| Cache Size | 38.7 MB | 10.1 MB | SQLite ✓ |
Verdict: SQLite is strongly recommended at this scale. Array + igbinary still wins the small recent-items fast path, but SQLite is substantially faster for complex and deep queries while using far less PHP memory.
* Search timing currently covers the benchmark's configured search path. Treat it as directional only until body-only search has been tested separately.
Benchmark Environment & Methodology
Environment:
- Ava CMS: v26.02.0
- OS: Linux x86_64 (Ubuntu)
- PHP: 8.5.1 (CLI)
- Hardware: Budget Hetzner Cloud VPS (CX22: 2 vCPU, 4GB RAM)
Methodology:
- Content generated via
./ava stress:generate post <count> - Benchmarks run via
./ava benchmark --compare --iterations=5 - Each test iterated 5 times, average result shown
- In-memory backend state cleared between iterations, so these are cold backend-access measurements
- Index rebuilt fresh for each backend during comparison
Note: OPcache is disabled for CLI by default. This doesn't affect these results since OPcache only caches compiled PHP bytecode, not data operations. The benchmarks measure I/O, unserialization, and query performance. SQLite's reported ~2.7 KB memory usage is PHP allocation only; native SQLite page-cache and mmap memory are not included.
Understanding the Results
Why Array is faster for homepages: It uses a pre-sorted Recent Cache that loads instantly. Perfect for blog homepages and recent-content archive pages.
Why SQLite wins for indexed operations at scale: Database indexes let it count, filter, sort, paginate, and fetch individual rows without loading the entire index.
Why memory matters: Array loads the index into RAM per concurrent uncached request. At 10k posts, that's 70.7 MB per request. With 10 concurrent users hitting uncached pages, that's about 707 MB. SQLite's PHP allocation is only a few kilobytes per query, though native SQLite page-cache and mmap memory are outside PHP's reported allocation.
Real-world impact: With webpage caching enabled (the default), 95%+ of requests are served from cache and don't touch the index at all. The benchmarks above matter mainly for:
- Search results (always uncached)
- First visit to any page (until cached)
- Deep archive pagination beyond page 20
Choosing a Backend
| Situation | Recommended Backend |
|---|---|
| Up to roughly 1,000 items | Array + igbinary (default) |
| 1,000-10,000 items | Benchmark both; favour SQLite for filtering, sorting, deep pagination, or constrained memory |
| 10,000+ items | SQLite strongly recommended |
| Memory-limited server | SQLite |
| No igbinary extension | Consider SQLite much earlier |
| No pdo_sqlite extension | Array |
How to decide:
- Start with the default (Array + igbinary)
- Run
./ava benchmark --compareon your actual server - Monitor memory with
./ava statusor your host's tools - Switch if needed—it's one line in
app/config/ava.php
Configuration Reference
All content index settings live in app/config/ava.php:
'content_index' => [
// When to rebuild the index
'mode' => 'auto', // 'auto' | 'never' | 'always'
// Storage backend
'backend' => 'array', // 'array' | 'sqlite'
// Compression (array backend only)
'use_igbinary' => true, // Uses igbinary if available, otherwise serialize()
// Optional: pre-render Markdown → HTML during rebuild
'prerender_html' => true, // Stores rendered HTML for published items
],
| Option | Values | Recommendation |
|---|---|---|
mode |
'auto' (rebuild on changes)'never' (manual only)'always' (every request) |
'auto' for development'never' for production |
backend |
'array' or 'sqlite' |
'array' for small sites'sqlite' for memory-constrained sites or 10k+ items |
use_igbinary |
true or false |
Keep true (auto-detects) |
prerender_html |
true or false |
true (default). Disable if you prefer faster rebuilds. |
About prerender_html: This pre-renders Markdown → HTML during rebuild and stores it in html_cache.bin. It speeds up uncached renders but increases rebuild time and cache size.
Webpage Caching
Webpage caching is where the real performance magic happens. After the first visit to any page, Ava CMS saves the complete HTML to disk. Subsequent visitors receive the cached file directly—up to 250× faster than rendering.
Performance:
- First visit: approximately 1.5-5ms (renders template, processes Markdown)
- Cached visit: approximately 0.02-0.04ms (serves static HTML file)
- Handles thousands of requests/second on modest hardware
How it works:
- Visitor requests
/blog/my-post - Check if
storage/cache/pages/blog_my-post_[hash].htmlexists - HIT: Return cached HTML (response headers:
X-Page-Cache: HIT,X-Cache-Age: <seconds>) - MISS: Render page, save to cache, return HTML (header:
X-Page-Cache: MISS)
Fast path optimization: In manual index mode (content_index.mode = never), cache HITs for GET requests without query strings (except UTM) can serve the HTML before the application even boots. No plugin loading, no index checks—just pure static file serving. In automatic mode, Ava CMS must boot first to check whether source content is fresh.
Configuration
Webpage caching is configured in app/config/ava.php:
'webpage_cache' => [
'enabled' => true, // Enable/disable caching
'ttl' => null, // null = forever, or seconds (3600 = 1 hour)
'exclude' => [ // URL patterns to never cache
'/api/*',
'/search',
],
],
TTL (Time-To-Live):
null(default): Cache until you run./ava rebuildor./ava cache:clear3600: Cache for 1 hour86400: Cache for 24 hours
Exclude patterns: Use glob-style patterns to prevent caching specific URLs.
Per-page control: Add cache: false to any page's frontmatter to bypass caching:
---
title: Live Dashboard
cache: false # This page always renders fresh
---
What Gets Cached (and What Doesn't)
✅ Cached:
- Regular pages and posts
- Archive/list pages
- Taxonomy pages (categories, tags)
- Homepage and pagination
❌ Never cached:
- URLs with query parameters (except UTM marketing params)
- Pages with
cache: falsein frontmatter - POST/PUT/DELETE requests
- URLs matching
excludepatterns
UTM parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) are automatically ignored, so marketing campaigns don't pollute your cache.
Cache Management
Automatic clearing: The cache clears automatically when you run ./ava rebuild or when content changes are detected in 'auto' mode.
Manual clearing:
./ava cache:stats # View cache statistics
./ava cache:clear # Clear all cached webpages
./ava cache:clear /blog/* # Clear specific paths
Tools & Troubleshooting
Check Your Site's Performance
Use ./ava status to see everything at a glance:
▄▄▄ ▄▄ ▄▄ ▄▄▄ ▄▄▄▄ ▄▄ ▄▄ ▄▄▄▄ ██▀██ ██▄██ ██▀██ ██▀▀▀ ██▀▄▀██ ███▄▄ ██▀██ ▀█▀ ██▀██ ▀████ ██ ██ ▄▄██▀ ─── Content Index ───────────────────────────────────── Status: ● Fresh Mode: auto Backend: Array (igbinary) Cache: Full index 4.2 MB, Slug lookup 412 KB Built: 2026-01-12 14:30:00 ─── Webpage Cache ───────────────────────────────────── Status: ● Enabled TTL: Forever (until cleared) Cached: 42 webpages Size: 1.2 MB
Common Issues & Solutions
Content changes not appearing
Cause: Index mode is set to 'never' or index hasn't rebuilt.
Fix:
./ava rebuild # Rebuild index and clear webpage cache
If that doesn't work:
rm storage/cache/fingerprint.json # Force rebuild on next request
Webpages not caching
Check these in order:
- Is caching enabled? Run
./ava statusor check'webpage_cache.enabled'in config - Query parameters? URLs with
?params(except UTM) won't cache - Exclude pattern match? Check
'webpage_cache.exclude'in config - Page has
cache: false? Check the page's frontmatter
High memory usage
Symptoms: Server running out of memory, PHP fatal errors
Solutions:
- Check your content size:
./ava status - Test memory usage:
./ava benchmark --compare - Switch to SQLite if > 10k posts:
'backend' => 'sqlite'in config - Check for memory leaks in custom plugins/themes
SQLite backend errors
Error: "could not find driver" or "pdo_sqlite not installed"
Fix:
php -m | grep -i sqlite # Check if installed
If not installed:
- Contact your host to enable
pdo_sqlite - Or use
'backend' => 'array'instead
Slow performance despite caching
Troubleshooting steps:
- Check cache hit rate: Look for
X-Page-Cache: HITin response headers - Test uncached speed:
./ava benchmark - Check server resources: CPU, RAM, disk I/O
- Profile specific pages: Add
?XDEBUG_PROFILE=1if Xdebug installed
Running Your Own Benchmarks
Test performance on your own server:
# 1. Generate test content
./ava stress:generate post 5000
# 2. Run benchmarks (current backend)
./ava benchmark
# 3. Compare all backends
./ava benchmark --compare
# 4. More iterations for accuracy
./ava benchmark --compare --iterations=10
# 5. Clean up when done
./ava stress:clean post
Benchmark Options
| Option | Description |
|---|---|
--compare |
Test all available backends side-by-side |
--iterations=N |
Number of test iterations (default: 5) |
--help |
Show benchmark help |
Performance Best Practices
Development settings (see changes immediately):
'content_index' => ['mode' => 'auto'],
'webpage_cache' => ['enabled' => false],
Production settings (maximum performance):
'content_index' => [
'mode' => 'never', // Manual rebuilds only
'backend' => 'array', // Or 'sqlite' for constrained memory or 10k+ items
],
'webpage_cache' => [
'enabled' => true,
'ttl' => null, // Cache until rebuild
],
Deployment workflow:
git pull # Pull latest changes
./ava rebuild # Rebuild index and clear cache
CDN Integration
Ava CMS's webpage cache is already very fast, but adding a CDN provides:
- Global edge caching: Serve from locations worldwide
- DDoS protection: Absorb attack traffic
- SSL termination: Offload HTTPS processing
Popular options: Cloudflare (free tier), BunnyCDN, Fastly. See the Ava for Cloudflare® plugin for handy Cloudflare utilities such as automatic cache purging.
Quick Reference
Check status:
./ava status # View cache status and configuration
./ava cache:stats # Webpage cache statistics
Rebuild after changes:
./ava rebuild # Rebuild index and clear webpage cache
./ava cache:clear # Clear webpage cache only
Test performance:
./ava benchmark # Test current backend
./ava benchmark --compare # Compare all backends
Generate test content:
./ava stress:generate post 5000 # Create 5,000 test posts
./ava stress:clean post # Remove test posts
Key files:
app/config/ava.php- Configure caching and backendstorage/cache/- All cache filesstorage/cache/fingerprint.json- Delete to force rebuild