OriginChainDB
Elasticsearch clients

Worked examples by industry

These four examples show how to model a catalog, application logs, payments and support tickets. Each starts with the field mapping, then shows the query it enables. They are illustrative datasets, not customer case studies.

Prepare a supported Elasticsearch-compatible connection before running them.

Build a useful log-search workflowThe industry examples are illustrative; prepare their mappings and sample data before querying.
  1. Collect log fieldsKeep the message, service name and timestamp.
  2. Choose field mappingsUse text for messages, keyword for services and date for time.
  3. Find the relevant errorsSearch the message and filter the intended service.
  4. Group results over timeCheck error counts in the requested UTC time buckets.

Retail: a product catalog with facets

A search box over product text, plus the filters down the side of the page: brand, price band, availability. The trap here is wanting to search a name and group by it, which needs both views of the same field.

The mapping

Name and description are prose, so they are text. Brand and colour are picked from a list, so they are keyword. Name also carries a .keyword twin so it can be grouped exactly.

mappings: { properties: {
  name:        { type: 'text', fields: { keyword: { type: 'keyword' } } },
  description: { type: 'text', analyzer: 'english' },
  brand:       { type: 'keyword' },
  colour:      { type: 'keyword' },
  price:       { type: 'long' },
  in_stock:    { type: 'boolean' },
  added:       { type: 'date' }
} }

Search plus the facet counts, in one request

The search ranks by text relevance while the filters narrow it, and the aggregations count what is left so the sidebar numbers always match the results.

await es.search({
    index: 'shop.products',
    body: {
        size: 20,
        query: { bool: {
                must: [{ match: { description: 'waterproof running' } }],
                filter: [{ term: { in_stock: true } }]
            } },
        aggs: {
            by_brand: { terms: { field: 'brand' } },
            by_colour: { terms: { field: 'colour' } },
            avg_price: { avg: { field: 'price' } }
        }
    }
})

Price bands, and a sample product per band

Bands are not a field, they are a question, so they are named filters. Send this one on its own: a filters aggregation is answered with one search per filter.

aggs: { bands: {
  filters: { other_bucket_key: 'mid', filters: {
    budget:  { range: { price: { lt: 50 } } },
    premium: { range: { price: { gte: 200 } } }
  } },
  aggs: { example: { top_hits: { size: 1, _source: ['name','price'] } } }
} }

Offer a spelling correction.

Run the search and a suggester in the same request. If the search returns nothing, show the correction instead of an empty page.

Software: an application log stream

High write volume, mostly time-ranged reads, and one question repeated all day: what broke, where, and when did it start.

The mapping

The message is searched, so text. Service, level and host are grouped and filtered, so keyword. The timestamp drives every chart, so it is a date — an ISO string or epoch milliseconds, either works.

mappings: { properties: {
  message:   { type: 'text' },
  service:   { type: 'keyword' },
  level:     { type: 'keyword' },
  host:      { type: 'keyword' },
  latency_ms:{ type: 'long' },
  ts:        { type: 'date' }
} }

Errors per hour for one service

A date histogram over the filtered set. Buckets are UTC and calendar-aligned.

await es.search({
    index: 'ops.logs',
    body: {
        size: 0,
        query: { bool: { filter: [
                    { term: { service: 'checkout' } },
                    { term: { level: 'error' } },
                    { range: { ts: { gte: '2026-09-05T00:00:00Z' } } }
                ] } },
        aggs: { per_hour: { date_histogram: {
                    field: 'ts', calendar_interval: 'hour', time_zone: 'UTC'
                } } }
    }
})

The worst offenders, with an example line each

Group by service, then pull the slowest request in each group so the chart has something to click into.

aggs: { by_service: { terms: { field: 'service' },
  aggs: {
    p95:  { percentiles: { field: 'latency_ms' } },
    worst:{ top_hits: { size: 1, sort: [{ latency_ms: 'desc' }],
            _source: ['message','host','ts'] } }
  } } }

Import in batches.

Send logs with _bulk in batches of a few hundred to a few thousand, and check errors on the response — a failed item inside a successful request is the classic way to lose data quietly. See bulk ingest.

Financial services: a payment ledger

Amounts and dates you summarise, counterparties you look up exactly, and a free-text reference people actually search. The distinguishing requirement is that not everyone may see every row.

The mapping

The reference is prose. Everything you group or compare is keyword, long or date. Money is stored in minor units as an integer, so totals never drift.

mappings: { properties: {
  reference:   { type: 'text' },
  counterparty:{ type: 'keyword' },
  status:      { type: 'keyword' },
  currency:    { type: 'keyword' },
  amount_minor:{ type: 'long' },   // 12345 = 123.45
  booked_at:   { type: 'date' }
} }

Daily totals by currency

A date histogram with a nested terms aggregation and a sum, which is one request rather than a report job.

await es.search({
    index: 'fin.payments',
    body: {
        size: 0,
        query: { bool: { filter: [{ term: { status: 'settled' } }] } },
        aggs: { per_day: {
                date_histogram: { field: 'booked_at', calendar_interval: 'day' },
                aggs: { by_ccy: { terms: { field: 'currency' },
                        aggs: { total: { sum: { field: 'amount_minor' } } } } }
            } }
    }
})

Configure the intended database identity and access rules, then test both permitted and denied searches and aggregations. An application filter is not a substitute for database authorization. See authentication.

Support: a ticket desk

Agents search what customers wrote, managers want queue health, and both want it in the same shape.

The mapping

Subject and body are searched. Queue, status and priority are grouped. First response time is a number you average.

mappings: { properties: {
  subject:   { type: 'text' },
  body:      { type: 'text', analyzer: 'english' },
  queue:     { type: 'keyword' },
  status:    { type: 'keyword' },
  priority:  { type: 'keyword' },
  first_reply_mins: { type: 'long' },
  opened_at: { type: 'date' }
} }

Open tickets mentioning a problem, with queue health beside them

One request answers the agent's search and the manager's dashboard, over the same match set.

await es.search({
    index: 'support.tickets',
    body: {
        size: 20,
        query: { bool: {
                must: [{ match: { body: 'refund not received' } }],
                filter: [{ term: { status: 'open' } }]
            } },
        aggs: { by_queue: { terms: { field: 'queue' },
                aggs: { avg_first_reply: { avg: { field: 'first_reply_mins' } } } } }
    }
})

Two agents editing one ticket

Read the ticket, then write it back conditionally. If someone else saved first, you get a conflict instead of silently overwriting their edit.

const { body: cur } = await es.get({ index: 'support.tickets', id })
await es.bulk({
    body: [
        { index: { _index: 'support.tickets', _id: id,
                if_seq_no: cur._version - 1, if_primary_term: 1 } },
        { ...cur._source, status: 'resolved' }
    ]
})

// someone else saved first -> items[0].index.status === 409

What these four have in common

  • The mapping is the design. Every question you can ask later is decided by which fields are text and which are keyword. See indexing.

  • Search and summary in one request. Hits and aggregations come from the same match set, so a dashboard cannot disagree with the list beneath it.

  • Verify a write with a read. Index a known document and test its expected searches. Separate vector and cross-query workflows still need explicit application coordination.

  • Start from zero — Connect a client and run the first search.

  • All examples — Sixteen single-purpose recipes with their responses.

Elasticsearch is a trademark of Elasticsearch B.V., registered in the U.S. and in other countries. OriginChainDB is not affiliated with, endorsed by, or sponsored by Elasticsearch B.V. OriginChainDB implements a compatible HTTP API so that existing Elasticsearch clients can talk to it; it does not distribute Elasticsearch software.

© 2026 Silicoyn Technologies Pvt Ltd

On this page