Commit c2ae8bd
Skip already-processed transactions on restart (#4668)
## Summary
On restart, the persistent replication slot can replay transactions the
consumer has already applied and persisted. The consumer deduplicated
these on its multi-fragment path, but the complete-transaction **fast
paths short-circuited before reaching that check** — so a replayed
transaction was re-written to the shape log (duplicating ops) and
re-notified to dependent subquery materializers, which re-applied it and
crashed with `Key ... already exists`.
This is **bug 2** from the restart-aware oracle property-test triage
(see #4648 / `packages/sync-service/bugs.md`).
## Problem
After a `StackSupervisor` restart, the source consumer reconnects to the
persistent slot and replays from its confirmed LSN, which can lag the
shape's on-disk offset. Complete single-fragment transactions (the
common case — one statement per txn) took the fast path and were
re-processed:
- **Normal shapes:** duplicate / orphan operations appended to the shape
log.
- **Subquery shapes:** the re-notified duplicate is re-applied by the
dependency materializer, which is a stateful reducer and crashes on the
duplicate insert (`"Key ... already exists"`). That crash then cascades
into tearing down and *removing* the healthy dependent shapes and
returning `409 must-refetch` to clients.
## Solution
Consolidate the offset dedup into a **single `handle_txn_fragment/2`
clause** that skips any fragment already at or below `latest_offset`
(routing to the existing `skip_txn_fragment`), placed before the
complete-transaction fast paths. The dedup now lives in one place and
covers single- and multi-fragment paths uniformly, replacing the
separate `fragment_already_processed?/2` check. Normal operation is
untouched: new transactions have `offset > latest_offset`, so the guard
is false and they fall through to the existing clauses.
This relies on a transaction's fragments being entirely at-or-below or
entirely above `latest_offset` (which only advances at commit
boundaries) — the same assumption the previous per-fragment check
already made.
## Test Plan
- [x] `test/electric/shapes/consumer_test.exs` — two regression tests
(single-fragment and multi-fragment) replay an already-applied
transaction straight to the consumer, bypassing the log collector's own
dedup (as happens on restart with a fresh collector), and assert it is
skipped: no `append_to_log!`, no `:new_changes` notification, log
unchanged. Fail without the fix.
- [x] `test/electric/shapes/` + `test/electric/replication/` — 937
tests, 100 doctests, 6 properties, 0 failures.
---
Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent ed04ee4 commit c2ae8bd
3 files changed
Lines changed: 183 additions & 6 deletions
File tree
- .changeset
- packages/sync-service
- lib/electric/shapes
- test/electric/shapes
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
587 | 587 | | |
588 | 588 | | |
589 | 589 | | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
590 | 606 | | |
591 | 607 | | |
592 | 608 | | |
| |||
652 | 668 | | |
653 | 669 | | |
654 | 670 | | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
655 | 686 | | |
656 | 687 | | |
657 | 688 | | |
658 | 689 | | |
659 | 690 | | |
660 | | - | |
661 | | - | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
662 | 695 | | |
663 | 696 | | |
664 | 697 | | |
| |||
1097 | 1130 | | |
1098 | 1131 | | |
1099 | 1132 | | |
1100 | | - | |
1101 | | - | |
1102 | | - | |
1103 | | - | |
1104 | 1133 | | |
1105 | 1134 | | |
1106 | 1135 | | |
| |||
Lines changed: 143 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
725 | 725 | | |
726 | 726 | | |
727 | 727 | | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
728 | 784 | | |
729 | 785 | | |
730 | 786 | | |
| |||
834 | 890 | | |
835 | 891 | | |
836 | 892 | | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
837 | 980 | | |
838 | 981 | | |
839 | 982 | | |
| |||
0 commit comments