ankane/searchkick · error · Searchkick::MissingIndexError
No search context found for id
Error message
No search context found for id
What it means
When a scrolled search returns 404 with 'No search context found for id', the scroll cursor no longer exists on the server — the keep-alive (searchkick default `1m`) expired between batches, the scroll was explicitly cleared, or the node restarted/failed over. `Query#handle_error` maps this to `Searchkick::MissingIndexError` with the message 'No search context found for id'.
Source
Thrown at lib/searchkick/query.rb:193
puts "Results"
puts JSON.pretty_generate(response.to_h)
end
# set execute for multi search
@execute = Results.new(searchkick_klass, response, opts)
end
def retry_misspellings?(response)
@misspellings_below && response["error"].nil? && Results.new(searchkick_klass, response).total_count < @misspellings_below
end
private
def handle_error(e)
status_code = e.message[1..3].to_i
if status_code == 404
if e.message.include?("No search context found for id")
raise MissingIndexError, "No search context found for id"
else
raise MissingIndexError, "Index missing - run #{reindex_command}"
end
elsif status_code == 500 && (
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
e.message.include?("No query registered for [multi_match]") ||
e.message.include?("[match] query does not support [cutoff_frequency]") ||
e.message.include?("No query registered for [function_score]")
)
raise UnsupportedVersionError
elsif status_code == 400
if (
e.message.include?("bool query does not support [filter]") ||
e.message.include?("[bool] filter does not support [filter]")
)
raise UnsupportedVersionErrorView on GitHub (pinned to 93e901a75b)
Solutions
- Increase the keep-alive to comfortably exceed your worst-case per-batch time: `scroll: "10m"` (each batch request extends it).
- Move expensive per-batch work out of the scroll loop — collect ids in the loop, process afterwards.
- Rescue `Searchkick::MissingIndexError` and restart the scroll from the beginning (make the job idempotent, e.g. keyed on updated_at or a cursor table).
- If eviction is the cause, raise `search.max_open_scroll_context` on the cluster or ensure scroll cursors are closed promptly.
Example fix
# before
Product.search("*", scroll: "1m", batch_size: 1000) do |batch|
HeavyExport.run(batch) # slower than 1m => MissingIndexError
end
# after
Product.search("*", scroll: "10m", batch_size: 1000) do |batch|
ids += batch.map(&:id) # cheap loop; heavy work afterwards
end
HeavyExport.run_later(ids) Defensive patterns
Strategy: retry
Validate before calling
# Before long jobs, size scroll keep-alive to worst-case batch time
per_batch_seconds = 45
scroll_keepalive = "#{(per_batch_seconds * 4 / 60.0).ceil}m" # 4x headroom
Product.search("*", scroll: scroll_keepalive, batch_size: 500) { |batch| process(batch) } Try / catch
def with_scroll_retry(klass, attempts: 2)
yield
rescue Searchkick::MissingIndexError => e
raise unless e.message.include?("No search context found")
retry if (attempts -= 1) > 0 # restart scroll from beginning; job must be idempotent
raise
end
with_scroll_retry(Product) do
Product.search("*", scroll: "10m", batch_size: 1000) { |b| Export.run(b) }
end Prevention
- Set `scroll:` well above worst-case batch processing time (e.g. "10m" for heavy workloads).
- Keep the scroll loop cheap: collect ids, do heavy work after it completes.
- Make scroll-driven jobs idempotent (cursor column or updated_at watermark) so a restart from scratch is safe.
- Monitor `search.max_open_scroll_context` on the cluster if you run many concurrent scrolls.
When it happens
Trigger: `Model.search("*", scroll: "1m", batch_size: 1000) { |batch| ... }` where per-batch processing (e.g. slow API calls, large N+1 loading) exceeds the scroll keep-alive; resuming/serializing a scroll cursor after a long pause; scrolling during a node restart or when the search context is evicted (max_open_scroll_context limit hit).
Common situations: Bulk export/reindex jobs that do heavy per-batch work; parallel scroll workers sharing cursors; low `search.max_open_scroll_context` defaults causing eviction; ES node maintenance mid-scroll.
Related errors
- Pass `scroll` option to the search method for scrolling
- Scroll id has expired
- Multiple clients found - set Searchkick.client_type = :elast
- No client found - install the `elasticsearch` or `opensearch
- Could not find class: #{class_name}
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/eb8b56e79a95a04a.
Report an issue: GitHub.