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

  # private

View on GitHub (pinned to 93e901a75b)

Solutions

  1. Use distance 'cosine' for exact queries on cosine-mapped fields under ES 9 - drop the override or set it to 'cosine'
  2. If another metric is truly required, change the field's distance in searchkick options and reindex so it is mapped that way
  3. 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

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


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