技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
data-search-engine【数据与研究】Use this skill when asked about Elasticsearch, OpenSearch, Solr, search engine, full-text search, inverted index, indexing, search analytics, aggregation, cluster management, or shard routing. This skill enforces: Elasticsearch architecture (node types, shards, replicas), indexing strategy (mapping, analysis, tokenization), search queries (full-text,…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Design and configure search engine clusters for full-text search, log analytics, and real-time data exploration with proper indexing, query design, and cluster management.
Exact user phrases: "Elasticsearch", "OpenSearch", "Solr", "search engine", "full-text search", "inverted index", "indexing", "search analytics", "aggregation", "cluster management", "shard routing", "mapping", "analysis", "tokenization", "ILM", "index lifecycle".
Before activating, verify:
Search index mapping with analyzers, query templates, aggregation pipelines, and cluster configuration as JSON and YAML.
// Index mapping with analyzers
// Search query template
// Aggregation pipeline
# Cluster configuration
# Index lifecycle policy
# Shard allocation rules
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
300 lines of configuration and queries.
Explicit mapping required — never use dynamic mapping for production. Define field types: text for full-text search with analyzer, keyword for exact match/aggregations/sorting, integer/long/double for numeric, date with format, geo_point for location, nested for arrays of objects (preserves independence), flattened for semi-structured metadata, object for simple JSON.
{
"mappings": {
"dynamic": "strict",
"properties": {
"title": { "type": "text", "analyzer": "english", "fields": { "keyword": { "type": "keyword" } } },
"description": { "type": "text", "analyzer": "english" },
"category": { "type": "keyword" },
"price": { "type": "double" },
"created_at": { "type": "date", "format": "yyyy-MM-dd'T'HH:mm:ss||epoch_millis" },
"location": { "type": "geo_point" },
"tags": { "type": "keyword" },
"specs": { "type": "flattened" },
"reviews": {
"type": "nested",
"properties": {
"user": { "type": "keyword" },
"rating": { "type": "byte" },
"text": { "type": "text", "analyzer": "english" }
}
}
}
}
}
Character filters: HTML strip, pattern replace, mapping. Tokenizer: standard (grammar-based), whitespace, keyword (no split), ngram (autocomplete), edge_ngram (prefix autocomplete), uax_url_email (URLs kept whole). Token filters: lowercase, stop, synonym, stemmer, shingle (n-gram phrases), edge_ngram (for search-as-you-type). Custom analyzer combining these components.
{
"settings": {
"analysis": {
"char_filter": {
"html_strip": { "type": "html_strip" }
},
"tokenizer": {
"autocomplete": { "type": "edge_ngram", "min_gram": 2, "max_gram": 20 }
},
"filter": {
"synonyms": { "type": "synonym", "synonyms": ["laptop, notebook", "phone, smartphone, mobile"] }
},
"analyzer": {
"product_search": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": ["lowercase", "synonyms", "stop", "stemmer"]
},
"autocomplete": {
"type": "custom",
"tokenizer": "autocomplete",
"filter": ["lowercase"]
}
}
}
}
}
match: full-text with analysis (best for user search). match_phrase: exact phrase with slop. match_bool_prefix: last term as prefix. term: exact value for keyword fields. terms: multiple exact values. range: numeric/date range filters. exists: field presence. prefix: prefix match on keyword. wildcard: pattern matching (expensive). regexp: regex (very expensive). fuzzy: Levenshtein edit distance. bool: compound with must/should/filter/must_not.
{
"query": {
"bool": {
"must": [
{ "match": { "title": { "query": "wireless headphones", "boost": 3 } } },
{ "match": { "description": "wireless headphones" } }
],
"filter": [
{ "term": { "category": "electronics" } },
{ "range": { "price": { "gte": 50, "lte": 500 } } },
{ "term": { "status": "active" } }
],
"should": [
{ "match": { "title": { "query": "bluetooth", "boost": 2 } } }
],
"minimum_should_match": 1
}
}
}
Metric: avg, sum, min, max, stats, extended_stats, cardinality, percentiles. Bucket: terms, date_histogram, range, histogram, filter, filters, geohash_grid. Pipeline: avg_bucket, sum_bucket, moving_avg, derivative, cumulative_sum, bucket_sort, bucket_selector. Sub-aggregations: nest buckets inside buckets for drill-down.
{
"size": 0,
"aggs": {
"categories": {
"terms": { "field": "category", "size": 20, "order": { "sales": "desc" } },
"aggs": {
"sales": { "sum": { "field": "price" } },
"price_ranges": {
"range": { "field": "price", "ranges": [
{ "key": "Budget", "to": 50 },
{ "key": "Mid", "from": 50, "to": 200 },
{ "key": "Premium", "from": 200 }
]}
},
"sales_over_time": {
"date_histogram": { "field": "created_at", "calendar_interval": "month" },
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"moving_avg": { "moving_avg": { "buckets_path": "avg_price" } }
}
}
}
}
}
}
Node roles: master (cluster state management, odd count 3-5), data_hot (fast storage, high IOPS), data_warm (standard storage), data_cold (cheap storage, less replicas), data_frozen (searchable snapshots), ingest (preprocessing pipelines), ml (machine learning), transform. Shard sizing: 10-50 GB per shard, max 20 shards per GB of heap. Replicas: 1 for production (2 for read-heavy). Shard allocation awareness: rack/zone tags.
# elasticsearch.yml
cluster.name: production-search
node.name: node-data-hot-1
node.roles: [data_hot, ingest]
path.data: /var/lib/elasticsearch
discovery.seed_hosts: ["master-1", "master-2", "master-3"]
cluster.initial_master_nodes: ["master-1", "master-2", "master-3"]
# Shard allocation awareness
cluster.routing.allocation.awareness.attributes: zone
cluster.routing.allocation.awareness.force.zone.values: [us-east, us-west]
# Performance settings
indices.memory.index_buffer_size: 10%
indices.queries.cache.size: 20%
thread_pool.search.queue_size: 5000
thread_pool.write.queue_size: 1000
Hot phase: full indexing, high IOPS, many replicas. Warm phase: read-only, merge to single segment, reduce replicas. Cold phase: searchable snapshot, minimal storage. Delete phase: automatic deletion after retention period. Rollover: based on max size (50 GB), max age (30d), or max docs.
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": { "max_primary_shard_size": "50gb", "max_age": "30d" },
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "30d",
"actions": {
"forcemerge": { "max_num_segments": 1 },
"shrink": { "number_of_shards": 1 },
"allocate": { "number_of_replicas": 1, "require": { "data_tier": "data_warm" } },
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "90d",
"actions": {
"searchable_snapshot": { "snapshot_repository": "s3-backup" },
"set_priority": { "priority": 0 }
}
},
"delete": {
"min_age": "365d",
"actions": { "delete": {} }
}
}
}
}
OpenSearch is the open-source fork of Elasticsearch 7.10. API compatibility: most endpoints are identical. Key differences: Opensearch uses opensearch.yml instead of elasticsearch.yml, security plugin built-in (not X-Pack), k-NN plugin for vector search, PPL (Piped Processing Language) for SQL-like queries, Dashboards replaces Kibana, alerting and anomaly detection plugins built-in.
-- OpenSearch PPL
source = products
| where category = 'electronics' AND price > 50
| stats avg(price) by category
| sort - avg(price)
| head 10
Meilisearch is a lightweight search engine in Rust providing instant search-as-you-type (sub-50ms), typo tolerance out of the box, and an intuitive REST API. Features: automatic indexing (no explicit schema), faceted search with filters/ranges, synonym management, geo-search, multi-tenancy via API key scoping. Uses milli (Rust) core with LMDB key-value storage. Single-node only — data must fit on one instance. Use for datasets up to 10M docs, site search, ecommerce product search, and rapid setup.
// Meilisearch: index creation with searchable attributes
POST /indexes
{
"uid": "products",
"primaryKey": "id"
}
// Add documents (auto-indexed)
POST /indexes/products/documents
[
{ "id": 1, "title": "Wireless Headphones", "brand": "AudioPro", "price": 89.99, "category": "Electronics" },
{ "id": 2, "title": "Bluetooth Speaker", "brand": "AudioPro", "price": 49.99, "category": "Electronics" }
]
// Search with typo tolerance (automatic)
GET /indexes/products/search?q=wireles&filter=price<100
// Returns: Wireless Headphones (typo "wireles" matches "Wireless")
// Configure ranking rules
PATCH /indexes/products/settings
{
"rankingRules": ["words", "typo", "proximity", "attribute", "sort", "exactness"],
"sortableAttributes": ["price"],
"filterableAttributes": ["category", "brand"]
}
Typesense is an open-source, typo-tolerant search engine in C++ for sub-50ms search on TB-scale data. Configurable ranking combining text relevance, numeric fields, and custom formulas. Key features: built-in vector search for semantic/embedding-based retrieval alongside full-text, scoped API keys for multi-tenancy, curation rules (pin/boost results), query suggestions via synonyms. High availability via replication-based cluster. Use for apps needing both full-text and vector search in one system, ecommerce with curation, or as an Elasticsearch alternative with simpler ops and better per-node performance.
// Typesense: schema with vector search
{
"name": "products",
"fields": [
{"name": "title", "type": "string"},
{"name": "description", "type": "string"},
{"name": "price", "type": "float", "sort": true},
{"name": "category", "type": "string", "facet": true},
{"name": "embedding", "type": "float[]", "num_dim": 384}
],
"default_sorting_field": "price"
}
// Hybrid search: text + vector (semantic)
GET /collections/products/documents/search
?q=wireless headphones
&query_by=title,description
&vector_query=embedding:([0.02, 0.15, ...]) # 384-dim embedding
&sort_by=price:asc
Dynamic mapping creates thousands of unused fields, bloats the mapping, and causes mapping explosions. Always use explicit mapping with dynamic: "strict" for production indices.
Too many shards wastes resources and degrades cluster performance. Maximum 20 shards per GB of heap. A 30GB heap cluster should have max 600 shards total. Delete unused indices.
Too few shards limits indexing parallelism and prevents effective scaling. Target 10-50GB per shard. A 500GB index needs 10-50 shards.
Without ILM, time-series indices grow unbounded, consume all disk space, and degrade performance. Always define hot/warm/cold/delete phases.
text for AggregationsText fields are analyzed and cannot be used for terms aggregations, sorting, or scripting. Always use .keyword multifield or explicit keyword type for aggregatable fields.
Using from + size beyond page 100 causes massive heap pressure. Use search_after for deep pagination or scroll for batch processing.
Without timeouts, slow queries block thread pool threads and degrade cluster responsiveness. Set timeout on all search requests.
Querying array objects as if they were independent leads to incorrect results. Use nested type and nested query when array element independence matters.
Each replica doubles storage and indexing load. One replica is sufficient for high availability. Two for read-heavy. More than two rarely needed.
Yellow cluster status (unassigned shards) degrades read capacity. Red cluster status means missing data. Monitor and alert on cluster health.
dynamic: "strict" for all production indices.text for search + keyword for sorting/aggregations.refresh_interval: 30s (or -1 during bulk indexing) for write-heavy workloads.search_after for deep pagination. Never use from/size beyond page 100.timeout on all search requests to prevent thread pool exhaustion.nested type only when array element independence is required.index.search.slowlog.threshold.query.warn: 10s.terms over wildcard/regexp queries. They are much faster.forcemerge to 1 segment in warm phase for faster reads.OpenSearch is a fork of Elasticsearch 7.10 with built-in security, k-NN vector search, PPL, and alerting. Elasticsearch 8.x has more advanced features (ELSER, vector search with HNSW, better performance). Choose OpenSearch for open-source commitment and built-in security. Choose Elasticsearch for the latest search and AI features.
Elasticsearch is a full-featured search and analytics engine for datasets from GB to PB. Meilisearch is lightweight (Rust, single-node, sub-50ms) for datasets up to 10M docs. Choose Elasticsearch for complex querying, aggregations, and large-scale analytics. Choose Meilisearch for simple, fast site search.
Typesense is C++, typo-tolerant, sub-50ms, with built-in vector search and curations. Elasticsearch is more feature-rich but operationally heavier. Choose Typesense for simpler ops and higher per-node performance. Choose Elasticsearch for complex analytics and cluster-scale deployments.
Solr is older but excels at faceted search and has a mature ecosystem. Elasticsearch has better ecosystem (Kibana, Beats, Logstash), easier scaling, and more active development. Choose Elasticsearch for new projects. Choose Solr for legacy systems with specific faceted search requirements.
-1 during initial loads.max_merged_segment: 5GB default. Larger for read-heavy indices.indices.breaker.total.limit: 70% of heap for field data/requests.eager loading for aggregations on high-cardinality fields.text + keyword) for full-text + sorting/aggregationnested type only when array element independence matterskeyword over text for exact match and aggregationsSearch Engine Selection
├── Full-text search focus?
│ ├── Yes → Elasticsearch / OpenSearch (mature, ecosystem)
│ ├── Lightweight → Meilisearch / Typesense (dev-friendly, fast)
│ └── Typo-tolerant → Meilisearch (built-in typo tolerance)
├── Vector search required?
│ ├── Yes → Elasticsearch (dense vector + hybrid search)
│ ├── Scale (> 1B vectors) → Pinecone / Qdrant (dedicated vector DB)
│ └── No → Traditional BM25 search
├── Real-time indexing?
│ ├── Yes → Elasticsearch (near real-time refresh interval)
│ └── No → Batch indexing (cron-based reindex)
└── Self-hosted or managed?
├── Managed → Elastic Cloud / Algolia / Meilisearch Cloud
└── Self-hosted → OpenSearch on K8s / Elasticsearch on EC2
Decision criteria: Evaluate search latency, query complexity, indexing volume, and operational expertise.
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2,
"analysis": {
"analyzer": {
"custom_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "stop", "snowball"]
}
}
}
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "custom_analyzer", "boost": 3.0 },
"description": { "type": "text", "analyzer": "custom_analyzer" },
"category": { "type": "keyword" },
"price": { "type": "float" },
"in_stock": { "type": "boolean" },
"created_at": { "type": "date" },
"embedding": { "type": "dense_vector", "dims": 384, "index": true, "similarity": "cosine" }
}
}
}
# search_engine/hybrid_search.py
class HybridSearch:
def __init__(self, client, index: str):
self.client = client
self.index = index
def search(self, query: str, vector: list[float], alpha: float = 0.5, size: int = 20):
bm25_weight = alpha
vec_weight = 1 - alpha
resp = self.client.search(
index=self.index,
body={
"query": {
"bool": {
"should": [
{"match": {"title": {"query": query, "boost": bm25_weight}}},
{"match": {"description": {"query": query, "boost": bm25_weight * 0.5}}},
{
"script_score": {
"query": {"match_all": {}},
"script": {
"source": f"cosineSimilarity(params.query_vector, 'embedding') + 1.0",
"params": {"query_vector": vector}
},
"boost": vec_weight
}
}
]
}
},
"size": size
}
)
return resp["hits"]["hits"]
refresh_interval to 30s for bulk indexing; revert to 1s for serving.| Anti-Pattern | Consequence | Solution |
|---|---|---|
| Over-sharding (too many shards) | Cluster metadata overhead, slow recovery | 1 shard per 10-50 GB data |
| No field boosting strategy | Irrelevant results rank high | Boost title, exact matches, recency |
| Wildcard queries on text fields | Full scan, cluster slowdown | Use ngram or edge_ngram tokenizer |
| No query normalization | Long queries dominate short ones | Apply query norm in scoring |
| Ignoring index mapping design | Type conflicts, bad relevance | Define explicit mapping, not dynamic |
routing to hit only relevant shards.doc_values for sorting/aggregations; avoid fielddata on high-cardinality fields.data-relational-database for source data
ml-feature-engineering for text feature extraction from search data