chewy 7.5.1 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +12 -0
- data/CHANGELOG.md +11 -0
- data/README.md +6 -3
- data/chewy.gemspec +1 -1
- data/gemfiles/base.gemfile +3 -3
- data/lib/chewy/config.rb +2 -2
- data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +33 -16
- data/lib/chewy/strategy/delayed_sidekiq/worker.rb +30 -6
- data/lib/chewy/strategy/delayed_sidekiq.rb +4 -4
- data/lib/chewy/version.rb +1 -1
- data/spec/chewy/config_spec.rb +2 -2
- data/spec/chewy/strategy/delayed_sidekiq_spec.rb +13 -7
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a15165f889275fecc6a0d590c3339ce0ac9b7944f916306fd6883e7c2be67747
|
4
|
+
data.tar.gz: 8efc6201add68bf1c378f598934b6c52c74bd9a62056e7aafdd357358d5cd2b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfc7f1297fc72fcbdfdbedbcd82c6afe12dd9e15583b4e3467a8a77da89cd1aa8f20b9d0c0a8dd1841275e2916715030e28e9ed10cf510d6985904be9574890e
|
7
|
+
data.tar.gz: 6917027945ce94dab50d2f6d679570be58e4bb86472f1d591805d915ca425ecbd24e4cc13e30a459fe2dae4d572002735b69cef138a5c6038c52803a251b8a54
|
data/.github/workflows/ruby.yml
CHANGED
@@ -23,6 +23,18 @@ jobs:
|
|
23
23
|
env:
|
24
24
|
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
25
25
|
|
26
|
+
services:
|
27
|
+
redis:
|
28
|
+
# Docker Hub image
|
29
|
+
image: redis
|
30
|
+
ports:
|
31
|
+
- '6379:6379'
|
32
|
+
# Set health checks to wait until redis has started
|
33
|
+
options: >-
|
34
|
+
--health-cmd "redis-cli ping"
|
35
|
+
--health-interval 10s
|
36
|
+
--health-timeout 5s
|
37
|
+
--health-retries 5
|
26
38
|
steps:
|
27
39
|
- uses: actions/checkout@v4
|
28
40
|
- uses: ruby/setup-ruby@v1
|
data/CHANGELOG.md
CHANGED
@@ -8,6 +8,17 @@
|
|
8
8
|
|
9
9
|
### Bugs Fixed
|
10
10
|
|
11
|
+
## 7.6.0 (2024-05-03)
|
12
|
+
|
13
|
+
### Changes
|
14
|
+
|
15
|
+
* [#933](https://github.com/toptal/chewy/pull/933): Relax allowed `elasticsearch` dependency versions. ([@mjankowski][])
|
16
|
+
|
17
|
+
### Bugs Fixed
|
18
|
+
* [#937](https://github.com/toptal/chewy/pull/937): Fix for race condition while using the `delayed_sidekiq` strategy. Also, fix for Redis bloating in case of reindexing error ([@skcc321](https://github.com/skcc321))
|
19
|
+
|
20
|
+
* [#947](https://github.com/toptal/chewy/pull/947): Fix intermittent time-based failure in delayed sidekiq spec. ([@mjankowski][])
|
21
|
+
|
11
22
|
## 7.5.1 (2024-01-30)
|
12
23
|
|
13
24
|
### New Features
|
data/README.md
CHANGED
@@ -776,9 +776,12 @@ Chewy.settings[:sidekiq] = {queue: :low}
|
|
776
776
|
|
777
777
|
#### `:delayed_sidekiq`
|
778
778
|
|
779
|
-
It accumulates
|
780
|
-
|
781
|
-
It supports `update_fields` option, so it will
|
779
|
+
It accumulates IDs of records to be reindexed during the latency window in Redis and then performs the reindexing of all accumulated records at once.
|
780
|
+
This strategy is very useful in the case of frequently mutated records.
|
781
|
+
It supports the `update_fields` option, so it will attempt to select just enough data from the database.
|
782
|
+
|
783
|
+
Keep in mind, this strategy does not guarantee reindexing in the event of Sidekiq worker termination or an error during the reindexing phase.
|
784
|
+
This behavior is intentional to prevent continuous growth of Redis db.
|
782
785
|
|
783
786
|
There are three options that can be defined in the index:
|
784
787
|
```ruby
|
data/chewy.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.require_paths = ['lib']
|
18
18
|
|
19
19
|
spec.add_dependency 'activesupport', '>= 5.2' # Remove with major version bump, 8.x
|
20
|
-
spec.add_dependency 'elasticsearch', '>= 7.
|
20
|
+
spec.add_dependency 'elasticsearch', '>= 7.14.0', '< 8'
|
21
21
|
spec.add_dependency 'elasticsearch-dsl'
|
22
22
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
23
23
|
end
|
data/gemfiles/base.gemfile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
gem 'database_cleaner'
|
2
2
|
gem 'elasticsearch-extensions'
|
3
3
|
gem 'method_source'
|
4
|
-
gem 'mock_redis'
|
5
4
|
gem 'rake'
|
5
|
+
gem 'redis', require: false
|
6
6
|
gem 'rspec', '>= 3.7.0'
|
7
7
|
gem 'rspec-collection_matchers'
|
8
8
|
gem 'rspec-its'
|
9
|
-
gem 'rubocop', '1.
|
10
|
-
gem 'sqlite3'
|
9
|
+
gem 'rubocop', '1.63.4'
|
10
|
+
gem 'sqlite3', '~> 1.4'
|
11
11
|
gem 'timecop'
|
12
12
|
gem 'unparser'
|
data/lib/chewy/config.rb
CHANGED
@@ -70,12 +70,12 @@ module Chewy
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def transport_logger=(logger)
|
73
|
-
Chewy.client.transport.logger = logger
|
73
|
+
Chewy.client.transport.transport.logger = logger
|
74
74
|
@transport_logger = logger
|
75
75
|
end
|
76
76
|
|
77
77
|
def transport_tracer=(tracer)
|
78
|
-
Chewy.client.transport.tracer = tracer
|
78
|
+
Chewy.client.transport.transport.tracer = tracer
|
79
79
|
@transport_tracer = tracer
|
80
80
|
end
|
81
81
|
|
@@ -12,13 +12,43 @@ module Chewy
|
|
12
12
|
class DelayedSidekiq
|
13
13
|
require_relative 'worker'
|
14
14
|
|
15
|
+
LUA_SCRIPT = <<~LUA
|
16
|
+
local timechunk_key = KEYS[1]
|
17
|
+
local timechunks_key = KEYS[2]
|
18
|
+
local serialize_data = ARGV[1]
|
19
|
+
local at = ARGV[2]
|
20
|
+
local ttl = tonumber(ARGV[3])
|
21
|
+
|
22
|
+
local schedule_job = false
|
23
|
+
|
24
|
+
-- Check if the 'sadd?' method is available
|
25
|
+
if redis.call('exists', 'sadd?') == 1 then
|
26
|
+
redis.call('sadd?', timechunk_key, serialize_data)
|
27
|
+
else
|
28
|
+
redis.call('sadd', timechunk_key, serialize_data)
|
29
|
+
end
|
30
|
+
|
31
|
+
-- Set expiration for timechunk_key
|
32
|
+
redis.call('expire', timechunk_key, ttl)
|
33
|
+
|
34
|
+
-- Check if timechunk_key exists in the sorted set
|
35
|
+
if not redis.call('zrank', timechunks_key, timechunk_key) then
|
36
|
+
-- Add timechunk_key to the sorted set
|
37
|
+
redis.call('zadd', timechunks_key, at, timechunk_key)
|
38
|
+
-- Set expiration for timechunks_key
|
39
|
+
redis.call('expire', timechunks_key, ttl)
|
40
|
+
schedule_job = true
|
41
|
+
end
|
42
|
+
|
43
|
+
return schedule_job
|
44
|
+
LUA
|
45
|
+
|
15
46
|
class Scheduler
|
16
47
|
DEFAULT_TTL = 60 * 60 * 24 # in seconds
|
17
48
|
DEFAULT_LATENCY = 10
|
18
49
|
DEFAULT_MARGIN = 2
|
19
50
|
DEFAULT_QUEUE = 'chewy'
|
20
51
|
KEY_PREFIX = 'chewy:delayed_sidekiq'
|
21
|
-
ALL_SETS_KEY = "#{KEY_PREFIX}:all_sets".freeze
|
22
52
|
FALLBACK_FIELDS = 'all'
|
23
53
|
FIELDS_IDS_SEPARATOR = ';'
|
24
54
|
IDS_SEPARATOR = ','
|
@@ -67,21 +97,8 @@ module Chewy
|
|
67
97
|
# | chewy:delayed_sidekiq:CitiesIndex:1679347868
|
68
98
|
def postpone
|
69
99
|
::Sidekiq.redis do |redis|
|
70
|
-
#
|
71
|
-
if redis.
|
72
|
-
redis.sadd?(ALL_SETS_KEY, timechunks_key)
|
73
|
-
redis.sadd?(timechunk_key, serialize_data)
|
74
|
-
else
|
75
|
-
redis.sadd(ALL_SETS_KEY, timechunks_key)
|
76
|
-
redis.sadd(timechunk_key, serialize_data)
|
77
|
-
end
|
78
|
-
|
79
|
-
redis.expire(timechunk_key, ttl)
|
80
|
-
|
81
|
-
unless redis.zrank(timechunks_key, timechunk_key)
|
82
|
-
redis.zadd(timechunks_key, at, timechunk_key)
|
83
|
-
redis.expire(timechunks_key, ttl)
|
84
|
-
|
100
|
+
# do the redis stuff in a single command to avoid concurrency issues
|
101
|
+
if redis.eval(LUA_SCRIPT, keys: [timechunk_key, timechunks_key], argv: [serialize_data, at, ttl])
|
85
102
|
::Sidekiq::Client.push(
|
86
103
|
'queue' => sidekiq_queue,
|
87
104
|
'at' => at + margin,
|
@@ -6,13 +6,40 @@ module Chewy
|
|
6
6
|
class Worker
|
7
7
|
include ::Sidekiq::Worker
|
8
8
|
|
9
|
+
LUA_SCRIPT = <<~LUA
|
10
|
+
local type = ARGV[1]
|
11
|
+
local score = tonumber(ARGV[2])
|
12
|
+
local prefix = ARGV[3]
|
13
|
+
local timechunks_key = prefix .. ":" .. type .. ":timechunks"
|
14
|
+
|
15
|
+
-- Get timechunk_keys with scores less than or equal to the specified score
|
16
|
+
local timechunk_keys = redis.call('zrangebyscore', timechunks_key, '-inf', score)
|
17
|
+
|
18
|
+
-- Get all members from the sets associated with the timechunk_keys
|
19
|
+
local members = {}
|
20
|
+
for _, timechunk_key in ipairs(timechunk_keys) do
|
21
|
+
local set_members = redis.call('smembers', timechunk_key)
|
22
|
+
for _, member in ipairs(set_members) do
|
23
|
+
table.insert(members, member)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
-- Remove timechunk_keys and their associated sets
|
28
|
+
for _, timechunk_key in ipairs(timechunk_keys) do
|
29
|
+
redis.call('del', timechunk_key)
|
30
|
+
end
|
31
|
+
|
32
|
+
-- Remove timechunks with scores less than or equal to the specified score
|
33
|
+
redis.call('zremrangebyscore', timechunks_key, '-inf', score)
|
34
|
+
|
35
|
+
return members
|
36
|
+
LUA
|
37
|
+
|
9
38
|
def perform(type, score, options = {})
|
10
39
|
options[:refresh] = !Chewy.disable_refresh_async if Chewy.disable_refresh_async
|
11
40
|
|
12
41
|
::Sidekiq.redis do |redis|
|
13
|
-
|
14
|
-
timechunk_keys = redis.zrangebyscore(timechunks_key, -1, score)
|
15
|
-
members = timechunk_keys.flat_map { |timechunk_key| redis.smembers(timechunk_key) }.compact
|
42
|
+
members = redis.eval(LUA_SCRIPT, keys: [], argv: [type, score, Scheduler::KEY_PREFIX])
|
16
43
|
|
17
44
|
# extract ids and fields & do the reset of records
|
18
45
|
ids, fields = extract_ids_and_fields(members)
|
@@ -22,9 +49,6 @@ module Chewy
|
|
22
49
|
index.strategy_config.delayed_sidekiq.reindex_wrapper.call do
|
23
50
|
options.any? ? index.import!(ids, **options) : index.import!(ids)
|
24
51
|
end
|
25
|
-
|
26
|
-
redis.del(timechunk_keys)
|
27
|
-
redis.zremrangebyscore(timechunks_key, -1, score)
|
28
52
|
end
|
29
53
|
end
|
30
54
|
|
@@ -9,11 +9,11 @@ module Chewy
|
|
9
9
|
# leak and potential flaky tests.
|
10
10
|
def self.clear_timechunks!
|
11
11
|
::Sidekiq.redis do |redis|
|
12
|
-
|
13
|
-
break if timechunk_sets.empty?
|
12
|
+
keys_to_delete = redis.keys("#{Scheduler::KEY_PREFIX}*")
|
14
13
|
|
15
|
-
|
16
|
-
|
14
|
+
# Delete keys one by one
|
15
|
+
keys_to_delete.each do |key|
|
16
|
+
redis.del(key)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/chewy/version.rb
CHANGED
data/spec/chewy/config_spec.rb
CHANGED
@@ -22,7 +22,7 @@ describe Chewy::Config do
|
|
22
22
|
|
23
23
|
specify do
|
24
24
|
expect { subject.transport_logger = logger }
|
25
|
-
.to change { Chewy.client.transport.logger }.to(logger)
|
25
|
+
.to change { Chewy.client.transport.transport.logger }.to(logger)
|
26
26
|
end
|
27
27
|
specify do
|
28
28
|
expect { subject.transport_logger = logger }
|
@@ -40,7 +40,7 @@ describe Chewy::Config do
|
|
40
40
|
|
41
41
|
specify do
|
42
42
|
expect { subject.transport_tracer = tracer }
|
43
|
-
.to change { Chewy.client.transport.tracer }.to(tracer)
|
43
|
+
.to change { Chewy.client.transport.transport.tracer }.to(tracer)
|
44
44
|
end
|
45
45
|
specify do
|
46
46
|
expect { subject.transport_tracer = tracer }
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
if defined?(Sidekiq)
|
4
4
|
require 'sidekiq/testing'
|
5
|
-
require '
|
5
|
+
require 'redis'
|
6
6
|
|
7
7
|
describe Chewy::Strategy::DelayedSidekiq do
|
8
8
|
around do |example|
|
@@ -10,9 +10,10 @@ if defined?(Sidekiq)
|
|
10
10
|
end
|
11
11
|
|
12
12
|
before do
|
13
|
-
redis =
|
13
|
+
redis = Redis.new
|
14
14
|
allow(Sidekiq).to receive(:redis).and_yield(redis)
|
15
15
|
Sidekiq::Worker.clear_all
|
16
|
+
described_class.clear_timechunks!
|
16
17
|
end
|
17
18
|
|
18
19
|
before do
|
@@ -35,7 +36,7 @@ if defined?(Sidekiq)
|
|
35
36
|
|
36
37
|
it "respects 'refresh: false' options" do
|
37
38
|
allow(Chewy).to receive(:disable_refresh_async).and_return(true)
|
38
|
-
expect(CitiesIndex).to receive(:import!).with([city.id, other_city.id], refresh: false)
|
39
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id]), refresh: false)
|
39
40
|
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id, other_city.id])
|
40
41
|
scheduler.postpone
|
41
42
|
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
@@ -47,7 +48,7 @@ if defined?(Sidekiq)
|
|
47
48
|
expect(Sidekiq::Client).to receive(:push).with(
|
48
49
|
hash_including(
|
49
50
|
'queue' => 'chewy',
|
50
|
-
'at' =>
|
51
|
+
'at' => expected_at_time.to_i,
|
51
52
|
'class' => Chewy::Strategy::DelayedSidekiq::Worker,
|
52
53
|
'args' => ['CitiesIndex', an_instance_of(Integer)]
|
53
54
|
)
|
@@ -62,6 +63,11 @@ if defined?(Sidekiq)
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
66
|
+
|
67
|
+
def expected_at_time
|
68
|
+
target = described_class::Scheduler::DEFAULT_LATENCY.seconds.from_now.to_i
|
69
|
+
target - (target % described_class::Scheduler::DEFAULT_LATENCY) + described_class::Scheduler::DEFAULT_MARGIN.seconds
|
70
|
+
end
|
65
71
|
end
|
66
72
|
|
67
73
|
context 'with custom config' do
|
@@ -103,7 +109,7 @@ if defined?(Sidekiq)
|
|
103
109
|
context 'two reindex call within the timewindow' do
|
104
110
|
it 'accumulates all ids does the reindex one time' do
|
105
111
|
Timecop.freeze do
|
106
|
-
expect(CitiesIndex).to receive(:import!).with([
|
112
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id])).once
|
107
113
|
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id])
|
108
114
|
scheduler.postpone
|
109
115
|
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id])
|
@@ -115,7 +121,7 @@ if defined?(Sidekiq)
|
|
115
121
|
context 'one call with update_fields another one without update_fields' do
|
116
122
|
it 'does reindex of all fields' do
|
117
123
|
Timecop.freeze do
|
118
|
-
expect(CitiesIndex).to receive(:import!).with([
|
124
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id])).once
|
119
125
|
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id], update_fields: ['name'])
|
120
126
|
scheduler.postpone
|
121
127
|
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id])
|
@@ -128,7 +134,7 @@ if defined?(Sidekiq)
|
|
128
134
|
context 'both calls with different update fields' do
|
129
135
|
it 'deos reindex with union of fields' do
|
130
136
|
Timecop.freeze do
|
131
|
-
expect(CitiesIndex).to receive(:import!).with([
|
137
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id]), update_fields: %w[name description]).once
|
132
138
|
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id], update_fields: ['name'])
|
133
139
|
scheduler.postpone
|
134
140
|
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id], update_fields: ['description'])
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chewy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.
|
4
|
+
version: 7.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Toptal, LLC
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-05-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -31,20 +31,20 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 7.
|
34
|
+
version: 7.14.0
|
35
35
|
- - "<"
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version:
|
37
|
+
version: '8'
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
requirements:
|
42
42
|
- - ">="
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: 7.
|
44
|
+
version: 7.14.0
|
45
45
|
- - "<"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '8'
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: elasticsearch-dsl
|
50
50
|
requirement: !ruby/object:Gem::Requirement
|
@@ -320,7 +320,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
320
320
|
- !ruby/object:Gem::Version
|
321
321
|
version: '0'
|
322
322
|
requirements: []
|
323
|
-
rubygems_version: 3.
|
323
|
+
rubygems_version: 3.4.10
|
324
324
|
signing_key:
|
325
325
|
specification_version: 4
|
326
326
|
summary: Elasticsearch ODM client wrapper
|