searchkick 4.4.0 → 5.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,173 +0,0 @@
1
- module Searchkick
2
- class BulkIndexer
3
- attr_reader :index
4
-
5
- def initialize(index)
6
- @index = index
7
- end
8
-
9
- def import_scope(relation, resume: false, method_name: nil, async: false, batch: false, batch_id: nil, full: false, scope: nil)
10
- if scope
11
- relation = relation.send(scope)
12
- elsif relation.respond_to?(:search_import)
13
- relation = relation.search_import
14
- end
15
-
16
- if batch
17
- import_or_update relation.to_a, method_name, async
18
- Searchkick.with_redis { |r| r.srem(batches_key, batch_id) } if batch_id
19
- elsif full && async
20
- full_reindex_async(relation)
21
- elsif relation.respond_to?(:find_in_batches)
22
- if resume
23
- # use total docs instead of max id since there's not a great way
24
- # to get the max _id without scripting since it's a string
25
-
26
- # TODO use primary key and prefix with table name
27
- relation = relation.where("id > ?", index.total_docs)
28
- end
29
-
30
- relation = relation.select("id").except(:includes, :preload) if async
31
-
32
- relation.find_in_batches batch_size: batch_size do |items|
33
- import_or_update items, method_name, async
34
- end
35
- else
36
- each_batch(relation) do |items|
37
- import_or_update items, method_name, async
38
- end
39
- end
40
- end
41
-
42
- def bulk_index(records)
43
- Searchkick.indexer.queue(records.map { |r| RecordData.new(index, r).index_data })
44
- end
45
-
46
- def bulk_delete(records)
47
- Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| RecordData.new(index, r).delete_data })
48
- end
49
-
50
- def bulk_update(records, method_name)
51
- Searchkick.indexer.queue(records.map { |r| RecordData.new(index, r).update_data(method_name) })
52
- end
53
-
54
- def batches_left
55
- Searchkick.with_redis { |r| r.scard(batches_key) }
56
- end
57
-
58
- private
59
-
60
- def import_or_update(records, method_name, async)
61
- if records.any?
62
- if async
63
- Searchkick::BulkReindexJob.perform_later(
64
- class_name: records.first.class.searchkick_options[:class_name],
65
- record_ids: records.map(&:id),
66
- index_name: index.name,
67
- method_name: method_name ? method_name.to_s : nil
68
- )
69
- else
70
- records = records.select(&:should_index?)
71
- if records.any?
72
- with_retries do
73
- # call out to index for ActiveSupport notifications
74
- if method_name
75
- index.bulk_update(records, method_name)
76
- else
77
- index.bulk_index(records)
78
- end
79
- end
80
- end
81
- end
82
- end
83
- end
84
-
85
- def full_reindex_async(scope)
86
- if scope.respond_to?(:primary_key)
87
- # TODO expire Redis key
88
- primary_key = scope.primary_key
89
-
90
- scope = scope.select(primary_key).except(:includes, :preload)
91
-
92
- starting_id =
93
- begin
94
- scope.minimum(primary_key)
95
- rescue ActiveRecord::StatementInvalid
96
- false
97
- end
98
-
99
- if starting_id.nil?
100
- # no records, do nothing
101
- elsif starting_id.is_a?(Numeric)
102
- max_id = scope.maximum(primary_key)
103
- batches_count = ((max_id - starting_id + 1) / batch_size.to_f).ceil
104
-
105
- batches_count.times do |i|
106
- batch_id = i + 1
107
- min_id = starting_id + (i * batch_size)
108
- bulk_reindex_job scope, batch_id, min_id: min_id, max_id: min_id + batch_size - 1
109
- end
110
- else
111
- scope.find_in_batches(batch_size: batch_size).each_with_index do |batch, i|
112
- batch_id = i + 1
113
-
114
- bulk_reindex_job scope, batch_id, record_ids: batch.map { |record| record.id.to_s }
115
- end
116
- end
117
- else
118
- batch_id = 1
119
- # TODO remove any eager loading
120
- scope = scope.only(:_id) if scope.respond_to?(:only)
121
- each_batch(scope) do |items|
122
- bulk_reindex_job scope, batch_id, record_ids: items.map { |i| i.id.to_s }
123
- batch_id += 1
124
- end
125
- end
126
- end
127
-
128
- def each_batch(scope)
129
- # https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
130
- # use cursor for Mongoid
131
- items = []
132
- scope.all.each do |item|
133
- items << item
134
- if items.length == batch_size
135
- yield items
136
- items = []
137
- end
138
- end
139
- yield items if items.any?
140
- end
141
-
142
- def bulk_reindex_job(scope, batch_id, options)
143
- Searchkick.with_redis { |r| r.sadd(batches_key, batch_id) }
144
- Searchkick::BulkReindexJob.perform_later(**{
145
- class_name: scope.searchkick_options[:class_name],
146
- index_name: index.name,
147
- batch_id: batch_id
148
- }.merge(options))
149
- end
150
-
151
- def with_retries
152
- retries = 0
153
-
154
- begin
155
- yield
156
- rescue Faraday::ClientError => e
157
- if retries < 1
158
- retries += 1
159
- retry
160
- end
161
- raise e
162
- end
163
- end
164
-
165
- def batches_key
166
- "searchkick:reindex:#{index.name}:batches"
167
- end
168
-
169
- def batch_size
170
- @batch_size ||= index.options[:batch_size] || 1000
171
- end
172
- end
173
- end
@@ -1,246 +0,0 @@
1
- # based on https://gist.github.com/mnutt/566725
2
- require "active_support/core_ext/module/attr_internal"
3
-
4
- module Searchkick
5
- module QueryWithInstrumentation
6
- def execute_search
7
- name = searchkick_klass ? "#{searchkick_klass.name} Search" : "Search"
8
- event = {
9
- name: name,
10
- query: params
11
- }
12
- ActiveSupport::Notifications.instrument("search.searchkick", event) do
13
- super
14
- end
15
- end
16
- end
17
-
18
- module IndexWithInstrumentation
19
- def store(record)
20
- event = {
21
- name: "#{record.searchkick_klass.name} Store",
22
- id: search_id(record)
23
- }
24
- if Searchkick.callbacks_value == :bulk
25
- super
26
- else
27
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
28
- super
29
- end
30
- end
31
- end
32
-
33
- def remove(record)
34
- name = record && record.searchkick_klass ? "#{record.searchkick_klass.name} Remove" : "Remove"
35
- event = {
36
- name: name,
37
- id: search_id(record)
38
- }
39
- if Searchkick.callbacks_value == :bulk
40
- super
41
- else
42
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
43
- super
44
- end
45
- end
46
- end
47
-
48
- def update_record(record, method_name)
49
- event = {
50
- name: "#{record.searchkick_klass.name} Update",
51
- id: search_id(record)
52
- }
53
- if Searchkick.callbacks_value == :bulk
54
- super
55
- else
56
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
57
- super
58
- end
59
- end
60
- end
61
-
62
- def bulk_index(records)
63
- if records.any?
64
- event = {
65
- name: "#{records.first.searchkick_klass.name} Import",
66
- count: records.size
67
- }
68
- event[:id] = search_id(records.first) if records.size == 1
69
- if Searchkick.callbacks_value == :bulk
70
- super
71
- else
72
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
73
- super
74
- end
75
- end
76
- end
77
- end
78
- alias_method :import, :bulk_index
79
-
80
- def bulk_update(records, *args)
81
- if records.any?
82
- event = {
83
- name: "#{records.first.searchkick_klass.name} Update",
84
- count: records.size
85
- }
86
- event[:id] = search_id(records.first) if records.size == 1
87
- if Searchkick.callbacks_value == :bulk
88
- super
89
- else
90
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
91
- super
92
- end
93
- end
94
- end
95
- end
96
-
97
- def bulk_delete(records)
98
- if records.any?
99
- event = {
100
- name: "#{records.first.searchkick_klass.name} Delete",
101
- count: records.size
102
- }
103
- event[:id] = search_id(records.first) if records.size == 1
104
- if Searchkick.callbacks_value == :bulk
105
- super
106
- else
107
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
108
- super
109
- end
110
- end
111
- end
112
- end
113
- end
114
-
115
- module IndexerWithInstrumentation
116
- def perform
117
- if Searchkick.callbacks_value == :bulk
118
- event = {
119
- name: "Bulk",
120
- count: queued_items.size
121
- }
122
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
123
- super
124
- end
125
- else
126
- super
127
- end
128
- end
129
- end
130
-
131
- module SearchkickWithInstrumentation
132
- def multi_search(searches)
133
- event = {
134
- name: "Multi Search",
135
- body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join,
136
- }
137
- ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
138
- super
139
- end
140
- end
141
- end
142
-
143
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb
144
- class LogSubscriber < ActiveSupport::LogSubscriber
145
- def self.runtime=(value)
146
- Thread.current[:searchkick_runtime] = value
147
- end
148
-
149
- def self.runtime
150
- Thread.current[:searchkick_runtime] ||= 0
151
- end
152
-
153
- def self.reset_runtime
154
- rt = runtime
155
- self.runtime = 0
156
- rt
157
- end
158
-
159
- def search(event)
160
- self.class.runtime += event.duration
161
- return unless logger.debug?
162
-
163
- payload = event.payload
164
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
165
-
166
- index = payload[:query][:index].is_a?(Array) ? payload[:query][:index].join(",") : payload[:query][:index]
167
- type = payload[:query][:type]
168
- request_params = payload[:query].except(:index, :type, :body)
169
-
170
- params = []
171
- request_params.each do |k, v|
172
- params << "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
173
- end
174
-
175
- debug " #{color(name, YELLOW, true)} #{index}#{type ? "/#{type.join(',')}" : ''}/_search#{params.any? ? '?' + params.join('&') : nil} #{payload[:query][:body].to_json}"
176
- end
177
-
178
- def request(event)
179
- self.class.runtime += event.duration
180
- return unless logger.debug?
181
-
182
- payload = event.payload
183
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
184
-
185
- debug " #{color(name, YELLOW, true)} #{payload.except(:name).to_json}"
186
- end
187
-
188
- def multi_search(event)
189
- self.class.runtime += event.duration
190
- return unless logger.debug?
191
-
192
- payload = event.payload
193
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
194
-
195
- debug " #{color(name, YELLOW, true)} _msearch #{payload[:body]}"
196
- end
197
- end
198
-
199
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/controller_runtime.rb
200
- module ControllerRuntime
201
- extend ActiveSupport::Concern
202
-
203
- protected
204
-
205
- attr_internal :searchkick_runtime
206
-
207
- def process_action(action, *args)
208
- # We also need to reset the runtime before each action
209
- # because of queries in middleware or in cases we are streaming
210
- # and it won't be cleaned up by the method below.
211
- Searchkick::LogSubscriber.reset_runtime
212
- super
213
- end
214
-
215
- def cleanup_view_runtime
216
- searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime
217
- runtime = super
218
- searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime
219
- self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render
220
- runtime - searchkick_rt_after_render
221
- end
222
-
223
- def append_info_to_payload(payload)
224
- super
225
- payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime
226
- end
227
-
228
- module ClassMethods
229
- def log_process_action(payload)
230
- messages = super
231
- runtime = payload[:searchkick_runtime]
232
- messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
233
- messages
234
- end
235
- end
236
- end
237
- end
238
-
239
- Searchkick::Query.prepend(Searchkick::QueryWithInstrumentation)
240
- Searchkick::Index.prepend(Searchkick::IndexWithInstrumentation)
241
- Searchkick::Indexer.prepend(Searchkick::IndexerWithInstrumentation)
242
- Searchkick.singleton_class.prepend(Searchkick::SearchkickWithInstrumentation)
243
- Searchkick::LogSubscriber.attach_to :searchkick
244
- ActiveSupport.on_load(:action_controller) do
245
- include Searchkick::ControllerRuntime
246
- end