ankane/searchkick · error · ArgumentError
Must specify a distance for OpenSearch
Error message
Must specify a distance for OpenSearch
What it means
For vector (kNN) search, OpenSearch requires a `method` block with a `space_type` on every `knn_vector` field; without it the server can crash. Searchkick derives `space_type` from the `distance` you pass per field, so when `Searchkick.opensearch?` is true and `knn_options[:distance]` is nil it raises `ArgumentError` at index build time as a protective measure.
Source
Thrown at lib/searchkick/index_options.rb:441
(options[:locations] || []).map(&:to_s).each do |field|
mapping[field] = {
type: "geo_point"
}
end
options[:geo_shape] = options[:geo_shape].product([{}]).to_h if options[:geo_shape].is_a?(Array)
(options[:geo_shape] || {}).each do |field, shape_options|
mapping[field] = shape_options.merge(type: "geo_shape")
end
(options[:knn] || []).each do |field, knn_options|
distance = knn_options[:distance]
quantization = knn_options[:quantization]
if Searchkick.opensearch?
if distance.nil?
# avoid server crash if method not specified
raise ArgumentError, "Must specify a distance for OpenSearch"
end
vector_options = {
type: "knn_vector",
dimension: knn_options[:dimensions]
}
if !distance.nil?
space_type =
case distance
when "cosine"
"cosinesimil"
when "euclidean"
"l2"
when "inner_product"
"innerproduct"
else
raise ArgumentError, "Unknown distance: #{distance}"View on GitHub (pinned to 93e901a75b)
Solutions
- Add `distance:` to every knn entry: `searchkick knn: {embedding: {dimensions: 768, distance: "cosine"}}`.
- Use only the supported values `"cosine"`, `"euclidean"`, or `"inner_product"` (mapped to cosinesimil/l2/innerproduct for OpenSearch).
- If the config must be shared, branch it: `knn: {embedding: {dimensions: 768}.merge(OpenSearchClient? ? {distance: "cosine"} : {})}` or a helper that detects `Searchkick.opensearch?`.
- Reindex after the change so the mapping with `method: {name: "hnsw", ...}` is written.
Example fix
# before
searchkick knn: {embedding: {dimensions: 768}}
# on OpenSearch => ArgumentError: Must specify a distance for OpenSearch
# after
searchkick knn: {embedding: {dimensions: 768, distance: "cosine"}} Defensive patterns
Strategy: validation
Validate before calling
knn = {embedding: {dimensions: 768}}
knn.each_value { |opts| opts[:distance] ||= "cosine" } # OpenSearch requires it
raise ArgumentError, "knn field missing distance (required for OpenSearch)" if Searchkick.opensearch? && knn.any? { |_, o| o[:distance].nil? }
class Product < ApplicationRecord
searchkick knn: knn
end Type guard
def knn_config_valid?(knn, opensearch:)
knn.all? do |_, opts|
opts[:dimensions].is_a?(Integer) && (!opensearch || !opts[:distance].nil?)
end
end Prevention
- Always set `distance:` on every knn entry even on Elasticsearch — it is required the moment you point at OpenSearch.
- Assert `Searchkick.opensearch?` matches your intended engine in a boot check to catch environment mismatches early.
- Cover index creation with a test that runs `Model.reindex` in both engine modes when supporting both.
When it happens
Trigger: `searchkick knn: {embedding: {dimensions: 768}}` (no `distance:`) while connected to OpenSearch, then `Model.reindex` or any index creation. Works silently on Elasticsearch because the ES branch builds `similarity` only `if !distance.nil?`.
Common situations: Developing against Elasticsearch and deploying to OpenSearch (or flipping the `Searchkick.opensearch = true` / opensearch-version client env); copying an ES-oriented knn config from the README into an OpenSearch setup; upgrading searchkick and adding vector search without reading the OpenSearch-specific notes.
Related errors
- Unknown distance: #{distance}
- Quantization not supported yet for OpenSearch
- The `elasticsearch` gem must be 8+
- Use Searchkick.search to search multiple models
- Redis not configured
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/994a5f519468d934.
Report an issue: GitHub.