Skip to content

Benchmarks

Sairo ships with a benchmark suite you can run against your own deployment. The numbers below come from two complementary contexts, kept deliberately separate so they are never conflated:

  1. Server-compute microbenchmarks — single Uvicorn process in Docker against local MinIO. These isolate Sairo’s own latency (index lookups, FTS5, SQL aggregation) with negligible network overhead.
  2. Production validation — a live, read-only run against a real multi-hundred-terabyte deployment, to confirm the architecture holds at scale.

Measured read-only against a live production instance on Leaseweb S3-compatible object storage:

ParameterValue
Total indexed15.5M objects across 14 buckets, ~269 TB
Largest single bucket~9.86M objects
Access pathPublic internet, behind a CDN/TLS edge

Because this run crosses the public internet, every request carries a fixed transport round-trip (RTT) floor. We measured that floor against a zero-compute endpoint and subtract it to isolate Sairo’s server-side cost:

Operation (on the ~9.86M-object bucket)End-to-end p50Server compute (end-to-end − RTT floor)
/healthz (zero-compute reference)284ms(this is the RTT floor)
Folder listing281ms≈ 0ms — at the network floor
Search (200 results)305ms~20–35ms
Storage breakdown (root)304ms~20–35ms

The headline result: folder listing on a ~9.86M-object bucket lands at the network floor — Sairo adds no measurable server-side latency, because the listing is a single indexed lookup against a pre-computed prefix hierarchy regardless of folder size. Search and storage breakdown stay in the tens of milliseconds server-side at ~10M objects.

ParameterValue
RuntimeDocker container, single Uvicorn process
HostmacOS, Apple Silicon (Docker Desktop)
StorageLocal MinIO, plus a local Sairo pointed at production S3 indexes
Methodology15–30 iterations per measurement, percentile reporting

FTS5 trigram search, measured locally to isolate server cost:

DatasetQueryResultsp50p95
134K-object indextypical (limit=200)2002.2–3.1ms3–22ms
134K-object indexbroad match, limit=50050040ms43ms
~10M-object bucket (live, minus RTT)200 results200~20–35ms

Typical queries return in single-digit milliseconds on hundreds of thousands of objects, and in tens of milliseconds on ~10M-object buckets. Larger result sets (500+ rows) cost more because of row serialization, not search.

Listing is served from the SQLite index, never a live S3 LIST. Folder navigation uses a pre-computed prefix_children hierarchy, so it is a constant-time index lookup at any folder size.

ScenarioBeforeAfterNotes
Folder listing, 557K-object bucket114ms0.048mspre-computed prefix hierarchy
Folder listing, >1M-object bucketOOM (disabled)constant-timepreviously turned off above 1M objects; now lands at the network floor on a live 9.86M-object bucket
Leaf listing (large folder), paginated8.7ms, ~1 MB payload4.8ms, ~238 KB payloadpagination (v3.3.0) caps payload + latency
Flat folder with 1,000,000 objects in one prefix~1.3sworst case: a single non-hierarchical folder of 1M keys

The flat-1M-folder case is the deliberate stress test — a single prefix holding a million keys with no sub-structure. Even then it returns in ~1.3s; any normally-partitioned layout lists in low single-digit milliseconds.

BucketObjectsThroughput
bench-small1,000926 obj/s
bench-mixed2,4161,348 obj/s
production buckets15.5M (live)crawled + kept fresh via adaptive delta crawler

Indexing rate: 1,000–1,350 objects/second on local MinIO. At production scale, large buckets are kept current by an adaptive delta crawler that re-lists only the newest partitions instead of re-walking the whole bucket each cycle, with periodic full reconciles.

Since v3.4.0, uploads go directly from the browser to the S3 endpoint — file bytes never pass through the Sairo server. Small files use a single presigned PUT; large files use presigned multipart, splitting the file into parts uploaded in parallel, each part signed just-in-time.

PropertyBehavior
Server memory during uploadFlat, independent of file size — no buffering, so no OOM or pod restarts at scale
Maximum object sizeUp to S3’s 5 TB per-object limit (no single-PUT ceiling)
Large-file transferParallel multipart parts; an in-progress upload can be stopped and is cleanly aborted on S3
ThroughputBounded by the client’s bandwidth to S3, not by Sairo
Endpointp50p95
/healthz2.1ms3.6ms
/api/buckets4.3ms5.8ms
Object listing2.4ms4.6ms
Storage breakdown (root)2.4–3.9ms5–6ms
Presigned URL generation3.1ms5.6ms

Sub-5ms p50 for standard endpoints on local hardware.

Concurrent search queries, single Uvicorn process:

Concurrent UsersRequests/sec
5236
10333
25528

Throughput scales with worker count; these are single-process numbers.

The index database is opened with: cache_size=-64000 (64 MB), mmap_size=268435456 (256 MB memory-mapped I/O), temp_store=MEMORY. Impact at scale:

QueryBeforeAfterDataset
COUNT(*)11.3ms7.3ms2M objects
COUNT(*)2.0ms1.5ms557K objects
Folder stats rebuild63ms56ms139K objects
SettingValue
Crawl workers12
Prefix workers16
Batch insert size10,000
FTS rebuildBackground thread (never blocks listing/search)
Large bucketsAdaptive delta crawl + periodic full reconcile
Sub-prefix splittingAutomatic for buckets with few top-level prefixes

The benchmark suite lives in the benchmark/ directory of the repository.

Terminal window
# Requires MinIO CLI (mc) configured with alias "local"
cd benchmark
./seed-data.sh # Seeds bench-small (1K) + bench-mixed (2.4K)
./seed-data.sh medium # Seeds bench-medium (10K objects)
./seed-data.sh large # Seeds bench-large (50K objects)
BucketObjectsPattern
bench-small1,0005 dirs × 10 months × 20 files
bench-mixed~2,400Parquet data lake, logs, configs, CSV reports
bench-medium10,00010 dirs × 10 months × 10 sub-dirs × 10 files
bench-large50,00010 dirs × 50 partitions × 100 records
Terminal window
./run-benchmarks.sh # all categories
./run-benchmarks.sh search # search latency only
./run-benchmarks.sh crawl listing # crawl + listing

Prerequisites: Sairo on localhost:8000 (or set SAIRO_URL), MinIO on localhost:9000, test buckets seeded, and admin credentials.

Results are saved to benchmark/results/ as machine-readable JSON and a human-readable LATEST-RESULTS.md.

Landing Page ClaimBenchmark Evidence
”Proven at 269 TB / 15.5M objects”Live deployment, 14 buckets, largest ~9.86M objects
”Constant-time folder listing at any scale”Listing lands at the network floor on a ~9.86M-object bucket
”Single-digit millisecond search”p50 = 2.2–3.1ms on 134K objects (tens of ms at ~10M)
“1,300+ obj/sec indexing”1,348 obj/s measured on bench-mixed
”Sub-5ms API responses”healthz p50 = 2.1ms, most endpoints < 5ms
”500+ requests/second”528 req/s at 25 concurrent users
”Uploads of any size, zero server memory”Direct browser→S3 multipart up to 5 TB; flat server memory