ankane/searchkick · error · Searchkick::Error
Need primary key to load records
Error message
Need primary key to load records
What it means
For exact kNN on OpenSearch, Searchkick translates the distance metric into a knn_score script space_type and only recognizes cosine (cosinesimil), euclidean (l2), taxicab (l1), inner_product (innerproduct) and chebyshev (linf). Any other string raises ArgumentError 'Unknown distance: ...' at query-build time, before a request is sent.
Source
Thrown at lib/searchkick.rb:289
redis.with do |r|
yield r
end
else
yield redis
end
end
end
def self.warn(message)
super("[searchkick] WARNING: #{message}")
end
# private
def self.load_records(relation, ids)
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"View on GitHub (pinned to 93e901a75b)
Solutions
- Use one of Searchkick's names: 'cosine', 'euclidean', 'taxicab', 'inner_product' or 'chebyshev'
- If you meant Elasticsearch's dot_product, use 'inner_product' and normalize your vectors beforehand
- Check casing and whitespace - values are matched as exact lowercase strings
Example fix
# before
Product.search('*', knn: {field: :v, vector: vec, distance: 'dot_product', exact: true})
# => ArgumentError: Unknown distance: dot_product
# after
Product.search('*', knn: {field: :v, vector: vec, distance: 'inner_product', exact: true}) Defensive patterns
Strategy: type-guard
Type guard
OPENSEARCH_EXACT_DISTANCES = %w[cosine euclidean taxicab inner_product chebyshev].freeze
def valid_opensearch_exact_distance?(value)
OPENSEARCH_EXACT_DISTANCES.include?(value)
end
raise ArgumentError, "bad distance: #{d}" unless valid_opensearch_exact_distance?(d)
Product.search('*', knn: {field: :v, vector: vec, distance: d, exact: true}) Try / catch
begin
Product.search('*', knn: opts)
rescue ArgumentError => e
raise unless e.message.start_with?('Unknown distance')
opts[:distance] = 'cosine' # safe default
Product.search('*', knn: opts)
end Prevention
- Keep a frozen constant of supported metric names per server flavor and validate user-provided metrics against it
- Do not accept metric names directly from API consumers - map them through your own vocabulary
- Add a unit test listing every metric your app passes to knn
When it happens
Trigger: Product.search('*', knn: {field: :embedding, vector: vec, distance: 'dot_product', exact: true}) against OpenSearch; passing Elasticsearch-style names like 'l2_norm' or 'cosinesimil' instead of Searchkick's own names.
Common situations: Porting metric names from other libraries (Faiss uses ip/l2; Elasticsearch knn uses dot_product/cosine/l2_norm); typos or wrong casing such as 'Cosine'; using a metric supported only by the Elasticsearch branch of the code.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Use Searchkick.search to search multiple models
- #{class_name} is not a searchkick model
- Unknown distance: #{distance}
- The `elasticsearch` gem must be 8+
- Redis not configured
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/9563fa733f3e6b60.
Report an issue: GitHub.