CipherStashDocs
Reference

Benchmarks

Query latency on encrypted columns versus plaintext PostgreSQL. Exact match and range run within 1.2-1.4x of plaintext and stay flat from 10k to 10M rows.

CipherStash encrypts data in your application and queries it in PostgreSQL through EQL. These benchmarks measure what that costs: the same query shapes run against encrypted columns and against plaintext tables with equivalent indexes, so every figure is an honest encrypted-versus-plaintext ratio rather than a number in isolation.

All results are medians on Apple M1 Max, PostgreSQL 17, EQL v3, across datasets from 10,000 to 10,000,000 rows. The full suite (per-scenario SQL, EXPLAIN plans, charts, and the v2-to-v3 regression comparison) lives in cipherstash/benches.

Headline

On the fast paths, querying encrypted data costs almost nothing. Exact match and OPE range queries run at about 0.12 ms, within 1.2-1.4x of plaintext PostgreSQL, and stay flat from 10k to 10M rows. Latency tracks the index, not the row count.

Query latency

Median latency at 1,000,000 rows, with the ratio to the same query on a plaintext table with an equivalent index.

OperationEQL indexEncrypted medianvs plaintext
Exact match (=)B-tree (equality term)0.12 ms1.3x
Range / ORDER BY (OPE)B-tree (OPE order term)0.12 ms1.2x
Range / ORDER BY (ORE)B-tree (ORE order term)0.52 ms5.2x
JSON containment (@>)GIN (SteVec)0.40 ms1.4x
GROUP BY (low cardinality)B-tree (equality term)83 ms2.2x

Range and ordering have two index options. The _ord variants use order-preserving encryption (OPE), which is EQL v3's default: near-plaintext latency, and an index that builds in about 1 s at 1M rows. The _ord_ore variants use order-revealing encryption (ORE): roughly 5x plaintext, and the index takes about 44 s to build at 1M. They have different leakage profiles, so the right choice is per column rather than universal, see Searchable encryption for the tradeoff and Numbers for the variants.

Free-text match uses a bloom-filter term (there is no LIKE on encrypted text) and runs at about 15.7 ms at 1M rows. What matters most is that the index engages at all: the bloom GIN is 100-400x faster than a sequential scan. See Text.

Scaling

Latency on an indexed encrypted column tracks the index, not the table, so the encrypted-versus-plaintext ratio barely moves as the dataset grows 1000x:

Operation10k100k1M10M
Exact match (=)0.13 ms (1.4x)0.11 ms (1.2x)0.12 ms (1.3x)0.13 ms (1.4x)
Range (OPE)0.12 ms (1.2x)0.12 ms (1.2x)0.12 ms (1.2x)0.12 ms (1.2x)

Aggregations that scan every matched row, like GROUP BY, grow with the number of rows in the group exactly as they do on plaintext: about 2x plaintext throughout (2.1 ms at 10k, 83 ms at 1M).

Ingest

Encryption happens in your application on write, so insert throughput reflects client-side work rather than database load. On the reference machine, encrypted insert throughput ranges from about 11k rows/s for exact-match and OPE columns down to about 1.3k rows/s for the free-text text_search domain, which generates a bloom and an order term per value. Batching and parallel writers scale this well past the single-threaded figures here. The per-domain breakdown is in the ingest report.

Methodology

  • Environment: Apple M1 Max, PostgreSQL 17, EQL v3. Datasets of 10k, 100k, 1M, and 10M rows.
  • Baseline: every encrypted scenario is compared to the same query shape on a plaintext table with an equivalent index (benches/plaintext_v3.rs). Ratios are encrypted median divided by plaintext median.
  • Indexes: queries use functional indexes over EQL's extractor terms, exactly as documented in Indexes. A query that cannot use its index falls back to a sequential scan, as it would on plaintext.
  • Caveats: medians are single-machine and single-connection, so absolute numbers on your hardware and under concurrency will differ. One outlier is called out in the report: JSON contains at 10M rows uses a query recipe that materializes the full GIN bitmap before LIMIT (a documented plan artifact, not the operator's real cost).

Reproduce

git clone https://github.com/cipherstash/benches
cd benches
mise run setup-db-v3
mise run bench:v3:query:all 1000000
mise run report:v3-compare

On this page