Benchmarks with their method attached.
One rule governs this page: no number is called measured unless a run produced it. Every value is tagged measured, target, vendor-published or pending, and every scenario and profile behind them is described below.
Results generated 2026-10-05 from runs of 2026-10-05. The harness repository is not published yet, so ask through contact if you want to reproduce a figure.
How LyEve compares
LyEve, Strapi, Directus, Payload: a compiled Go binary against Node.js runtimes. A cell reads pending until that target has run through the same harness.
Container image size
Uncompressed image: the sum of its layers from `docker history` (amd64). A compiled Go binary against a Node runtime plus node_modules.
- LyEve 78MB Measured
- Strapi 1,884MB Measured
- Directus 970MB Measured
- Payload 1,994MB Measured
Measured LyEve: median of 4 runs, each on a fresh stack
Every metric for all four targets.
Every value here is the median of at least 3 runs per target on Intel Core Ultra 5 125H (18 threads), 32 GB RAM, NVMe, Ubuntu 26.04, Docker 29.8, each on a fresh stack, with the lowest and highest run beneath it. Every target ran on the same machine, so the results compare directly.
| Metric | LyEveGo | StrapiNode.js | DirectusNode.js | PayloadNode.js | LyEve lead |
|---|---|---|---|---|---|
| Container image size Uncompressed image: the sum of its layers from `docker history` (amd64). A compiled Go binary against a Node runtime plus node_modules. | 78 MB Measured 78 to 78 | 1,884 MB Measured 1,884 to 1,884 | 970 MB Measured 970 to 970 | 1,994 MB Measured 1,994 to 1,994 | 12.4× |
| Idle memory Container memory from `docker stats` at rest after a restart, empty database, default config: the median of five samples taken after 45 seconds of settling. LyEve runs the free engine: every plugin compiled in, the free baseline started, no license. | 26.5 MB Measured 26 to 28 | 100.5 MB Measured 97 to 102 | 188 MB Measured 181 to 197 | 101 MB Measured 100 to 101 | 3.8× |
| p99 read-by-id One record by id at 500 req/s (scenario S01). | 1.8 ms Measured 2 to 2 | 751.9 ms Measured 717 to 779served 301 of 500 req/s | 112.1 ms Measured 66 to 120 | 9.7 ms Measured 9 to 24 | 5.4× |
| p99 list-50 First page of 50 at 300 req/s (scenario S02). | 2.9 ms Measured 3 to 3 | 1,621 ms Measured 1,612 to 1,659served 133.5 of 300 req/s | 15.2 ms Measured 12 to 16 | 8.6 ms Measured 8 to 16 | 3× |
| p99 create Create one record at 100 req/s (scenario S03). | 6.7 ms Measured 6 to 7 | 17.2 ms Measured 17 to 22 | 12.2 ms Measured 11 to 12 | 7.8 ms Measured 7 to 8 | 1.2× |
| p99 filter Filter by an indexed field, 25 rows, at 200 req/s (scenario S04). Each run first proves the filter applies: a value no record holds must return no rows, and a real value must return only matching rows. | 2.4 ms Measured 2 to 2 | 1,379 ms Measured 1,360 to 1,456served 158.5 of 200 req/s | 5.9 ms Measured 6 to 6 | 5.5 ms Measured 5 to 6 | 2.3× |
| p99 login Token issuance at 20 req/s, where slower is expected (password hash). A target whose stock config rate-limits authentication refuses the burst and reads pending here rather than posting a number for refused logins. Scenario S05. | - | - | 505.9 ms Measured 506 to 506 | 65.5 ms Measured 65 to 66 | - |
| Cold start From `docker start` of a stopped container to the first request the application serves (includes DB connect and migration check), polled every 50 ms. | 488 ms Measured 329 to 751 | 2,213 ms Measured 1,711 to 2,345 | 7,251 ms Measured 7,129 to 7,271 | 2,665 ms Measured 2,614 to 2,821 | 4.5× |
Engine micro-benchmarks
Hot-path micro-benchmarks from LyEve's own suite. Measured 2026-10-05 on Intel Core Ultra 5 125H (18 threads), Go 1.27.1. They are CPU-bound and are not comparable with the end-to-end rows above.
Auth / crypto
| JWT sign (HMAC) | 5.2 µs |
| JWT verify (parse) | 6.4 µs |
| Ed25519 sign | 16.1 µs |
| Ed25519 verify | 36.5 µs |
| Password hash (bcrypt, cost 10) | 49 ms |
| Password hash (argon2id) | 18 ms |
Database (placeholder rewrite hot path)
| PostgreSQL passthrough | 2.1 ns |
| MySQL rewrite ($N → ?) | 269 ns |
| MSSQL rewrite ($N → @pN) | 246 ns |
Middleware
| Full stack (4 middlewares) | 5.1 µs |
| Tenant header (no-op) | 452 ns |
| Rate limiter (allow) | 2.0 µs |
core bench suite (go test -bench -benchmem -count=5, median), commit da80fdd (v0.51.2)
REST targets we ship against.
Per-endpoint p50 / p99 we build against. These are engineering targets, not measurements, and each is promoted to measured only when a sustained-throughput sweep has run it.
| Endpoint | Sustained RPS | p50 | p99 |
|---|---|---|---|
| GET /content/{schema} (50 items) | 1,000 | 8 ms | 50 ms |
| GET /content/{schema}/{id} | 2,000 | 3 ms | 20 ms |
| POST /content/{schema} | 500 | 15 ms | 80 ms |
| PUT /content/{schema}/{id} | 400 | 20 ms | 100 ms |
| DELETE /content/{schema}/{id} | 800 | 8 ms | 40 ms |
| GET /schemas (list 20) | 500 | 5 ms | 30 ms |
The scenarios
Each scenario is a workload run identically against every target, defined by behavior so it's fair across different APIs. Same rig, same 2,020-record dataset, same offered load.
Read one content record by ID
The single most common CMS request: a client fetches one record by its primary key and gets JSON back. Exercises routing, auth check, one indexed point lookup, and JSON marshaling.
List 50 records (page 1)
The list view behind every admin table and public index page. Exercises a bounded, ordered scan plus serializing 50 rows.
Create one record
The write path: validate a body, insert one row, fire lifecycle hooks, return the created record. The scenario that stresses the DB write side and any hook/event machinery.
Filter by an indexed field
A query with a WHERE clause on an indexed column plus pagination, the shape most real API reads take. Catches missing indexes and N+1 relation loading.
Authenticate (token issuance)
Login deliberately runs a slow password hash (bcrypt or argon2), and this scenario measures how gracefully each platform absorbs a burst of logins without the hash starving everything else.
S04 was withdrawn once: an earlier run sent a query parameter the engine does not read and measured an unfiltered page. Every S04 run now proves the filter applies before it measures: a value no record holds must return no rows, and a real value only matching rows. Each scenario's shape is described in the cards above. The harness that runs them is not published yet.
Ten sites, each with its own traffic mix.
A profile is a set of content schemas, seeded data, and a weighted traffic mix that stands in for a real site. Each run below drove the free-tier part of that mix only. The segments that need a paid plugin were left out, and every card names its own.
Small Blog
200 req/s · p95 2 ms · p99 2.4 ms measuredA content blog: mostly readers, an occasional search.
Schema
Seeded: 10 blog_authors, 20 blog_categories, 500 blog_posts
Traffic mix · 200 req/s · p95 < 120 ms
The run drove 92% of this mix. The other 8% needs a paid plugin (8% full-text post search, on the admin router) and was dropped before the run, with the remaining weights renormalized. The latency above is the 92% that ran, not the whole mix.
Community Forum
200 req/s · p95 1.9 ms · p99 2.3 ms measuredA forum: topics read constantly, and the thread under each one.
Schema
Seeded: 1,200 forum_topics
Traffic mix · 200 req/s · p95 < 150 ms
The run drove 68% of this mix. The other 32% needs a paid plugin (22% the thread under a topic, 10% topic search) and was dropped before the run, with the remaining weights renormalized. The latency above is the 68% that ran, not the whole mix.
Company Profile
150 req/s · p95 2.1 ms · p99 2.6 ms measuredA corporate site: a small, stable content set read constantly.
Schema
Seeded: 30 site_pages, 15 site_team, 10 site_openings
Traffic mix · 150 req/s · p95 < 100 ms
Documentation Portal
200 req/s · p95 3.5 ms · p99 4.6 ms measuredA docs site: a deep page tree, assembled by the application.
Schema
Seeded: 10 docs_spaces, 2,000 docs_pages
Traffic mix · 200 req/s · p95 < 140 ms
The run drove 85% of this mix. The other 15% needs a paid plugin (15% documentation search) and was dropped before the run, with the remaining weights renormalized. The latency above is the 85% that ran, not the whole mix.
Events and Registration
200 req/s · p95 2 ms · p99 2.4 ms measuredAn events site: reads against a counter the application maintains.
Schema
Seeded: 300 events_events, 6,000 events_registrations
Traffic mix · 200 req/s · p95 < 140 ms
Job Board
200 req/s · p95 2 ms · p99 2.5 ms measuredA job board: search-led browsing over a mid-sized listing set.
Schema
Seeded: 200 jobs_companies, 3,000 jobs_listings
Traffic mix · 200 req/s · p95 < 150 ms
The run drove 74% of this mix. The other 26% needs a paid plugin (26% the way most candidates arrive) and was dropped before the run, with the remaining weights renormalized. The latency above is the 74% that ran, not the whole mix.
Course Platform
180 req/s · p95 1.9 ms · p99 2.3 ms measuredA course platform: three levels of nesting per page load.
Schema
Seeded: 120 lms_courses, 600 lms_modules, 4,000 lms_lessons
Traffic mix · 180 req/s · p95 < 160 ms
Small Marketplace
250 req/s · p95 1.9 ms · p99 2.4 ms measuredA multi-seller catalog: browse-heavy, with search and reviews.
Schema
Seeded: 100 shop_sellers, 50 shop_categories, 2,000 shop_products, 5,000 shop_reviews
Traffic mix · 250 req/s · p95 < 150 ms
The run drove 92% of this mix. The other 8% needs a paid plugin (8% product search) and was dropped before the run, with the remaining weights renormalized. The latency above is the 92% that ran, not the whole mix.
Multi-language Site
180 req/s · p95 1.9 ms · p99 2.4 ms measuredA translated site: every page load resolves a locale.
Schema
Seeded: 12 i18n_sections, 800 i18n_pages
Traffic mix · 180 req/s · p95 < 140 ms
The run drove 70% of this mix. The other 30% needs a paid plugin (30% the translation for a locale) and was dropped before the run, with the remaining weights renormalized. The latency above is the 70% that ran, not the whole mix.
Editorial Newsroom
180 req/s · p95 2 ms · p99 2.5 ms measuredAn editorial desk: a public front page over a draft-heavy store.
Schema
Seeded: 8 news_desks, 25 news_reporters, 1,500 news_stories
Traffic mix · 180 req/s · p95 < 140 ms
Each card is the median of the runs of that profile on 2026-10-05, on the free engine with no license, and the definitions shown are the ones that ran.
Plugins activate inside the process.
Every plugin compiles into the same binary. A signed license token is verified on the machine, and activation is a function call.
License token check
< 5 ms
Ed25519, offline
Plugin registry resolution
< 1 ms
compile-time map lookup
Activator gate check
< 5 ms
compiled, licensed and requested
Plugin Start() per active
< 50 ms
Database schema check and route registration
The four phases above are targets, and together they budget under 100 ms for plugin activation. That is not a container start. Measured cold start, from container start to the first served request, is 488 ms, and it is the number in the comparison table above.
Methodology
The rig, the dataset, the fairness rules and the provenance of every value are below. Each figure carries the date of the run that produced it.
Rig
Every published figure was measured on Intel Core Ultra 5 125H (18 threads), 32 GB RAM, NVMe, Ubuntu 26.04, Docker 29.8, with PostgreSQL 16, one container per target on the same host, every target side by side, so the results compare directly. The LyEve engine was built from commit da80fdd. The load generator is k6 2.3.0 on the same machine, constant arrival rate.
Dataset
The scenario path seeds 2,020 records across 2 content types, from a fixed random seed, with an index on the queried columns so no target wins or loses on a missing index. Profile runs seed their own schemas.
Arrival-rate load
k6 pins the request rate and lets concurrency float up to a cap, so a slow target cannot quietly lower its own offered load. A target that still falls behind is marked with the share of the rate it served, and its latency then understates how slow it is. A 10 s warm-up is discarded, then 60 s is measured. Each target runs several times, interleaved with the others and each time on a fresh stack, and the page shows the median with the lowest and highest run.
Provenance
Every value is measured, target, vendor-published, or pending. A competitor cell is only ever a real number or a dash, never a guess.
These rules are the whole method. There is no longer form of them to read.
Read the fine print
- Pending is not zero. A blank competitor cell means we haven't run that platform through the harness yet, not that it scored badly. We fill them as we run them.
- Targets are not measurements. A target is a number we build against. It becomes measured only after a run produces it.
- Numbers age. Every figure here comes from the run dated on it, and the comparison rows are all from 2026-10-05. LyEve ships a rolling image, so a later build can differ from what was measured.
- Synthetic load on one rig. Your network, region, and concurrent workload will move every number.
- A hosted platform runs on its vendor's own infrastructure, so it can never share a column with a number we measured ourselves. There is no hosted target on this page.
Check them yourself.
Every figure above names the scenario, the profile and the date it was measured, so you can run the same shape against your own data and compare. The harness we use is not published yet, and the LyEve image needs registry credentials because it refuses anonymous pulls. If you want a figure reproduced, ask and we will walk through how it was taken.