Chapter 16 · Search Execution, Profiling, Slow Logs, Async/Search-After/PIT, and Pagination

Profile API, Explain, Search Slow Logs, Hot Threads, Tasks, and Distinguishing Query from Fetch Cost

Build an evidence chain for slow AtlasMart searches with profile, explain, slow logs, hot threads and task state while separating shard query work, fetch work, queues and coordinator/network time.

Intermediate → Advanced115–150 minutesDistributed search & pagination labElasticsearch 9.5.3 · OpenSearch 3.8.0Last reviewed: September 2026

Learning outcomes

AtlasMart receives a complaint: “category pages sometimes take two seconds.” One operator opens Profile, another reads slow logs, another checks hot threads, and a fourth lists tasks. All are valid tools, but each sees a different slice of reality. The goal is to join those slices into one causal timeline rather than argue over whichever metric looks dramatic.

01

Use profile to locate expensive query/collector/aggregation components without treating its timings as production latency.

02

Use explain for a document-level scoring question, not as a cluster performance profiler.

03

Configure temporary shard slow logs and interpret query versus fetch thresholds.

04

Correlate hot threads and task state with client latency, shard failures and queue/backpressure evidence.

05

Build a reproducible diagnosis that distinguishes query, fetch, coordination, queueing and network cost.

Chapter baseline reviewed 11 September 2026

Examples target self-managed Elasticsearch 9.5.3 / Kibana 9.5.3 and OpenSearch 3.8.0 / OpenSearch Dashboards 3.8.0. AtlasMart keeps https://localhost:9200 for Elasticsearch with CA verification and https://localhost:9201 for the disposable OpenSearch demo certificate using OPENSEARCH_INITIAL_ADMIN_PASSWORD. The established containers are atlasmart-es and atlasmart-os. This chapter deliberately creates a disposable index with three primary shards and zero replicas to make shard fan-out observable on one local node; that is a teaching topology, not a production sizing recommendation. OpenSearch demo -k remains local-only; production must validate certificates. No moving latest tags are used.

Execution note

The generation environment does not run the AtlasMart containers, so latency, profile nanoseconds, slow-log lines, task IDs and PIT IDs are not fabricated. Expected outputs describe invariant fields and directions of change. Run the bounded lab locally and record your own p50/p95/p99, shard counts, profile trees and resource statistics before accepting a performance conclusion.

1. Profile: execution detail with deliberate overhead

The Profile API instruments search components and returns per-shard operator/collector timing trees. It is ideal for answering questions such as “did the wildcard expansion dominate?”, “which aggregation consumed most query-phase time?”, or “did a filter rewrite into something cheaper?” It is intentionally intrusive and adds overhead.

Both products document important omissions. Profile is not the client clock: network latency, queue residence and coordinating-node idle/merge time are not fully represented. OpenSearch explicitly notes these omissions; Elastic likewise warns that Profile does not cover all search overhead. Therefore profile numbers must be paired with a normal, unprofiled latency distribution.

Profile an AtlasMart query
GET atlasmart-search-exec-v1/_search
{
  "profile": true,
  "size": 20,
  "query": {
    "bool": {
      "must": [{"match":{"name":"wireless headset"}}],
      "filter": [{"term":{"available":true}}]
    }
  },
  "aggs": {
    "categories": {"terms":{"field":"category","size":10}}
  }
}
  • Inspect per-shard query tree descriptions and time breakdowns.
  • Inspect collector/aggregation timings separately from hit fetch behavior.
  • Repeat without profile:true for performance measurement; do not compare profiled request latency to an unprofiled baseline as if instrumentation were free.

2. Explain: why one document scored that way

_explain answers a relevance question for a specific document and query. It decomposes scoring contributions such as BM25 term frequency, document frequency, field length and boosts. It does not tell you whether the whole search is resource-efficient, and a deep explanation tree is not a benchmark.

Use it after Chapter 07’s relevance regression identifies a suspicious document. Keep a small number of representative documents; explaining thousands of hits is diagnostic abuse.

Explain one known AtlasMart document
GET atlasmart-search-exec-v1/_explain/P-1000
{
  "query": {"match":{"name":"wireless headset"}}
}

3. Slow logs: historical shard evidence, not client latency

Elasticsearch search slow logs are shard-level and have separate query and fetch thresholds. Current Elastic documentation also recommends its newer query logging when end-to-end Elasticsearch request duration is the desired search signal. OpenSearch retains shard slow logs and, since 2.12, also has request-level search slow logs with phase timing such as query and fetch in the emitted entry.

Enable aggressive thresholds only in a disposable lab. Low thresholds can create substantial logging I/O and distort the workload being diagnosed.

Temporary shard-level slow-log thresholds (both products)
PUT atlasmart-search-exec-v1/_settings
{
  "index.search.slowlog.threshold.query.warn":"50ms",
  "index.search.slowlog.threshold.fetch.warn":"50ms"
}

# Restore after the experiment:
PUT atlasmart-search-exec-v1/_settings
{
  "index.search.slowlog.threshold.query.warn":"-1",
  "index.search.slowlog.threshold.fetch.warn":"-1"
}
Privacy/security boundary

Slow logs can contain request/source-related context depending on product/settings. Treat them as operational data: restrict access, avoid logging secrets or tenant-sensitive query bodies, and define retention.

4. Hot threads and tasks: “what is running now?”

Hot threads samples threads spending CPU, blocking or waiting. It is a snapshot, so one sample can miss intermittent work. The Tasks API shows cancellable/running operations and is better for long-lived operations. Neither tool replaces application tracing because client retries and upstream queueing may happen before Elasticsearch/OpenSearch sees the request.

Runtime correlation
GET _nodes/hot_threads?threads=5&ignore_idle_threads=true
GET _tasks?detailed=true&actions=*search*
GET _nodes/stats/thread_pool,jvm,process,breaker?human
GET atlasmart-search-exec-v1/_stats/search?human
Observation Likely next question Do not conclude yet
High search CPU in hot threads Which query/aggregation is active? Profile a bounded reproduction. That adding nodes is automatically the best fix.
Fetch slow log only Is _source large, stored-field access costly, or storage cold? That Query DSL is the bottleneck.
Long search task Is it expected analytics, stuck client, remote search or expensive query? That timeout already cancelled it.
No hot thread during client spike Was work queued, network-bound, already completed, or on another node? That the cluster was idle.

5. Controlled diagnosis workflow

  1. Capture the exact request, target, routing/preference, client timeout and timestamp.
  2. Measure an unprofiled latency distribution and response bytes.
  3. Inspect _shards, failures/skips and task/thread-pool state during the event.
  4. Enable temporary slow logging only if needed and with a narrow threshold/window.
  5. Profile a bounded reproduction, not the entire production traffic stream.
  6. If ranking is questioned, explain only representative documents.
  7. Change one causal variable and rerun the same workload.

6. Lab: query-bound versus fetch-bound

Disposable Chapter 16 index — run separately against each product
PUT atlasmart-search-exec-v1
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 0,
    "index.max_result_window": 100
  },
  "mappings": {
    "properties": {
      "sku":        {"type":"keyword"},
      "name":       {"type":"text","fields":{"raw":{"type":"keyword"}}},
      "category":   {"type":"keyword"},
      "price":      {"type":"double"},
      "available":  {"type":"boolean"},
      "updated_at": {"type":"date"},
      "popularity": {"type":"integer"}
    }
  }
}
Deterministic fixture generator (Python 3, writes NDJSON)
# Save as chapter16_fixture.py and redirect stdout to fixture.ndjson.
import json
from datetime import datetime, timedelta, timezone
base = datetime(2026, 9, 1, tzinfo=timezone.utc)
for i in range(240):
    sku = f"P-{1000+i:04d}"
    meta = {"index":{"_index":"atlasmart-search-exec-v1","_id":sku}}
    doc = {
      "sku": sku,
      "name": f"AtlasMart {'wireless' if i%3==0 else 'wired'} headset model {i:03d}",
      "category": ["audio","mobile","office"][i%3],
      "price": round(20 + (i%80)*1.25, 2),
      "available": i%5 != 0,
      "updated_at": (base + timedelta(minutes=i)).isoformat().replace('+00:00','Z'),
      "popularity": (i*17)%101
    }
    print(json.dumps(meta,separators=(',',':')))
    print(json.dumps(doc,separators=(',',':')))
# Then POST fixture.ndjson to /_bulk?refresh=true with Content-Type application/x-ndjson.
Compare small and large fetch projections
GET atlasmart-search-exec-v1/_search
{
  "size":50,
  "_source":["sku"],
  "query":{"match_all":{}}
}

GET atlasmart-search-exec-v1/_search
{
  "size":50,
  "_source":true,
  "query":{"match_all":{}}
}

The synthetic fixture has small sources, so the latency delta may be tiny. That is acceptable. The lab objective is to prove the diagnostic method, not invent a fetch bottleneck. If you want a fetch-heavy reproduction, add a deterministic description payload to a disposable copy and disclose the payload size.

7. Production judgment

Observability tools answer different questions: client timing measures user experience; slow logs preserve historical server-side evidence; profile explains internal query execution; explain decomposes one score; hot threads samples runtime hotspots; tasks expose active operations. The reliable diagnosis is the intersection of these signals.

Never promote a profiled query directly to “X milliseconds in production.” Profile overhead, cache warmness, concurrency, shard placement, storage state and coordinator/network effects all change the end-to-end request.

Check your understanding

  1. What is the Profile API best for?
  2. Why can a fetch slow log matter when query profile looks cheap?
  3. What does _explain answer?
  4. Why are low slow-log thresholds risky?
  5. Why take more than one hot-thread sample?
Review the answers

1. Finding expensive internal search components and understanding execution structure on a bounded reproduction.

2. The expensive work may be loading/returning winning documents after ranking has already completed.

3. Why a particular document matches/scores as it does for a given query.

4. They can produce heavy log volume/I/O and expose sensitive request context.

5. It is a point-in-time sample and can miss intermittent or node-specific saturation.

Summary and next step

You now have an evidence hierarchy for slow search. Lesson 3 applies it to the most common correctness/performance pagination failure: deep from/size without a stable point-in-time view.

Authoritative 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.