Twój strumień delta gubi późne commity: zbuduj niezawodny kursor
Jeden wiersz dotarł za późno, bo znak wodny nie był granicą commita. Prześledź awarię, a potem zdefiniuj pokrycie, granicę, retencję i bezpieczne wznowienie.
Oto awaria, którą widziałem dwa razy, w dwóch różnych silnikach. Eksport uruchamia się o 10:00 i zapisuje najwyższy zaobserwowany znacznik czasu ostatniej modyfikacji. Jedna transakcja, rozpoczęta o 09:58, jest wciąż otwarta, commituje o 10:03 i niesie znacznik czasu z chwili rozpoczęcia. Następny eksport prosi tylko o wiersze nowsze niż 10:00 i nigdy jej nie widzi. Rozmiar transferu był doskonały, a dane błędne.
Prześledź eksport, który zgubił wiersz
Śledź kolejność zdarzeń, a nie SQL. Aplikacja zapisuje czas modyfikacji wiersza przy rozpoczęciu transakcji, a nie przy commicie. Eksporter czyta znak wodny zegara ściennego, gdy transakcja jest jeszcze otwarta. Potem transakcja commituje z czasem modyfikacji starszym niż zapisany znak wodny. Następna partia konsumenta zaczyna się po znaku wodnym, więc wiersz zostaje za kursorem i nigdy nie zostanie ponownie wybrany.
Nic nie rejestruje błędu, bo nic nie zawiodło. To czyni tę klasę błędów kosztowną: strumień delta jest szybki, liczby wierszy wyglądają wiarygodnie, a brakujący wiersz wypływa tygodnie później jako zgłoszenie uzgodnienia, którego nikt nie potrafi odtworzyć.
Numery sekwencji mają ten sam kształt wady z innego powodu. Monotoniczny ID mówi o kolejności przydziału wartości, a nie o kolejności commitów transakcji. Transakcja, która wzięła niski numer, może zacommitować po tej, która wzięła wysoki, więc kursor oparty na kolejności przydziału też może zostawić dziurę.
Pięć obietnic, które musi spełnić strumień
Zanim wybierzesz transport, poproś system źródłowy o kontrakt z pięcioma klauzulami. Jeśli brakuje którejkolwiek, masz heurystykę, a nie strumień zmian.
- Pokrycie: wstawienia, aktualizacje i usunięcia, każde ze stabilnym kluczem potrzebnym do zastosowania.
- Granica: co oznacza wartość kursora i co zawiera spójna partia.
- Spójność: jak czytać powiązane tabele bez mieszania niezgodnych stanów źródła.
- Retencja: jak długo odłączony konsument może wznowić od zapisanej pozycji.
- Reinicjalizacja: obsługiwany pełny zrzut, gdy ta pozycja nie jest już ważna.
Obsługiwany mechanizm może spełnić wszystkie pięć. Dokumentacja SQL Servera o śledzeniu zmian to jeden konkretny opublikowany przykład: definiuje minimalną ważną wersję, rejestruje usunięcia oraz zmiany i opisuje transakcję zrzutu obejmującą walidację, przechwycenie wersji i powiązane zapytania o zmiany. To przykład tego, czego żądać od źródła, a nie składnia do wklejenia w innym silniku.
Zaprojektuj punkt kontrolny jako część potoku
Zapisana pozycja konsumenta to prawdziwa maszyna stanów, więc tak ją zapisz. Kursor może przesuwać się tylko przez przepływ pracy, który już zastosował partię i zacommitował transakcję docelową.
cursor = durable_checkpoint() # last fully applied position
batch = extract(cursor, upper_bound) # one consistent batch
apply(batch) # idempotent at the target
commit()
checkpoint(batch.upper_bound) # only now
Jeśli dostawa zawiedzie w połowie, uzgodnij tę samą partię, zamiast wymyślać nową pozycję źródła. Ponowne wyciągnięcie ze świeższą górną granicą to droga, na której częściowe zastosowanie staje się trwałą luką. Reguła lustra obowiązuje też po drugiej stronie: idempotentne zastosowanie u celu zapobiega duplikatom i nic nie robi dla zmiany, której ekstrakcja nigdy nie widziała.
Usunięcia wymagają zapisów, a nie wniosków
Porównywanie wierszy, które nadal istnieją, nie powie konsumentowi, które klucze zniknęły. Źródło, które nigdy nie rejestruje usunięć, zostawia dwie niedoskonałe opcje: trzymać historię kluczy wystarczająco długo do diffu albo uruchamiać jawnie pełne uzgadnianie według harmonogramu. Oba to decyzje projektowe z kosztem, i żadna nie istnieje, jeśli ktoś jej nie posiada.
Retencja to ten sam rodzaj decyzji. Każdy obsługiwany strumień zmian ma okno; wskaż je w kontrakcie i ostrzegaj, gdy punkt kontrolny konsumenta się do niego zbliża. Konsument, który budzi się po dwutygodniowej przerwie, powinien przeczytać jasny błąd „wymagana reinicjalizacja”, a nie po cichu otrzymać częściową partię.
Potem zmierz, czy delta była warta zachodu
Powrót do porównywania każdego klucza i skrótu bywa słuszny i warto wiedzieć, ile kosztuje. Nadal przesyłasz informacje o każdym wierszu, skróty nie dają spójnego zrzutu między tabelami, a schemat skrótów niesie własne pytania o reprezentację i kolizje. Mniej bajtów z weryfikowalną granicą to cel; mniej bajtów, które po cichu gubią zacommitowaną zmianę, to wcale nie delta.
Kolumna ostatniej modyfikacji to wskazówka. Kursor, który przetrwa późny commit, usunięcie i długą przerwę, to kontrakt.