Dlaczego integracja tworzy duplikaty danych i jak temu zapobiec?

Dlaczego integracja tworzy duplikaty danych i jak temu zapobiec?

Dwa identyczne zamówienia, podwójny wpis w CRM albo dwa powiadomienia o tej samej płatności. Takie błędy potrafią pojawić się nawet wtedy, gdy każdy system osobno działa poprawnie. Problem kryje się na styku aplikacji: wiadomość może dotrzeć ponownie, odpowiedź może zaginąć, a dwa procesy mogą zacząć pracę jednocześnie.

Dobra integracja powinna zakładać, że powtórzenia wystąpią. Trzeba zaprojektować sposób ich rozpoznawania oraz ustalić, co zrobić z operacją, której wynik pozostaje nieznany.

Skąd biorą się duplikaty?

Przykład hipotetyczny: sklep wysyła zamówienie do aplikacji obsługującej sprzedaż. Aplikacja zapisuje dokument, ale połączenie urywa się przed zwróceniem odpowiedzi. Sklep widzi błąd i wysyła żądanie ponownie. Jeżeli aplikacja przy każdym wywołaniu tworzy nowy dokument, powstają dwa zapisy tej samej sprzedaży.

Samo wydłużenie limitu czasu nie rozwiązuje tego problemu. Potrzebne jest rozpoznanie, że druga próba dotyczy tej samej czynności. W dokumentacji Stripe opisano zarówno ponowne dostarczanie webhooków, jak i możliwość otrzymania tego samego zdarzenia więcej niż raz. To zachowanie trzeba obsłużyć w integracji, zamiast traktować je jako wyjątkową awarię. Źródło: Stripe — obsługa powtórzeń.

Idempotencja: powtórzenie bez drugiego skutku

Idempotencja oznacza tu, że ponowienie tej samej operacji nie tworzy kolejnego skutku biznesowego. Można wielokrotnie przekazać polecenie „utwórz dokument dla zamówienia X”, ale dokument ma powstać jeden.

Najpierw ustal identyfikator operacji. Dla utworzenia dokumentu może to być zestaw: firma, system źródłowy, identyfikator zamówienia i rodzaj dokumentu. W systemie obsługującym wiele firm sam numer zamówienia może być niewystarczający. Z kolei przy aktualizacjach trzeba uwzględnić wersję lub identyfikator zmiany — inaczej blokada powtórzeń zablokuje również prawidłowe kolejne aktualizacje.

Rozdziel dwa poziomy: identyfikator zdarzenia rozpoznaje ponowne doręczenie wiadomości, a identyfikator operacji biznesowej chroni konkretny skutek. Dwa różne zdarzenia mogą przecież dotyczyć tej samej czynności.

Kontrola w aplikacji potrzebuje wsparcia bazy

Popularny pomysł brzmi: „sprawdź, czy rekord istnieje, a jeśli nie, dodaj go”. Przy równoległych żądaniach oba procesy mogą sprawdzić bazę przed pierwszym zapisem i oba uznać, że rekord nie istnieje.

Warto przenieść regułę unikalności także do bazy danych. SQL Server pozwala zdefiniować ograniczenie UNIQUE obejmujące wybrane kolumny. Dokumentacja Microsoft opisuje tworzenie takich ograniczeń. Poniższy fragment jest propozycją reguły dla przykładowej tabeli, a nie kompletnym wdrożeniem:

ALTER TABLE dbo.ImportedOrders
ADD CONSTRAINT UQ_ImportedOrders_Source
UNIQUE (TenantId, SourceSystem, ExternalOrderId);

Te kolumny powinny być wymagane i mieć jasno określony format. Przed dodaniem ograniczenia do istniejącej bazy trzeba sprawdzić obecne duplikaty. Aplikacja musi następnie rozpoznawać konflikt tej konkretnej reguły i odnaleźć istniejący rekord; innych błędów zapisu nie wolno automatycznie uznawać za udane powtórzenie.

Potwierdzenie odbioru i wykonanie zadania to dwie rzeczy

Praktyczny wariant architektury to trwała skrzynka zdarzeń, czyli inbox. Odbiorca weryfikuje wiadomość i zapisuje ją w bazie z unikalnym kluczem. Niezależny proces roboczy wykonuje właściwe zadanie, przechowując jego status oraz informacje potrzebne do ponowienia po awarii.

GitHub zaleca szybkie odpowiadanie na webhooki i wskazuje kolejkę jako sposób przeniesienia pracy do tła. Opisuje też identyfikator X-GitHub-Delivery, który pozostaje taki sam podczas ponownego dostarczenia. Źródło: dobre praktyki GitHub. Konkretne nagłówki oraz reguły ponawiania zależą od dostawcy.

Schemat integracji: weryfikacja webhooka, trwały inbox z unikalnym kluczem, nowe zdarzenie kierowane do pracy w tle, duplikat bez nowego zadania; odpowiedź 2xx po trwałym przyjęciu.
Diagram autorski przygotowany dla Inkluzji. Schemat koncepcyjny, bez danych klientów.

W proponowanym wariancie odpowiedź sukcesu następuje dopiero po trwałym przyjęciu zdarzenia. Duplikat nie dodaje nowego zadania, ale istniejące zadanie nadal może czekać na wykonanie lub ponowienie. Proces roboczy potrzebuje mechanizmu przejmowania zadań, aby kilka instancji nie wykonywało ich równolegle.

A co, jeśli drugi system już wykonał operację?

Najtrudniejszy moment następuje wtedy, gdy zewnętrzna aplikacja wykonała żądanie, ale lokalnie nie udało się zapisać wyniku. Sama unikalność wpisu w inboxie nie chroni przed ponownym wystawieniem dokumentu po drugiej stronie.

Jeżeli docelowe API obsługuje klucze idempotencji, powtórzenie tej samej operacji powinno korzystać z tego samego klucza. Stripe udostępnia taki mechanizm dla żądań POST, z własnymi zasadami przechowywania kluczy i sprawdzania parametrów. Źródło: Stripe — idempotentne żądania. Tych zasad nie należy automatycznie przenosić na inne API.

Jeżeli API nie ma odpowiedniego mechanizmu, sprawdź możliwość wyszukania wyniku po identyfikatorze zewnętrznym. Gdy wyniku nie da się potwierdzić, oznacz operację jako wymagającą wyjaśnienia. Ślepe ponawianie wystawienia dokumentu może być gorsze od chwilowego zatrzymania jednego zadania.

Testy, które warto wykonać przed uruchomieniem

  • Wyślij tę samą wiadomość dwukrotnie oraz równolegle. Sprawdź liczbę skutków biznesowych, nie tylko odpowiedź HTTP.
  • Zatrzymaj proces po zapisie do inboxa. Po uruchomieniu zadanie powinno zostać odnalezione i wykonane.
  • Przerwij połączenie po wykonaniu operacji w zewnętrznym systemie. Sprawdź, jak integracja ustali wynik.
  • Przekaż dwie prawidłowe aktualizacje tego samego obiektu. Obie powinny zostać uwzględnione.
  • Sprawdź błędny podpis, nieprawidłowe dane i niedostępną bazę. Monitorowanie powinno odróżniać te sytuacje od duplikatu.

Dla każdego zadania warto zapisać identyfikator operacji, status, liczbę prób i krótki opis ostatniego błędu. Pełne dane klientów oraz sekrety nie są potrzebne w zwykłych logach diagnostycznych. Alert powinien wskazywać także zadania, które zbyt długo pozostają bez postępu.

Od czego zacząć w istniejącej integracji?

Wybierz jedną operację, której powtórzenie kosztuje najwięcej: utworzenie dokumentu, rezerwację towaru lub wysłanie płatnego powiadomienia. Ustal jej klucz, prześledź zapis i zasymuluj utratę odpowiedzi. Dopiero potem rozszerzaj zabezpieczenia na kolejne procesy.

Jeżeli łączysz aplikacje korzystające z różnych baz, na blogu znajdziesz również starszy tekst „MS SQL Server i Linked Server MySQL”. Omawia inną warstwę łączenia systemów; jego historyczna konfiguracja wymaga sprawdzenia dla obecnego środowiska. W sprawie konkretnego procesu do zintegrowania możesz skorzystać z kontaktu z Inkluzją.

Dokumentację źródłową sprawdzono 20 października 2026 r. Przykłady oraz projekt architektury mają charakter edukacyjny; nie opisują realizacji klienta.