ankane/searchkick · error · Searchkick::Error
Could not find class: #{class_name}
Error message
Could not find class: #{class_name} What it means
On Elasticsearch 9.0.0+ exact kNN, if a field was mapped with cosine distance (via searchkick options) but the query requests a different distance, Searchkick raises ArgumentError 'distance must match searchkick options'. The guard exists to prevent incorrect distances/results seen with Elasticsearch 9.0.0-rc1 when exact script scoring uses a non-cosine metric on a cosine-mapped field.
Source
Thrown at lib/searchkick.rb:304
relation =
if relation.respond_to?(:primary_key)
primary_key = relation.primary_key
raise Error, "Need primary key to load records" if !primary_key
relation.where(primary_key => ids)
elsif relation.respond_to?(:queryable)
relation.queryable.for_ids(ids)
end
raise Error, "Not sure how to load records" if !relation
relation
end
# public (for reindexing conversions)
def self.load_model(class_name, allow_child: false)
model = class_name.safe_constantize
raise Error, "Could not find class: #{class_name}" unless model
if allow_child
unless model.respond_to?(:searchkick_klass)
raise Error, "#{class_name} is not a searchkick model"
end
else
unless Searchkick.models.include?(model)
raise Error, "#{class_name} is not a searchkick model"
end
end
model
end
# private
def self.indexer
Thread.current[:searchkick_indexer] ||= Indexer.new
end
# privateView on GitHub (pinned to 93e901a75b)
Solutions
- Use distance 'cosine' for exact queries on cosine-mapped fields under ES 9 - drop the override or set it to 'cosine'
- If another metric is truly required, change the field's distance in searchkick options and reindex so it is mapped that way
- Treat staying on Elasticsearch < 9 only as a stopgap - the guard keeps firing on 9+
Example fix
# before (ES 9.x, field mapped cosine)
Product.search('*', knn: {field: :v, vector: vec, distance: 'euclidean', exact: true})
# => ArgumentError: distance must match searchkick options
# after
Product.search('*', knn: {field: :v, vector: vec, distance: 'cosine', exact: true}) Defensive patterns
Strategy: validation
Validate before calling
def exact_knn(vector, field: :embedding)
mapped = Product.searchkick_options.dig(:knn, field)&.[](:distance)
on_es9 = !Searchkick.server_below?('9.0.0')
distance = on_es9 && mapped == 'cosine' ? 'cosine' : (mapped || 'cosine')
Product.search('*', knn: {field: field, vector: vector, distance: distance, exact: true})
end Prevention
- For exact search, default the metric to the field's mapped distance instead of overriding it
- Track Elasticsearch major upgrades as a release step: re-run kNN integration tests against the new version
- Centralize version-conditional behavior in helpers, not scattered call sites
When it happens
Trigger: Model mapped with searchkick knn: {embedding: {distance: 'cosine'}}, an Elasticsearch 9.x server, and Product.search('*', knn: {field: :embedding, vector: vec, distance: 'euclidean', exact: true}).
Common situations: Upgrading the cluster to Elasticsearch 9 while existing code used per-query distance overrides for exact search; reusing a metric override that worked on ES 8; benchmarking different metrics with exact: true.
Related errors
- #{class_name} is not a searchkick model
- Use Searchkick.search to search multiple models
- Redis not configured
- Need primary key to load records
- Unknown distance: #{distance}
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/c24eb0f7402eb2d1.
Report an issue: GitHub.