Translate one AtlasMart telemetry analysis among Query DSL+aggregations, ES|QL, and PPL, prove semantic parity, and benchmark cost without inventing results.

Translate One Analytical Requirement Between Query DSL+Aggregations, ES|QL, and PPL and Compare Semantics/Cost

Teach ES|QL and OpenSearch PPL/SQL as distinct analytical interfaces with explicit boundaries relative to Query DSL, aggregations, dashboards, security, and resource cost.

Intermediate → Advanced130–180 minutesCross-interface semantic parity & benchmark lab · Chapter 23 · Lesson 05Elasticsearch/Kibana 9.5.3 · OpenSearch/Dashboards 3.8.0 · bundled JVMsLast reviewed: September 2026

Learning outcomes

The chapter ends with a controlled translation exercise. The goal is not to declare a universal winner. It is to prove that the same AtlasMart business question can produce equivalent domain facts through three different interfaces while exposing different response shapes, planning surfaces, feature gaps, and resource costs.

01

Implement the same telemetry requirement in Query DSL+aggregations, ES|QL, and OpenSearch PPL.

02

Normalize responses into one domain schema and compare exact versus approximate values correctly.

03

Test nulls, time bucketing, high-cardinality approximation risk, and unsupported syntax explicitly.

04

Measure each interface with the same dataset, hardware, warmup, concurrency, and security scope.

05

Write an interface decision record based on semantics, ergonomics, latency, portability, maturity, and operational ownership.

Pinned analytics-language baseline

Examples are reviewed against Elasticsearch/Kibana 9.5.3 and OpenSearch/OpenSearch Dashboards 3.8.0, using their bundled JVMs and the course's existing local TLS/auth conventions. Elasticsearch examples use ES|QL through POST /_query. OpenSearch examples use the bundled SQL plugin through /_plugins/_ppl and /_plugins/_sql; a minimal OpenSearch distribution may require that plugin to be installed. Syntax, commands, functions, cross-cluster support, result limits, and preview/experimental status are version-sensitive—verify them against the exact deployment before promoting a query. No live Elasticsearch/OpenSearch cluster is available in this generation environment; the requests are reproducible, deterministic fixture invariants are stated explicitly, and runtime/benchmark values are left as MEASURED rather than fabricated.

Canonical Chapter 23 requirement

For production telemetry from 10:00 through 10:15 UTC, group by 5-minute bucket + service and report total requests, total errors, error rate, average duration, and p95 duration. The missing duration_ms on e10 is intentional: every interface must document null semantics rather than silently changing the denominator. Exact request/error sums and average-of-present durations are deterministic; percentile values are approximate and must not be asserted as exact cross-interface equality.

Execution and safety note

Run mutating, destructive, security, lifecycle, snapshot, failure-injection, and load-test commands only in the disposable AtlasMart lab or an equivalently isolated environment. Verify the target cluster, index, tenant, credentials, and rollback path before execution; treat shown output as an expected invariant unless the lesson explicitly labels it as captured evidence.

1. One fixture, one semantic oracle

Shared AtlasMart telemetry fixture
PUT atlasmart-telemetry-v23
{
  "settings": {"number_of_shards": 1, "number_of_replicas": 0},
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "@timestamp": {"type": "date"},
      "service": {"type": "keyword"},
      "environment": {"type": "keyword"},
      "tenant_id": {"type": "keyword"},
      "request_count": {"type": "long"},
      "error_count": {"type": "long"},
      "duration_ms": {"type": "double"}
    }
  }
}

POST atlasmart-telemetry-v23/_bulk?refresh=true
{"index":{"_id":"e01"}}
{"@timestamp":"2026-09-11T10:00:00Z","service":"catalog-api","environment":"prod","tenant_id":"tenant-a","request_count":100,"error_count":2,"duration_ms":120}
{"index":{"_id":"e02"}}
{"@timestamp":"2026-09-11T10:01:00Z","service":"catalog-api","environment":"prod","tenant_id":"tenant-a","request_count":120,"error_count":3,"duration_ms":140}
{"index":{"_id":"e03"}}
{"@timestamp":"2026-09-11T10:02:00Z","service":"checkout-api","environment":"prod","tenant_id":"tenant-a","request_count":80,"error_count":4,"duration_ms":220}
{"index":{"_id":"e04"}}
{"@timestamp":"2026-09-11T10:03:00Z","service":"checkout-api","environment":"prod","tenant_id":"tenant-a","request_count":90,"error_count":5,"duration_ms":260}
{"index":{"_id":"e05"}}
{"@timestamp":"2026-09-11T10:05:00Z","service":"catalog-api","environment":"prod","tenant_id":"tenant-a","request_count":130,"error_count":1,"duration_ms":110}
{"index":{"_id":"e06"}}
{"@timestamp":"2026-09-11T10:06:00Z","service":"catalog-api","environment":"prod","tenant_id":"tenant-a","request_count":140,"error_count":2,"duration_ms":130}
{"index":{"_id":"e07"}}
{"@timestamp":"2026-09-11T10:07:00Z","service":"checkout-api","environment":"prod","tenant_id":"tenant-a","request_count":100,"error_count":2,"duration_ms":210}
{"index":{"_id":"e08"}}
{"@timestamp":"2026-09-11T10:08:00Z","service":"checkout-api","environment":"prod","tenant_id":"tenant-a","request_count":110,"error_count":3,"duration_ms":250}
{"index":{"_id":"e09"}}
{"@timestamp":"2026-09-11T10:10:00Z","service":"catalog-api","environment":"prod","tenant_id":"tenant-a","request_count":150,"error_count":1,"duration_ms":100}
{"index":{"_id":"e10"}}
{"@timestamp":"2026-09-11T10:11:00Z","service":"catalog-api","environment":"prod","tenant_id":"tenant-a","request_count":160,"error_count":1}
{"index":{"_id":"e11"}}
{"@timestamp":"2026-09-11T10:12:00Z","service":"checkout-api","environment":"prod","tenant_id":"tenant-a","request_count":120,"error_count":6,"duration_ms":280}
{"index":{"_id":"e12"}}
{"@timestamp":"2026-09-11T10:13:00Z","service":"checkout-api","environment":"prod","tenant_id":"tenant-a","request_count":130,"error_count":7,"duration_ms":300}

The deterministic oracle covers exact fields only. Across all six 5-minute/service buckets, request/error sums can be calculated exactly. e10 is missing duration and must not be invented as zero. Percentiles are deliberately outside the exact oracle.

Bucket Service Requests Errors Error rate Average present duration
10:00 catalog-api 220 5 0.022727… 130 ms
10:00 checkout-api 170 9 0.052941… 240 ms
10:05 catalog-api 270 3 0.011111… 120 ms
10:05 checkout-api 210 5 0.023810… 230 ms
10:10 catalog-api 310 2 0.006452… 100 ms (1 present value)
10:10 checkout-api 250 13 0.052000 290 ms

2. Query DSL + aggregations implementation

Query DSL expresses the source filter plus nested bucket/metric aggregations. The client receives an aggregation tree; error rate can be computed with a pipeline aggregation or in domain code. The date histogram explicitly uses a fixed five-minute interval.

Query DSL + aggregations
POST atlasmart-telemetry-v23/_search
{
  "size": 0,
  "query": {"bool": {"filter": [
    {"term": {"environment": "prod"}},
    {"range": {"@timestamp": {"gte": "2026-09-11T10:00:00Z", "lt": "2026-09-11T10:15:00Z"}}}
  ]}},
  "aggs": {
    "time": {
      "date_histogram": {"field": "@timestamp", "fixed_interval": "5m", "min_doc_count": 0},
      "aggs": {
        "service": {
          "terms": {"field": "service", "size": 20},
          "aggs": {
            "requests": {"sum": {"field": "request_count"}},
            "errors": {"sum": {"field": "error_count"}},
            "avg_ms": {"avg": {"field": "duration_ms"}},
            "p95_ms": {"percentiles": {"field": "duration_ms", "percents": [95]}},
            "error_rate": {"bucket_script": {"buckets_path": {"e":"errors","r":"requests"}, "script":"params.r == 0 ? null : params.e / params.r"}}
          }
        }
      }
    }
  }
}

Terms aggregation can be approximate for high-cardinality distributed data when buckets are truncated. Our two-service fixture avoids that edge case; production tests must not infer global exactness from this toy dataset.

3. ES|QL implementation

ES|QL pipeline
POST /_query?format=json
{
  "query": """
    FROM atlasmart-telemetry-v23
    | WHERE environment == "prod"
      AND @timestamp >= "2026-09-11T10:00:00Z"
      AND @timestamp <  "2026-09-11T10:15:00Z"
    | STATS requests=SUM(request_count), errors=SUM(error_count),
            avg_ms=AVG(duration_ms), p95_ms=PERCENTILE(duration_ms,95)
      BY bucket=BUCKET(@timestamp,5 minutes), service
    | EVAL error_rate=TO_DOUBLE(errors)/TO_DOUBLE(requests)
    | SORT bucket, service
  """
}

The result is a column schema plus rows. Normalize by column name, not by position hard-coded from a screenshot. The query's readability is a real developer benefit, but row caps and different search-feature coverage remain part of the interface decision.

4. OpenSearch PPL implementation

PPL pipeline
POST /_plugins/_ppl?format=jdbc
{
  "query": "source=atlasmart-telemetry-v23 | where environment='prod' AND @timestamp >= timestamp('2026-09-11 10:00:00') AND @timestamp < timestamp('2026-09-11 10:15:00') | stats sum(request_count) as requests, sum(error_count) as errors, avg(duration_ms) as avg_ms, percentile(duration_ms,95) as p95_ms by span(@timestamp,5m), service | eval error_rate=errors/requests | sort + `span(@timestamp,5m)`, + service"
}

PPL's span, aggregation functions, and response metadata are OpenSearch contracts. Do not copy the ES|QL BUCKET expression or assume identical type names. If PPL explains to a terms/date-histogram plan, that helps explain why distributed approximation boundaries can resemble native aggregations—but runtime behavior still needs measurement.

5. Normalize before comparing

Create one domain shape: {bucket, service, requests, errors, error_rate, avg_ms, p95_ms}. Compare exact fields with a strict oracle. Compare floating error rates within a declared numeric tolerance. Compare percentiles only for plausibility/algorithm tolerance, not bitwise identity.

Pseudo-test for semantic parity
for row in normalized_rows:
    assert row.requests == oracle[row.key].requests
    assert row.errors == oracle[row.key].errors
    assert abs(row.error_rate - oracle[row.key].error_rate) < 1e-9
    assert abs(row.avg_ms - oracle[row.key].avg_ms) < 1e-9
    assert row.p95_ms is not null
    # Do NOT require identical approximate percentile output across engines/interfaces.

assert normalized[('10:10','catalog-api')].avg_ms == 100.0
# This proves missing duration_ms was ignored rather than converted to 0.

6. Controlled edge cases

Edge case Expected test Why it matters
Missing duration e10 contributes requests/errors but not duration average Null semantics change KPIs if mishandled.
Zero requests Inject isolated row/bucket and define error_rate=null rather than divide blindly Derived metric must define denominator policy.
High-cardinality service Synthetic many-service fixture; inspect terms/PPL approximation Distributed bucket truncation can change grouped results.
Timezone/DST Use fixed UTC window and document calendar vs fixed intervals Bucket boundaries are semantics.
Unsupported command Require explicit error and version note; do not silently translate Language feature parity is not guaranteed.
Wrong approach: benchmark before proving semantic parity.

A 20% faster query that answers a different question is not an optimization. Correctness gates performance testing.

7. Fair benchmark protocol

Run Elasticsearch Query DSL and ES|QL against the same Elasticsearch node/data; run OpenSearch Query DSL and PPL against the same OpenSearch node/data. Do not compare Elastic-vs-OpenSearch latency as if language alone caused the difference. For interface comparisons within a product, keep mappings, shard count, cache state, hardware, network, authentication, concurrency, and output cardinality fixed.

Benchmark matrix — record, do not invent
product=Elasticsearch_9.5.3
interfaces=QueryDSL,ESQL
product=OpenSearch_3.8.0
interfaces=QueryDSL,PPL

docs=MEASURED
index_bytes=MEASURED
shards=1
concurrency=MEASURED
warmup_runs=MEASURED
measured_runs=MEASURED
cache_state=DOCUMENTED
result_rows=6
client_p50_ms=MEASURED
client_p95_ms=MEASURED
client_p99_ms=MEASURED
cpu=MEASURED
heap=MEASURED
bytes_received=MEASURED
semantic_test=PASS|FAIL

8. Decision record

Dimension Query DSL + aggs ES|QL OpenSearch PPL
Mental model Search/filter + aggregation tree Typed table pipeline OpenSearch pipeline
Result shape Hits/aggregation JSON Columns + rows Plugin-defined table/JDBC/raw forms
Search feature breadth Native/fullest Evolving analytical/search subset Plugin-specific analytical subset
Transform ergonomics Often verbose Strong Strong
Portability Product-native JSON differs by feature Elastic-only OpenSearch-only
UI workflow Dev Tools/Kibana visual tools Kibana ES|QL workflows Query Workbench/notebooks/PPL visualizations

A practical AtlasMart decision might use Query DSL for latency-sensitive product search, ES|QL for Elastic operational investigations and analyst tables, and PPL for OpenSearch observability/notebook work. That is a workload allocation, not a language ranking.

9. Chapter release gate and bridge to Chapter 24

Check your understanding

  1. What must happen before performance comparison?
  2. Which values are exact in this lab?
  3. Why is p95 not an exact parity assertion?
  4. Why not compare Elastic ES|QL latency directly with OpenSearch PPL and blame the language?
  5. What is the enduring interface rule?
Review the answers

1. Normalize and prove semantic correctness across interfaces.

2. Request/error sums, error rate derived from them, and averages over present durations.

3. Distributed percentile algorithms are approximate and can differ slightly.

4. They run on different products/execution engines; confounders must be controlled.

5. Choose the interface whose semantics fit the workload, then validate resource cost, security, maturity, and operations.

Final cleanup
DELETE atlasmart-telemetry-v23
# Delete any temporary lookup/service-directory indices created during the chapter.
DELETE atlasmart-service-directory-v23

Chapter 24 turns the measurements used here into an operational evidence model: cluster/node/index metrics, dashboards, alerts, incidents, and search SLOs.

Summary and next step

Preserve the evidence, assumptions, version boundaries, and safety checks established in this lesson. Carry them into the next lesson—or, at the end of the capstone, into the production runbook—rather than treating this lesson as an isolated recipe.

References

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.