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

  1. Use one of Searchkick's names: 'cosine', 'euclidean', 'taxicab', 'inner_product' or 'chebyshev'
  2. If you meant Elasticsearch's dot_product, use 'inner_product' and normalize your vectors beforehand
  3. 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

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


AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21). Data as JSON: /api/errors/9563fa733f3e6b60. Report an issue: GitHub.