{"record":{"id":"a726af536c7f7874","repo":"ankane/searchkick","slug":"records-in-search-index-do-not-exist-in-database","errorCode":null,"errorMessage":"Records in search index do not exist in database: #{missing_records.map { |v| \"#{Array(v[:model]).map(&:model_name).sort.join(\"/\")} #{v[:id]}\" }.join(\", \")}","messagePattern":"Records in search index do not exist in database: #(.+?) #(.+?)\" \\}\\.join\\(\", \"\\)\\}","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/searchkick/results.rb","lineNumber":312,"sourceCode":"                highlight = hit[\"highlight\"].to_a.to_h { |k, v| [base_field(k), v.first] }\n                options[:highlighted_fields].map { |k| base_field(k) }.each do |k|\n                  result[\"highlighted_#{k}\"] ||= (highlight[k] || result[k])\n                end\n              end\n\n              result[\"id\"] ||= result[\"_id\"] # needed for legacy reasons\n              [HashWrapper.new(result), hit]\n            end\n        end\n\n       [results, missing_records]\n      end\n    end\n\n    def build_hits\n      @build_hits ||= begin\n        if missing_records.any?\n          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(\", \")}\")\n        end\n        with_hit_and_missing_records[0]\n      end\n    end\n\n    def results_query(records, hits)\n      records = Searchkick.scope(records)\n\n      ids = hits.map { |hit| hit[\"_id\"] }\n      if options[:includes] || options[:model_includes]\n        included_relations = []\n        combine_includes(included_relations, options[:includes])\n        combine_includes(included_relations, options[:model_includes][records]) if options[:model_includes]\n\n        records = records.includes(included_relations)\n      end\n\n      if options[:scope_results]","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/ankane/searchkick/blob/93e901a75b11a25101668a616e006b158251b16e/lib/searchkick/results.rb#L294-L330","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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"],"exampleFix":"# before\nProduct.where(archived: true).delete_all # skips Searchkick callbacks\nProduct.search(\"milk\").results # warns: Records in search index do not exist in database\n\n# after\nProduct.where(archived: true).find_each(&:destroy) # deindexes each record\n# or, for bulk SQL deletes, follow with:\nProduct.where(archived: true).delete_all\nProduct.reindex","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["searchkick","out-of-sync","reindex","callbacks","ruby"],"backgroundTag":"stale-search-index","analyzedSha":"93e901a75b11a25101668a616e006b158251b16e","analyzedAt":"2026-08-21T19:06:23.767Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}