Performance Architecture
2027-ready allocation-floor database layer with schema-aware conversion, ring-buffer pooling, fused query paths, and zero-tax SDK dispatching.
On this page
π Performance Benchmarks (2026-2027)
SveltyCMS delivers enterprise-grade performance through its database-agnostic architecture. For the complete set of verified benchmarks, stress test results, and hardware context, please refer to the Performance Benchmarks document.
SQLite β Local/Edge (Verified June 2026)
| Operation | Latency | RPS |
|---|---|---|
| FIND ONE | 0.090 ms | 10,386 |
| FIND MANY (50) | 0.109 ms | 8,863 |
| INSERT | 0.142 ms | 2,657 |
| DELETE | 0.051 ms | 14,447 |
| NATIVE UPSERT | 0.093 ms | 9,102 |
| Peak Throughput | β | 15,617 req/s |
| LocalCMS SDK Overhead | β | 0.00% (verified parity) |
Cross-Database Impact (All 4 Engines)
| Optimization | SQLite | PostgreSQL | MariaDB | MongoDB |
|---|---|---|---|---|
| Schema-aware row conversion | β Full | β Full | π‘ Planned | N/A (own path) |
| Ring-buffer result pool | β Full | β Full | π‘ Planned | β Full |
| Conditions array pool | β Full | β Full | π‘ Planned | N/A (own path) |
| Fused mapQuery (no IR objects) | β Full | β Full | π‘ Planned | β Full |
| forβ¦in (zero Object.entries) | β Full | β Full | π‘ Planned | β Full |
| Pre-allocated meta object | β Full | β Full | π‘ Planned | β Full |
| MariaDB double-parse isolated | N/A | β Benefit | π‘ Planned | N/A |
Parallel list + count (websiteTokens.getAll) |
β Full | β Full | π‘ Planned | β Full |
SQL family gets ~5-8 fewer allocations per filtered query. MongoDB gets ~2-3 fewer (result pool + fused mapQuery). All 4 databases benefit from shared BaseAdapter optimizations.
Credential list hot-path
system.websiteTokens.getAll() issues the row query and COUNT(*) in parallel on all four engines (Promise.all in relational-system.ts and website-token-methods.ts). Combined with the { tenantId, name } compound index, admin token tables stay sub-millisecond even under multi-tenant filtering.
Philosophy
SveltyCMS implements best-practice performance patterns from day one. Every feature is built with performance as a core requirement, not an afterthought. The 2027 allocation-floor work pushes the database layer to the practical limit of the current architecture (Drizzle ORM + DatabaseResult contract).
2027 Allocation-Floor Optimizations
Schema-Aware Row Conversion
Instead of checking every row key against DATE_FIELDS/JSON_FIELDS Set lookups on every row read/write, table schemas are pre-computed once during table creation. Only known date and JSON columns are converted β eliminating per-row Set.has() calls and enabling targeted iteration.
// Before: checks every key against Sets
for (const k in row) {
if (DATE_FIELDS.has(k)) {
/* convert date */
} else if (JSON_FIELDS.has(k)) {
/* parse JSON */
}
result[k] = row[k];
}
// After: only touches known columns
registerTableSchema("posts", ["_id", "title", "createdAt", "metadata"]);
// converts only "createdAt" (date) and "metadata" (JSON) β skips everything else
Fused Query Construction (SQL + MongoDB)
Both mapQuery implementations now build database conditions directly from user queries without intermediate IR (Intermediate Representation) objects. The old pattern (query β QueryIR β LogicalGroup β QueryCondition[] β SQL conditions) is replaced with a single fused walk.
- SQL path (
drizzle-sql-helpers.ts):addFilterConds+addSingleConditionbuildSQL[]directly - Mongo path (
mongodb/adapter-core.ts):addMongoConds+addMongoConditionbuild filter objects directly - Eliminated: QueryIR, LogicalGroup, QueryCondition[] intermediates (~3-5 objects per filtered query)
- Eliminated: All
Object.entries()array allocations (replaced withfor...in)
Ring-Buffer Result Pool
All BaseAdapter.wrap() calls now use a 64-slot ring-buffer pool for { success, data } result wrappers. Pooled slots are safe in single-threaded JS β callers consume results synchronously after await. Eliminates 100% of result wrapper allocations.
Ring-Buffer Conditions Array Pool
Filtered queries (mapQuery) reuse conditions: SQL[] arrays from a 32-slot ring-buffer pool instead of allocating [] each time. Arrays are cleared via .length = 0 without deallocation.
Pre-Allocated Meta Object
The { executionTime } meta sub-object is pre-allocated once per adapter. wrap() mutates this._meta.executionTime instead of creating a new object per call.
MariaDB Double-Parse Isolation
MariaDBβs native JSON columns can double-encode. The double-parse loop is now opt-in (mariaDoubleParseJson: true) β PostgreSQL and SQLite paths never pay this cost.
SQLite RETURNING Cache
The insertMany path caches whether RETURNING is supported per SQLite instance. Avoids repeated try/catch + logger.warn overhead in benchmarks and unsupported table scenarios.
Zero-Tax SDK Dispatcher
Self-overwriting getters eliminate Proxy overhead after first access. Verified: 0.00% middleware tax (LocalCMS is statistically identical to direct adapter calls).
Structural Improvements (Shared by All Adapters)
- Proxy extraction: 3 scattered Proxy factories β 1 shared
core/proxy-utils.tswith 23 dedicated tests - Tenant filter centralization: ~4+ duplicate sites β single source of truth in
relational-utils.ts - Query IR removal: standalone
query-ir.tsremoved; translator inlined into the only 2 real consumers - Mongo file reduction: 43 β 39 files (4 thin module/model pairs merged)
- Code reduction: 1,105 lines deleted across 36 files
- Lazy Zero-Allocation GraphQL Loaders: Eager per-request user, media, and collection loaders are refactored into on-demand lazy getters, eliminating memory overhead on requests that do not query those fields.
- Direct Primary Key Index Loading: Replaced MongoDB-style
$infilter translations with explicit, database-agnosticdb.crud.findByIdsindex lookups, allowing SQL and document database engines to resolve relational widget fields with zero query-parsing overhead.
Phase 1: Database Indexing & Optimization
Status: β FULLY IMPLEMENTED Impact: Exponential query speedup (O(n) β O(log n))
Background IndexOptimizer service automatically manages database performance through schema-driven indexing, background building, and multi-database support (MongoDB Compound/Text, SQL B-Tree).
Batch Relational Upserts
setMany and bulkUpdate use true multi-row INSERT ... ON CONFLICT DO UPDATE via Drizzle ORM. During setup seeding, ~80 individual DB queries β single batch operation. Dialect-specific handling (PostgreSQL/SQLite excluded.*, MariaDB/MySQL VALUES()).
Negative Caching
O(1) missing-key cache prevents βDB miss stormsβ (verified 2392x speedup for repeated misses).
Prefix-Bucketed Invalidation
O(1) namespace clearing replaces O(N) cache scans.
Database Tuning Recommendations
SveltyCMS ships with zero-configuration defaults that work out of the box with any Docker Compose setup. For production or high-throughput workloads, the following database-level tuning tweaks can yield 2-10x latency improvements without changing any CMS code.
All tuning below is applied at the database server level (e.g., docker compose command overrides or my.cnf). SveltyCMS adapters adapt automatically β no CMS config changes needed.
MariaDB / MySQL
| Setting | Default (Docker) | Recommended | Why |
|---|---|---|---|
max_connections |
151 | 500β1000 | Default ceiling easily exhausted under concurrent CMS workloads. Verified: Max_used_connections=152 exceeded the 151 cap, causing connection saturation and 1041ms HTTP latency spikes. |
innodb_buffer_pool_size |
128 MB | 70-80% of available RAM | Tiny default forces constant disk reads. For a 4 GB container: innodb_buffer_pool_size=3G. This is the single most impactful tuning knob for MariaDB performance. |
innodb_log_file_size |
96 MB | 512 MB β 2 GB | Larger redo logs reduce write contention and checkpoint pressure. |
innodb_io_capacity |
200 | 2000β5000 | Modern SSDs can handle 50k+ IOPS. Default throttles InnoDB background I/O. |
innodb_flush_log_at_trx_commit |
1 (fsync per commit) | 2 | Setting to 2 avoids per-commit fsync (flushes once per second). Caution: trade 1-second durability window for faster writes β acceptable for most CMS workloads. |
table_open_cache |
2000 | 4000β8000 | Reduces table open/close churn when many collections are active. |
Measured gains with all tweaks (Intel i7-13700H, Docker, MariaDB 12.2.2):
| Operation | Default (avg / p95) | Tuned (avg / p95) | Improvement |
|---|---|---|---|
| INSERT | 1.229ms / 3.061ms | 0.791ms / 1.100ms | -36% / -64% |
| DELETE | 2.044ms / 2.664ms | 0.665ms / 0.827ms | -67% / -69% |
| BULK INSERT (100) | 5.497ms / 10.170ms | 4.460ms / 6.772ms | -19% / -33% |
| TX Commit | 4.268ms / 8.675ms | 2.250ms / 3.869ms | -47% / -55% |
| TX Rollback | 3.016ms / 5.393ms | 3.450ms / 6.188ms | ~same |
| INSERT RPS | 716 req/s | 994 req/s | +39% |
| DELETE RPS | 428 req/s | 1,293 req/s | +202% |
| TX Commit RPS | 225 tx/s | 371 tx/s | +65% |
Read operations (SELECT, FIND) are minimally affected by tuning on small datasets because the default 128MB buffer pool is sufficient for light workloads. Gains compound significantly on production datasets (10k+ entries) and under concurrent multi-tenant load.
Docker Compose override example (docker-compose.override.yml):
services:
mariadb:
command:
- --max_connections=500
- --innodb_buffer_pool_size=4G
- --innodb_log_file_size=512M
- --innodb_io_capacity=2000
- --innodb_flush_log_at_trx_commit=2
PostgreSQL
| Setting | Default (Docker) | Recommended | Why |
|---|---|---|---|
max_connections |
100 | 200β400 | Default caps connection pool quickly. Beyond 400, add PgBouncer for connection pooling. |
shared_buffers |
128 MB | 25% of available RAM | Default is too small for active working sets. |
effective_cache_size |
4 GB | 75% of available RAM | Helps query planner choose index scans over seq scans. |
work_mem |
4 MB | 16β64 MB | Per-sort/join memory. Important for complex relational queries. |
wal_buffers |
4 MB | 64 MB | Reduces WAL write contention during bulk operations. |
random_page_cost |
4.0 | 1.1 | Modern SSDs have near-zero seek cost. Lower value encourages index scans. |
synchronous_commit |
on |
off |
Disables per-transaction fsync. Caution: 1-second durability window for ~10x faster writes. |
Measured gains with all tweaks (Intel i7-13700H, Docker, PostgreSQL 18.3):
| Operation | Default (avg / p95) | Tuned (avg / p95) | Improvement |
|---|---|---|---|
| INSERT | 2.201ms / 4.301ms | 1.463ms / 1.850ms | -34% / -57% |
| UPDATE | 3.036ms / 4.437ms | 1.424ms / 1.729ms | -53% / -61% |
| DELETE | 2.587ms / 3.112ms | 1.200ms / 1.422ms | -54% / -54% |
| NATIVE UPSERT | 2.638ms / 3.208ms | 1.235ms / 1.454ms | -53% / -55% |
| TX Commit | 4.620ms / 7.688ms | 4.029ms / 6.843ms | -13% / -11% |
| INSERT RPS | 431 req/s | 577 req/s | +34% |
| UPDATE RPS | 305 req/s | 603 req/s | +98% |
| DELETE RPS | 333 req/s | 703 req/s | +111% |
| HTTP E2E RPS | 810 req/s | 999 req/s | +23% |
Write-heavy operations (INSERT, UPDATE, DELETE, UPSERT) benefit most from
synchronous_commit=offand largershared_buffers. Read operations see moderate gains from better query planning (random_page_cost=1.1,effective_cache_size=6GB). The HTTP E2E throughput improves by 23% as the cumulative middleware + DB pipeline becomes more efficient under load.
MongoDB
| Setting | Default (Docker) | Recommended | Why |
|---|---|---|---|
wiredTigerCacheSizeGB |
50% of RAM β 1 GB | 60-70% of available RAM | On machines with <8GB RAM, the default may be too small. On machines with >16GB RAM, defaults are already generous and tuning yields minimal gain. |
maxConnections |
1000 | 2000+ | Default is generous; only raise if you see connection saturation. Connections available on a typical system exceed 400k. |
syncdelay |
60s | Keep default | Lower values do NOT improve performance β more frequent journal flushes add I/O overhead. Verified: syncdelay=10 made 1000-doc inserts 26% slower (43ms vs 34ms) on the same hardware. Keep default at 60s; reduce only if you need tighter durability guarantees. |
Measured baseline (Intel i7-13700H, 31GB RAM, Docker, WiredTiger cache 16GB):
| Metric | Default | Notes |
|---|---|---|
| INSERT 1000 docs (1KB each) | 34ms (29,412 ops/s) | Cache already 16GB β no disk bottleneck |
| READ 1000 docs | 14ms (71,429 ops/s) | All in cache |
| HTTP E2E (Truth Audit) | 1.175ms avg / 2.157ms p95 | 851 req/s |
On this machine (31GB RAM), MongoDBβs wiredTigerCacheSizeGB default is already ~16GB β far larger than the benchmark dataset. The tuning benefit of raising it further is minimal unless you have a dataset exceeding the cache size. On constrained machines (<4GB RAM), explicitly setting wiredTigerCacheSizeGB=1-2GB prevents OOM issues.
MongoDB tuning requires a custom config file mounted into the container (not just command flags). See MongoDB docs for details.
All 4 Adapters Now Run Under Bun
All four database adapters (MariaDB, PostgreSQL, SQLite, MongoDB) run natively under bun test β no need for vitest/Node.js for any database.
The fix: A v8 shim in src/utils/v8-shim.ts patches process.getBuiltinModule('v8') to return a safe stub for startupSnapshot.isBuildingSnapshot() β the only Bun-incompatible API call in the dependency chain:
| Layer | Technology | Bun compat |
|---|---|---|
| ORM | Mongoose 9.x | β Works |
| Driver | mongodb npm package |
β Works |
| Serialization | bson |
β
Shimmed β v8.isBuildingSnapshot() β returns false |
Previously (pre-shim):
NotImplementedError: node:v8 isBuildingSnapshot is not yet implemented in Bun.
at node_modules/bson/lib/bson.cjs:2610:30
Now: The bson package loads cleanly under Bun, and all MongoDB benchmarks pass (database-performance, transaction-acid, truth-latency, etc.) with no runtime differences from SQL adapters.
The benchmark matrix runner no longer needs special vitest dispatch for MongoDB β every database uses bun test uniformly.
SQLite
SQLite is embedded (no Docker tuning needed). For WAL mode (enabled by default in SveltyCMS):
| Setting | Default | Recommended | Why |
|---|---|---|---|
PRAGMA cache_size |
-2000 (2 MB) | -64000 (64 MB) | Larger page cache reduces disk reads. |
PRAGMA mmap_size |
0 (disabled) | 26843545600 (26 GB) | Memory-maps the database file for zero-copy reads. |
These SQLite PRAGMAs are set automatically by SveltyCMS on connection β no manual action required.
Redis
Redis is used for caching layers (L2 cache, rate limiting, session store). In-memory β tuning focuses on eviction policy, persistence, and connection handling.
| Setting | Default (Docker) | Recommended | Why |
|---|---|---|---|
maxmemory |
0 (no limit) | 70-80% of available RAM | Prevents Redis from consuming all host memory under cache-heavy workloads. |
maxmemory-policy |
noeviction |
allkeys-lru |
Evicts least-recently-used keys when memory limit is hit. Prevents write errors on cache saturation. |
tcp-keepalive |
300s | 60s | Detects dead connections 5x faster, freeing connection slots under concurrency. |
timeout |
0 (no timeout) | 300s | Closes idle client connections after 5 minutes. Prevents connection leak accumulation. |
maxclients |
10000 | 20000 | Default is generous; only raise if you see connection saturation. |
save |
3600 1 300 100 60 10000 (RDB snapshots) |
"" (disable) |
Disabling RDB persistence avoids fork pauses. Caution: trade durability for latency stability β acceptable when Redis is a cache (not primary store). |
appendonly |
no |
no |
Keep AOF off for cache-only Redis. Enabling adds fsync overhead with no benefit for ephemeral cache data. |
Measured gains with all tweaks (Intel i7-13700H, Docker, redis-benchmark -c 10 -n 5000 -d 100):
| Metric | Default | Tuned | Improvement |
|---|---|---|---|
| SET throughput | 138,889 req/s | 156,250 req/s | +12.5% |
| GET throughput | 161,290 req/s | 166,667 req/s | +3.3% |
| p50 latency | 0.039ms | 0.039ms | ~same |
The raw throughput gain is modest because redis-benchmark uses short-lived connections. The real value of tuning is operational:
allkeys-lruprevents OOM write errors when cache fills (infinite improvement vs crashing)tcp-keepalive=60detects dead connections 5x faster, preventing socket exhaustion under concurrent loadtimeout=300auto-reclaims idle connections that would otherwise accumulate over days of uptime- Disabling RDB (
save "") avoids fork pauses that can spike p99 latency by 100ms+
Docker Compose override example (docker-compose.override.yml):
services:
redis:
command:
- --maxmemory 2gb
- --maxmemory-policy allkeys-lru
- --tcp-keepalive 60
- --timeout 300
Docker Compose Override β All Databases
Combine all overrides into a single docker-compose.override.yml alongside your existing compose.yaml:
services:
mariadb:
command:
- --max_connections=500
- --innodb_buffer_pool_size=4G
- --innodb_log_file_size=512M
- --innodb_io_capacity=2000
- --innodb_flush_log_at_trx_commit=2
postgres:
command:
- -c max_connections=400
- -c shared_buffers=2GB
- -c effective_cache_size=6GB
- -c work_mem=32MB
- -c wal_buffers=64MB
- -c random_page_cost=1.1
- -c synchronous_commit=off
redis:
command:
- --maxmemory 2gb
- --maxmemory-policy allkeys-lru
- --tcp-keepalive 60
- --timeout 300
MongoDB tuning requires a custom config file mounted into the container (not just command flags). See the MongoDB section above for details and the official docs for syntax.