This project is under very early, active development and may contain bugs or security issues. It is likely not ready for production websites.

You are responsible for reviewing, testing, and securing any deployment. Ava CMS is provided as free, open-source software without warranty (GNU General Public License), see LICENSE.

Performance

Ava CMS is designed to be fast by default. To achieve this, it uses a two-layer performance strategy:

  1. Content Indexing: A pre-built index of your content metadata to avoid parsing Markdown files on every request.
  2. 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 igbinary installed and webpage caching enabled)
  • Run ./ava rebuild after content changes in production
  • Check ./ava status to 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' in app/config/ava.php)
  • Without igbinary: Consider SQLite much earlier

Having performance issues?

  • Run ./ava status to check index freshness
  • Run ./ava cache:stats to check webpage cache
  • Run ./ava benchmark --compare to 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:

  1. Scans all your Markdown files recursively
  2. Parses frontmatter and extracts metadata
  3. Validates content for common issues (YAML syntax, required fields, duplicate slugs/IDs)
  4. Builds optimized indexes for fast lookups
  5. 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_sqlite PHP 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:

  1. Content generated via ./ava stress:generate post <count>
  2. Benchmarks run via ./ava benchmark --compare --iterations=5
  3. Each test iterated 5 times, average result shown
  4. In-memory backend state cleared between iterations, so these are cold backend-access measurements
  5. 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:

  1. Start with the default (Array + igbinary)
  2. Run ./ava benchmark --compare on your actual server
  3. Monitor memory with ./ava status or your host's tools
  4. 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:

  1. Visitor requests /blog/my-post
  2. Check if storage/cache/pages/blog_my-post_[hash].html exists
  3. HIT: Return cached HTML (response headers: X-Page-Cache: HIT, X-Cache-Age: <seconds>)
  4. 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 rebuild or ./ava cache:clear
  • 3600: Cache for 1 hour
  • 86400: 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: false in frontmatter
  • POST/PUT/DELETE requests
  • URLs matching exclude patterns

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:

  1. Is caching enabled? Run ./ava status or check 'webpage_cache.enabled' in config
  2. Query parameters? URLs with ?params (except UTM) won't cache
  3. Exclude pattern match? Check 'webpage_cache.exclude' in config
  4. Page has cache: false? Check the page's frontmatter

High memory usage

Symptoms: Server running out of memory, PHP fatal errors

Solutions:

  1. Check your content size: ./ava status
  2. Test memory usage: ./ava benchmark --compare
  3. Switch to SQLite if > 10k posts: 'backend' => 'sqlite' in config
  4. 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:

  1. Check cache hit rate: Look for X-Page-Cache: HIT in response headers
  2. Test uncached speed: ./ava benchmark
  3. Check server resources: CPU, RAM, disk I/O
  4. Profile specific pages: Add ?XDEBUG_PROFILE=1 if 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 backend
  • storage/cache/ - All cache files
  • storage/cache/fingerprint.json - Delete to force rebuild