search-engine-for-typesense 30.1.6.13 → 30.1.6.14
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/errors.rb +2 -1
- data/lib/search_engine/schema.rb +40 -4
- data/lib/search_engine/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 751fec345b2fe0e2e9290ffba4af92639a58fc7663d6cd78e09527ace07d5104
|
|
4
|
+
data.tar.gz: 3332605b4afa0d24b66c4f4f0ab5f7be4a6676765213bf9441b508fddc1c8f9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdc7e4250b406a06004d532cd379a597866a1a5419c90697fd177692f6b8a31c682e7dbc1c1f3727a823190d95c6612022a64520bc88e3dd3cec9a98e555e7c9
|
|
7
|
+
data.tar.gz: 68f460a120e7763bb2353531b0fadbc3f1b4f642eafebcc6cecd2af6ec1423db936159f6cce586f74d47f4e10782e0e02dfaf3673caa1e94d037f0628dcd9844
|
data/lib/search_engine/errors.rb
CHANGED
|
@@ -131,7 +131,8 @@ module SearchEngine
|
|
|
131
131
|
# returns a non-ok status. Aborting the block prevents the alias swap,
|
|
132
132
|
# keeping the previous (fully-indexed) physical collection active.
|
|
133
133
|
#
|
|
134
|
-
# The partially-indexed new physical is
|
|
134
|
+
# The partially-indexed new physical is automatically cleaned up by
|
|
135
|
+
# the +ensure+ block in {Schema.apply!}.
|
|
135
136
|
class IndexationAborted < Error
|
|
136
137
|
# @return [Hash] the indexation result hash (:status, :docs_total, etc.)
|
|
137
138
|
attr_reader :result
|
data/lib/search_engine/schema.rb
CHANGED
|
@@ -156,9 +156,10 @@ module SearchEngine
|
|
|
156
156
|
# The reindexing step can be provided via an optional block (yielded with the new
|
|
157
157
|
# physical name). If no block is given, and the klass responds to
|
|
158
158
|
# `reindex_all_to(physical_name)`, that method will be called. If neither is available,
|
|
159
|
-
# an ArgumentError is raised and no alias swap occurs. If reindexing fails
|
|
160
|
-
# newly created physical is
|
|
161
|
-
# after a successful
|
|
159
|
+
# an ArgumentError is raised and no alias swap occurs. If reindexing fails or
|
|
160
|
+
# is interrupted, the newly created physical collection is automatically deleted
|
|
161
|
+
# to prevent orphan accumulation. Retention cleanup only runs after a successful
|
|
162
|
+
# alias swap.
|
|
162
163
|
#
|
|
163
164
|
# @param klass [Class] model class inheriting from {SearchEngine::Base}
|
|
164
165
|
# @param client [SearchEngine::Client] optional client wrapper (for tests)
|
|
@@ -168,8 +169,12 @@ module SearchEngine
|
|
|
168
169
|
# @raise [SearchEngine::Errors::Api, ArgumentError]
|
|
169
170
|
# @see `https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/schema#lifecycle`
|
|
170
171
|
# @see `https://typesense.org/docs/latest/api/collections.html`
|
|
171
|
-
def apply!(klass, client: nil, force_rebuild: false)
|
|
172
|
+
def apply!(klass, client: nil, force_rebuild: false) # rubocop:disable Metrics/MethodLength
|
|
172
173
|
client ||= SearchEngine.client
|
|
174
|
+
new_physical = nil
|
|
175
|
+
physical_created = false
|
|
176
|
+
apply_succeeded = false
|
|
177
|
+
|
|
173
178
|
# Optimization: Try in-place update first if not forced to rebuild.
|
|
174
179
|
# If update! returns true, the schema is synced (either no changes or successfully patched).
|
|
175
180
|
if !force_rebuild && update!(klass, client: client)
|
|
@@ -178,6 +183,7 @@ module SearchEngine
|
|
|
178
183
|
# Resolve current physical to return consistent result
|
|
179
184
|
physical = client.resolve_alias(logical) || logical
|
|
180
185
|
|
|
186
|
+
apply_succeeded = true
|
|
181
187
|
return update_result_payload(logical, physical)
|
|
182
188
|
end
|
|
183
189
|
|
|
@@ -203,6 +209,7 @@ module SearchEngine
|
|
|
203
209
|
end
|
|
204
210
|
|
|
205
211
|
client.create_collection(create_schema)
|
|
212
|
+
physical_created = true
|
|
206
213
|
|
|
207
214
|
if block_given?
|
|
208
215
|
yield new_physical
|
|
@@ -216,6 +223,7 @@ module SearchEngine
|
|
|
216
223
|
current_after_reindex = client.resolve_alias(logical)
|
|
217
224
|
swapped = current_after_reindex != new_physical
|
|
218
225
|
client.upsert_alias(logical, new_physical) if swapped
|
|
226
|
+
apply_succeeded = true
|
|
219
227
|
|
|
220
228
|
# Retention cleanup
|
|
221
229
|
_, dropped = enforce_retention!(logical, new_physical, client: client, keep_last: effective_keep_last(klass))
|
|
@@ -245,6 +253,8 @@ module SearchEngine
|
|
|
245
253
|
dropped_physicals: dropped,
|
|
246
254
|
action: :rebuild
|
|
247
255
|
}
|
|
256
|
+
ensure
|
|
257
|
+
cleanup_interrupted_physical!(new_physical, client) if physical_created && !apply_succeeded
|
|
248
258
|
end
|
|
249
259
|
|
|
250
260
|
# Roll back the alias for the given klass to the previous retained physical collection.
|
|
@@ -390,6 +400,32 @@ module SearchEngine
|
|
|
390
400
|
|
|
391
401
|
private
|
|
392
402
|
|
|
403
|
+
# Best-effort cleanup of a physical collection left behind by an interrupted
|
|
404
|
+
# or failed {.apply!}. Rescues all errors so it never masks the original
|
|
405
|
+
# exception. If the delete itself fails, a warning directs users to
|
|
406
|
+
# {.prune_orphans!} as a fallback.
|
|
407
|
+
#
|
|
408
|
+
# @param physical_name [String, nil] the physical collection to remove
|
|
409
|
+
# @param client [SearchEngine::Client, nil]
|
|
410
|
+
# @return [void]
|
|
411
|
+
def cleanup_interrupted_physical!(physical_name, client)
|
|
412
|
+
return if physical_name.nil? || client.nil?
|
|
413
|
+
|
|
414
|
+
client.delete_collection(physical_name, timeout_ms: 60_000)
|
|
415
|
+
|
|
416
|
+
if defined?(ActiveSupport::Notifications)
|
|
417
|
+
SearchEngine::Instrumentation.instrument(
|
|
418
|
+
'search_engine.schema.cleanup_interrupted',
|
|
419
|
+
physical: physical_name, status: :ok
|
|
420
|
+
) {}
|
|
421
|
+
end
|
|
422
|
+
rescue StandardError => error
|
|
423
|
+
warn(
|
|
424
|
+
'SearchEngine::Schema — failed to clean up interrupted physical ' \
|
|
425
|
+
"'#{physical_name}': #{error.message}. Run prune_orphans! to remove it."
|
|
426
|
+
)
|
|
427
|
+
end
|
|
428
|
+
|
|
393
429
|
# Generate a new physical name using UTC timestamp + 3-digit sequence.
|
|
394
430
|
# Example: "products_20250131_235959_001"
|
|
395
431
|
def generate_physical_name(logical, client:)
|