Understanding Elasticsearch slow logs
Slow queries are silent killers in production search systems. By the time you notice response times creeping up, your users have already experienced degraded performance. Search slow logs: Elasticsearch best practices explained is about proactive detection and systematic remediation. In this guide, we'll walk through how to configure, monitor, and act on slow logs so you catch performance issues before they impact your platform.
Slow logs in Elasticsearch are designed to surface queries and fetch operations that exceed configurable thresholds. Unlike metrics that show aggregated statistics, slow logs give you the actual queries, their execution times, and detailed breakdowns of where time was spent. This visibility is critical when you're running a search system at scale, especially in e-commerce or SaaS where milliseconds matter.
Enable per-index thresholds
The first step is configuring slow logs at the index level. Elasticsearch allows you to set different thresholds for different indices based on their performance requirements. A frequently-accessed product catalog might have stricter thresholds than an analytics index that's queried less often.
You configure slow logs via the index settings API. Here's how to enable query and fetch phase logging for an index:
PUT /products/_settings
{
"index.search.slowlog.threshold.query.warn": "100ms",
"index.search.slowlog.threshold.query.info": "200ms",
"index.search.slowlog.threshold.query.debug": "500ms",
"index.search.slowlog.threshold.query.trace": "1s",
"index.search.slowlog.level": "info"
}
This configuration means:
- Any query taking over 100ms gets logged at WARN level
- Queries over 200ms are logged at INFO level
- DEBUG and TRACE levels capture even slower queries
- The overall log level is set to INFO, so INFO, WARN and above are captured
The key insight here is per-index thresholds. Your primary search index might use stricter settings (50ms warn threshold) while a read-heavy analytics index could be 500ms or higher. This prevents noise—you're not logging every query, just the ones that genuinely hurt performance for that specific index.
In a real scenario, imagine you're running an e-commerce platform with three indices: products (high-traffic, strict thresholds), orders (moderate traffic), and audit_logs (append-only, lower priority). You'd configure each differently:
PUT /products/_settings
{
"index.search.slowlog.threshold.query.warn": "50ms",
"index.search.slowlog.threshold.query.info": "100ms",
"index.search.slowlog.level": "info"
}
PUT /orders/_settings
{
"index.search.slowlog.threshold.query.warn": "200ms",
"index.search.slowlog.threshold.query.info": "500ms",
"index.search.slowlog.level": "info"
}
PUT /audit_logs/_settings
{
"index.search.slowlog.threshold.query.warn": "1s",
"index.search.slowlog.level": "warn"
}
This tiered approach gives you signal without drowning in noise. The products index will flag even moderate slowdowns, while audit_logs only logs truly problematic queries.
Log query and fetch phases
Elasticsearch breaks search execution into phases. Understanding where time is spent requires logging both the query and fetch phases separately.
The query phase is where Elasticsearch identifies which documents match your criteria. The fetch phase is where it retrieves the actual document bodies and returns them to you. A slow query phase suggests your filters or scoring logic is expensive. A slow fetch phase typically means you're retrieving too many documents or documents are too large.
Enable both phases with these settings:
PUT /products/_settings
{
"index.search.slowlog.threshold.query.warn": "100ms",
"index.search.slowlog.threshold.fetch.warn": "50ms",
"index.search.slowlog.level": "info"
}
When a query exceeds thresholds, you'll see logs like this (formatted for readability):
{
"type": "index_search_slowlog",
"timestamp": "2024-01-15T10:23:45.123Z",
"message": "[products][2] took[250ms], took_millis[250], ...",
"index": "products",
"shard": "2",
"search": {
"request": {
"qs": "category:electronics AND price:<100"
},
"total_time_in_millis": 250,
"query_time_in_millis": 180,
"fetch_time_in_millis": 70
}
}
This tells you that of the 250ms total, 180ms was spent in the query phase and 70ms in the fetch phase. If query time is high, you're doing expensive filtering or aggregations. If fetch time is high, you need to optimize document retrieval.
In practice, you'd parse these logs from your logging system (ELK stack, Splunk, DataDog, etc.) and analyze patterns. If you consistently see high fetch times on certain queries, it often means you're fetching fields you don't need or documents contain too much data.
Review regularly
Enabling slow logs is only half the battle. The real work happens in regular review and analysis. Set up a weekly cadence to examine slow logs and identify trends.
Create a simple dashboard or query that surfaces the most common slow queries. If you're using the ELK stack, a query like this helps:
{
"query": {
"bool": {
"must": [
{ "match": { "type": "index_search_slowlog" } },
{ "range": { "timestamp": { "gte": "now-7d" } } }
]
}
},
"aggs": {
"by_query": {
"terms": {
"field": "search.request.qs.keyword",
"size": 20
},
"aggs": {
"avg_time": { "avg": { "field": "search.total_time_in_millis" } },
"max_time": { "max": { "field": "search.total_time_in_millis" } }
}
}
}
}
This aggregation shows you the top 20 slowest query patterns over the past week, including average and maximum execution times. Run this weekly and track whether the same queries keep appearing.
Regular review also means setting alerts. In production, configure your logging system to notify the team when query times breach thresholds consistently. For example, alert if the 95th percentile of query time on the products index exceeds 200ms for more than 5 minutes.
Create a backlog item for each repeated offender. Document the query, its frequency, and the current performance. This becomes your optimization roadmap.
Fix with mappings or query changes
Once you've identified slow queries, it's time to fix them. Most improvements come from two areas: optimizing your mappings or rewriting queries.
Mapping optimizations often yield the biggest wins. If a field is analyzed when it should be a keyword, or vice versa, you're doing unnecessary work. Consider this mapping that causes problems:
{
"mappings": {
"properties": {
"sku": { "type": "text" },
"price": { "type": "text" },
"tags": { "type": "text" }
}
}
}
All three fields are analyzed text, which means Elasticsearch tokenizes and indexes each word. If you're filtering on exact SKU values or price ranges, this is wasteful. A better mapping:
{
"mappings": {
"properties": {
"sku": {
"type": "keyword"
},
"price": {
"type": "scaled_float",
"scaling_factor": 100
},
"tags": {
"type": "keyword"
}
}
}
}
Now SKU and tags are keywords (exact matches), and price is numeric. Queries filtering on these fields will be orders of magnitude faster.
Other mapping optimizations include:
- Disable scoring on filter fields: Use
"index": falsefor fields you only filter on, never score - Use doc_values=true: Enables efficient aggregations and sorting on numeric/keyword fields
- Set correct analyzer: Use simpler analyzers (standard, whitespace) instead of language analyzers when possible
- Optimize nested documents: Nested queries are expensive; sometimes a denormalized approach is faster
For query optimizations, the main principle is filter before scoring. Filters are cached and faster; queries score every match, which is expensive. Here's an inefficient query:
{
"query": {
"bool": {
"must": [
{ "match": { "description": "laptop" } },
{ "match": { "category": "electronics" } },
{ "range": { "price": { "lte": 1000 } } }
]
}
}
}
All three conditions score, including the exact category match and price range. Better version:
{
"query": {
"bool": {
"must": [
{ "match": { "description": "laptop" } }
],
"filter": [
{ "term": { "category": "electronics" } },
{ "range": { "price": { "lte": 1000 } } }
]
}
}
}
Now category and price are filters (cached, no scoring), and only the description is scored. Depending on your data, this can be 10-100x faster.
Common mistakes
Setting thresholds too low: If you set warn threshold to 10ms, you'll log 90% of queries and create noise that hides real problems. Start with something realistic for your infrastructure (100-500ms depending on complexity), then lower it gradually.
Not differentiating between phases: If you don't separately log query and fetch phases, you're guessing at the root cause. Always enable both and interpret them together.
Ignoring slow logs for weeks: Slow logs are only useful if you act on them. Schedule regular review or you'll miss patterns that reveal systemic issues.
Optimizing the wrong queries: Don't optimize the slowest individual query if it runs once a month. Focus on high-frequency slow queries that impact users daily. Use frequency + duration as your metric.
Expecting quick fixes from query rewrites alone: Sometimes mapping changes are necessary. A query rewrite on a poorly-mapped field might only improve performance by 20%, but fixing the mapping improves it by 80%.
Key takeaways
- Configure per-index slow log thresholds based on each index's performance requirements and traffic patterns
- Log both query and fetch phases separately to understand where time is actually spent during search execution
- Establish a weekly review cadence and aggregate slow logs to identify high-frequency offenders that impact users
- Optimize mappings first (field types, analyzers, doc_values) before rewriting queries, as mapping issues often cause the biggest performance gaps
- Use filters instead of queries for non-scoring conditions to leverage caching and eliminate unnecessary scoring overhead
- Set realistic thresholds that catch genuine problems without creating noise; adjust based on your infrastructure and user expectations






Comments
Loading comments…