
Data
OpenSearch vs Elasticsearch: Benchmarks, Drift, Licence
OpenSearch vs Elasticsearch: both ship Lucene 10.5, the vendor benchmarks disagree, and Elastic's AGPL covers the source, not the releases you actually run.
Both engines descend from the same Apache Lucene core and, as of September 2026, the same Lucene minor — OpenSearch 3.8.0 on 10.5.0, Elasticsearch 9.5.2 on 10.5.1. That settles less than it sounds. What follows is the comparison, then the part comparisons skip: which licence governs the bytes you run for a decade.
Two Forks of One Engine
The split has a version number that returns below. Elastic's licensing FAQ states it: "In 2021, with the 7.11 release, we moved our Apache 2.0-licensed source code in Elasticsearch and Kibana to be dual licensed under Server Side Public License (SSPL) and the Elastic License, giving users the choice of which license to apply."
OpenSearch is what happened next. Its FAQ puts it in one sentence — "we made the decision to create a fork from the last Apache 2.0 version of Elasticsearch and Kibana and provide OpenSearch under the Apache License, Version 2.0 (ALv2)" — and it has stayed there since, Dashboards included.
That fork point is also a data-format boundary, stated in the same FAQ: "OpenSearch can use indices from Elasticsearch versions 6.0 up to 7.10." Five and a half years of releases sit above it — if your cluster is on 8.x or 9.x, the cheap migration path every fork retrospective describes does not apply.
The Benchmark War, and Why Nobody Wins It
The published numbers point in opposite directions, for structural reasons.
- Elastic, 2023. Elastic's own performance comparison concludes "Elasticsearch is 40%–140% faster than OpenSearch while using fewer compute resources."
- Elastic, 2024. A follow-up vector-search comparison claims "Elasticsearch is out-of-the-box 2x–12x faster than OpenSearch for vector search."
- Trail of Bits, 2025. An independent assessment found the reverse: "OpenSearch v2.17.1 is 1.6x faster on the Big5 workload and 11% faster on the Vectorsearch workload than Elasticsearch v8.15.4." It names its sponsor — "Amazon Web Services (AWS) requested that we conduct an independent benchmark assessment" — and states "We have not modified either codebase."
Two are published by one vendor, the third commissioned by the other. Independently conducted is not independently funded — a distinction the report draws itself, and most summaries citing it do not.
The tempting shortcut is to reason from Lucene: same engine underneath, therefore parity. Elastic's own 2024 post closes that door, noting alongside the 2x–12x claim that "both products use the same version of Lucene." A shared minor is a substrate, not a bound — codecs, defaults, merge policies and query planners sit above it.
So measure — one harness against both, with the assessment's own aggregation. OpenSearch Benchmark "records the 90th percentile (p90) of service times for each operation," but a single run's p90 is noisy: Trail of Bits "calculated the median of each workload operation's p90 service time" before taking "the geometric mean of their queries." Median across runs first, geomean across queries second.
#!/usr/bin/env bash
# benchmark-only mode drives any REST-speaking cluster, so one harness
# measures both.
set -euo pipefail
WORKLOAD="${WORKLOAD:-big5}"
RESULTS_DIR="${RESULTS_DIR:-./osb-results}"
RUNS="${RUNS:-11}"
SEARCH_PASSWORD="${SEARCH_PASSWORD:?export SEARCH_PASSWORD before running}"
CLIENT_OPTS="use_ssl:true,verify_certs:true,basic_auth_user:benchmark,basic_auth_password:${SEARCH_PASSWORD}"
# Size both clusters identically; anything else measures infrastructure.
CANDIDATES="opensearch=https://os.internal.example:9200 elasticsearch=https://es.internal.example:9200"
mkdir -p "${RESULTS_DIR}"
for run in $(seq 1 "${RUNS}"); do
for candidate in ${CANDIDATES}; do
name="${candidate%%=*}"
echo "==> run ${run} :: ${name}"
opensearch-benchmark run \
--pipeline=benchmark-only \
--target-hosts="${candidate#*=}" \
--workload="${WORKLOAD}" \
--client-options="${CLIENT_OPTS}" \
--results-format=csv \
--results-file="${RESULTS_DIR}/${name}-${WORKLOAD}-${run}.csv" \
--on-error=abort
done
done
python3 - "${RESULTS_DIR}" <<'PY'
import csv, math, pathlib, re, statistics, sys
P90 = re.compile(r"^90(?:\.0+)?th percentile service time$", re.IGNORECASE)
ANY = re.compile(r"percentile service time$", re.IGNORECASE)
def cell(row, key):
return " ".join((row.get(key) or "").split())
runs, status = {}, 0
for path in sorted(pathlib.Path(sys.argv[1]).glob("*.csv")):
with path.open(newline="") as handle:
rows = list(csv.DictReader(handle))
# Bulk-ingest tasks report docs/s; dropping them keeps indexing out.
ingest = {cell(r, "Task") for r in rows if cell(r, "Unit") == "docs/s"}
seen, p90 = set(), {}
for row in rows:
task, metric = cell(row, "Task"), cell(row, "Metric")
if not task or task in ingest or not ANY.search(metric):
continue
seen.add(task)
if not P90.match(metric):
continue
try:
value = float(row["Value"])
except (KeyError, TypeError, ValueError):
continue
if value > 0:
p90[task] = value
# OSB only publishes a percentile it had enough requests to fill.
if seen - set(p90):
print(f"{path.name}: no p90 for {', '.join(sorted(seen - set(p90)))}")
status = 1
runs.setdefault(re.sub(r"-\d+$", "", path.stem), []).append(p90)
for name, samples in sorted(runs.items()):
# Median of each query's p90 across runs, then one geomean over the
# queries. Geomeaning a single run is a different statistic.
tasks = sorted(set.intersection(*(set(s) for s in samples)))
if not tasks:
print(f"{name}: no query completed in every run")
status = 1
continue
if len(samples) < 5:
print(f"{name}: {len(samples)} runs is too few for a stable median")
status = 1
medians = [statistics.median([s[t] for s in samples]) for t in tasks]
geo = math.exp(sum(math.log(v) for v in medians) / len(medians))
print(f"{name}: geomean of median p90 {geo:.2f} ms, {len(tasks)} queries, {len(samples)} runs")
sys.exit(status)
PYWhere the Two Have Actually Drifted
Five years on, the interesting differences sit above the core search path. Elastic's two are a query language and a storage mode. ES|QL is "a piped query language for filtering, transforming, and analyzing data" — a second interface, not a wrapper over the DSL. The storage one matters more for log estates: Elastic documents "in benchmarks, logsdb index mode reduced the storage footprint of log data by up to 60%, with a small impact (10-20%) to indexing performance."
OpenSearch answers the query-language half with two — its docs offer "SQL and Piped Processing Language (PPL)" as alternatives to the DSL. Its storage answer is architectural, not compression: remote-backed storage, from 2.10, works by "automatically creating backups of all index transactions and sending them to remote storage," and "segment replication must also be enabled."
Neither list is a scoreboard: the question is whether one item touches your workload — logsdb for a log estate, remote-backed storage for a cluster whose recovery story is "three replicas and hope," on the reasoning behind self-hosted observability on open collectors. Everyone else chooses on the two axes below.
What the Licences Actually Say
Here measurement stops helping, and the popular summary is wrong in an expensive way.
In September 2024 Elastic added AGPLv3 as a third source-code option, "expected to take place before the 8.16 release is generally available" — widely reported as Elasticsearch becoming open source again. One sentence further the same FAQ narrows it: "Our releases will continue to be under the Elastic License."
The repository confirms it. Elasticsearch's root LICENSE.txt: "the default throughout the repository is a triple license under the 'GNU Affero General Public License v3.0 only', 'the Server Side Public License, v 1', and the 'Elastic License 2.0' … Code that is licensed solely under the 'Elastic License 2.0' is found only in the x-pack folder."
Resist the obvious inference: x-pack is not a synonym for "the paid features" — free-tier TLS and role-based access control live in that tree too. What it establishes is narrower: some Elasticsearch code has no AGPL option at all.
The Elastic License forbids three things, and the first is why the fork happened. Per ELv2, you "may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software," nor "circumvent the license key functionality," nor "alter, remove, or obscure any licensing, copyright, or other notices." For a team running its own cluster none bite; for a product embedding search as a service, the first is the whole conversation.
Both copyleft options get summarised into something they do not say. AGPLv3 section 13 reads: "if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network … an opportunity to receive the Corresponding Source of your version." Both conditions are required: an unmodified copy serving a network does not trigger it, and neither does a modified copy nothing remote talks to.
SSPL section 13 is a different instrument: "If you make the functionality of the Program or a modified version available to third parties as a service, you must make the Service Source Code available via network download to everyone at no charge." No modification trigger — and Service Source Code covers "all programs that you use to make the Program … available as a service, including … management software, user interfaces, application program interfaces … backup software, storage software and hosting software." A platform, not a daemon.
OpenSearch needs no reading between lines: "all of the software in the OpenSearch project is released under the Apache License, Version 2.0 (ALv2)." Both sides editorialise — OpenSearch's FAQ says of SSPL and ELv2 that "these are not open source" — but the quoted texts bind. OpenTofu's migration and OpenBao's are this problem in different tooling.
The Line Where Features Become Paid
Here the licence becomes a line item, and it is easy to overstate. Elasticsearch does not charge for security: transport encryption and role-based access control sit in the free Basic tier, as in OpenSearch. The divergence is at the controls an auditor asks about second.
In Elastic's subscription matrix dated 2026-07-01 — read off its published columns, whose marks are icons, not text — four rows sit outside "Free and open — Basic" while marked under Platinum and Enterprise: Elasticsearch audit logging, Kibana audit logging, "Single sign-on (SAML, OpenID Connect, Kerberos, JWT)" and "LDAP, PKI, Active Directory authentication". The subscriptions page puts "Field- and document-level security" there too, and the matrix's footnotes mark Gold and Platinum "no longer available for new customers" — so a new customer needing audit logging on their own hardware chooses between Enterprise and a different engine. A newer PDF is now the live download; re-check.
On the OpenSearch side those controls ship in the Apache 2.0 Security plugin, so the cost is configuration, not procurement: audit logging is "disabled by default" and switched on in cluster settings.
# Two-node OpenSearch 3.x: Security plugin, TLS on REST, audit logging on.
# Demo certificates -- mount your own at
# plugins.security.ssl.http.pemcert_filepath before this carries real data.
x-node: &node
image: opensearchproject/opensearch:3.8.0
environment: &node-env
cluster.name: sovereign-search
discovery.seed_hosts: os-node-1,os-node-2
cluster.initial_cluster_manager_nodes: os-node-1,os-node-2
bootstrap.memory_lock: "true"
OPENSEARCH_JAVA_OPTS: "-Xms4g -Xmx4g"
OPENSEARCH_INITIAL_ADMIN_PASSWORD: "${OPENSEARCH_INITIAL_ADMIN_PASSWORD:?set this in .env}"
plugins.security.ssl.http.enabled: "true"
plugins.security.audit.type: internal_opensearch
plugins.security.audit.config.disabled_rest_categories: NONE
plugins.security.audit.config.disabled_transport_categories: NONE
ulimits:
memlock: { soft: -1, hard: -1 }
nofile: { soft: 65536, hard: 65536 }
services:
os-node-1:
<<: *node
environment:
<<: *node-env
node.name: os-node-1
ports:
- "9200:9200"
volumes:
- os-data-1:/usr/share/opensearch/data
os-node-2:
<<: *node
environment:
<<: *node-env
node.name: os-node-2
volumes:
- os-data-2:/usr/share/opensearch/data
volumes:
os-data-1:
os-data-2:Field- and document-level security are roles, not settings, and both carry a caveat bigger than their tier. OpenSearch documents that field-level security "applies only to read operations … and does not prevent users with write or delete permissions from indexing, updating, or deleting data in those fields," and document-level security "does not restrict write operations." Treating either as a write barrier builds a gap.
{
"cluster_permissions": ["cluster_composite_ops_ro"],
"index_permissions": [
{
"index_patterns": ["orders-*"],
"dls": "{\"term\": {\"data_region\": \"eu\"}}",
"fls": ["~customer_email", "~customer_phone", "~payment_token"],
"masked_fields": ["customer_id"],
"allowed_actions": ["read", "search"]
}
]
}Migrating Either Direction, and Where It Breaks
Migration is where the fork point turns back into a cost. Three obstacles at three layers; conflating them is how a plan grows a month.
Obstacle one is the index format. OpenSearch's FAQ gives the supported range as indices "from Elasticsearch versions 6.0 up to 7.10," with a direct upgrade "to OpenSearch 1.x from Elasticsearch OSS and Kibana OSS 6.8.0-7.10.2, and Open Distro 1.x." Note the scope: OpenSearch 1.x. Today's 3.x ships Lucene 10, so a 7.10-era index landing cleanly in 3.8 is neither promised nor ruled out. Plan a staged hop and verify in a lab. Read the boundary off index.version.created, not created_string — an Elastic engineer confirmed the latter as a bug after it began repeating the raw integer in 8.12.
#!/usr/bin/env bash
set -euo pipefail
ES_HOST="${ES_HOST:-https://es.internal.example:9200}"
ES_AUTH="${ES_AUTH:?export ES_AUTH as user:password}"
PATTERN="${PATTERN:-orders-*}"
# index.version.created is an integer MMmmppbb: 7.10.2 is 7100299, 7.11.0 is
# 7110099. Anything at or above 7110000 is past the ceiling.
CEILING=7110000
versions="$(curl -sS -u "${ES_AUTH}" \
"${ES_HOST}/${PATTERN}/_settings/index.version.created?flat_settings=true" \
| jq -r 'to_entries[]
| .key + " " + (.value.settings["index.version.created"] | tostring)')"
blocked=0
while read -r index created; do
[ -n "${index}" ] || continue
case "${created}" in
''|*[!0-9]*) echo " ? ${index}: unparsable index version '${created}'"; blocked=1; continue ;;
esac
if [ "${created}" -ge "${CEILING}" ]; then
echo " x ${index}: index version ${created} — outside the documented ceiling"
blocked=1
fi
done <<< "${versions}"
if [ "${blocked}" -ne 0 ]; then
echo "Snapshot restore is not the path for this estate. Rebuild instead." >&2
exit 1
fi
echo "All indices inside the documented ceiling; a restore is worth testing."Obstacle two is the client, at another layer. Elasticsearch returns a product header — the pull request introducing it describes "a header to all Elasticsearch responses that confirms the type of service operating on the other end of the connection … X-elastic-product: Elasticsearch" — the mechanism behind the client product check. Your data can migrate perfectly and your applications still fail, because the official clients validate what they are talking to. Behaviour varies by client and version.
Obstacle three is that above the ceiling, the crossing is a rebuild, not a restore. Elastic's snapshot documentation does describe restoring an old index "to another cluster running the latest version of Elasticsearch that's compatible with both the index and your current cluster," then reindexing from it — but that is Elasticsearch-to-Elasticsearch index compatibility. No Elasticsearch version is compatible with both an 8.x index and an OpenSearch destination, so an intermediate cluster buys nothing; pull the documents directly. Two conditions from those docs carry to any reindex-from-remote: it "is only possible if the index's _source is enabled," and it "can take significantly longer than restoring a snapshot." The destination must also be told which remotes it may read, spelled differently on each side: reindex.remote.whitelist in Elasticsearch, reindex.remote.allowlist in OpenSearch.
{
"source": {
"remote": {
"host": "https://es-source.internal.example:9200",
"username": "reindex_reader",
"password": "${REINDEX_READER_PASSWORD}"
},
"index": "orders-2026.08",
"size": 2000,
"query": {
"range": {
"@timestamp": { "gte": "2026-08-01", "lt": "2026-09-01" }
}
}
},
"dest": {
"index": "orders-2026.08",
"op_type": "create"
},
"conflicts": "proceed"
}Note the asymmetry that request sits inside: OpenSearch documents source.remote as "a remote OpenSearch cluster," so an Elasticsearch source is outside what OpenSearch itself states — lab it. The route OpenSearch does document is the Migration Assistant, offering "one migration model for snapshot-based migrations with planned downtime (called backfill-only) and zero-downtime migrations that use live-traffic Capture and Replay," via "Reindex-from-Snapshot (RFS)." Its compatibility matrix answers the 8.x question outright: an Elasticsearch 8.x source is "No" against an OpenSearch 1.x target and "Yes" against 2.x and 3.x. Elasticsearch 9.x is absent from that matrix — a gap to test, not assume across.
One more variable belongs in the model: whether to self-manage at all. AWS's OpenSearch Service pricing shows "$0.068/hr" on demand for an m7g.medium.search instance in US East (N. Virginia), read 2026-09-02 — one instance type, one region, one date: an anchor, not a total cost of ownership. The arithmetic has the shape of any exit-cost model: the untested reindex is the missing number.
Governance Is the Real Exit Ramp
Every fact above is a fact about today; the question that outlives them — who changes the terms next — is one no benchmark can answer. OpenSearch answered it structurally: on 16 September 2024 the Linux Foundation announced that "AWS transfers OpenSearch to the Linux Foundation to support a vendor-neutral community for search, analytics, observability, and vector database software." The project is overseen by a technical steering committee, with premier members AWS, SAP and Uber.
That does not make OpenSearch faster, nor Elasticsearch a bad engine — its logsdb compression claim is a real answer to log storage cost. What foundation governance changes is the mechanism by which terms move: a single company can relicense its own project, and the 2021 change is the proof, with Terraform, Vault, Redis and MinIO each supplying another. A steering committee inside a foundation cannot do it on a Tuesday.
That is not ideology but a ten-year risk question: if the licence you depend on changed next quarter, what would it cost to leave? For self-managed Elasticsearch the answer runs through the previous section — index ceiling, client product check, a rebuild slower than a restore. For an Apache 2.0 engine the licence cannot change under code already published. Both are legitimate; only one is chosen deliberately, which is what digital sovereignty as testable architecture is about.
So: benchmark to choose the engine — your corpus, one harness, the published numbers ignored. Then read the licence to find out whether you can still be running that choice in ten years. The comparison asks the first question; the second decides whether the answer holds.
§FAQ/Common questions
Frequently asked
Is OpenSearch or Elasticsearch faster?
There is no trustworthy general answer, and the published head-to-heads are the reason. Elastic's 2023 comparison concluded Elasticsearch was 40%–140% faster than OpenSearch, testing 8.7 against OpenSearch 2.7; its 2024 vector-search post claimed 2x–12x out of the box. A 2025 Trail of Bits assessment measured the opposite — OpenSearch 2.17.1 1.6x faster on Big5 and 11% faster on Vectorsearch than Elasticsearch 8.15.4 — and states in its first paragraph that AWS requested it. Two were published by one vendor, the third commissioned by the other, and every version under test is several majors behind today's Elasticsearch 9.5.2 and OpenSearch 3.8.0. Run OpenSearch Benchmark against both candidates on your own corpus, mappings and query mix.
Is Elasticsearch open source again after the 2024 AGPL change?
The source code has an AGPLv3 option; the releases do not. Elastic's licensing FAQ describes adding AGPLv3 alongside SSPL and the Elastic License in September 2024, before 8.16 went generally available, then states directly: "Our releases will continue to be under the Elastic License." So the artefact you download and run is governed by ELv2 unless you build it yourself from source. A further limit sits in the repository: the root LICENSE.txt makes a triple AGPL/SSPL/ELv2 licence the default, but code licensed solely under the Elastic License 2.0 is found only in the x-pack folder. Do not read x-pack as a synonym for paid features, though — free-tier TLS and role-based access control live in that tree too.
Does running Elasticsearch or OpenSearch over a network trigger AGPL section 13?
Not by itself. AGPLv3 section 13 conditions the obligation on modification: if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network an opportunity to receive the Corresponding Source. Both conditions have to hold, so an unmodified copy serving a network does not trigger it. SSPL section 13 has no modification trigger: making the functionality available to third parties as a service obliges you to publish the Service Source Code, defined broadly enough to include the management, backup, storage and hosting software you use to offer it. This is a reading of the licence texts, not legal advice.
Which security features cost money on Elasticsearch but not OpenSearch?
Not security as a category — transport encryption and role-based access control are in Elastic's free Basic tier, as they are in OpenSearch. The line falls at the second set of controls an auditor asks for. In Elastic's subscription matrix dated 2026-07-01, Elasticsearch audit logging, Kibana audit logging, single sign-on (SAML, OpenID Connect, Kerberos, JWT) and LDAP/PKI/Active Directory authentication all sit outside the "Free and open — Basic" column while carrying marks under Platinum and Enterprise; the subscriptions page places field- and document-level security there too. A newer PDF is now the live download, so re-check. On OpenSearch those controls ship in the Apache 2.0 Security plugin, audit logging disabled by default and enabled through configuration. Gold and Platinum are both closed to new customers, per the matrix's own footnotes.
Can I migrate from Elasticsearch 8.x or 9.x to OpenSearch with a snapshot restore?
No — not by restore. OpenSearch's FAQ states it can use indices from Elasticsearch versions 6.0 up to 7.10, with a direct upgrade to OpenSearch 1.x from Elasticsearch OSS and Kibana OSS 6.8.0-7.10.2 and Open Distro 1.x. An 8.x or 9.x index is outside that range, so the crossing is a rebuild. Do not reach for Elastic's restore-to-an-intermediate-cluster recipe: that covers Elasticsearch-to-Elasticsearch index compatibility, and no Elasticsearch version is compatible with both an 8.x index and an OpenSearch destination. Rebuild directly — either reindex-from-remote from the live source, which requires the index's _source to be enabled and can take significantly longer than restoring a snapshot, or OpenSearch's Migration Assistant, whose compatibility matrix marks an Elasticsearch 8.x source Yes against OpenSearch 2.x and 3.x targets and No against 1.x. Elasticsearch 9.x is absent from that matrix entirely. Separately from any data-format question, Elasticsearch returns an X-elastic-product header so clients can confirm what they are connected to — the mechanism behind the client product check; budget for an application-layer change too.
Further reading
- OpenTofu Migration: The Registry Is the Hard Part
- OpenBao vs Vault: Three Gates Before You Move a Secret
- Self-Hosted Grafana vs Datadog: Kubernetes Observability
- Vendor Lock-In in the Cloud: Pricing Your Exit as a Number
- On-Prem RAG: Qdrant vs pgvector for Sovereign Retrieval
- Kubernetes Audit Logs Into a SIEM You Operate: Wazuh
- ClickHouse, Iceberg, DuckLake: A Lakehouse Off Snowflake
- Digital Sovereignty: From Slogan to Testable Architecture
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.