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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46b318e4be1f0f8d12e5f3ead35ad4945c472773f8f7117427d1ffd2a0f44732
4
- data.tar.gz: 4db7bc9cf0e081ff21b87c8b089627fe02e651020ad8a9df7fc0a3edb0cc7dea
3
+ metadata.gz: 933b8b58a98c81e8e6b72f3083cfcf2ee1de5ea9f709d4c10aea5d09dba59d99
4
+ data.tar.gz: 626111c57bc0ee533d21938d7a218c13b80cc23aa27827731994a968f97de672
5
5
  SHA512:
6
- metadata.gz: cccc05756309485db9c5c49a880e45aa09a3f8f7e6afade62fd2d064eb3ccca286c96791aed830da01090ee10c60b6cf5c602269b55859b3466f76d3d22bd704
7
- data.tar.gz: 112cbcd585dfeec20a848c9b153024fcb48526b4f56127a75ef45300693baf6ca666899f607f436e32889d50d2e7f1ad1b29d1daf6af6c5f59b1611db8d9b683
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
- puts(SearchEngine::Logging::Color.dim('Cleanup — skip (no stale configuration)'))
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
- puts("Cleanup — filter=#{merged_filter.inspect}")
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
- puts("Cleanup — #{SearchEngine::Logging::Color.apply("deleted=#{deleted}", :green)}")
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
- presence = if missing
60
- SearchEngine::Logging::Color.apply('missing', :yellow)
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
- puts("Step 2: Create+Apply Schema — #{SearchEngine::Logging::Color.bold('processing')}")
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
- puts("Step 2: Create+Apply Schema — #{SearchEngine::Logging::Color.apply('done', :green)}")
87
+ step.finish('created')
90
88
  else
91
- puts(SearchEngine::Logging::Color.dim('Step 2: Create+Apply Schema — skip (collection present)'))
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
- puts("Step 3: Check Schema Status — #{SearchEngine::Logging::Color.bold('processing')}")
99
+ step.update('checking')
99
100
  drift = __se_schema_drift?(diff)
100
101
  if force_rebuild && !drift
101
- puts("Step 3: Check Schema Status — #{SearchEngine::Logging::Color.apply('force_rebuild', :yellow)}")
102
+ step.finish_warn('force_rebuild')
102
103
  return true
103
104
  end
104
- schema_status = if drift
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
- puts(SearchEngine::Logging::Color.dim('Step 3: Check Schema Status — skip (just created)'))
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
- puts("Step 4: Apply New Schema — #{SearchEngine::Logging::Color.bold('processing')}")
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
- puts("Step 4: Apply New Schema — #{SearchEngine::Logging::Color.apply('done', :green)}")
123
+ step.finish('applied')
124
124
  else
125
- puts(SearchEngine::Logging::Color.dim('Step 4: Apply New Schema — skip'))
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
- puts(SearchEngine::Logging::Color.dim('Step 5: Indexing — skip (performed during schema apply)'))
136
+ step.skip('performed during schema apply')
134
137
  result = indexed_inside_apply if indexed_inside_apply.is_a?(Hash)
135
138
  else
136
- puts("Step 5: Indexing — #{SearchEngine::Logging::Color.bold('processing')}")
139
+ step.update('indexing')
140
+ step.yield_line!
137
141
  result = __se_index_partitions!(into: nil)
138
- puts("Step 5: Indexing — #{SearchEngine::Logging::Color.apply('done', :green)}")
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
- puts(SearchEngine::Logging::Color.dim('Step 6: Retention Cleanup — skip (handled by schema apply)'))
155
+ step.skip('handled by schema apply')
149
156
  else
150
- puts("Step 6: Retention Cleanup — #{SearchEngine::Logging::Color.bold('processing')}")
157
+ step.update('cleaning')
151
158
  dropped = __se_retention_cleanup!(logical: logical, client: client)
152
- dropped_str = SearchEngine::Logging::Color.apply("dropped=#{dropped.inspect}", :green)
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
- presence = if missing
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
- msg = SearchEngine::Logging::Color.apply(
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
- puts("Step 2: Check Schema Status — #{SearchEngine::Logging::Color.bold('processing')}")
179
+ step = SearchEngine::Logging::StepLine.new('Schema Status')
180
+ step.update('checking')
179
181
  drift = __se_schema_drift?(diff)
180
182
  if drift
181
- msg = SearchEngine::Logging::Color.apply(
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
- puts("Step 2: Check Schema Status — #{SearchEngine::Logging::Color.apply('in_sync', :green)}")
187
+ step.finish('in_sync')
189
188
 
190
189
  __se_preflight_dependencies!(mode: pre, client: client) if pre
191
190
 
192
- puts("Step 3: Partial Indexing — #{SearchEngine::Logging::Color.bold('processing')}")
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
- puts("Step 3: Partial Indexing — #{SearchEngine::Logging::Color.apply('done', :green)}")
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
- status_word = SearchEngine::Logging::Color.bold('analyzing diff for in-place update...')
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
- status_word = SearchEngine::Logging::Color.apply('schema updated in-place (PATCH)', :green)
37
- puts "Update Collection — #{status_word}"
35
+ step.finish('updated in-place (PATCH)')
38
36
  else
39
- msg = SearchEngine::Logging::Color.dim(
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
- puts(SearchEngine::Logging::Color.dim('Drop Collection — skip (not present)'))
58
+ step.skip('not present')
62
59
  return
63
60
  end
64
61
 
65
62
  puts
66
- header = SearchEngine::Logging::Color.header(%(>>>>>> Dropping Collection "#{logical}"))
67
- puts(header)
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
- puts("Drop Collection — #{SearchEngine::Logging::Color.apply('done', :green)}")
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
- status_word = SearchEngine::Logging::Color.apply('dropping existing', :yellow)
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
- msg = SearchEngine::Logging::Color.dim('Recreate Collection — no existing collection (skip drop)')
95
- puts(msg)
90
+ step.update("creating (logical=#{logical})")
96
91
  end
97
92
 
98
93
  schema = SearchEngine::Schema.compile(self)
99
- status_word = SearchEngine::Logging::Color.bold('creating collection with schema')
100
- puts("Recreate Collection — #{status_word} (logical=#{logical})")
94
+ step.update("creating with schema (logical=#{logical})")
101
95
  client.create_collection(schema)
102
- puts("Recreate Collection — #{SearchEngine::Logging::Color.apply('done', :green)}")
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.each do |part|
315
- summary = SearchEngine::Indexer.rebuild_partition!(self, partition: part, into: into)
316
- summaries << summary
317
- puts(SearchEngine::Logging::PartitionProgress.line(part, summary))
318
- __se_log_batches_from_summary(summary.batches) if summary.batches_total.to_i > 1
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
- begin
334
- parts.each do |part|
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
- summary = SearchEngine::Indexer.rebuild_partition!(self, partition: part, into: into)
338
- mtx.synchronize do
339
- summaries << summary
340
- puts(SearchEngine::Logging::PartitionProgress.line(part, summary))
341
- __se_log_batches_from_summary(summary.batches) if summary.batches_total.to_i > 1
342
- end
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
- err_msg = " partition=#{part.inspect} → error=#{error.class}: #{error.message.to_s[0, 200]}"
347
- warn(SearchEngine::Logging::Color.apply(err_msg, :red))
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
- result = __se_build_index_result(summaries)
358
- if partition_errors.any?
359
- result[:status] = :failed
360
- result[:sample_error] ||= partition_errors.first
402
+
403
+ begin
404
+ renderer.stop
405
+ rescue StandardError
406
+ nil
361
407
  end
362
- result
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/PerceivedComplexity, Metrics/AbcSize
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
- begin
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/PerceivedComplexity, Metrics/AbcSize
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]
@@ -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