Use this skill when designing search functionality, indexing strategies, or relevance tuning. This skill enforces: derived index from source of truth, index aliases for zero-downtime reindex, proper field mapping with analyzers, and resource limits. Applies to Elasticsearch, Meilisearch, Algolia, or any search engine. Do NOT use for: primary database querie…
Independent GitHub snapshot · Automated classification · Original documentation governs
name: backend-search-patterns
description: >
Use this skill when designing search functionality, indexing strategies, or relevance tuning. This skill enforces: derived index from source of truth, index aliases for zero-downtime reindex, proper field mapping with analyzers, and resource limits. Applies to Elasticsearch, Meilisearch, Algolia, or any search engine. Do NOT use for: primary database queries, simple LIKE/ILIKE lookups, or full-text search in application DB.
version: "2.0.0"
author: "j4flmao"
license: "MIT"
compatibility:
claude-code: true
cursor: true
codex: true
windsurf: true
tags: [backend, search, phase-6, universal]
Backend Search Patterns
Purpose
Design search architecture with indexing strategy, query design, and relevance tuning.
What kind of search do you need?
├── Complex search: aggregations, geo, custom scoring, multi-language
│ └── Elasticsearch / OpenSearch — full control, steep learning curve
├── Simple typo-tolerant full-text search, instant setup
│ └── Meilisearch — excellent out-of-box relevance, minimal config
├── Managed, no ops, global edge network, per-query pricing
│ └── Algolia — fastest time-to-value for e-commerce and content
├── Fast, simpler alternative to Elasticsearch, lower resource usage
│ └── Typesense — REST-first, good relevance, low memory footprint
└── Already using AWS ecosystem, simple search needs
└── OpenSearch Service — managed, familiar if coming from ES 6.x
How to Index?
How fresh does search data need to be?
├── Real-time (seconds), high throughput
│ └── CDC (Debezium) → Kafka → search consumer
├── Near real-time (seconds-minutes), moderate throughput
│ └── Webhook / event-driven index updates
├── Near real-time, strong consistency (read-your-writes)
│ └── Dual-write: DB + search in same operation (with outbox pattern)
├── Batch (hours-days), small dataset, infrequent changes
│ └── Periodic full reindex — truncate and retransform all data
└── I don't know yet
└── Start with batch reindex daily. Add CDC when freshness requirement emerges
Workflow
Step 1: Search Provider Selection
Provider
Type
Hosting
Query Complexity
Relevance
Performance
Cost
Elasticsearch
Self-managed/SaaS
Cloud/on-prem
Full DSL
Custom BM25
High
Infra cost
OpenSearch
Self-managed/SaaS
Cloud/on-prem
Full DSL
Custom BM25
High
Infra cost
Meilisearch
Self-managed/SaaS
Cloud/on-prem
Simple REST
High OOTB
Very high
Open source
Typesense
Self-managed/SaaS
Cloud/on-prem
Simple REST
High OOTB
Very high
Open source
Algolia
SaaS
Managed
Simple REST
Very high OOTB
Edge-cached
Per search op
Elasticsearch for complex search with aggregations, geo, custom scoring. Meilisearch for simple typo-tolerant search, instant setup. Algolia for managed high relevance out-of-box, global CDN, no ops. Typesense as a faster simpler alternative to Elasticsearch. OpenSearch as the open-source fork of Elasticsearch (after license change).
Step 2: Index Mapping
Define every field with explicit type: keyword for exact match/filtering/sorting, text with analyzer for full-text, integer/float/date for range queries, geo_point for geo-spatial, boolean for flags. Set language-specific analyzers per field. Use doc_values: true on fields used for aggregations and sorting (critical for performance). nested type for arrays of objects (maintains document boundaries). enabled: false on fields not searched (saves index space).
CDC: Debezium captures DB changes, emits to Kafka, consumer updates search index. Bulk: periodic batch job reads all data, truncates index, rebulks. Webhook: app emits events on data change, consumer updates index. Dual-write: app writes to both DB and search in same transaction. Queue-based: app enqueues index event, consumer processes.
Step 4: Search Query DSL
Full-text: match and multi_match with field boosting (title^3, description^2). Filters: term/terms for exact, range for numeric/date, geo_distance for geo, exists for field presence. Faceted: terms aggregation with size limit, range aggregation for price buckets, nested aggregations for drill-down. Autocomplete: match_phrase_prefix or edge-ngram analyzer + completion suggester. Post-filters: apply after aggregation calculation (filter UI without affecting facet counts).
BM25 parameters: k1 (1.2 default) controls term frequency saturation — increase to 2.0 for long descriptions, decrease to 0.5 for short titles. b (0.75 default) controls length normalization — decrease to 0.3 for uniform-length fields. Field boosting: title^5, description^2, tags^1.5. Function scoring: recency (Gaussian on date), popularity (field_value_factor), personalization (script score for user history). Synonyms: configure as analyzer filter for query expansion. Rescore: run expensive scoring on top-100 results only.
Always use aliases for production reads/writes. Reindex: create new index with updated mappings → bulk load data → atomically swap alias from old to new → delete old index. Write alias points to index accepting writes. Read alias points to index serving queries.