elasticsearch-patterns

Mapping design, query optimization, aggregation patterns, index lifecycle management, and search relevance tuning.

vibeeval/vibecosystem11 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: elasticsearch-patterns
description: Mapping design, query optimization, aggregation patterns, index lifecycle management, and search relevance tuning.
license: MIT
---

# Elasticsearch Patterns

Search and analytics patterns for Elasticsearch deployments.

## Mapping Design

```json
{
  "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

```typescript
import { Client } from '@elastic/elasticsearch'

const client = new Client({ node: process.env.ELASTICSEARCH_URL })

// Full-text search with boosting and highlighting
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'],  // Title 3x boost
                type: 'best_fields',
                fuzziness: 'AUTO',         // Typo tolerance
                prefix_length: 2,          // First 2 chars must match exactly
              }
            }
          ],
          filter: [
            ...(filters.category ? [{ term: { category: filters.category } }] : []),
            ...(filters.minPrice || filters.maxPrice ? [{
              range: {
                price: {
                  ...(filters.minPrice && { gte: filters.minPrice }),
                  ...(filters.maxPrice && { lte: filters.maxPrice }),
                }
              }
            }] : []),
            ...(filters.tags?.length ? [{ terms: { tags: filters.tags } }] : []),
          ],
        }
      },
      highlight: {
        fields: {
          title: { number_of_fragments: 0 },       // Full field highlight
          description: { fragment_size: 150 },      // Snippet
        },
        pre_tags: ['<mark>'],
        post_tags: ['</mark>'],
      },
      sort: [
        { _score: 'desc' },
        { created_at: 'desc' },
      ],
      from: filters.offset ?? 0,
      size: filters.limit ?? 20,
    }
  })

  return {
    hits: result.hits.hits.map(hit => ({
      ...hit._source,
      score: hit._score,
      highlights: hit.highlight,
    })),
    total: (result.hits.total as { value: number }).value,
  }
}

// Autocomplete search (edge_ngram)
async function autocomplete(prefix: string) {
  const result = await client.search({
    index: 'products',
    body: {
      query: {
        match: {
          'title.autocomplete': {
            query: prefix,
            operator: 'and',
          }
        }
      },
      _source: ['title', 'category'],
      size: 10,
    }
  })
  return result.hits.hits.map(h => h._source)
}
```

## Aggregation Patterns

```typescript
// Faceted search: get filter counts alongside results
async function searchWithFacets(query: string) {
  const result = await client.search({
    index: 'products',
    body: {
      query: { match: { title: query } },
      size: 20,
      aggs: {
        // Category facets
        categories: {
          terms: { field: 'category', size: 20 }
        },
        // Price ranges
        price_ranges: {
          range: {
            field: 'price',
            ranges: [
              { key: 'budget', to: 50 },
              { key: 'mid', from: 50, to: 200 },
              { key: 'premium', from: 200 },
            ]
          }
        },
        // Price statistics
        price_stats: {
          stats: { field: 'price' }
        },
        // Date histogram
        created_over_time: {
          date_histogram: {
            field: 'created_at',
            calendar_interval: 'month',
          }
        },
      }
    }
  })

  return {
    hits: result.hits.hits,
    facets: {
      categories: result.aggregations?.categories,
      priceRanges: result.aggregations?.price_ranges,
      priceStats: result.aggregations?.price_stats,
      timeline: result.aggregations?.created_over_time,
    }
  }
}
```

## Index Lifecycle Management (ILM)

```json
{
  "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 },
          "forcemerge": { "max_num_segments": 1 },
          "set_priority": { "priority": 50 },
          "allocate": {
            "number_of_replicas": 0,
            "require": { "data": "warm" }
          }
        }
      },
      "cold": {
        "min_age": "90d",
        "actions": {
          "set_priority": { "priority": 0 },
          "freeze": {},
          "allocate": {
            "require": { "data": "cold" }
          }
        }
      },
      "delete": {
        "min_age": "365d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}
```

## Bulk Indexing

```typescript
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 })  // No refresh for throughput

  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)
    }
  }
}

// Reindex with zero downtime using aliases
async function reindexWithAlias(oldIndex: string, newIndex: string, alias: string) {
  // 1. Create new index with updated mappings
  await client.indices.create({ index: newIndex, body: newMappings })

  // 2. Reindex data
  await client.reindex({
    body: { source: { index: oldIndex }, dest: { index: newIndex } },
    wait_for_completion: true,
  })

  // 3. Atomic alias swap
  await client.indices.updateAliases({
    body: {
      actions: [
        { remove: { index: oldIndex, alias } },
        { add: { index: newIndex, alias } },
      ]
    }
  })
}
```

## Checklist

- [ ] Explicit mappings with `dynamic: strict` (no surprise field types)
- [ ] Multi-field mappings: keyword for filtering, text for search
- [ ] Edge n-gram analyzer for autocomplete fields
- [ ] Boost important fields in multi_match (title > description)
- [ ] Use filter context for non-scoring queries (cacheable, faster)
- [ ] ILM policy for hot/warm/cold/delete lifecycle
- [ ] Alias-based indexing for zero-downtime reindexing
- [ ] Bulk API for batch writes (never single-document in loops)

## 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

More Database skills

supabase-postgres-best-practices

supabase/agent-skills

Postgres best practices maintained by Supabase, for Postgres running anywhere. Load this skill BEFORE writing or changing anything that lives in a Postgres database: creating or altering tables and columns (including choosing column types), schema design, migrations and declarative schema files, RLS policies and the tests that verify them, indexes, triggers, database functions, queues and scheduled jobs (pg_cron, pgmq), vector/semantic search (pgvector), and restoring dumps (pg_restore) or importing data. Also load it when diagnosing slow queries, high CPU, timeouts, EXPLAIN plans, connection exhaustion, locking, bloat, or rows visible to the wrong user or tenant. This is not just a performance guide — schema, migration, security, and SQL authoring tasks need these rules too, even for a one-column change or a single query.

372.5k

prisma-database-setup

prisma/skills

Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.). Use when setting up a new project, changing databases, or troubleshooting connection issues. Triggers on "configure postgres", "connect to mysql", "setup mongodb", "sqlite setup".

241.5k

prisma-postgres

prisma/skills

Prisma Postgres setup and operations guidance across Console, create-db CLI, Management API, and Management API SDK. Use when creating Prisma Postgres databases, working in Prisma Console, provisioning with create-db/create-pg/create-postgres, or integrating programmatic provisioning with service tokens or OAuth.

235.0k

← All Database skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY