| name | elasticsearch-patterns |
| description | Mapping design, query optimization, aggregation patterns, index lifecycle management, and search relevance tuning. |
Elasticsearch Patterns
Search and analytics patterns for Elasticsearch deployments.
Mapping Design
{
"mappings": {
"dynamic": "strict",
"properties": {
"id": { "type": "keyword" },
"title": {
"type": "text",
"analyzer": "standard",
"fields": {
"keyword": { "type": "keyword" },
"autocomplete": {
"type": "text",
"analyzer": "autocomplete_analyzer"
}
}
},
"description": {
"type": "text",
"analyzer": "standard"
},
"price": { "type": "scaled_float", "scaling_factor": 100 },
"category": { "type": "keyword" },
"tags": { "type": "keyword" },
"location": { "type": "geo_point" },
"created_at": { "type": "date" },
"metadata": {
"type": "object",
"enabled": false
}
}
},
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"autocomplete_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "autocomplete_filter"]
}
},
"filter": {
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 20
}
}
}
}
}
Query Patterns
import { Client } from '@elastic/elasticsearch'
const client = new Client({ node: process.env.ELASTICSEARCH_URL })
async function searchProducts(query: string, filters: ProductFilters) {
const result = await client.search({
index: 'products',
body: {
query: {
bool: {
must: [
{
multi_match: {
query,
fields: ['title^3', 'description', 'tags^2'],
type: 'best_fields',
fuzziness: 'AUTO',
prefix_length: 2,
}
}
],
filter: [
...(filters.category ? [{ term: { category: filters.category } }] : []),
...(filters.minPrice || filters. ? [{
: {
: {
...(filters. && { : filters. }),
...(filters. && { : filters. }),
}
}
}] : []),
...(filters.?. ? [{ : { : filters. } }] : []),
],
}
},
: {
: {
: { : },
: { : },
},
: [],
: [],
},
: [
{ : },
{ : },
],
: filters. ?? ,
: filters. ?? ,
}
})
{
: result...( ({
...hit.,
: hit.,
: hit.,
})),
: (result.. { : }).,
}
}
() {
result = client.({
: ,
: {
: {
: {
: {
: prefix,
: ,
}
}
},
: [, ],
: ,
}
})
result...( h.)
}
Aggregation Patterns
async function searchWithFacets(query: string) {
const result = await client.search({
index: 'products',
body: {
query: { match: { title: query } },
size: 20,
aggs: {
categories: {
terms: { field: 'category', size: 20 }
},
price_ranges: {
range: {
field: 'price',
ranges: [
{ key: 'budget', to: 50 },
{ key: 'mid', from: 50, to: 200 },
{ key: 'premium', from: 200 },
]
}
},
price_stats: {
stats: { field: 'price' }
},
: {
: {
: ,
: ,
}
},
}
}
})
{
: result..,
: {
: result.?.,
: result.?.,
: result.?.,
: result.?.,
}
}
}
Index Lifecycle Management (ILM)
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "7d"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "30d",
"actions": {
"shrink": { "number_of_shards": 1 },
Bulk Indexing
async function bulkIndex(documents: Product[]): Promise<void> {
const body = documents.flatMap(doc => [
{ index: { _index: 'products', _id: doc.id } },
doc,
])
const result = await client.bulk({ body, refresh: false })
if (result.errors) {
const erroredItems = result.items.filter((item: any) => item.index?.error)
console.error(`Bulk indexing errors: ${erroredItems.length}/${documents.length}`)
for (const item of erroredItems.slice(0, 5)) {
console.error(item.index?.error)
}
}
}
async function () {
client..({ : newIndex, : newMappings })
client.({
: { : { : oldIndex }, : { : newIndex } },
: ,
})
client..({
: {
: [
{ : { : oldIndex, alias } },
{ : { : newIndex, alias } },
]
}
})
}
Checklist
Anti-Patterns
- Dynamic mapping in production: unexpected field types cause search failures
- Searching keyword fields with full-text queries (no tokenization)
- Refreshing after every write: kills indexing throughput
- Single huge index instead of time-based indices with ILM
- Deep pagination with from/size beyond 10,000 (use search_after)
- Storing data only in ES without a source-of-truth database