AWS Database Blog

Building search experiences for JSON data with Amazon OpenSearch Service

In this post, you learn how to use Amazon OpenSearch Service as a discovery layer that combines full-text, vector, and geospatial search across JSON documents in a single query, using a restaurant discovery app as a working example.

Modern applications rarely rely on a single data store. A restaurant discovery app, for example, stores user sessions in Amazon DynamoDB for single-digit millisecond key-value lookups. It maintains catalog records in Amazon DocumentDB (with MongoDB compatibility) for flexible document operations, and processes transactions in Amazon Aurora for relational integrity. Each store excels at its operational workload, but none provides the multi-modal search experience your users expect. Amazon OpenSearch Service fills this gap as a dedicated discovery layer. It receives data from operational stores, and you can then run full-text, vector, and geospatial search across those same documents in a single query.

Imagine you’re building that restaurant discovery app. A user types “vegan Thai food” while standing in downtown Austin. They want results that are relevant to their intent, near their location, and well-reviewed. A traditional database struggles here. It cannot match “vegan” to “plant-based,” does not understand that “Thai” relates to “Asian cuisine,” and treats geographic proximity as an afterthought.

Each restaurant in your catalog is a JSON document with a name, description, cuisine tags, location coordinates, rating, hours, and a nested array of menu items. You want all of that searchable, filterable, and rankable, without flattening the document, splitting it across tables, or running separate queries against separate systems.

Amazon OpenSearch Service solves this discovery challenge. Built on Apache Lucene, OpenSearch Service exposes a Representational State Transfer (REST) API. Documents, queries, mappings, and results are all expressed as JSON. This end-to-end JSON interface removes the mismatch between your application objects and how those objects are stored and queried. Your application sends a JSON document in, OpenSearch Service indexes it, and you get JSON back.

This post explores what makes the JSON API support in OpenSearch Service well suited to discovery workloads. It then walks through building a restaurant discovery app that combines full-text, vector, and geospatial search in a single query.

How Amazon OpenSearch Service supports JSON workloads

Many systems “support JSON.” OpenSearch is built around it. Four characteristics set OpenSearch Service apart for JSON workloads:

  • Per-field indexing strategy. Every field in a single JSON document can be indexed differently. One field undergoes full-text analysis for natural language, while others are stored as multi-dimensional vectors, exact-match keywords, or geospatial points. You can also disable indexing entirely (index: false) on fields used only for display payloads, such as a hero image URL or a formatted display string. Those fields stay stored and retrievable without the overhead of building an index for them. Traditional databases lock an entire column into a single indexing strategy. OpenSearch Service evaluates and indexes each field independently within the same document context.
  • Nested objects and arrays preserve their structure. A restaurant document with an array of menu items, each with its own name, price, and dietary tags, stays queryable as a structured object. When standard object schemas flatten arrays during indexing, traditional engines collapse boundaries and cross-match values across separate items incorrectly. The nested data type preserves array element boundaries, so queries across distinct items return accurate results.
  • Schema is flexible but controllable. You don’t need to declare every field up front. New fields can be auto-mapped as documents arrive, or you can lock the schema with dynamic: "strict". You can evolve the document shape over time without migrations, while still controlling exactly how each field is indexed.
  • The query language is itself JSON. Queries mirror the structure of documents. They compose and nest naturally, which makes complex queries readable and programmatically constructible without string concatenation or object-relational mapping (ORM) abstractions.

Together, these properties mean a single JSON document can support full-text, semantic, geospatial, and faceted search at the same time. You query the same fields in one request, with no extract, transform, and load (ETL) process between specialized systems.

Architecture: Amazon OpenSearch Service as a discovery layer

Amazon OpenSearch Service functions as a discovery and relevance-ranking layer on top of operational data stores, rather than a primary transactional database. This separation of concerns keeps transactional workloads on dedicated source databases optimized for specific application contexts. DynamoDB handles user state, Amazon DocumentDB handles product catalogs, and Aurora handles transactions. Search traffic targets OpenSearch Service independently, without competing for resources with transactional operations.

Data flows into OpenSearch Service through Change Data Capture (CDC) pipelines managed by OpenSearch Ingestion (OSI). DynamoDB uses zero-ETL links. Amazon DocumentDB, Aurora PostgreSQL, and Aurora MySQL use the continuous CDC synchronization in OSI, through Change Streams and CDC logs. Amazon Simple Storage Service (Amazon S3) connects through the OSI S3 Source Connector, which supports batch and incremental ingestion of data files and application logs. That connector makes Amazon S3 a first-class source for log analytics and archival search workloads. OSI transforms, normalizes, and indexes the raw source JSON documents directly into your destination OpenSearch Service domain.

This architectural separation preserves strict data integrity inside your transactional databases. OpenSearch Service handles advanced search functionality independently, including full-text matching, vector similarity, complex filtering, and geospatial calculations, without adding query load to operational stores.

CDC pipelines carrying data from DynamoDB, Amazon DocumentDB, Aurora, and Amazon S3 into an OpenSearch search index

Figure 1: Amazon OpenSearch Service as a discovery layer, with CDC pipelines synchronizing data from DynamoDB, Amazon DocumentDB, Aurora, and Amazon S3 into the OpenSearch index

Building the restaurant discovery app

With the architecture in place, let’s design an index that puts these properties to work. A single mapping supports text, vector, and geospatial search over the same documents.

PUT /restaurants

{
  "settings": {
    "index": { "knn": true },
    "analysis": {
      "analyzer": {
        "cuisine_analyzer": {
          "type": "english"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "description": { "type": "text", "analyzer": "cuisine_analyzer" },
      "cuisine": { "type": "keyword" },
      "location": { "type": "geo_point" },
      "rating": { "type": "float" },
      "menu_items": {
        "type": "nested",
        "properties": {
          "item_name": { "type": "text" },
          "price": { "type": "float" },
          "dietary_tags": { "type": "keyword" }
        }
      },
      "description_embedding": {
        "type": "knn_vector",
        "dimension": 768,
        "method": {
          "name": "hnsw",
          "space_type": "cosinesimil",
          "engine": "faiss"
        }
      }
    }
  }
}

Each field is indexed for its purpose, all within one document type. The following table summarizes the mapping strategy.

Field Type Purpose
name text Fuzzy matching for restaurant names
description text + English analyzer Stemming and linguistic normalization
cuisine keyword Exact-match filtering and faceting
location geo_point Distance filtering and proximity sorting
rating float Numeric range filtering
menu_items nested Structured queries across distinct array elements
description_embedding knn_vector Semantic similarity scoring with ML embeddings

Text analysis: Handling vocabulary and language

The preceding English analyzer tokenizes text, lowercases it, and applies English stemming. As a result, “running” matches “run” and “restaurants” matches “restaurant.” You get built-in linguistic intelligence without writing custom filter chains.

To make “vegan” match “plant-based,” apply a synonym_graph filter inside a search-time analyzer. For more information, see synonym expansion. The filter intercepts incoming search phrases dynamically, so you can update synonyms at any time without forcing a cluster-wide reindex. For autocomplete experiences, use edge_ngram tokenization instead of standard n-grams. Standard n-grams split characters globally across words and can increase index storage size by 3 to 10 times.

Note: use the _analyze API to inspect exactly how your analyzer tokenizes any input phrase before you commit to a mapping. The output surfaces stemming behavior, stop-word removal, and synonym expansion, so you can tune the analyzer iteratively without reindexing the entire dataset.

Vector search for semantic understanding

Traditional keyword search fails when your users describe what they want in different vocabulary than what your data contains. Someone searching for “budget-friendly Italian food” does not find a restaurant described as “affordable pasta and pizza” through keyword matching alone. Vector search addresses this by storing machine learning (ML)-generated embeddings directly inside the same JSON document, alongside the text fields.

OpenSearch Service orchestrates embedding generation through four approaches:

  1. Automatic semantic enrichment provides semantic search without requiring machine learning infrastructure or vector expertise. It expands raw text into AI-powered neural sparse vector embeddings during data ingestion, which adds context-aware intent to traditional keyword matching and improves search relevance.
  2. The neural search plugin manages embedding pipeline orchestration natively inside Amazon OpenSearch Service, during both the ingestion and search phases.
  3. Ingest pipelines with ML connectors call external managed foundation models hosted on Amazon Bedrock, Amazon SageMaker, or providers such as Cohere.
  4. AWS Lambda invokes custom ML models from OSI pipelines programmatically.

Hierarchical Navigable Small World (HNSW), used in the preceding mapping, provides recall of more than 95% with sub-millisecond query latency. For datasets larger than 1 billion vectors, consider Inverted File Index (IVF) or quantization algorithms. IVF reduces search scope by partitioning the vector space. Quantization reduces memory footprint by compressing vector representations. Choose IVF when latency is the bottleneck, and quantization when memory is constrained.

Geospatial search for proximity

With the geo_point type, you can express queries such as “within 5 km” (geo_distance), “in this rectangle” (geo_bounding_box), or “in this polygon” (geo_shape). You can also sort results by distance from the user. All of these use the same field, embedded directly in the document.

Because the coordinates live inside the same document as the restaurant name, description, and rating, a geospatial filter requires no join and no secondary lookup. OpenSearch Service evaluates the distance constraint as an efficient cached filter, pruning ineligible venues before any relevance scoring occurs.

Query type Use case Example scenario
geo_distance Radius filter “Show restaurants within 5 km of my location”
geo_bounding_box Rectangle filter “Show all venues in the downtown map view”
geo_shape Polygon filter “Show restaurants inside a drawn neighborhood boundary”
sort by _geo_distance Proximity ranking “Rank by nearest first, then by rating”

Hybrid search: Combining modalities in one query

Now back to the original use case. A user searches for “vegan Thai food” near downtown Austin, with a 4-star minimum. The following single compound query combines all three modalities, full-text, vector, and geospatial, with efficient filtering.

GET /restaurants/_search

{
  "query": {
    "hybrid": {
      "queries": [
        {
          "multi_match": {
            "query": "vegan Thai food",
            "fields": ["name^2", "description"],
            "fuzziness": "AUTO"
          }
        },
        {
          "neural": {
            "description_embedding": {
              "query_text": "vegan Thai food",
              "model_id": "<your-model-id>",
              "k": 50
            }
          }
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "5km",
            "location": { "lat": 30.2672, "lon": -97.7431 }
          }
        },
        { "range": { "rating": { "gte": 4.0 } } }
      ]
    }
  }
}

Breaking down what each component contributes:

  1. Text search (multi_match) provides built-in fuzzy matching and field-level boosting. The name^2 setting weights the restaurant name twice as heavily as the description.
  2. Vector search (neural) provides semantic matching, so “plant-based” correctly maps to “vegan” even when the exact keyword is absent.
  3. Geospatial filter (geo_distance) prunes venues beyond the 5 km radius before scoring begins.
  4. Rating filter (range) enforces a hard business rule requiring a minimum review quality of 4.0 stars.

Efficient filtering in OpenSearch Service applies non-relevance constraints directly within the sub-query execution of the hybrid query. Ineligible documents are pruned before scoring occurs rather than after. For versions before OpenSearch Service 3.x, efficient filtering was more complex to configure. Consider upgrading so that you can place inline filter clauses directly inside the hybrid query.

Hybrid search pipeline: text and vector sub-queries scored separately, normalized, then filtered by location and rating

Figure 2: The multi_match text sub-query and the neural vector sub-query are scored separately, combined by the normalization processor, then filtered by geospatial and rating constraints

Common search mistakes and best practices

Place non-relevance constraints inside filter clauses rather than scoring loops. OpenSearch Service automatically caches structural filters, which bypasses expensive score evaluations. The filter clause removes documents before the scoring phase executes.

{
  "query": {
    "bool": {
      "must": [ { "match": { "description": "wireless" } } ],
      "filter": [ { "range": { "price": { "lte": 100 } } } ]
    }
  }
}

Multi-field mappings index the same field multiple ways to support different query patterns without document duplication.

"name": {
  "type": "text",
  "fields": {
    "keyword": { "type": "keyword" },
    "edge_ngram": { "type": "text", "analyzer": "edge_ngram_analyzer" }
  }
}

One logical field, multiple indexed views. Use name for fuzzy text search, name.keyword for exact-match aggregations, and name.edge_ngram for autocomplete, all without duplicating the document. The following table consolidates common pitfalls with additional best practices.

Mistake Better approach
Indexing every field as standard text Use keyword fields for exact phrases and structured calculations
Processing structural rules inside scoring loops Route non-relevance boundaries through cached filter components
Running custom script_score blocks for hybrid queries Use native hybrid queries with search pipeline normalization
Using text fields for aggregations Use keyword or multi-field mappings for exact-match operations
Leaving display-only fields searchable Set index: false for fields retrieved but never queried
Relying on dynamic mapping in production Define mappings explicitly to avoid type conflicts and unexpected behavior

Integrating with operational data stores

Operational data stores handle transactional workloads such as key-value lookups, document create, read, update, and delete (CRUD) operations, relational integrity, and durable object storage. Those stores are Amazon DynamoDB, Amazon DocumentDB, Amazon Aurora, and Amazon S3. OpenSearch Service complements them by adding full-text search, semantic similarity, geospatial calculations, and multi-field weighted rankings for monitoring, analysis, and key performance indicator (KPI) discovery. The following table captures when each store is the right fit. Amazon S3 is included as a source for log analytics and archival search workloads, with ingestion handled through the OSI S3 Source Connector.

Source Use OpenSearch Service for Keep in source for
Amazon DynamoDB Full-text search across user interaction history High-speed key-value lookups, point-in-time states
Amazon DocumentDB Full-text exploration across flexible product catalogs Atomic transaction updates and document CRUD
Amazon Aurora PostgreSQL Multi-faceted full-text searching across semi-structured JSONB columns Atomicity, consistency, isolation, and durability (ACID) guarantees, and complex joins
Amazon S3 Log analytics, archival search, and full-text exploration across stored data files Durable object storage for raw files. Not suited for low-latency query workloads

Synchronize when: Your users require full-text search, vector similarity, geospatial calculation, or multi-field weighted result rankings.

Skip synchronization when: Your core query pattern is ID-based point lookups, or your operations demand multi-row ACID compliance.

Ingestion paths from DynamoDB, Amazon DocumentDB, Aurora, and Amazon S3 converging on one OpenSearch index

Figure 3: Ingestion patterns by operational store, all converging on one OpenSearch index

Conclusion

Amazon OpenSearch Service is built on Apache Lucene and exposes a full JSON REST API. It treats documents, queries, mappings, and results as JSON from start to finish. With that coherent interface, your applications can combine full-text indexing, vector representations, and geospatial processing within a single query. You avoid stitching together multiple specialized systems or writing transformation layers between them.

The restaurant discovery app in this post illustrates the pattern: one index, one query, three search modalities working together. The same approach applies to ecommerce catalog search, content discovery platforms, location-based services, and any workload where users expect intelligent, context-aware results from richly structured data.

As a discovery layer on top of your operational stores, OpenSearch Service gives you both the transactional guarantees of purpose-built databases and the search intelligence of a dedicated relevance engine. CDC pipelines keep the index current without coupling your application logic to the ingestion process.

To stand up your first discovery layer, follow the steps in Getting started with Amazon OpenSearch Service. What are you building with these query types? Let us know in the comments.


About the author

Ezat Karimi

Ezat Karimi

Ezat is a Senior Solutions Architect at AWS, based in Austin, TX. Ezat specializes in designing and delivering modernization solutions and strategies for database applications. Working closely with multiple AWS teams, Ezat helps customers migrate their database workloads to the AWS Cloud.

Jon Handler

Jon Handler

Jon is a Senior Principal Solutions Architect for Search Services at Amazon Web Services. Jon works closely with OpenSearch and Amazon OpenSearch Service, providing help and guidance to a broad range of customers who have search and log analytics workloads. Prior to joining AWS, Jon’s career as a software developer included four years of coding a large-scale, eCommerce search engine.