Skip to content
Numbers, not adjectives

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.

Side by side

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.

12.4× better than the best measured competitor
  • 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

Measured Target Vendor-published Pending
results generated 2026-10-05
The full table

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×
Measured: We ran it on the rig described in this report and kept the raw output. Target: A number we engineer against and gate regressions on, not an end-to-end measurement. Vendor-published: A competitor's own published figure, cited, not independently reproduced by us. Pending: The harness supports it. We have not run it yet. Shown as a dash, never a guess.
measured

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)

target

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
How we test

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.

S01 read

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.

load 500/s p95 < 50 ms
S02 read

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.

load 300/s p95 < 80 ms
S03 write

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.

load 100/s p95 < 120 ms
S04 read

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.

load 200/s p95 < 90 ms
S05 auth

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.

load 20/s p95 < 1000 ms

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.

Application profiles

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 measured

A content blog: mostly readers, an occasional search.

Schema

blog_authors3f blog_categories2f blog_posts7f

Seeded: 10 blog_authors, 20 blog_categories, 500 blog_posts

Traffic mix · 200 req/s · p95 < 120 ms

45% read one post 25% recent posts, newest first 12% a post with its author and category inflated, which is what the page renders 10% posts in a category, filtered on the foreign key 8% full-text post search, on the admin router · needs search, not run

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 measured

A forum: topics read constantly, and the thread under each one.

Schema

forum_topics7f

Seeded: 1,200 forum_topics

Traffic mix · 200 req/s · p95 < 150 ms

38% read one topic 30% the topic index 22% the thread under a topic · needs comments, not run 10% topic search · needs search, not run

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 measured

A corporate site: a small, stable content set read constantly.

Schema

site_pages6f site_team7f site_openings9f

Seeded: 30 site_pages, 15 site_team, 10 site_openings

Traffic mix · 150 req/s · p95 < 100 ms

40% read one page 20% look a page up by slug, which is a filtered list because there is no get-by-slug route 18% open roles 15% the team 7% page index

Documentation Portal

200 req/s · p95 3.5 ms · p99 4.6 ms measured

A docs site: a deep page tree, assembled by the application.

Schema

docs_spaces4f docs_pages6f

Seeded: 10 docs_spaces, 2,000 docs_pages

Traffic mix · 200 req/s · p95 < 140 ms

35% read one page 30% every page in a space, which is what building the navigation tree costs 20% resolve a documentation URL 15% documentation search · needs search, not run

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 measured

An events site: reads against a counter the application maintains.

Schema

events_events9f events_registrations5f

Seeded: 300 events_events, 6,000 events_registrations

Traffic mix · 200 req/s · p95 < 140 ms

38% read one event 30% the upcoming list, sorted by the app because the engine cannot 22% the door list for an event 10% resolve an event URL

Job Board

200 req/s · p95 2 ms · p99 2.5 ms measured

A job board: search-led browsing over a mid-sized listing set.

Schema

jobs_companies4f jobs_listings9f jobs_applications7f

Seeded: 200 jobs_companies, 3,000 jobs_listings

Traffic mix · 200 req/s · p95 < 150 ms

30% read one listing 26% the way most candidates arrive · needs search, not run 16% a listing with its company 16% every role at one company 12% the index

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 measured

A course platform: three levels of nesting per page load.

Schema

lms_courses6f lms_enrollments6f lms_modules4f lms_lessons6f

Seeded: 120 lms_courses, 600 lms_modules, 4,000 lms_lessons

Traffic mix · 180 req/s · p95 < 160 ms

32% read one lesson 24% the first level of the walk 22% the second level 14% the course itself 8% the catalog

Small Marketplace

250 req/s · p95 1.9 ms · p99 2.4 ms measured

A multi-seller catalog: browse-heavy, with search and reviews.

Schema

shop_categories2f shop_sellers4f shop_products8f shop_reviews5f

Seeded: 100 shop_sellers, 50 shop_categories, 2,000 shop_products, 5,000 shop_reviews

Traffic mix · 250 req/s · p95 < 150 ms

30% read one product 24% browse a page, and ranking is left to the application 14% product with seller and category inflated 14% products in a category 10% reviews for a product 8% product search · needs search, not run

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 measured

A translated site: every page load resolves a locale.

Schema

i18n_sections3f i18n_pages6f

Seeded: 12 i18n_sections, 800 i18n_pages

Traffic mix · 180 req/s · p95 < 140 ms

34% the default-locale entry 30% the translation for a locale · needs localization, not run 20% resolve a localized URL 16% the pages in a section

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 measured

An editorial desk: a public front page over a draft-heavy store.

Schema

news_desks2f news_reporters4f news_stories8f

Seeded: 8 news_desks, 25 news_reporters, 1,500 news_stories

Traffic mix · 180 req/s · p95 < 140 ms

34% read one story 26% the front page 16% a story with its byline and desk 14% one desk output 10% the revision history of a story

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.

Plugin activation

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.

1

License token check

< 5 ms

Ed25519, offline

2

Plugin registry resolution

< 1 ms

compile-time map lookup

3

Activator gate check

< 5 ms

compiled, licensed and requested

4

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.

How we measure

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.