search-engine-for-typesense 30.1.8.18 → 30.1.8.19
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/README.md +5 -0
- data/lib/search_engine/postgres_outbox/repository.rb +156 -54
- data/lib/search_engine/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20f330c7ec29b4f4ea6366c48d23f05d5a089e47eaeba79f0ad741c2a83b09ec
|
|
4
|
+
data.tar.gz: 1359ad4c68d304d334c238bae7beddeee57a6350565b8b8e5b6b8d3f97c75e82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 55191671cd8fb5dc620d25b605715b7608db8f8de48c220e7fe3d59727c239a9dd386574a421fd4477b6170f71c010575acf8b5c3e961a4fe2a455a568fe3e1c
|
|
7
|
+
data.tar.gz: f74ac6499069ed09918eac51273bcfcd5e82e0d91295001a9f3c5505b1ad69159f3c92f75c796d0cef43af29ee3159ecc79438c285fd15b2a4b0cb666d4618e1
|
data/README.md
CHANGED
|
@@ -405,6 +405,11 @@ inspected before deletion because they contain the last error and retry state. A
|
|
|
405
405
|
only rows with `status IN ('processed', 'superseded')` and `processed_at` older than
|
|
406
406
|
`c.postgres_outbox.retention_s`.
|
|
407
407
|
|
|
408
|
+
In delivery-target mode, cleanup can first call
|
|
409
|
+
`SearchEngine::PostgresOutbox::Repository#refresh_terminal_delivery_event_statuses!`. This bounded helper
|
|
410
|
+
repairs parent events whose delivery rows are all terminal but whose parent status is still non-terminal,
|
|
411
|
+
then normal retention cleanup can delete the repaired parent rows and cascade their deliveries.
|
|
412
|
+
|
|
408
413
|
## Example app
|
|
409
414
|
|
|
410
415
|
See `examples/demo_shop` — demonstrates single/multi search, JOINs, grouping, presets/curation, and DX/observability. Supports offline mode via the stub client (see [Testing](https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/testing)).
|
|
@@ -128,6 +128,24 @@ module SearchEngine
|
|
|
128
128
|
rows
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
+
# Refresh cleanup-eligible parent event statuses from terminal delivery rows.
|
|
132
|
+
#
|
|
133
|
+
# This repairs historical residue where every delivery row for an event is
|
|
134
|
+
# terminal but the parent event still has a non-terminal status. The
|
|
135
|
+
# candidate set is bounded before aggregate work to keep cleanup safe on
|
|
136
|
+
# large outbox tables.
|
|
137
|
+
#
|
|
138
|
+
# @param retention_s [Integer] retention window in seconds
|
|
139
|
+
# @param limit [Integer, nil] maximum parent events to refresh
|
|
140
|
+
# @return [Integer] refreshed parent event count
|
|
141
|
+
def refresh_terminal_delivery_event_statuses!(retention_s:, limit: nil)
|
|
142
|
+
return 0 unless delivery_table_exists?
|
|
143
|
+
|
|
144
|
+
rows = select_rows(terminal_delivery_event_status_refresh_sql(retention_s: retention_s, limit: limit))
|
|
145
|
+
|
|
146
|
+
rows.size
|
|
147
|
+
end
|
|
148
|
+
|
|
131
149
|
# Check whether the optional drain slot table exists.
|
|
132
150
|
#
|
|
133
151
|
# @return [Boolean]
|
|
@@ -360,14 +378,27 @@ module SearchEngine
|
|
|
360
378
|
|
|
361
379
|
def reset_stale_delivery_processing!
|
|
362
380
|
execute(<<~SQL)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
381
|
+
WITH reset_deliveries AS (
|
|
382
|
+
UPDATE #{quoted_delivery_table}
|
|
383
|
+
SET status = 'pending',
|
|
384
|
+
locked_at = NULL,
|
|
385
|
+
locked_by = NULL,
|
|
386
|
+
updated_at = CURRENT_TIMESTAMP
|
|
387
|
+
WHERE target_key = #{quote(target_key)}
|
|
388
|
+
AND status = 'processing'
|
|
389
|
+
AND locked_at < (CURRENT_TIMESTAMP - interval '#{processing_timeout_s} seconds')
|
|
390
|
+
RETURNING event_id
|
|
391
|
+
),
|
|
392
|
+
aggregate AS (
|
|
393
|
+
#{event_status_aggregate_sql('SELECT event_id FROM reset_deliveries')}
|
|
394
|
+
)
|
|
395
|
+
UPDATE #{quoted_table} events
|
|
396
|
+
SET status = aggregate.status,
|
|
397
|
+
processed_at = #{aggregate_processed_at_sql},
|
|
398
|
+
last_error = aggregate.last_error,
|
|
367
399
|
updated_at = CURRENT_TIMESTAMP
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
AND locked_at < (CURRENT_TIMESTAMP - interval '#{processing_timeout_s} seconds')
|
|
400
|
+
FROM aggregate
|
|
401
|
+
WHERE events.id = aggregate.event_id
|
|
371
402
|
SQL
|
|
372
403
|
end
|
|
373
404
|
|
|
@@ -504,10 +535,7 @@ module SearchEngine
|
|
|
504
535
|
)
|
|
505
536
|
UPDATE #{quoted_table} events
|
|
506
537
|
SET status = aggregate.status,
|
|
507
|
-
processed_at =
|
|
508
|
-
WHEN aggregate.status IN ('processed', 'superseded') THEN CURRENT_TIMESTAMP
|
|
509
|
-
ELSE NULL
|
|
510
|
-
END,
|
|
538
|
+
processed_at = #{aggregate_processed_at_sql},
|
|
511
539
|
last_error = aggregate.last_error,
|
|
512
540
|
updated_at = CURRENT_TIMESTAMP
|
|
513
541
|
FROM aggregate
|
|
@@ -565,10 +593,7 @@ module SearchEngine
|
|
|
565
593
|
)
|
|
566
594
|
UPDATE #{quoted_table} events
|
|
567
595
|
SET status = aggregate.status,
|
|
568
|
-
processed_at =
|
|
569
|
-
WHEN aggregate.status IN ('processed', 'superseded') THEN CURRENT_TIMESTAMP
|
|
570
|
-
ELSE NULL
|
|
571
|
-
END,
|
|
596
|
+
processed_at = #{aggregate_processed_at_sql},
|
|
572
597
|
last_error = aggregate.last_error,
|
|
573
598
|
updated_at = CURRENT_TIMESTAMP
|
|
574
599
|
FROM aggregate
|
|
@@ -595,47 +620,60 @@ module SearchEngine
|
|
|
595
620
|
ids = Array(event_ids).compact
|
|
596
621
|
return if ids.empty?
|
|
597
622
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
623
|
+
with_delivery_status_refresh(ids) do
|
|
624
|
+
execute(<<~SQL)
|
|
625
|
+
UPDATE #{quoted_delivery_table}
|
|
626
|
+
SET status = #{quote(status)},
|
|
627
|
+
#{extra},
|
|
628
|
+
locked_at = NULL,
|
|
629
|
+
locked_by = NULL,
|
|
630
|
+
updated_at = CURRENT_TIMESTAMP
|
|
631
|
+
WHERE target_key = #{quote(target_key)}
|
|
632
|
+
AND event_id IN (#{ids_sql(ids)})
|
|
633
|
+
SQL
|
|
634
|
+
end
|
|
609
635
|
end
|
|
610
636
|
|
|
611
637
|
def mark_delivery_retryable!(event_ids, error:)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
638
|
+
with_delivery_status_refresh(event_ids) do
|
|
639
|
+
execute(<<~SQL)
|
|
640
|
+
UPDATE #{quoted_delivery_table}
|
|
641
|
+
SET attempts = attempts + 1,
|
|
642
|
+
status = CASE WHEN attempts + 1 >= #{max_attempts} THEN 'failed' ELSE 'pending' END,
|
|
643
|
+
next_attempt_at = CURRENT_TIMESTAMP + #{retry_interval_case_sql},
|
|
644
|
+
locked_at = NULL,
|
|
645
|
+
locked_by = NULL,
|
|
646
|
+
last_error = #{quote(truncate_error(error))},
|
|
647
|
+
updated_at = CURRENT_TIMESTAMP
|
|
648
|
+
WHERE target_key = #{quote(target_key)}
|
|
649
|
+
AND event_id IN (#{ids_sql(event_ids)})
|
|
650
|
+
SQL
|
|
651
|
+
end
|
|
625
652
|
end
|
|
626
653
|
|
|
627
654
|
def mark_delivery_failed!(event_ids, error:)
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
655
|
+
with_delivery_status_refresh(event_ids) do
|
|
656
|
+
execute(<<~SQL)
|
|
657
|
+
UPDATE #{quoted_delivery_table}
|
|
658
|
+
SET status = 'failed',
|
|
659
|
+
locked_at = NULL,
|
|
660
|
+
locked_by = NULL,
|
|
661
|
+
last_error = #{quote(truncate_error(error))},
|
|
662
|
+
updated_at = CURRENT_TIMESTAMP
|
|
663
|
+
WHERE target_key = #{quote(target_key)}
|
|
664
|
+
AND event_id IN (#{ids_sql(event_ids)})
|
|
665
|
+
SQL
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
def with_delivery_status_refresh(event_ids)
|
|
670
|
+
ids = Array(event_ids).compact
|
|
671
|
+
return if ids.empty?
|
|
672
|
+
|
|
673
|
+
connection.transaction do
|
|
674
|
+
yield
|
|
675
|
+
refresh_event_statuses!(ids)
|
|
676
|
+
end
|
|
639
677
|
end
|
|
640
678
|
|
|
641
679
|
def refresh_event_statuses!(event_ids)
|
|
@@ -645,10 +683,7 @@ module SearchEngine
|
|
|
645
683
|
execute(<<~SQL)
|
|
646
684
|
UPDATE #{quoted_table} events
|
|
647
685
|
SET status = aggregate.status,
|
|
648
|
-
processed_at =
|
|
649
|
-
WHEN aggregate.status IN ('processed', 'superseded') THEN CURRENT_TIMESTAMP
|
|
650
|
-
ELSE NULL
|
|
651
|
-
END,
|
|
686
|
+
processed_at = #{aggregate_processed_at_sql},
|
|
652
687
|
last_error = aggregate.last_error,
|
|
653
688
|
updated_at = CURRENT_TIMESTAMP
|
|
654
689
|
FROM (
|
|
@@ -668,6 +703,7 @@ module SearchEngine
|
|
|
668
703
|
WHEN COUNT(*) FILTER (WHERE status = 'processed') > 0 THEN 'processed'
|
|
669
704
|
ELSE 'pending'
|
|
670
705
|
END AS status,
|
|
706
|
+
MAX(processed_at) FILTER (WHERE status IN ('processed', 'superseded')) AS terminal_processed_at,
|
|
671
707
|
(ARRAY_AGG(last_error ORDER BY updated_at DESC) FILTER (WHERE last_error IS NOT NULL))[1] AS last_error
|
|
672
708
|
FROM #{quoted_delivery_table}
|
|
673
709
|
WHERE event_id IN (#{event_ids_sql})
|
|
@@ -675,6 +711,63 @@ module SearchEngine
|
|
|
675
711
|
SQL
|
|
676
712
|
end
|
|
677
713
|
|
|
714
|
+
def terminal_delivery_event_status_refresh_sql(retention_s:, limit:)
|
|
715
|
+
retention_seconds = [retention_s.to_i, 0].max
|
|
716
|
+
batch_limit = global_limit_for(limit)
|
|
717
|
+
|
|
718
|
+
<<~SQL
|
|
719
|
+
WITH candidate_events AS MATERIALIZED (
|
|
720
|
+
SELECT events.id
|
|
721
|
+
FROM #{quoted_table} events
|
|
722
|
+
WHERE events.status NOT IN ('processed', 'superseded')
|
|
723
|
+
AND EXISTS (
|
|
724
|
+
SELECT 1
|
|
725
|
+
FROM #{quoted_delivery_table} deliveries
|
|
726
|
+
WHERE deliveries.event_id = events.id
|
|
727
|
+
)
|
|
728
|
+
AND NOT EXISTS (
|
|
729
|
+
SELECT 1
|
|
730
|
+
FROM #{quoted_delivery_table} deliveries
|
|
731
|
+
WHERE deliveries.event_id = events.id
|
|
732
|
+
AND deliveries.status IN ('pending', 'processing', 'failed')
|
|
733
|
+
)
|
|
734
|
+
ORDER BY events.id ASC
|
|
735
|
+
LIMIT #{batch_limit}
|
|
736
|
+
FOR UPDATE SKIP LOCKED
|
|
737
|
+
),
|
|
738
|
+
eligible_events AS (
|
|
739
|
+
SELECT candidate_events.id
|
|
740
|
+
FROM candidate_events
|
|
741
|
+
WHERE (
|
|
742
|
+
SELECT MAX(deliveries.processed_at)
|
|
743
|
+
FROM #{quoted_delivery_table} deliveries
|
|
744
|
+
WHERE deliveries.event_id = candidate_events.id
|
|
745
|
+
AND deliveries.status IN ('processed', 'superseded')
|
|
746
|
+
) < (CURRENT_TIMESTAMP - interval '#{retention_seconds} seconds')
|
|
747
|
+
),
|
|
748
|
+
aggregate AS (
|
|
749
|
+
#{event_status_aggregate_sql('SELECT id FROM eligible_events')}
|
|
750
|
+
),
|
|
751
|
+
updated_events AS (
|
|
752
|
+
UPDATE #{quoted_table} events
|
|
753
|
+
SET status = aggregate.status,
|
|
754
|
+
processed_at = #{aggregate_processed_at_sql},
|
|
755
|
+
last_error = aggregate.last_error,
|
|
756
|
+
updated_at = CURRENT_TIMESTAMP
|
|
757
|
+
FROM aggregate
|
|
758
|
+
WHERE events.id = aggregate.event_id
|
|
759
|
+
RETURNING events.id
|
|
760
|
+
)
|
|
761
|
+
SELECT id
|
|
762
|
+
FROM updated_events
|
|
763
|
+
SQL
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
def aggregate_processed_at_sql
|
|
767
|
+
"CASE WHEN aggregate.status IN ('processed', 'superseded') " \
|
|
768
|
+
'THEN COALESCE(aggregate.terminal_processed_at, CURRENT_TIMESTAMP) ELSE NULL END'
|
|
769
|
+
end
|
|
770
|
+
|
|
678
771
|
def select_rows(sql)
|
|
679
772
|
result = connection.select_all(sql)
|
|
680
773
|
return result.to_a if result.respond_to?(:to_a)
|
|
@@ -832,6 +925,15 @@ module SearchEngine
|
|
|
832
925
|
SearchEngine.config.postgres_outbox.drain_slot_table_name
|
|
833
926
|
end
|
|
834
927
|
|
|
928
|
+
def delivery_table_exists?
|
|
929
|
+
table_name = SearchEngine.config.postgres_outbox.delivery_table_name
|
|
930
|
+
if connection.respond_to?(:data_source_exists?)
|
|
931
|
+
connection.data_source_exists?(table_name)
|
|
932
|
+
else
|
|
933
|
+
connection.table_exists?(table_name)
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
|
|
835
937
|
def quote(value)
|
|
836
938
|
connection.quote(value)
|
|
837
939
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: search-engine-for-typesense
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 30.1.8.
|
|
4
|
+
version: 30.1.8.19
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nikita Shkoda
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|