ankane/searchkick · warning
Records in search index do not exist in database: #{missing_
Error message
Records in search index do not exist in database: #{missing_records.map { |v| "#{Array(v[:model]).map(&:model_name).sort.join("/")} #{v[:id]}" }.join(", ")} What it means
When results load, Searchkick pairs each hit with its database record; hits whose `_id` matches no row are collected as `missing_records` (results.rb:268-278) and reported with this warning when hits are built (results.rb:311-315). It means the search index is stale relative to the database: matched documents no longer exist as rows. The missing records are excluded from results - the warning is informational, and `results.missing_records` exposes the details.
Source
Thrown at lib/searchkick/results.rb:312
highlight = hit["highlight"].to_a.to_h { |k, v| [base_field(k), v.first] }
options[:highlighted_fields].map { |k| base_field(k) }.each do |k|
result["highlighted_#{k}"] ||= (highlight[k] || result[k])
end
end
result["id"] ||= result["_id"] # needed for legacy reasons
[HashWrapper.new(result), hit]
end
end
[results, missing_records]
end
end
def build_hits
@build_hits ||= begin
if missing_records.any?
Searchkick.warn("Records in search index do not exist in database: #{missing_records.map { |v| "#{Array(v[:model]).map(&:model_name).sort.join("/")} #{v[:id]}" }.join(", ")}")
end
with_hit_and_missing_records[0]
end
end
def results_query(records, hits)
records = Searchkick.scope(records)
ids = hits.map { |hit| hit["_id"] }
if options[:includes] || options[:model_includes]
included_relations = []
combine_includes(included_relations, options[:includes])
combine_includes(included_relations, options[:model_includes][records]) if options[:model_includes]
records = records.includes(included_relations)
end
if options[:scope_results]View on GitHub (pinned to 93e901a75b)
Solutions
- Reindex the model to resync: `Product.reindex` (or `reindex(:async)` / `reindex(:queue)` for large sets)
- Avoid callback-skipping deletes: use `find_each(&:destroy)`, or run `Product.reindex` immediately after `delete_all`/SQL deletes
- Check the background job queue for failed Searchkick reindex/deindex jobs when callbacks are async
- Inspect `results.missing_records` after searches in data-sensitive flows and alert or trigger a reindex when it is non-empty
Example fix
# before
Product.where(archived: true).delete_all # skips Searchkick callbacks
Product.search("milk").results # warns: Records in search index do not exist in database
# after
Product.where(archived: true).find_each(&:destroy) # deindexes each record
# or, for bulk SQL deletes, follow with:
Product.where(archived: true).delete_all
Product.reindex Defensive patterns
Strategy: fallback
Prevention
- Never delete rows with delete_all or raw SQL without a matching reindex or destroy-based deletion
- Monitor logs/stderr for '[searchkick] WARNING: Records in search index' and alert when it appears
- Check results.missing_records after searches in admin/export flows and trigger a reindex when non-empty
- Keep Searchkick callbacks enabled (or compensate with a reindex) during bulk data operations
When it happens
Trigger: `Product.search('milk').results` where matched documents were deleted from the DB but not from the index: `delete_all` or raw SQL deletes that skip Searchkick callbacks; failed or still-queued reindex jobs with `callbacks: :async` or `:queue`; records destroyed while a scroll export was mid-flight.
Common situations: Bulk deletes via `delete_all` in data cleanups; `callbacks: false` used for import speed without a follow-up reindex; silently failing background reindex workers; long-running exports over data being mutated concurrently.
Related errors
- The `conversions` option is deprecated in favor of `conversi
- wait only available in :async mode
- Full reindex does not support :queue mode - use :async mode
- No index to resume
- knn requires OpenSearch 2.4+
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/a726af536c7f7874.
Report an issue: GitHub.