Benchmarks
Timing measurements for sct commands run against a real SNOMED CT release:
- UK Monolith -
SnomedCT_MonolithRF2_PRODUCTION_20260701T120000Z(837,930 active concepts)
Machine: Lenovo Yoga 9i Pro - Intel Core Ultra 9 185H (16 cores), 64 GB RAM, NVMe SSD.
Last verified: 2026-07-09, against sct 0.18.2. If you're reading this much later than that date, treat the numbers as a rough shape rather than gospel - re-run How to benchmark yourself below for your own hardware and release.
Methodology
Each command was timed with time (wall-clock) on a warm filesystem (page cache pre-populated by cat-ing the source file first). Disk is NVMe SSD.
This is a single documented run, not an average over many iterations - treat the numbers as a real, reproducible order of magnitude rather than a precise statistical claim. Wall-clock time on a dev laptop is also sensitive to whatever else is running at the time; a quiet machine will do better than the numbers below, which were captured with the usual background load of an active dev environment (editors, a couple of local dev servers, Docker).
FHIR terminology server timings should be treated differently from command timings. Run the FHIR conformance harness first, then benchmark only servers that pass the relevant profile:
benchmarks/conformance.sh --server http://localhost:8080/fhir
benchmarks/bench.sh --db snomed.db --server http://localhost:8080/fhir --runs 20 --warmup 5
See FHIR Conformance And Benchmarks for the full methodology.
time sct ndjson --rf2 ~/downloads/SnomedCT_MonolithRF2_PRODUCTION_20260701T120000Z/
time sct sqlite --ndjson snomed.ndjson
time sct parquet --ndjson snomed.ndjson
time sct markdown --ndjson snomed.ndjson
time sct tct --db snomed.db
time sct fst build --ndjson snomed.ndjson
Results - UK Monolith Edition (837,930 concepts)
| Command | Concepts | Output size | Wall time | Notes |
|---|---|---|---|---|
sct ndjson |
837,930 | 1.3 GB | 51.6 s | RF2 parsing + join + sort + serialise |
sct sqlite |
837,930 | 1.9 GB | 32.4 s | Stream NDJSON → WAL SQLite + FTS5 rebuild |
sct parquet |
837,930 | 785 MB | 6.4 s | Batched Arrow writes (50k rows/batch) |
sct markdown |
837,930 | 3.2 GB | 32.3 s | One file per concept (837,930 files) |
sct tct |
837,930 | 2.6 GB (db grows 1.9 → 2.6 GB) | 42.1 s | 11.6M ancestor/descendant pairs over IS-A; INTEGER SCTID columns |
sct fst build |
837,930 | 135 MB | 18.0 s | 1.25M distinct keys, 178k word tokens, 61 semantic tags |
sct markdown is the most I/O-bound stage here, not CPU-bound - most of its wall time is filesystem syscalls creating 837,930 individual small files, not computation.
Only the UK Monolith is benchmarked currently. The previous version of this page also carried UK Clinical Edition numbers; they've been dropped rather than left stale, since re-running them needs a fresh TRUD-authenticated download this environment didn't have to hand. Re-add if useful - Clinical is ~24x smaller and everything scales down accordingly.
MCP server startup time
The sct mcp server should start fast enough to avoid a perceptible delay when a client like Claude Desktop opens it. It answers the initialize handshake in a few milliseconds regardless of database size, because it opens the SQLite file rather than loading it into memory:
time echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
| (stdbuf -o0 sct mcp --db snomed.db & sleep 1; kill %1) 2>/dev/null
| Database | Size | Response time |
|---|---|---|
Synthetic test fixture (tests/fixtures/rf2/) |
136 KB, 22 concepts | ~2.5 ms (3 runs: 2.1 / 2.6 / 2.8 ms) |
| UK Monolith, with TCT | 2.6 GB, 837,930 concepts | ~2.3 ms (3 runs: 2.6 / 2.3 / 2.0 ms) |
Startup is a few milliseconds regardless of database size: the server opens the SQLite file (near-instant, it does not read it into memory) and reads provenance from a small keyed table. The response carries a serverInfo block embedding a _provenance object describing the loaded release:
{"id":1,"jsonrpc":"2.0","result":{"capabilities":{"tools":{}},"protocolVersion":"2024-11-05","serverInfo":{"_provenance":{"created_at":"2026-07-09T16:18:53Z","edition_label":"uk_sct2mo_42.3.0_20260701000001Z","release_date":"2026-07-01","release_id":"uk_sct2mo_42.3.0_20260701000001Z","sct_version":"0.18.2","source_paths":["..."]},"name":"sct-mcp","version":"0.18.2"}}}
Note on an earlier regression: a prior release briefly took ~370 ms to start against a full Monolith database, because its startup schema-version check ran SELECT MAX(schema_version) FROM concepts - a full-table scan of an unindexed column. Reading a single row instead (the value is uniform across concepts) restored the few-millisecond startup shown above, on databases of any size. See issue #32.
How to benchmark yourself
sct ndjson
--rf2 accepts either an RF2 directory or a .zip file directly:
# Using a zip file
time sct ndjson --rf2 ~/downloads/SnomedCT_MonolithRF2_PRODUCTION_20260701T120000Z.zip
# Using a pre-extracted directory (warm the page cache first for a fair comparison)
find ~/downloads/SnomedCT_MonolithRF2_PRODUCTION_20260701T120000Z -type f -exec cat {} + > /dev/null 2>&1
time sct ndjson --rf2 ~/downloads/SnomedCT_MonolithRF2_PRODUCTION_20260701T120000Z/
sct sqlite
time sct sqlite --ndjson snomedct-monolithrf2-production-20260701t120000z.ndjson --output snomed.db
ls -lh snomed.db
Verify FTS works:
sqlite3 snomed.db "SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"
sct parquet
time sct parquet --ndjson snomedct-monolithrf2-production-20260701t120000z.ndjson --output snomed.parquet
ls -lh snomed.parquet
Verify DuckDB can read it:
duckdb -c "SELECT hierarchy, COUNT(*) n FROM 'snomed.parquet' GROUP BY hierarchy ORDER BY n DESC LIMIT 5"
sct markdown
time sct markdown --ndjson snomedct-monolithrf2-production-20260701t120000z.ndjson --output snomed-concepts/
du -sh snomed-concepts/
find snomed-concepts/ -name "*.md" | wc -l
sct tct
Builds the transitive closure table (concept_ancestors) over an existing SQLite database - needed for subsumption-heavy workloads or the SCT-QL compiler, not built by default:
time sct tct --db snomed.db
ls -lh snomed.db
sqlite3 snomed.db "SELECT COUNT(*) FROM concept_ancestors"
sct fst build
time sct fst build --ndjson snomedct-monolithrf2-production-20260701t120000z.ndjson --output snomed.fst
ls -lh snomed.fst
Verify search works:
sct fst search "myocardial infarction" --index snomed.fst