fix(notifications): dispatcher race с OutboxPoller — emails never sent

This commit is contained in:
Александр Зимин
2026-05-15 15:34:36 +00:00
parent 11d0846893
commit 9f885e50c2
3 changed files with 43 additions and 10 deletions
@@ -5,6 +5,8 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
public interface OutboxEventRepository extends JpaRepository<OutboxEvent, Long> {
@@ -20,6 +22,31 @@ public interface OutboxEventRepository extends JpaRepository<OutboxEvent, Long>
""")
List<OutboxEvent> findUnpublished(Pageable pageable);
/**
* Recent events filtered by event_type, regardless of {@code published_at}.
* Used by NotificationsDispatcher (and future side-channel processors) что
* RACE с OutboxPoller (Kafka publisher) — Poller polls every 1s и flips
* {@code published_at} быстрее чем 30s notifications tick → notifications
* никогда не находил event'ы с {@link #findUnpublished}.
*
* <p>Idempotency обеспечивается через own dedupe table (notification_log
* UNIQUE (event_id, channel, recipient)) — same event может быть processed
* многократно без duplicate sends.
*
* @param types event_type filter (e.g. RecordDraftSubmitted, ...)
* @param since createdAt cutoff — обычно last 24h чтобы не сканировать
* историческую таблицу при cold start
*/
@Query("""
SELECT o FROM OutboxEvent o
WHERE o.eventType IN :types
AND o.createdAt >= :since
AND o.dlqAt IS NULL
ORDER BY o.id ASC
""")
List<OutboxEvent> findRecentByEventTypes(
Collection<String> types, OffsetDateTime since, Pageable pageable);
/**
* Сколько ожидает публикации (без DLQ). Метрика
* {@code ordinis_outbox_pending_events}.