search-engine-for-typesense 30.1.6.4 → 30.1.6.5
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/lib/search_engine/base/index_maintenance/cleanup.rb +5 -3
- data/lib/search_engine/base/index_maintenance/lifecycle.rb +49 -46
- data/lib/search_engine/base/index_maintenance/schema.rb +20 -24
- data/lib/search_engine/base/index_maintenance.rb +94 -28
- data/lib/search_engine/cascade.rb +4 -8
- data/lib/search_engine/config.rb +5 -1
- data/lib/search_engine/indexer/bulk_import.rb +137 -71
- data/lib/search_engine/indexer.rb +9 -4
- data/lib/search_engine/interruptible_pool.rb +39 -0
- data/lib/search_engine/logging/cursor_guard.rb +52 -0
- data/lib/search_engine/logging/live_renderer.rb +366 -0
- data/lib/search_engine/logging/spinner.rb +106 -0
- data/lib/search_engine/logging/step_line.rb +170 -0
- data/lib/search_engine/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 933b8b58a98c81e8e6b72f3083cfcf2ee1de5ea9f709d4c10aea5d09dba59d99
|
|
4
|
+
data.tar.gz: 626111c57bc0ee533d21938d7a218c13b80cc23aa27827731994a968f97de672
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e4456f56ddd18865793f0d61890967ba53e50eac9d2b573362800ed0f509aad397bc9233b4f4a0d308e5a9dadfc0a0aac9e9ca946516b638f5eee0a4728fa3b
|
|
7
|
+
data.tar.gz: 80133e73075e0047968ed9afb3d7756eb5cf47aadeb1c1bbb73d332ee2d44c61e0cc6e6fb5b8932f23ab280f8259319c36d16c288e7d3817a0fdc3844fcde24a
|
|
@@ -50,13 +50,14 @@ module SearchEngine
|
|
|
50
50
|
filters = SearchEngine::StaleRules.compile_filters(self, partition: partition)
|
|
51
51
|
filters.compact!
|
|
52
52
|
filters.reject! { |f| f.to_s.strip.empty? }
|
|
53
|
+
step = SearchEngine::Logging::StepLine.new('Cleanup')
|
|
53
54
|
if filters.empty?
|
|
54
|
-
|
|
55
|
+
step.skip('no stale configuration')
|
|
55
56
|
return 0
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
merged_filter = SearchEngine::StaleRules.merge_filters(filters)
|
|
59
|
-
|
|
60
|
+
step.update("filter=#{merged_filter.inspect}")
|
|
60
61
|
|
|
61
62
|
deleted = SearchEngine::Deletion.delete_by(
|
|
62
63
|
klass: self,
|
|
@@ -65,13 +66,14 @@ module SearchEngine
|
|
|
65
66
|
partition: partition
|
|
66
67
|
)
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
step.finish("deleted=#{deleted}")
|
|
69
70
|
deleted
|
|
70
71
|
rescue StandardError => error
|
|
71
72
|
err_msg = "Cleanup — error=#{error.class}: #{error.message.to_s[0, 200]}"
|
|
72
73
|
warn(SearchEngine::Logging::Color.apply(err_msg, :red))
|
|
73
74
|
0
|
|
74
75
|
ensure
|
|
76
|
+
step&.close
|
|
75
77
|
if clear_cache
|
|
76
78
|
begin
|
|
77
79
|
puts("Cleanup — #{SearchEngine::Logging::Color.bold('cache clear')}")
|
|
@@ -56,12 +56,8 @@ module SearchEngine
|
|
|
56
56
|
|
|
57
57
|
diff = SearchEngine::Schema.diff(self, client: client)[:diff] || {}
|
|
58
58
|
missing = __se_schema_missing?(diff)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
else
|
|
62
|
-
SearchEngine::Logging::Color.apply('present', :green)
|
|
63
|
-
end
|
|
64
|
-
puts("Step 1: Presence — #{SearchEngine::Logging::Color.bold('processing')} → #{presence}")
|
|
59
|
+
step = SearchEngine::Logging::StepLine.new('Presence')
|
|
60
|
+
missing ? step.finish_warn('missing') : step.finish('present')
|
|
65
61
|
|
|
66
62
|
applied, indexed_inside_apply = __se_full_apply_if_missing(client, missing)
|
|
67
63
|
drift = __se_full_check_drift(diff, missing, force_rebuild)
|
|
@@ -80,78 +76,90 @@ module SearchEngine
|
|
|
80
76
|
def __se_full_apply_if_missing(client, missing)
|
|
81
77
|
applied = false
|
|
82
78
|
indexed_inside_apply = false
|
|
79
|
+
step = SearchEngine::Logging::StepLine.new('Schema')
|
|
83
80
|
if missing
|
|
84
|
-
|
|
81
|
+
step.update('creating')
|
|
85
82
|
SearchEngine::Schema.apply!(self, client: client) do |new_physical|
|
|
83
|
+
step.yield_line!
|
|
86
84
|
indexed_inside_apply = __se_index_partitions!(into: new_physical)
|
|
87
85
|
end
|
|
88
86
|
applied = true
|
|
89
|
-
|
|
87
|
+
step.finish('created')
|
|
90
88
|
else
|
|
91
|
-
|
|
89
|
+
step.skip('collection present')
|
|
92
90
|
end
|
|
93
91
|
[applied, indexed_inside_apply]
|
|
92
|
+
ensure
|
|
93
|
+
step&.close
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def __se_full_check_drift(diff, missing, force_rebuild)
|
|
97
|
+
step = SearchEngine::Logging::StepLine.new('Schema Status')
|
|
97
98
|
unless missing
|
|
98
|
-
|
|
99
|
+
step.update('checking')
|
|
99
100
|
drift = __se_schema_drift?(diff)
|
|
100
101
|
if force_rebuild && !drift
|
|
101
|
-
|
|
102
|
+
step.finish_warn('force_rebuild')
|
|
102
103
|
return true
|
|
103
104
|
end
|
|
104
|
-
|
|
105
|
-
SearchEngine::Logging::Color.apply('drift', :yellow)
|
|
106
|
-
else
|
|
107
|
-
SearchEngine::Logging::Color.apply('in_sync', :green)
|
|
108
|
-
end
|
|
109
|
-
puts("Step 3: Check Schema Status — #{schema_status}")
|
|
105
|
+
drift ? step.finish_warn('drift') : step.finish('in_sync')
|
|
110
106
|
return drift
|
|
111
107
|
end
|
|
112
|
-
|
|
108
|
+
step.skip('just created')
|
|
113
109
|
false
|
|
110
|
+
ensure
|
|
111
|
+
step&.close
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
def __se_full_apply_if_drift(client, drift, applied, indexed_inside_apply, force_rebuild)
|
|
115
|
+
step = SearchEngine::Logging::StepLine.new('Schema Apply')
|
|
117
116
|
if drift
|
|
118
|
-
|
|
117
|
+
step.update('applying')
|
|
119
118
|
SearchEngine::Schema.apply!(self, client: client, force_rebuild: force_rebuild) do |new_physical|
|
|
119
|
+
step.yield_line!
|
|
120
120
|
indexed_inside_apply = __se_index_partitions!(into: new_physical)
|
|
121
121
|
end
|
|
122
122
|
applied = true
|
|
123
|
-
|
|
123
|
+
step.finish('applied')
|
|
124
124
|
else
|
|
125
|
-
|
|
125
|
+
step.skip
|
|
126
126
|
end
|
|
127
127
|
[applied, indexed_inside_apply]
|
|
128
|
+
ensure
|
|
129
|
+
step&.close
|
|
128
130
|
end
|
|
129
131
|
|
|
130
132
|
def __se_full_indexation(applied, indexed_inside_apply)
|
|
131
133
|
result = nil
|
|
134
|
+
step = SearchEngine::Logging::StepLine.new('Indexing')
|
|
132
135
|
if applied && indexed_inside_apply
|
|
133
|
-
|
|
136
|
+
step.skip('performed during schema apply')
|
|
134
137
|
result = indexed_inside_apply if indexed_inside_apply.is_a?(Hash)
|
|
135
138
|
else
|
|
136
|
-
|
|
139
|
+
step.update('indexing')
|
|
140
|
+
step.yield_line!
|
|
137
141
|
result = __se_index_partitions!(into: nil)
|
|
138
|
-
|
|
142
|
+
step.finish('done')
|
|
139
143
|
end
|
|
140
144
|
|
|
141
145
|
cascade_ok = result.is_a?(Hash) ? result[:status] == :ok : false
|
|
142
146
|
__se_cascade_after_indexation!(context: :full) if cascade_ok
|
|
143
147
|
result
|
|
148
|
+
ensure
|
|
149
|
+
step&.close
|
|
144
150
|
end
|
|
145
151
|
|
|
146
152
|
def __se_full_retention(applied, logical, client)
|
|
153
|
+
step = SearchEngine::Logging::StepLine.new('Retention')
|
|
147
154
|
if applied
|
|
148
|
-
|
|
155
|
+
step.skip('handled by schema apply')
|
|
149
156
|
else
|
|
150
|
-
|
|
157
|
+
step.update('cleaning')
|
|
151
158
|
dropped = __se_retention_cleanup!(logical: logical, client: client)
|
|
152
|
-
|
|
153
|
-
puts("Step 6: Retention Cleanup — #{dropped_str}")
|
|
159
|
+
step.finish("dropped=#{dropped.inspect}")
|
|
154
160
|
end
|
|
161
|
+
ensure
|
|
162
|
+
step&.close
|
|
155
163
|
end
|
|
156
164
|
|
|
157
165
|
def __se_index_partial(partition:, client:, pre: nil)
|
|
@@ -160,47 +168,42 @@ module SearchEngine
|
|
|
160
168
|
diff = diff_res[:diff] || {}
|
|
161
169
|
|
|
162
170
|
missing = __se_schema_missing?(diff)
|
|
163
|
-
|
|
164
|
-
SearchEngine::Logging::Color.apply('missing', :yellow)
|
|
165
|
-
else
|
|
166
|
-
SearchEngine::Logging::Color.apply('present', :green)
|
|
167
|
-
end
|
|
168
|
-
puts("Step 1: Presence — #{SearchEngine::Logging::Color.bold('processing')} → #{presence}")
|
|
171
|
+
step = SearchEngine::Logging::StepLine.new('Presence')
|
|
169
172
|
if missing
|
|
170
|
-
|
|
171
|
-
'Step 1: Partial — collection is not present. Quit early.', :yellow
|
|
172
|
-
)
|
|
173
|
-
puts(msg)
|
|
173
|
+
step.finish_warn('missing — collection not present, exit early')
|
|
174
174
|
return { status: :failed, docs_total: 0, success_total: 0, failed_total: 0,
|
|
175
175
|
sample_error: 'Collection not present' }
|
|
176
176
|
end
|
|
177
|
+
step.finish('present')
|
|
177
178
|
|
|
178
|
-
|
|
179
|
+
step = SearchEngine::Logging::StepLine.new('Schema Status')
|
|
180
|
+
step.update('checking')
|
|
179
181
|
drift = __se_schema_drift?(diff)
|
|
180
182
|
if drift
|
|
181
|
-
|
|
182
|
-
'Step 2: Partial — schema is not up-to-date. Exit early (run full indexing).', :yellow
|
|
183
|
-
)
|
|
184
|
-
puts(msg)
|
|
183
|
+
step.finish_warn('drift — exit early (run full indexing)')
|
|
185
184
|
return { status: :failed, docs_total: 0, success_total: 0, failed_total: 0,
|
|
186
185
|
sample_error: 'Schema drift detected' }
|
|
187
186
|
end
|
|
188
|
-
|
|
187
|
+
step.finish('in_sync')
|
|
189
188
|
|
|
190
189
|
__se_preflight_dependencies!(mode: pre, client: client) if pre
|
|
191
190
|
|
|
192
|
-
|
|
191
|
+
step = SearchEngine::Logging::StepLine.new('Partial Indexing')
|
|
192
|
+
step.update('indexing')
|
|
193
|
+
step.yield_line!
|
|
193
194
|
summaries = []
|
|
194
195
|
partitions.each do |p|
|
|
195
196
|
summary = SearchEngine::Indexer.rebuild_partition!(self, partition: p, into: nil)
|
|
196
197
|
summaries << summary
|
|
197
198
|
puts(SearchEngine::Logging::PartitionProgress.line(p, summary))
|
|
198
199
|
end
|
|
199
|
-
|
|
200
|
+
step.finish('done')
|
|
200
201
|
|
|
201
202
|
result = __se_build_index_result(summaries)
|
|
202
203
|
__se_cascade_after_indexation!(context: :full) if result[:status] == :ok
|
|
203
204
|
result
|
|
205
|
+
ensure
|
|
206
|
+
step&.close
|
|
204
207
|
end
|
|
205
208
|
|
|
206
209
|
# rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
@@ -27,28 +27,24 @@ module SearchEngine
|
|
|
27
27
|
|
|
28
28
|
def update_collection!
|
|
29
29
|
client = SearchEngine.client
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
puts "Update Collection — #{status_word}"
|
|
30
|
+
step = SearchEngine::Logging::StepLine.new('Update Collection')
|
|
31
|
+
step.update('analyzing diff')
|
|
33
32
|
updated = SearchEngine::Schema.update!(self, client: client)
|
|
34
33
|
|
|
35
34
|
if updated
|
|
36
|
-
|
|
37
|
-
puts "Update Collection — #{status_word}"
|
|
35
|
+
step.finish('updated in-place (PATCH)')
|
|
38
36
|
else
|
|
39
|
-
|
|
40
|
-
'Update Collection — in-place update not possible (no changes or incompatible)'
|
|
41
|
-
)
|
|
42
|
-
puts(msg)
|
|
37
|
+
step.skip('no changes or incompatible')
|
|
43
38
|
end
|
|
44
39
|
updated
|
|
40
|
+
ensure
|
|
41
|
+
step&.close
|
|
45
42
|
end
|
|
46
43
|
|
|
47
44
|
def drop_collection!
|
|
48
45
|
client = SearchEngine.client
|
|
49
46
|
logical = respond_to?(:collection) ? collection.to_s : name.to_s
|
|
50
47
|
|
|
51
|
-
# Resolve alias with a safer timeout for control-plane operations
|
|
52
48
|
alias_target = client.resolve_alias(logical, timeout_ms: 10_000)
|
|
53
49
|
physical = if alias_target && !alias_target.to_s.strip.empty?
|
|
54
50
|
alias_target.to_s
|
|
@@ -57,21 +53,21 @@ module SearchEngine
|
|
|
57
53
|
live ? logical : nil
|
|
58
54
|
end
|
|
59
55
|
|
|
56
|
+
step = SearchEngine::Logging::StepLine.new('Drop Collection')
|
|
60
57
|
if physical.nil?
|
|
61
|
-
|
|
58
|
+
step.skip('not present')
|
|
62
59
|
return
|
|
63
60
|
end
|
|
64
61
|
|
|
65
62
|
puts
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
status_word = SearchEngine::Logging::Color.bold('processing')
|
|
69
|
-
puts("Drop Collection — #{status_word} (logical=#{logical} physical=#{physical})")
|
|
70
|
-
# Use an extended timeout to accommodate large collection drops
|
|
63
|
+
puts(SearchEngine::Logging::Color.header(%(>>>>>> Dropping Collection "#{logical}")))
|
|
64
|
+
step.update("dropping (logical=#{logical} physical=#{physical})")
|
|
71
65
|
client.delete_collection(physical, timeout_ms: 60_000)
|
|
72
|
-
|
|
66
|
+
step.finish('done')
|
|
73
67
|
puts(SearchEngine::Logging::Color.header(%(>>>>>> Dropped Collection "#{logical}")))
|
|
74
68
|
nil
|
|
69
|
+
ensure
|
|
70
|
+
step&.close
|
|
75
71
|
end
|
|
76
72
|
|
|
77
73
|
def recreate_collection!
|
|
@@ -86,21 +82,21 @@ module SearchEngine
|
|
|
86
82
|
live ? logical : nil
|
|
87
83
|
end
|
|
88
84
|
|
|
85
|
+
step = SearchEngine::Logging::StepLine.new('Recreate Collection')
|
|
89
86
|
if physical
|
|
90
|
-
|
|
91
|
-
puts("Recreate Collection — #{status_word} (logical=#{logical} physical=#{physical})")
|
|
87
|
+
step.update("dropping existing (logical=#{logical} physical=#{physical})")
|
|
92
88
|
client.delete_collection(physical)
|
|
93
89
|
else
|
|
94
|
-
|
|
95
|
-
puts(msg)
|
|
90
|
+
step.update("creating (logical=#{logical})")
|
|
96
91
|
end
|
|
97
92
|
|
|
98
93
|
schema = SearchEngine::Schema.compile(self)
|
|
99
|
-
|
|
100
|
-
puts("Recreate Collection — #{status_word} (logical=#{logical})")
|
|
94
|
+
step.update("creating with schema (logical=#{logical})")
|
|
101
95
|
client.create_collection(schema)
|
|
102
|
-
|
|
96
|
+
step.finish('done')
|
|
103
97
|
nil
|
|
98
|
+
ensure
|
|
99
|
+
step&.close
|
|
104
100
|
end
|
|
105
101
|
|
|
106
102
|
def __se_retention_cleanup!(_logical:, _client:)
|
|
@@ -6,6 +6,8 @@ require 'search_engine/base/index_maintenance/lifecycle'
|
|
|
6
6
|
require 'search_engine/base/index_maintenance/schema'
|
|
7
7
|
require 'search_engine/logging/color'
|
|
8
8
|
require 'search_engine/logging/batch_line'
|
|
9
|
+
require 'search_engine/logging/step_line'
|
|
10
|
+
require 'search_engine/logging/live_renderer'
|
|
9
11
|
|
|
10
12
|
module SearchEngine
|
|
11
13
|
class Base
|
|
@@ -275,6 +277,9 @@ module SearchEngine
|
|
|
275
277
|
summary = SearchEngine::Indexer.rebuild_partition!(self, partition: nil, into: into)
|
|
276
278
|
__se_build_index_result([summary])
|
|
277
279
|
end
|
|
280
|
+
rescue StandardError => error
|
|
281
|
+
{ status: :failed, docs_total: 0, success_total: 0, failed_total: 0,
|
|
282
|
+
sample_error: "#{error.class}: #{error.message.to_s[0, 200]}" }
|
|
278
283
|
end
|
|
279
284
|
|
|
280
285
|
# Aggregate an array of Indexer::Summary structs into a single result hash.
|
|
@@ -308,59 +313,120 @@ module SearchEngine
|
|
|
308
313
|
end
|
|
309
314
|
|
|
310
315
|
class_methods do
|
|
311
|
-
# Sequential processing of partition list
|
|
316
|
+
# Sequential processing of partition list with live progress rendering.
|
|
312
317
|
def __se_index_partitions_seq!(parts, into)
|
|
318
|
+
estimate = __se_per_partition_estimate(parts.size)
|
|
319
|
+
renderer = SearchEngine::Logging::LiveRenderer.new(
|
|
320
|
+
labels: parts.map(&:inspect), partitions: parts, per_partition_estimate: estimate
|
|
321
|
+
)
|
|
322
|
+
renderer.start
|
|
323
|
+
|
|
313
324
|
summaries = []
|
|
314
|
-
parts.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
325
|
+
parts.each_with_index do |part, idx|
|
|
326
|
+
slot = renderer[idx]
|
|
327
|
+
slot.start
|
|
328
|
+
begin
|
|
329
|
+
on_batch = ->(info) { slot.progress(**info) }
|
|
330
|
+
summary = SearchEngine::Indexer.rebuild_partition!(
|
|
331
|
+
self, partition: part, into: into, on_batch: on_batch
|
|
332
|
+
)
|
|
333
|
+
slot.finish(summary)
|
|
334
|
+
summaries << summary
|
|
335
|
+
rescue StandardError => error
|
|
336
|
+
slot.finish_error(error)
|
|
337
|
+
raise
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
begin
|
|
342
|
+
renderer.stop
|
|
343
|
+
rescue StandardError
|
|
344
|
+
nil
|
|
319
345
|
end
|
|
320
346
|
__se_build_index_result(summaries)
|
|
347
|
+
ensure
|
|
348
|
+
renderer&.stop
|
|
321
349
|
end
|
|
322
350
|
end
|
|
323
351
|
|
|
324
352
|
class_methods do
|
|
325
|
-
# Parallel processing via bounded thread pool
|
|
353
|
+
# Parallel processing via bounded thread pool with live progress rendering.
|
|
326
354
|
def __se_index_partitions_parallel!(parts, into, max_p)
|
|
327
355
|
require 'concurrent-ruby'
|
|
356
|
+
|
|
357
|
+
estimate = __se_per_partition_estimate(parts.size)
|
|
358
|
+
renderer = SearchEngine::Logging::LiveRenderer.new(
|
|
359
|
+
labels: parts.map(&:inspect), partitions: parts, per_partition_estimate: estimate
|
|
360
|
+
)
|
|
361
|
+
renderer.start
|
|
362
|
+
|
|
328
363
|
pool = Concurrent::FixedThreadPool.new(max_p)
|
|
364
|
+
cancelled = Concurrent::AtomicBoolean.new(false)
|
|
329
365
|
ctx = SearchEngine::Instrumentation.context
|
|
330
|
-
mtx = Mutex.new
|
|
331
366
|
summaries = []
|
|
332
367
|
partition_errors = []
|
|
333
|
-
|
|
334
|
-
|
|
368
|
+
mtx = Mutex.new
|
|
369
|
+
|
|
370
|
+
on_interrupt = lambda do
|
|
371
|
+
cancelled.make_true
|
|
372
|
+
renderer.stop
|
|
373
|
+
warn("\n Interrupted \u2014 stopping parallel partition workers\u2026")
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
SearchEngine::InterruptiblePool.run(pool, on_interrupt: on_interrupt) do
|
|
377
|
+
parts.each_with_index do |part, idx|
|
|
378
|
+
break if cancelled.true?
|
|
379
|
+
|
|
335
380
|
pool.post do
|
|
381
|
+
next if cancelled.true?
|
|
382
|
+
|
|
383
|
+
slot = renderer[idx]
|
|
384
|
+
slot.start
|
|
336
385
|
SearchEngine::Instrumentation.with_context(ctx) do
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
386
|
+
on_batch = ->(info) { slot.progress(**info) }
|
|
387
|
+
summary = SearchEngine::Indexer.rebuild_partition!(
|
|
388
|
+
self, partition: part, into: into, on_batch: on_batch
|
|
389
|
+
)
|
|
390
|
+
slot.finish(summary)
|
|
391
|
+
mtx.synchronize { summaries << summary }
|
|
343
392
|
end
|
|
344
393
|
rescue StandardError => error
|
|
394
|
+
renderer[idx].finish_error(error)
|
|
345
395
|
mtx.synchronize do
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
partition_errors << "#{error.class}: #{error.message.to_s[0, 200]}"
|
|
396
|
+
cancelled.make_true
|
|
397
|
+
partition_errors << error unless partition_errors.any?
|
|
349
398
|
end
|
|
350
399
|
end
|
|
351
400
|
end
|
|
352
|
-
ensure
|
|
353
|
-
pool.shutdown
|
|
354
|
-
pool.wait_for_termination(3600) || pool.kill
|
|
355
|
-
pool.wait_for_termination(60)
|
|
356
401
|
end
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
402
|
+
|
|
403
|
+
begin
|
|
404
|
+
renderer.stop
|
|
405
|
+
rescue StandardError
|
|
406
|
+
nil
|
|
361
407
|
end
|
|
362
|
-
|
|
408
|
+
raise partition_errors.first if partition_errors.any?
|
|
409
|
+
|
|
410
|
+
__se_build_index_result(summaries)
|
|
411
|
+
ensure
|
|
412
|
+
renderer&.stop
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
class_methods do
|
|
417
|
+
# Heuristic per-partition batch estimate for progress bars.
|
|
418
|
+
# @param partition_count [Integer]
|
|
419
|
+
# @return [Integer, nil]
|
|
420
|
+
def __se_per_partition_estimate(partition_count)
|
|
421
|
+
total = SearchEngine::Indexer::BulkImport.estimate_total_batches(self)
|
|
422
|
+
return nil unless total
|
|
423
|
+
|
|
424
|
+
(total.to_f / partition_count).ceil
|
|
425
|
+
rescue StandardError
|
|
426
|
+
nil
|
|
363
427
|
end
|
|
428
|
+
|
|
429
|
+
private :__se_per_partition_estimate
|
|
364
430
|
end
|
|
365
431
|
|
|
366
432
|
class_methods do
|
|
@@ -145,7 +145,7 @@ into: nil
|
|
|
145
145
|
# when no partitions are configured.
|
|
146
146
|
# @param ref_klass [Class]
|
|
147
147
|
# @return [void]
|
|
148
|
-
# rubocop:disable Metrics/
|
|
148
|
+
# rubocop:disable Metrics/AbcSize
|
|
149
149
|
def __se_full_reindex_for_referrer(ref_klass, client:, alias_cache:)
|
|
150
150
|
logical = ref_klass.respond_to?(:collection) ? ref_klass.collection.to_s : ref_klass.name.to_s
|
|
151
151
|
physical = resolve_physical_collection_name(logical, client: client, cache: alias_cache)
|
|
@@ -197,13 +197,9 @@ into: nil
|
|
|
197
197
|
pool = Concurrent::FixedThreadPool.new(mp)
|
|
198
198
|
ctx = SearchEngine::Instrumentation.context
|
|
199
199
|
mtx = Mutex.new
|
|
200
|
-
|
|
200
|
+
on_interrupt = -> { warn("\n Interrupted — stopping parallel cascade workers…") }
|
|
201
|
+
SearchEngine::InterruptiblePool.run(pool, on_interrupt: on_interrupt) do
|
|
201
202
|
post_partitions_to_pool!(pool, ctx, parts, ref_klass, mtx)
|
|
202
|
-
ensure
|
|
203
|
-
pool.shutdown
|
|
204
|
-
# Wait up to 1 hour, then force-kill and wait a bit more to ensure cleanup
|
|
205
|
-
pool.wait_for_termination(3600) || pool.kill
|
|
206
|
-
pool.wait_for_termination(60)
|
|
207
203
|
end
|
|
208
204
|
executed = true
|
|
209
205
|
else
|
|
@@ -218,7 +214,7 @@ into: nil
|
|
|
218
214
|
end
|
|
219
215
|
executed
|
|
220
216
|
end
|
|
221
|
-
# rubocop:enable Metrics/
|
|
217
|
+
# rubocop:enable Metrics/AbcSize
|
|
222
218
|
|
|
223
219
|
# Resolve logical alias to physical name with optional per-run memoization.
|
|
224
220
|
# @param logical [String]
|
data/lib/search_engine/config.rb
CHANGED
|
@@ -103,6 +103,8 @@ module SearchEngine
|
|
|
103
103
|
attr_accessor :dispatch
|
|
104
104
|
# @return [String] queue name for ActiveJob enqueues
|
|
105
105
|
attr_accessor :queue_name
|
|
106
|
+
# @return [Boolean] whether to run model.count for progress bar estimates (default true)
|
|
107
|
+
attr_accessor :estimate_progress
|
|
106
108
|
|
|
107
109
|
def initialize
|
|
108
110
|
@batch_size = 2000
|
|
@@ -111,6 +113,7 @@ module SearchEngine
|
|
|
111
113
|
@gzip = false
|
|
112
114
|
@dispatch = active_job_available? ? :active_job : :inline
|
|
113
115
|
@queue_name = 'search_index'
|
|
116
|
+
@estimate_progress = true
|
|
114
117
|
end
|
|
115
118
|
|
|
116
119
|
private
|
|
@@ -720,7 +723,8 @@ module SearchEngine
|
|
|
720
723
|
retries: indexer.retries,
|
|
721
724
|
gzip: indexer.gzip ? true : false,
|
|
722
725
|
dispatch: indexer.dispatch,
|
|
723
|
-
queue_name: indexer.queue_name
|
|
726
|
+
queue_name: indexer.queue_name,
|
|
727
|
+
estimate_progress: indexer.estimate_progress
|
|
724
728
|
}
|
|
725
729
|
end
|
|
726
730
|
|