chewy 7.2.1 → 7.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/dependabot.yml +42 -0
- data/.github/workflows/ruby.yml +28 -26
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +196 -0
- data/Gemfile +4 -3
- data/README.md +203 -20
- data/chewy.gemspec +4 -18
- data/gemfiles/base.gemfile +12 -0
- data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
- data/gemfiles/{rails.5.2.activerecord.gemfile → rails.7.0.activerecord.gemfile} +6 -3
- data/gemfiles/{rails.6.0.activerecord.gemfile → rails.7.1.activerecord.gemfile} +6 -3
- data/lib/chewy/config.rb +22 -14
- data/lib/chewy/elastic_client.rb +31 -0
- data/lib/chewy/errors.rb +11 -2
- data/lib/chewy/fields/base.rb +69 -13
- data/lib/chewy/fields/root.rb +2 -10
- data/lib/chewy/index/actions.rb +11 -16
- data/lib/chewy/index/adapter/active_record.rb +18 -3
- data/lib/chewy/index/adapter/object.rb +0 -10
- data/lib/chewy/index/adapter/orm.rb +4 -14
- data/lib/chewy/index/crutch.rb +15 -7
- data/lib/chewy/index/import/bulk_builder.rb +219 -32
- data/lib/chewy/index/import/bulk_request.rb +1 -1
- data/lib/chewy/index/import/routine.rb +3 -3
- data/lib/chewy/index/import.rb +45 -31
- data/lib/chewy/index/mapping.rb +2 -2
- data/lib/chewy/index/observe/active_record_methods.rb +87 -0
- data/lib/chewy/index/observe/callback.rb +34 -0
- data/lib/chewy/index/observe.rb +3 -58
- data/lib/chewy/index/syncer.rb +1 -1
- data/lib/chewy/index.rb +25 -0
- data/lib/chewy/journal.rb +17 -6
- data/lib/chewy/log_subscriber.rb +5 -1
- data/lib/chewy/minitest/helpers.rb +77 -0
- data/lib/chewy/minitest/search_index_receiver.rb +3 -1
- data/lib/chewy/rake_helper.rb +92 -11
- data/lib/chewy/rspec/build_query.rb +12 -0
- data/lib/chewy/rspec/helpers.rb +55 -0
- data/lib/chewy/rspec/update_index.rb +14 -7
- data/lib/chewy/rspec.rb +2 -0
- data/lib/chewy/runtime/version.rb +1 -1
- data/lib/chewy/runtime.rb +1 -1
- data/lib/chewy/search/parameters/collapse.rb +16 -0
- data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
- data/lib/chewy/search/parameters/indices.rb +1 -1
- data/lib/chewy/search/parameters/knn.rb +16 -0
- data/lib/chewy/search/parameters/order.rb +6 -19
- data/lib/chewy/search/parameters/storage.rb +1 -1
- data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
- data/lib/chewy/search/parameters.rb +4 -4
- data/lib/chewy/search/request.rb +74 -16
- data/lib/chewy/search/scoping.rb +1 -1
- data/lib/chewy/search.rb +5 -2
- data/lib/chewy/stash.rb +3 -3
- data/lib/chewy/strategy/active_job.rb +1 -1
- data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
- data/lib/chewy/strategy/base.rb +10 -0
- data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
- data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
- data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
- data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
- data/lib/chewy/strategy/sidekiq.rb +1 -1
- data/lib/chewy/strategy.rb +3 -0
- data/lib/chewy/version.rb +1 -1
- data/lib/chewy.rb +21 -14
- data/lib/tasks/chewy.rake +18 -2
- data/migration_guide.md +1 -1
- data/spec/chewy/config_spec.rb +2 -2
- data/spec/chewy/elastic_client_spec.rb +26 -0
- data/spec/chewy/fields/base_spec.rb +39 -18
- data/spec/chewy/index/actions_spec.rb +10 -10
- data/spec/chewy/index/adapter/active_record_spec.rb +88 -0
- data/spec/chewy/index/import/bulk_builder_spec.rb +309 -1
- data/spec/chewy/index/import/routine_spec.rb +5 -5
- data/spec/chewy/index/import_spec.rb +48 -26
- data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
- data/spec/chewy/index/observe/callback_spec.rb +139 -0
- data/spec/chewy/index/observe_spec.rb +27 -0
- data/spec/chewy/journal_spec.rb +13 -49
- data/spec/chewy/minitest/helpers_spec.rb +111 -1
- data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
- data/spec/chewy/rake_helper_spec.rb +170 -0
- data/spec/chewy/rspec/build_query_spec.rb +34 -0
- data/spec/chewy/rspec/helpers_spec.rb +61 -0
- data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
- data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
- data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
- data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
- data/spec/chewy/search/parameters/knn_spec.rb +5 -0
- data/spec/chewy/search/parameters/order_spec.rb +18 -11
- data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
- data/spec/chewy/search/parameters_spec.rb +6 -1
- data/spec/chewy/search/request_spec.rb +58 -9
- data/spec/chewy/search_spec.rb +9 -0
- data/spec/chewy/strategy/active_job_spec.rb +8 -8
- data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
- data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
- data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
- data/spec/chewy/strategy/sidekiq_spec.rb +4 -4
- data/spec/chewy_spec.rb +10 -7
- data/spec/spec_helper.rb +1 -2
- data/spec/support/active_record.rb +8 -1
- metadata +45 -264
- data/lib/chewy/backports/deep_dup.rb +0 -46
- data/lib/chewy/backports/duplicable.rb +0 -91
- data/lib/chewy/index/import/thread_safe_progress_bar.rb +0 -40
data/lib/chewy/search.rb
CHANGED
@@ -84,9 +84,12 @@ module Chewy
|
|
84
84
|
def delegate_scoped(source, destination, methods)
|
85
85
|
methods.each do |method|
|
86
86
|
destination.class_eval do
|
87
|
-
define_method method do |*args, &block|
|
88
|
-
scoping
|
87
|
+
define_method method do |*args, **kwargs, &block|
|
88
|
+
scoping do
|
89
|
+
source.public_send(method, *args, **kwargs, &block)
|
90
|
+
end
|
89
91
|
end
|
92
|
+
method
|
90
93
|
end
|
91
94
|
end
|
92
95
|
end
|
data/lib/chewy/stash.rb
CHANGED
@@ -28,12 +28,12 @@ module Chewy
|
|
28
28
|
# Cleans up all the journal entries until the specified time. If nothing is
|
29
29
|
# specified - cleans up everything.
|
30
30
|
#
|
31
|
-
# @param
|
31
|
+
# @param until_time [Time, DateTime] Clean everything before that date
|
32
32
|
# @param only [Chewy::Index, Array<Chewy::Index>] indexes to clean up journal entries for
|
33
|
-
def self.clean(until_time = nil, only: [])
|
33
|
+
def self.clean(until_time = nil, only: [], delete_by_query_options: {})
|
34
34
|
scope = self.for(only)
|
35
35
|
scope = scope.filter(range: {created_at: {lte: until_time}}) if until_time
|
36
|
-
scope.delete_all
|
36
|
+
scope.delete_all(**delete_by_query_options)
|
37
37
|
end
|
38
38
|
|
39
39
|
# Selects all the journal entries for the specified indices.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Strategy
|
3
|
+
# This strategy works like atomic but import objects with `refresh=false` parameter.
|
4
|
+
#
|
5
|
+
# Chewy.strategy(:atomic_no_refresh) do
|
6
|
+
# User.all.map(&:save) # Does nothing here
|
7
|
+
# Post.all.map(&:save) # And here
|
8
|
+
# # It imports all the changed users and posts right here
|
9
|
+
# # before block leaving with bulk ES API, kinda optimization
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
class AtomicNoRefresh < Atomic
|
13
|
+
def leave
|
14
|
+
@stash.all? { |type, ids| type.import!(ids, refresh: false) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/chewy/strategy/base.rb
CHANGED
@@ -22,6 +22,16 @@ module Chewy
|
|
22
22
|
# strategies stack
|
23
23
|
#
|
24
24
|
def leave; end
|
25
|
+
|
26
|
+
# This method called when some model record is created or updated.
|
27
|
+
# Normally it will just evaluate all the Chewy callbacks and pass results
|
28
|
+
# to current strategy's update method.
|
29
|
+
# However it's possible to override it to achieve delayed evaluation of
|
30
|
+
# callbacks, e.g. using sidekiq.
|
31
|
+
#
|
32
|
+
def update_chewy_indices(object)
|
33
|
+
object.run_chewy_callbacks
|
34
|
+
end
|
25
35
|
end
|
26
36
|
end
|
27
37
|
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../index'
|
4
|
+
|
5
|
+
# The class is responsible for accumulating in redis [type, ids]
|
6
|
+
# that were requested to be reindexed during `latency` seconds.
|
7
|
+
# The reindex job is going to be scheduled after a `latency` seconds.
|
8
|
+
# that job is going to read accumulated [type, ids] from the redis
|
9
|
+
# and reindex all them at once.
|
10
|
+
module Chewy
|
11
|
+
class Strategy
|
12
|
+
class DelayedSidekiq
|
13
|
+
require_relative 'worker'
|
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
|
+
|
46
|
+
class Scheduler
|
47
|
+
DEFAULT_TTL = 60 * 60 * 24 # in seconds
|
48
|
+
DEFAULT_LATENCY = 10
|
49
|
+
DEFAULT_MARGIN = 2
|
50
|
+
DEFAULT_QUEUE = 'chewy'
|
51
|
+
KEY_PREFIX = 'chewy:delayed_sidekiq'
|
52
|
+
FALLBACK_FIELDS = 'all'
|
53
|
+
FIELDS_IDS_SEPARATOR = ';'
|
54
|
+
IDS_SEPARATOR = ','
|
55
|
+
|
56
|
+
def initialize(type, ids, options = {})
|
57
|
+
@type = type
|
58
|
+
@ids = ids
|
59
|
+
@options = options
|
60
|
+
end
|
61
|
+
|
62
|
+
# the diagram:
|
63
|
+
#
|
64
|
+
# inputs:
|
65
|
+
# latency == 2
|
66
|
+
# reindex_time = Time.current
|
67
|
+
#
|
68
|
+
# Parallel OR Sequential triggers of reindex: | What is going on in reindex store (Redis):
|
69
|
+
# --------------------------------------------------------------------------------------------------
|
70
|
+
# |
|
71
|
+
# process 1 (reindex_time): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1]
|
72
|
+
# Schedule.new(CitiesIndex, [1]).postpone | chewy:delayed_sidekiq:timechunks = [{ score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}]
|
73
|
+
# | & schedule a DelayedSidekiq::Worker at 1679347869 (at + 3)
|
74
|
+
# | it will zpop chewy:delayed_sidekiq:timechunks up to 1679347866 score and reindex all ids with zpoped keys
|
75
|
+
# | chewy:delayed_sidekiq:CitiesIndex:1679347866
|
76
|
+
# |
|
77
|
+
# |
|
78
|
+
# process 2 (reindex_time): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1, 2]
|
79
|
+
# Schedule.new(CitiesIndex, [2]).postpone | chewy:delayed_sidekiq:timechunks = [{ score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}]
|
80
|
+
# | & do not schedule a new worker
|
81
|
+
# |
|
82
|
+
# |
|
83
|
+
# process 1 (reindex_time + (latency - 1).seconds): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1, 2, 3]
|
84
|
+
# Schedule.new(CitiesIndex, [3]).postpone | chewy:delayed_sidekiq:timechunks = [{ score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}]
|
85
|
+
# | & do not schedule a new worker
|
86
|
+
# |
|
87
|
+
# |
|
88
|
+
# process 2 (reindex_time + (latency + 1).seconds): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1, 2, 3]
|
89
|
+
# Schedule.new(CitiesIndex, [4]).postpone | chewy:delayed_sidekiq:CitiesIndex:1679347868 = [4]
|
90
|
+
# | chewy:delayed_sidekiq:timechunks = [
|
91
|
+
# | { score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}
|
92
|
+
# | { score: 1679347868, "chewy:delayed_sidekiq:CitiesIndex:1679347868"}
|
93
|
+
# | ]
|
94
|
+
# | & schedule a DelayedSidekiq::Worker at 1679347871 (at + 3)
|
95
|
+
# | it will zpop chewy:delayed_sidekiq:timechunks up to 1679347868 score and reindex all ids with zpoped keys
|
96
|
+
# | chewy:delayed_sidekiq:CitiesIndex:1679347866 (in case of failed previous reindex),
|
97
|
+
# | chewy:delayed_sidekiq:CitiesIndex:1679347868
|
98
|
+
def postpone
|
99
|
+
::Sidekiq.redis do |redis|
|
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])
|
102
|
+
::Sidekiq::Client.push(
|
103
|
+
'queue' => sidekiq_queue,
|
104
|
+
'at' => at + margin,
|
105
|
+
'class' => Chewy::Strategy::DelayedSidekiq::Worker,
|
106
|
+
'args' => [type_name, at]
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
attr_reader :type, :ids, :options
|
115
|
+
|
116
|
+
# this method returns predictable value that jumps by latency value
|
117
|
+
# another words each latency seconds it return the same value
|
118
|
+
def at
|
119
|
+
@at ||= begin
|
120
|
+
schedule_at = latency.seconds.from_now.to_f
|
121
|
+
|
122
|
+
(schedule_at - (schedule_at % latency)).to_i
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def fields
|
127
|
+
options[:update_fields].presence || [FALLBACK_FIELDS]
|
128
|
+
end
|
129
|
+
|
130
|
+
def timechunks_key
|
131
|
+
"#{KEY_PREFIX}:#{type_name}:timechunks"
|
132
|
+
end
|
133
|
+
|
134
|
+
def timechunk_key
|
135
|
+
"#{KEY_PREFIX}:#{type_name}:#{at}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def serialize_data
|
139
|
+
[ids.join(IDS_SEPARATOR), fields.join(IDS_SEPARATOR)].join(FIELDS_IDS_SEPARATOR)
|
140
|
+
end
|
141
|
+
|
142
|
+
def type_name
|
143
|
+
type.name
|
144
|
+
end
|
145
|
+
|
146
|
+
def latency
|
147
|
+
strategy_config.latency || DEFAULT_LATENCY
|
148
|
+
end
|
149
|
+
|
150
|
+
def margin
|
151
|
+
strategy_config.margin || DEFAULT_MARGIN
|
152
|
+
end
|
153
|
+
|
154
|
+
def ttl
|
155
|
+
strategy_config.ttl || DEFAULT_TTL
|
156
|
+
end
|
157
|
+
|
158
|
+
def sidekiq_queue
|
159
|
+
Chewy.settings.dig(:sidekiq, :queue) || DEFAULT_QUEUE
|
160
|
+
end
|
161
|
+
|
162
|
+
def strategy_config
|
163
|
+
type.strategy_config.delayed_sidekiq
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chewy
|
4
|
+
class Strategy
|
5
|
+
class DelayedSidekiq
|
6
|
+
class Worker
|
7
|
+
include ::Sidekiq::Worker
|
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
|
+
|
38
|
+
def perform(type, score, options = {})
|
39
|
+
options[:refresh] = !Chewy.disable_refresh_async if Chewy.disable_refresh_async
|
40
|
+
|
41
|
+
::Sidekiq.redis do |redis|
|
42
|
+
members = redis.eval(LUA_SCRIPT, keys: [], argv: [type, score, Scheduler::KEY_PREFIX])
|
43
|
+
|
44
|
+
# extract ids and fields & do the reset of records
|
45
|
+
ids, fields = extract_ids_and_fields(members)
|
46
|
+
options[:update_fields] = fields if fields
|
47
|
+
|
48
|
+
index = type.constantize
|
49
|
+
index.strategy_config.delayed_sidekiq.reindex_wrapper.call do
|
50
|
+
options.any? ? index.import!(ids, **options) : index.import!(ids)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def extract_ids_and_fields(members)
|
58
|
+
ids = []
|
59
|
+
fields = []
|
60
|
+
|
61
|
+
members.each do |member|
|
62
|
+
member_ids, member_fields = member.split(Scheduler::FIELDS_IDS_SEPARATOR).map do |v|
|
63
|
+
v.split(Scheduler::IDS_SEPARATOR)
|
64
|
+
end
|
65
|
+
ids |= member_ids
|
66
|
+
fields |= member_fields
|
67
|
+
end
|
68
|
+
|
69
|
+
fields = nil if fields.include?(Scheduler::FALLBACK_FIELDS)
|
70
|
+
|
71
|
+
[ids.map(&:to_i), fields]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chewy
|
4
|
+
class Strategy
|
5
|
+
class DelayedSidekiq < Sidekiq
|
6
|
+
require_relative 'delayed_sidekiq/scheduler'
|
7
|
+
|
8
|
+
# cleanup the redis sets used internally. Useful mainly in tests to avoid
|
9
|
+
# leak and potential flaky tests.
|
10
|
+
def self.clear_timechunks!
|
11
|
+
::Sidekiq.redis do |redis|
|
12
|
+
keys_to_delete = redis.keys("#{Scheduler::KEY_PREFIX}*")
|
13
|
+
|
14
|
+
# Delete keys one by one
|
15
|
+
keys_to_delete.each do |key|
|
16
|
+
redis.del(key)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def leave
|
22
|
+
@stash.each do |type, ids|
|
23
|
+
next if ids.empty?
|
24
|
+
|
25
|
+
DelayedSidekiq::Scheduler.new(type, ids).postpone
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Strategy
|
3
|
+
# The strategy works the same way as sidekiq, but performs
|
4
|
+
# async evaluation of all index callbacks on model create and update
|
5
|
+
# driven by sidekiq
|
6
|
+
#
|
7
|
+
# Chewy.strategy(:lazy_sidekiq) do
|
8
|
+
# User.all.map(&:save) # Does nothing here
|
9
|
+
# Post.all.map(&:save) # And here
|
10
|
+
# # It schedules import of all the changed users and posts right here
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
class LazySidekiq < Sidekiq
|
14
|
+
class IndicesUpdateWorker
|
15
|
+
include ::Sidekiq::Worker
|
16
|
+
|
17
|
+
def perform(models)
|
18
|
+
Chewy.strategy(strategy) do
|
19
|
+
models.each do |model_type, model_ids|
|
20
|
+
model_type.constantize.where(id: model_ids).each(&:run_chewy_callbacks)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def strategy
|
28
|
+
Chewy.disable_refresh_async ? :atomic_no_refresh : :atomic
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
# Use parent's @stash to store destroyed records, since callbacks for them have to
|
34
|
+
# be run immediately on the strategy block end because we won't be able to fetch
|
35
|
+
# records further in IndicesUpdateWorker. This will be done by avoiding of
|
36
|
+
# LazySidekiq#update_chewy_indices call and calling LazySidekiq#update instead.
|
37
|
+
super
|
38
|
+
|
39
|
+
# @lazy_stash is used to store all the lazy evaluated callbacks with call of
|
40
|
+
# strategy's #update_chewy_indices.
|
41
|
+
@lazy_stash = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def leave
|
45
|
+
# Fallback to Sidekiq#leave implementation for destroyed records stored in @stash.
|
46
|
+
super
|
47
|
+
|
48
|
+
# Proceed with other records stored in @lazy_stash
|
49
|
+
return if @lazy_stash.empty?
|
50
|
+
|
51
|
+
::Sidekiq::Client.push(
|
52
|
+
'queue' => sidekiq_queue,
|
53
|
+
'class' => Chewy::Strategy::LazySidekiq::IndicesUpdateWorker,
|
54
|
+
'args' => [@lazy_stash]
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_chewy_indices(object)
|
59
|
+
@lazy_stash[object.class.name] ||= []
|
60
|
+
@lazy_stash[object.class.name] |= Array.wrap(object.id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/chewy/strategy.rb
CHANGED
@@ -2,10 +2,13 @@ require 'chewy/strategy/base'
|
|
2
2
|
require 'chewy/strategy/bypass'
|
3
3
|
require 'chewy/strategy/urgent'
|
4
4
|
require 'chewy/strategy/atomic'
|
5
|
+
require 'chewy/strategy/atomic_no_refresh'
|
5
6
|
|
6
7
|
begin
|
7
8
|
require 'sidekiq'
|
8
9
|
require 'chewy/strategy/sidekiq'
|
10
|
+
require 'chewy/strategy/lazy_sidekiq'
|
11
|
+
require 'chewy/strategy/delayed_sidekiq'
|
9
12
|
rescue LoadError
|
10
13
|
nil
|
11
14
|
end
|
data/lib/chewy/version.rb
CHANGED
data/lib/chewy.rb
CHANGED
@@ -1,21 +1,22 @@
|
|
1
|
+
require 'active_support'
|
1
2
|
require 'active_support/version'
|
2
3
|
require 'active_support/concern'
|
3
4
|
require 'active_support/deprecation'
|
4
5
|
require 'active_support/json'
|
5
6
|
require 'active_support/log_subscriber'
|
6
7
|
|
8
|
+
require 'active_support/isolated_execution_state' if ActiveSupport::VERSION::MAJOR >= 7
|
7
9
|
require 'active_support/core_ext/array/access'
|
8
10
|
require 'active_support/core_ext/array/wrap'
|
9
11
|
require 'active_support/core_ext/enumerable'
|
10
12
|
require 'active_support/core_ext/hash/reverse_merge'
|
13
|
+
require 'active_support/core_ext/hash/keys'
|
11
14
|
require 'active_support/core_ext/numeric/time'
|
12
15
|
require 'active_support/core_ext/numeric/bytes'
|
13
16
|
require 'active_support/core_ext/object/blank'
|
14
17
|
require 'active_support/core_ext/object/inclusion'
|
15
18
|
require 'active_support/core_ext/string/inflections'
|
16
19
|
|
17
|
-
require 'i18n/core_ext/hash'
|
18
|
-
require 'chewy/backports/deep_dup' unless Object.respond_to?(:deep_dup)
|
19
20
|
require 'singleton'
|
20
21
|
require 'base64'
|
21
22
|
|
@@ -47,10 +48,11 @@ require 'chewy/index'
|
|
47
48
|
require 'chewy/fields/base'
|
48
49
|
require 'chewy/fields/root'
|
49
50
|
require 'chewy/journal'
|
50
|
-
require 'chewy/railtie' if defined?(
|
51
|
+
require 'chewy/railtie' if defined?(Rails::Railtie)
|
52
|
+
require 'chewy/elastic_client'
|
51
53
|
|
52
54
|
ActiveSupport.on_load(:active_record) do
|
53
|
-
|
55
|
+
include Chewy::Index::Observe::ActiveRecordMethods
|
54
56
|
end
|
55
57
|
|
56
58
|
module Chewy
|
@@ -62,6 +64,16 @@ module Chewy
|
|
62
64
|
class << self
|
63
65
|
attr_accessor :adapters
|
64
66
|
|
67
|
+
# A thread-local variables accessor
|
68
|
+
# @return [Hash]
|
69
|
+
def current
|
70
|
+
unless Thread.current.thread_variable?(:chewy)
|
71
|
+
Thread.current.thread_variable_set(:chewy, {})
|
72
|
+
end
|
73
|
+
|
74
|
+
Thread.current.thread_variable_get(:chewy)
|
75
|
+
end
|
76
|
+
|
65
77
|
# Derives an index for the passed string identifier if possible.
|
66
78
|
#
|
67
79
|
# @example
|
@@ -86,12 +98,7 @@ module Chewy
|
|
86
98
|
# Main elasticsearch-ruby client instance
|
87
99
|
#
|
88
100
|
def client
|
89
|
-
|
90
|
-
client_configuration = configuration.deep_dup
|
91
|
-
client_configuration.delete(:prefix) # used by Chewy, not relevant to Elasticsearch::Client
|
92
|
-
block = client_configuration[:transport_options].try(:delete, :proc)
|
93
|
-
::Elasticsearch::Client.new(client_configuration, &block)
|
94
|
-
end
|
101
|
+
Chewy.current[:chewy_client] ||= Chewy::ElasticClient.new
|
95
102
|
end
|
96
103
|
|
97
104
|
# Sends wait_for_status request to ElasticSearch with status
|
@@ -138,15 +145,15 @@ module Chewy
|
|
138
145
|
# city3.do_update! # index updated again
|
139
146
|
#
|
140
147
|
def strategy(name = nil, &block)
|
141
|
-
|
148
|
+
Chewy.current[:chewy_strategy] ||= Chewy::Strategy.new
|
142
149
|
if name
|
143
150
|
if block
|
144
|
-
|
151
|
+
Chewy.current[:chewy_strategy].wrap name, &block
|
145
152
|
else
|
146
|
-
|
153
|
+
Chewy.current[:chewy_strategy].push name
|
147
154
|
end
|
148
155
|
else
|
149
|
-
|
156
|
+
Chewy.current[:chewy_strategy]
|
150
157
|
end
|
151
158
|
end
|
152
159
|
|
data/lib/tasks/chewy.rake
CHANGED
@@ -54,7 +54,12 @@ namespace :chewy do
|
|
54
54
|
|
55
55
|
desc 'Update mapping of exising index with body hash'
|
56
56
|
task :update_mapping, %i[index_name] => :environment do |_task, args|
|
57
|
-
Chewy::RakeHelper.update_mapping(name: args[:
|
57
|
+
Chewy::RakeHelper.update_mapping(name: args[:index_name])
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'Creates missing indexes'
|
61
|
+
task create_missing_indexes: :environment do
|
62
|
+
Chewy::RakeHelper.create_missing_indexes!
|
58
63
|
end
|
59
64
|
|
60
65
|
namespace :parallel do
|
@@ -87,6 +92,11 @@ namespace :chewy do
|
|
87
92
|
end
|
88
93
|
|
89
94
|
namespace :journal do
|
95
|
+
desc 'Create manually journal, useful when `skip_journal_creation_on_import` is used'
|
96
|
+
task create: :environment do |_task, _args|
|
97
|
+
Chewy::RakeHelper.journal_create
|
98
|
+
end
|
99
|
+
|
90
100
|
desc 'Applies changes that were done after the specified time for the specified indexes/types or all of them'
|
91
101
|
task apply: :environment do |_task, args|
|
92
102
|
Chewy::RakeHelper.journal_apply(**parse_journal_args(args.extras))
|
@@ -94,7 +104,13 @@ namespace :chewy do
|
|
94
104
|
|
95
105
|
desc 'Removes journal records created before the specified timestamp for the specified indexes/types or all of them'
|
96
106
|
task clean: :environment do |_task, args|
|
97
|
-
Chewy::RakeHelper.
|
107
|
+
delete_options = Chewy::RakeHelper.delete_by_query_options_from_env(ENV)
|
108
|
+
Chewy::RakeHelper.journal_clean(
|
109
|
+
**[
|
110
|
+
parse_journal_args(args.extras),
|
111
|
+
{delete_by_query_options: delete_options}
|
112
|
+
].reduce({}, :merge)
|
113
|
+
)
|
98
114
|
end
|
99
115
|
end
|
100
116
|
end
|
data/migration_guide.md
CHANGED
@@ -9,7 +9,7 @@ Chewy alongside a matching Elasticsearch version.
|
|
9
9
|
In order to upgrade Chewy 6/Elasticsearch 6 to Chewy 7/Elasticsearch 7 in the most seamless manner you have to:
|
10
10
|
|
11
11
|
* Upgrade to the latest 6.x stable releases, namely Chewy 6.0, Elasticsearch 6.8
|
12
|
-
* Study carefully [Breaking changes in 7.0](https://www.elastic.co/guide/en/elasticsearch/reference/
|
12
|
+
* Study carefully [Breaking changes in 7.0](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/breaking-changes-7.0.html), make sure your application conforms.
|
13
13
|
* Run your test suite on Chewy 7.0 / Elasticsearch 7
|
14
14
|
* Run manual tests on Chewy 7.0 / Elasticsearch 7
|
15
15
|
* Upgrade to Chewy 7.0
|
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 }
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chewy::ElasticClient do
|
4
|
+
describe 'payload inspection' do
|
5
|
+
let(:filter) { instance_double('Proc') }
|
6
|
+
let!(:filter_previous_value) { Chewy.before_es_request_filter }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Chewy.massacre
|
10
|
+
stub_index(:products) do
|
11
|
+
field :id, type: :integer
|
12
|
+
end
|
13
|
+
ProductsIndex.create
|
14
|
+
Chewy.before_es_request_filter = filter
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
Chewy.before_es_request_filter = filter_previous_value
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'call filter with the request body' do
|
22
|
+
expect(filter).to receive(:call).with(:search, [{body: {size: 0}, index: ['products']}], {})
|
23
|
+
Chewy.client.search({index: ['products'], body: {size: 0}}).to_a
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|