Performance & Capacity
Autentico is designed for operational simplicity, not horizontal scale. This page documents what a single process, SQLite-backed instance can handle so you can make an informed decision about whether it fits your load profile.
What was measured
Section titled “What was measured”All numbers were produced by running the full PKCE Authorization Code flow end to end:
GET /oauth2/authorize → renders login page, extracts CSRF tokenPOST /oauth2/login → bcrypt password verification, issues auth codePOST /oauth2/token → auth code exchange, issues access + refresh tokensPOST /oauth2/introspect → token validationPOST /oauth2/token → refresh token grantThis is the worst-case sequence for the server: every step involves a database write, and login includes bcrypt verification (~60ms on modern hardware). Tests were run with k6 using the scripts in stress/.
Results
Section titled “Results”| Concurrent logins | Error rate | Login p95 | Token p95 | Introspect p95 | Assessment |
|---|---|---|---|---|---|
| 20 VUs | 0% | 86ms | 54ms | 17ms | Comfortable |
| 100 VUs | 0% | 611ms | 647ms | 271ms | Supported |
| 500 VUs | 0% | 3.36s | 3.89s | 1.60s | Degraded |
“Concurrent logins” means users who have submitted their password and are waiting for a response at the same moment — the peak of simultaneous bcrypt + SQLite write load.
The bottleneck
Section titled “The bottleneck”Two things serialize under load:
- bcrypt — password hashing is intentionally slow (~60ms per operation). At 100 concurrent users, logins queue behind each other, pushing median login time to ~275ms.
- SQLite single-writer lock — all write operations (login, token issuance, refresh) go through one connection. Under high concurrency, write operations queue behind the lock. SQLite’s busy timeout absorbs this gracefully — requests wait rather than fail.
The failure mode is graceful degradation: latency climbs, but error rates remain zero. No SQLITE_BUSY errors were observed at any concurrency level tested, up to 500 concurrent users.
Read-heavy operations (introspect, OIDC discovery, JWKS) are largely unaffected because SQLite allows concurrent reads.
Daily active users
Section titled “Daily active users”“Concurrent logins” translates to daily users through login distribution patterns. For enterprise deployments, 60–70% of logins cluster in the first 15–30 minutes of the workday.
| Daily active users | Peak concurrent logins | Expected experience |
|---|---|---|
| Up to ~10,000 | ~15–30 | Sub-100ms login, imperceptible |
| Up to ~20,000 | ~60–80 | Under 500ms, comfortable |
| Up to ~50,000 | ~150–200 | 1–2s login at peak, noticeable |
| Beyond 50,000 | 200+ | 3s+ login at peak, consider scaling |
For consumer apps with global users spread across timezones, the peak is flatter and the same server can comfortably serve more daily users than the enterprise estimates above.
Recommended production ceiling
Section titled “Recommended production ceiling”100 concurrent logins — all requests succeed, login p95 stays under 700ms. This corresponds to roughly 10,000–20,000 daily active users under a concentrated enterprise login pattern.
If your peak load exceeds this, options are:
- Reduce bcrypt cost factor — lowering it from 12 to 10 roughly halves login time at the cost of slightly reduced brute-force resistance
- Enable WAL mode on SQLite — improves concurrent read/write throughput (not currently the default)
- Sticky-session load balancer — multiple Autentico instances, each with its own SQLite file; the load balancer routes each user to the same instance for the duration of their session
- Replace the persistence layer — the
pkg/dbboundary is the correct migration point if you genuinely need horizontal write scaling
Reproducing these numbers
Section titled “Reproducing these numbers”# Start server with rate limiting disabledmake stress-server
# Run the full suitemake stress-smoke USERNAME=admin PASSWORD=yourpasswordmake stress-load USERNAME=admin PASSWORD=yourpasswordmake stress-spike USERNAME=admin PASSWORD=yourpasswordmake stress-ceiling USERNAME=admin PASSWORD=yourpasswordSee stress/README.md for prerequisites, client setup, and full configuration options.