ahmet gedikHow SQLite WAL mode enables concurrent reads and writes for PHP web applications, with real benchmar
SQLite gets dismissed as a "toy database" for web applications, but with the right configuration it can handle real production traffic. The secret weapon is WAL (Write-Ahead Logging) mode. Here's how I use it to power ViralVidVault — a video platform serving thousands of pages while cron jobs write new data concurrently.
In SQLite's default journal mode (DELETE), a write operation locks the entire database. If your cron job is inserting 200 videos, every web request that tries to read the database will wait. On a video platform, this is unacceptable.
WAL mode flips the locking model: readers never block writers, and writers never block readers. Multiple readers can access the database simultaneously while a single writer appends to a separate write-ahead log.
<?php
class Database
{
private \PDO $pdo;
public function __construct(string $dbPath)
{
$this->pdo = new \PDO("sqlite:{$dbPath}");
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// Enable WAL mode for concurrent access
$this->pdo->exec('PRAGMA journal_mode=WAL');
// Additional performance pragmas
$this->pdo->exec('PRAGMA synchronous=NORMAL');
$this->pdo->exec('PRAGMA cache_size=-8000'); // 8MB cache
$this->pdo->exec('PRAGMA temp_store=MEMORY');
$this->pdo->exec('PRAGMA mmap_size=268435456'); // 256MB mmap
}
public function getPdo(): \PDO
{
return $this->pdo;
}
}
| Pragma | Value | Effect |
|---|---|---|
journal_mode=WAL |
WAL | Enables Write-Ahead Logging |
synchronous=NORMAL |
NORMAL | Fsync at checkpoints only (safe for WAL) |
cache_size=-8000 |
8MB | Larger page cache for reads |
temp_store=MEMORY |
MEMORY | Temp tables in RAM |
mmap_size=268435456 |
256MB | Memory-map reads for speed |
On ViralVidVault, here's what happens during a cron run:
Timeline:
|---- Web request (SELECT videos) ----|
|---- Cron job (INSERT 200 videos) ----|
|---- Web request (SELECT categories) ----|
|---- Web request (SELECT trending) ----|
Without WAL: Web requests WAIT for cron INSERT to finish
With WAL: Everything runs concurrently, zero blocking
I benchmarked both modes with Apache Bench while running a simulated cron insert:
# Simulate cron writing 500 rows
php simulate_cron_write.php &
# Simultaneously benchmark reads
ab -n 1000 -c 10 https://viralvidvault.com/
| Metric | DELETE mode | WAL mode | Improvement |
|---|---|---|---|
| Avg response | 245ms | 92ms | 62% faster |
| P99 response | 1,200ms | 180ms | 85% faster |
| Failed requests | 12 | 0 | 100% |
| Throughput | 38 req/s | 105 req/s | 2.7x |
The P99 improvement is the most striking. Without WAL, tail latency spikes during writes. With WAL, response times stay consistent.
WAL files grow until a checkpoint merges them back into the main database. SQLite auto-checkpoints at 1000 pages by default. You can also trigger manual checkpoints:
// After cron completes all writes
$db->exec('PRAGMA wal_checkpoint(TRUNCATE)');
.db-wal and .db-shm files alongside your databaseFor sites like viralvidvault.com with:
SQLite with WAL mode is not just adequate — it's optimal. Zero config, zero maintenance, zero external dependencies. The entire database is a single file you can back up with cp.
synchronous=NORMAL for WAL (it's safe and faster)cache_size and mmap_size pragmas for read performanceThis powers ViralVidVault — your vault of viral videos from around the world.