Skip to content

Performance and footprint

Japanese analysis makes a visible difference: loading the seed took Go embedded from 2.13 ms to 319.7 ms. SDK launch adds another boundary—child-process creation, and for Java, extracting the bundled executable from the classpath. These are local measurements, not guarantees.

Measured on 2026-09-13 on an Apple M3, macOS arm64, Go 1.27.0, Python 3.14.7, Node.js 26.8.1, Java 25.0.2, and Docker 29.4.0 with OrbStack. Every osmem path loads the same 525-byte seed (3 documents across 2 indices). Japanese enabled means the seed’s kuromoji analyzer is initialized; disabled uses the CJK fallback.

API path Japanese Mean startup Range Timing boundary
Go embedded off 2.13 ms 1.54–2.87 ms (n=10) osmem.New() + LoadSeed(); the already-running Go test process and executable launch are excluded
Go embedded on 319.7 ms 312.0–347.8 ms (n=10) Same; imports and enables osmem/ja
Python server SDK off 23.5 ms 10.1–69.4 ms (n=5) OsmemServer.start() launches the child and waits for seeded-ready; Python runner already running
Python server SDK on 332.8 ms 321.1–366.8 ms (n=5) Same, Japanese analyzer enabled
Java server SDK off 435.2 ms 305–868 ms (n=5) OsmemServer.start() includes extracting the bundled executable from the classpath, then child startup and seed load
Java server SDK on 730.4 ms 653–797 ms (n=5) Same, Japanese analyzer enabled
Node.js server SDK off 10.8 ms 10.1–12.8 ms (n=5) OsmemServer.start() launches the child and waits for seeded-ready; Node runner already running
Node.js server SDK on 325.1 ms 318.6–344.1 ms (n=5) Same, Japanese analyzer enabled
Docker OpenSearch 6.07 s 5.99–6.31 s (n=5) Warm opensearchproject/opensearch:2.19.0, linux/arm64; until successful PUT /benchmark
Testcontainers Go + OpenSearch 6.26 s 5.79–6.89 s (n=5) Testcontainers-Go 0.44.0, fresh container each run, cached image; includes cold Ryuk in the first run
Devbox-managed OpenSearch 7.94 s 6.85–10.63 s (n=5) Devbox 0.17.5 services up -b; process-compose runs docker run; cached image

For Python, Java, and Node.js, the language runtime is already alive before timing begins, matching a test runner calling the SDK. The server binary is built before the timer; Java follows the default classpath-resource extraction path and copies the bundled executable to a temporary file on each start, while Python and Node use an already-present executable. One Java no-Japanese trial is a high outlier, retained in the mean. The Go embedded measurement has no HTTP listener and excludes starting the Go test process. Container rows have a different ready condition (PUT /benchmark) and are context, not a controlled comparison with the seeded osmem fixture.

Write latency is omitted: suites are expected to reuse prebuilt indexes, and per-test writes are not the startup cost this comparison is meant to explain.

Clone an already-built Japanese-enabled seed, then measure only clone creation. The test case can mutate its fork without affecting the shared base; no write or cleanup time is included.

API path Mean clone creation Samples
Go embedded Cluster.Clone() 14.4 µs 5 batches × 5,000 clones; seed setup outside timer
Python server.clone() 240 µs 300 clones from one running seeded server
Java server.clone() 583 µs 300 clones from one running seeded server
Node.js server.clone() 1.63 ms 300 clones from one running seeded server

The language SDK rows include the local HTTP management request and JSON decoding; the Go embedded row is an in-process copy-on-write fork without a port. Closing the clone and any subsequent index/document mutation are outside the timer. Outliers from process scheduling make the detailed clone samples noisier than the averages imply.

Path Resident memory average Query Conditions
osmem server, Japanese analyzer exercised 160.4 MiB RSS 5 processes; same seed; sample taken after a Japanese match query
Go in-process API not measured filtered search + sort + date histogram: 1.71 ms; exact term: 33.3 µs; read-only clone + count: 270 µs 10,000-document fixture; `go test -run ‘^$’ -bench ’Benchmark(CloneReadOnly
osmem HTTP query, Japanese disabled not measured median 1.53 ms, p95 1.84 ms, p99 2.94 ms 500 sequential match_all, size 10 requests
Docker OpenSearch 942.2 MiB container RSS median 4.45 ms, p95 9.76 ms, p99 19.02 ms docker stats after test index creation; query sample from a separate run
Testcontainers Go + OpenSearch 952.9 MiB container + 4.5 MiB runner RSS increase = 957.5 MiB Go runner: 15.3 MiB before startup, 19.9 MiB ready; excludes Docker daemon and Ryuk
Devbox-managed OpenSearch 947.6 MiB container + 61.0 MiB process-compose/Docker CLI = 1,008.6 MiB Excludes Docker daemon

The query rows do not use the same index or query plan: the Go benchmark searches 10,000 documents, the osmem HTTP check uses a small seed, and OpenSearch searches an empty index. They show local path costs, not a controlled engine shootout. Docker uses one node, a 512 MiB heap, and disabled security demo setup. Hardware, heap, architecture, storage driver, image cache, and startup policy can change these results substantially.

Testcontainers is a test-side wrapper around a container runtime, not another search engine. This comparison uses the Go implementation (testcontainers-go 0.44.0) to avoid attributing a large, noisy JVM baseline to the container. Five fresh-container trials averaged 6.26 seconds to the index-ready request. The Go test process averaged 15.3 MiB RSS immediately before container startup and 19.9 MiB when ready; only the 4.5 MiB increase is added to the container’s 952.9 MiB RSS. The resulting process-plus-container footprint is 957.5 MiB. The first trial also started Testcontainers’ Ryuk helper; the helper and Docker daemon are excluded from RSS. A class-scoped Testcontainers container can amortize one startup across that class’s tests.

Devbox can manage a service through process-compose; the official services guide describes devbox services up and background mode. Here, devbox services up -b starts the same cached OpenSearch image through a process-compose docker run service. Five fresh starts averaged 7.94 seconds to a successful PUT /benchmark. Once ready, the container averaged 947.6 MiB RSS and process-compose plus its persistent Docker CLI averaged 61.0 MiB, for 1,008.6 MiB total. The Docker daemon is excluded. The warm no-op devbox run measurement (156 ms) and first Maven/JDK closure download (194.1 MiB, 351.7 MiB unpacked) are separate development-toolchain costs—not server startup or server image size.

Linked application binary and container download size

Section titled “Linked application binary and container download size”

For library size, this reports the incremental size in the final linked Go executable, not source-code or package/archive size. Three minimal Go programs were built on this machine with Go 1.27.0, -trimpath, and -ldflags=-buildid=: an empty main, one that constructs and closes an osmem cluster, and one that also imports the Japanese analyzer. The optional analyzer measurement is cumulative from the empty baseline; its incremental addition over osmem alone is also shown.

Go executable Size
Minimal main baseline 1,815,314 bytes (1.73 MiB)
osmem-linked executable 25,668,722 bytes (24.48 MiB); +23,853,408 bytes (+22.75 MiB)
osmem + Japanese analyzer 38,529,298 bytes (36.74 MiB); +36,713,984 bytes (+35.01 MiB) vs baseline, of which the analyzer adds 12,860,576 bytes (12.26 MiB)

These are toolchain- and program-dependent linker results, not a universal package-size guarantee. Node.js and Python use a separate osmem child executable, and Java uses JVM artifacts, so their integration does not have a comparable statically linked application-binary delta.

The homepage puts three sizes on one decimal-MB scale: the linked Go app’s 36.7 MB increment (35.0 MiB), the first Devbox Maven/JDK environment download (203.5 MB, or 194.1 MiB), and the OpenSearch image’s 739.3 MB compressed linux/arm64 size listed by Docker Hub. These are deliberately distinct scopes: a linked executable delta, a one-time toolchain download, and a complete compressed server image. Docker’s local Size is expanded storage and is not used as a download-size proxy; actual transfer can be lower when layers are already cached.

Testcontainers uses that same OpenSearch image; it does not link a second search binary into the test application. Devbox’s 194.1 MiB figure is the downloaded Maven/JDK environment closure, a separate setup cost rather than an application binary size.

Run from the repository root:

Terminal window
go test -run '^$' -bench 'Benchmark(CloneReadOnly|Search10k|TermQuery10k)$' -benchmem -count=3

The benchmarks build their 10,000-document fixture before timing the query cases. For service startup and client-transport measurements, record whether compilation, binary extraction, image pulling, and seed loading are inside or outside the timer.