Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,21 @@ jobs:
matrix:
ruby: [ '3.2', '3.3', '3.4', '4.0' ]
gemfile: [rails.7.2.activerecord, rails.8.0.activerecord, rails.8.1.activerecord]
name: ${{ matrix.ruby }}-${{ matrix.gemfile }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlfonsoUceda Would it be better / cleaner to use something like appraisal to test against both Elasticsearch v8 and v9?

elasticsearch: ['9.4.3']
include:
- ruby: '3.3'
gemfile: rails.7.2.activerecord
elasticsearch: '8.19.3'
es_gem_constraint: '~> 8.14'
- ruby: '3.4'
gemfile: rails.8.0.activerecord
elasticsearch: '8.19.3'
es_gem_constraint: '~> 8.14'
- ruby: '4.0'
gemfile: rails.8.1.activerecord
elasticsearch: '8.19.3'
es_gem_constraint: '~> 8.14'
name: ${{ matrix.ruby }}-${{ matrix.gemfile }}-es${{ matrix.elasticsearch }}

env:
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
Expand All @@ -45,7 +59,7 @@ jobs:
--health-timeout 5s
--health-retries 5
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.13
image: docker.elastic.co/elasticsearch/elasticsearch:${{ matrix.elasticsearch }}
ports:
- '9250:9200'
env:
Expand All @@ -61,6 +75,9 @@ jobs:

steps:
- uses: actions/checkout@v7
- name: Pin elasticsearch gem for ES 8.x
if: matrix.es_gem_constraint
run: echo "gem 'elasticsearch', '${{ matrix.es_gem_constraint }}'" >> $BUNDLE_GEMFILE
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

### Changes

* [#1009](https://github.com/toptal/chewy/pull/1009): **(Breaking)** Add Elasticsearch 9.x support while retaining ES 8.x compatibility. The `elasticsearch` gem dependency now allows `>= 8.14, < 10.0`. ([@mattmenefee][])
* The `search_query.chewy` notification payload for scroll requests now nests `scroll_id` under `body:` (i.e. `{scroll: '1m', body: {scroll_id: '...'}}` instead of `{scroll: '1m', scroll_id: '...'}`). Update any application code that subscribes to scroll notification payloads.

## 8.4.1 (2026-06-19)

### New Features
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Chewy is compatible with MRI 3.2-4.0.

| Chewy version | Elasticsearch version |
| ------------- | ---------------------------------- |
| 9.0.x | 8.x, 9.x |
| 8.0.x | 8.x |
| 7.2.x | 7.x |
| 7.1.x | 7.x |
Expand Down
2 changes: 1 addition & 1 deletion chewy.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']

spec.add_dependency 'activesupport', '>= 7.2'
spec.add_dependency 'elasticsearch', '>= 8.14', '< 9.0'
spec.add_dependency 'elasticsearch', '>= 8.14', '< 10.0'
spec.add_dependency 'elasticsearch-dsl'
spec.metadata['rubygems_mfa_required'] = 'true'
end
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
elasticsearch_test:
image: "docker.elastic.co/elasticsearch/elasticsearch:${ES_VERSION:-8.19.13}"
image: "docker.elastic.co/elasticsearch/elasticsearch:${ES_VERSION:-9.4.3}"
environment:
- bootstrap.memory_lock=${ES_MEMORY_LOCK:-false}
- "ES_JAVA_OPTS=-Xms${TEST_ES_HEAP_SIZE:-500m} -Xmx${TEST_ES_HEAP_SIZE:-500m}"
Expand Down
7 changes: 4 additions & 3 deletions lib/chewy/search/scrolling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,10 @@ def scroll_objects(**options, &block)

private

def perform_scroll(body)
ActiveSupport::Notifications.instrument 'search_query.chewy', notification_payload(request: body) do
Chewy.client.scroll(body)
def perform_scroll(scroll:, scroll_id:)
request = {scroll: scroll, body: {scroll_id: scroll_id}}
ActiveSupport::Notifications.instrument 'search_query.chewy', notification_payload(request: request) do
Chewy.client.scroll(request)
end
end
end
Expand Down
40 changes: 40 additions & 0 deletions migration_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@ This document outlines the steps you need to take when migrating between major v
Chewy and Elasticsearch. For simplicity's sake the guide will assume that you're using
Chewy alongside a matching Elasticsearch version.

## Chewy 8/Elasticsearch 8 to Chewy 9/Elasticsearch 9

The Chewy 8 to 9 upgrade is relatively straightforward compared to previous major versions.
The primary changes concern the `elasticsearch` Ruby gem version and the scroll notification
payload format; no index mapping or query DSL changes are required.

In order to upgrade Chewy 8/Elasticsearch 8 to Chewy 9/Elasticsearch 9 in the most seamless manner you have to:

* Upgrade to the latest 8.x stable releases (Chewy 8.0.1, Elasticsearch 8.19.x). Your ES cluster must be on 8.19.x before upgrading to 9.x.
* Study carefully [Breaking changes in 9.0](https://www.elastic.co/docs/release-notes/elasticsearch/breaking-changes), make sure your application conforms. Notable changes that may affect Chewy users:
* Stricter bulk API request parsing — malformed action lines that were previously tolerated are now rejected.
* The legacy `_knn_search` endpoint has been removed — use the `knn` clause in `_search` instead.
* Date histogram aggregations on boolean fields are no longer supported.
* Elasticsearch now returns HTTP 429 (instead of 5xx) for certain timeout conditions — update any retry logic that keys on 5xx status codes.
* Update the `elasticsearch` gem — Chewy 9 requires `elasticsearch >= 8.14`.
* **Important:** The `elasticsearch` Ruby gem at version 9.x sends a `compatible-with=9` HTTP header that Elasticsearch 8.x servers reject with HTTP 400. Keep the gem pinned to `~> 8.14` (i.e. gem version 8.x) until after your Elasticsearch cluster has been upgraded to 9.x. You may optionally upgrade the gem to version 9.x after the cluster upgrade is complete.
* Update scroll notification subscribers:
* The `search_query.chewy` notification payload for scroll requests now nests `scroll_id` under `body:`:
* Old format: `{scroll: '1m', scroll_id: '...'}`
* New format: `{scroll: '1m', body: {scroll_id: '...'}}`
* Update any application code that subscribes to scroll notification payloads:

```ruby
# Before (Chewy < 9)
ActiveSupport::Notifications.subscribe('search_query.chewy') do |*, payload|
scroll_id = payload[:request][:scroll_id]
end

# After (Chewy 9+)
ActiveSupport::Notifications.subscribe('search_query.chewy') do |*, payload|
scroll_id = payload[:request].dig(:body, :scroll_id)
end
```

* Run your test suite on Chewy 9 / Elasticsearch 9.
* Run manual tests on Chewy 9 / Elasticsearch 9.
* Deploy Chewy 9 (with the `elasticsearch` gem still pinned to `~> 8.14`).
* Perform a [rolling upgrade](https://www.elastic.co/docs/deploy-manage/upgrade/deployment-or-cluster) of Elasticsearch to 9.x.
* (Optional) Remove the `elasticsearch` gem version pin, allowing the gem to resolve to 9.x.

## Chewy 7/Elasticsearch 7 to Chewy 8/Elasticsearch 8

In order to upgrade Chewy 7/Elasticsearch 7 to Chewy 8/Elasticsearch 8 in the most seamless manner you have to:
Expand Down
2 changes: 1 addition & 1 deletion spec/chewy/runtime_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
describe '.version' do
specify { expect(described_class.version).to be_a(described_class::Version) }
specify { expect(described_class.version).to be >= '8.0' }
specify { expect(described_class.version).to be < '9.0' }
specify { expect(described_class.version).to be < '10.0' }
end
end
8 changes: 6 additions & 2 deletions spec/chewy/search/scrolling_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@
end

context do
before { expect(Chewy.client).to receive(:scroll).twice.and_call_original }
before do
expect(Chewy.client).to receive(:scroll)
.with(hash_including(scroll: '1m', body: hash_including(:scroll_id)))
.twice.and_call_original
end
specify do
expect(request.scroll_batches(batch_size: 2).map do |batch|
batch.map { |hit| hit['_source']['rating'] }
Expand Down Expand Up @@ -181,7 +185,7 @@
hash_including(
index: [CitiesIndex, CountriesIndex],
indexes: [CitiesIndex, CountriesIndex],
request: {scroll: '1m', scroll_id: an_instance_of(String)}
request: {scroll: '1m', body: {scroll_id: an_instance_of(String)}}
)
])
end
Expand Down
5 changes: 3 additions & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@
# )

# Low-level substitute for now-obsolete drop_indices.
# Uses format: 'json' for version-portable parsing.
# Uses format: 'json' for version-portable parsing and `.body` to unwrap
# the Elasticsearch::API::Response (required for ES 9.x gem).
# System indices (prefixed with '.') are excluded to avoid deleting
# ES-internal indices like .security or .kibana.
def drop_indices
indices = Chewy.client.cat.indices(format: 'json')
indices = Chewy.client.cat.indices(format: 'json').body
.map { |entry| entry['index'] }
.reject { |name| name.start_with?('.') }
return if indices.blank?
Expand Down