Power Automate dubluje akcje po błędzie: jak zatrzymać to idempotencją raz na zawsze
Power Automate potrafi dublować akcje po błędach i time-outach. Zobacz, jak wykryć źródło duplikatów, wdrożyć idempotencję, deduplikację i testy awarii, by raz na zawsze zatrzymać retry‑duplikaty.
Dlaczego Power Automate wykonuje akcje podwójnie po błędzie lub time-oucie?
Najczęstsza przyczyna to mechanizm ponawiania (retry) i niejednoznaczny wynik poprzedniej próby. Gdy akcja kończy się błędem przejściowym (np. chwilowa niedostępność usługi) albo time-outem, Power Automate może automatycznie uruchomić ją ponownie. Jeśli pierwsza próba w rzeczywistości zdążyła wykonać operację po stronie systemu zewnętrznego (np. utworzyć rekord, wysłać e-mail), ale odpowiedź nie dotarła na czas lub została przerwana, przepływ „nie wie”, że efekt już nastąpił i wykonuje akcję drugi raz.
Do podwójnych efektów dochodzi szczególnie wtedy, gdy akcja nie jest idempotentna, czyli jej ponowne wywołanie tworzy kolejny, nowy skutek zamiast bezpiecznie „powtórzyć” ten sam (np. Create zamiast Upsert). Dublowanie może też wynikać z ustawień samej akcji/konektora (polityka retry) lub z ponownego uruchomienia całego przebiegu (run) przez platformę/wyzwalacz po niepewnym zakończeniu.
- Retry po błędzie przejściowym – platforma zakłada, że kolejna próba może się udać, więc ponawia wywołanie.
- Time-out/utrata odpowiedzi – operacja mogła zostać wykonana po stronie usługi, ale brak potwierdzenia powoduje ponowienie.
- Brak idempotencji – ponowne wywołanie tej samej akcji tworzy duplikat (np. drugi rekord/wiadomość).
Jak rozpoznać, czy to retry policy, równoległość czy wyzwalacz powoduje duplikaty?
Rozróżnisz źródło duplikatów, porównując historię uruchomień (Run history) oraz czas i statusy kroków w tym samym uruchomieniu. Najpierw ustal, czy duplikacja dzieje się w ramach jednego uruchomienia, czy wynika z wielu uruchomień tego samego przepływu — to od razu zawęża przyczynę.
Jeżeli widzisz jedno uruchomienie, a w jego szczegółach ta sama akcja jest wykonywana więcej niż raz po drodze do sukcesu, najczęściej odpowiada za to retry policy. W historii akcji typowy wzorzec to: próba zakończona błędem (np. 429/5xx, timeout), po chwili ponowienie, a następnie powodzenie — a efekt uboczny to zdublowane utworzenie/aktualizacja, jeśli operacja nie była idempotentna. W praktyce odróżnia to od równoległości fakt, że wykonania są sekwencyjne w czasie (jedno po drugim) i dotyczą tej samej instancji akcji w jednym runie.
Jeżeli duplikaty powstają w tym samym uruchomieniu, ale widzisz, że podobne kroki startują w tym samym momencie lub nakładają się czasowo (np. gałęzie równoległe albo pętla, w której iteracje lecą jednocześnie), to wskazuje na równoległość. Kluczowy sygnał to równoczesne przetwarzanie tego samego rekordu/zdarzenia przez kilka ścieżek wykonania, co w Run history zwykle widać jako akcje uruchamiane w podobnych znacznikach czasu, a nie jako kolejne retry po błędzie.
Jeżeli natomiast duplikacja wynika z wielu uruchomień (w Run history pojawiają się dwa lub więcej runów, które dotyczą tego samego zdarzenia/danych wejściowych), wtedy winny jest najczęściej wyzwalacz lub warunki jego odpalania. Charakterystyczne jest to, że każdy duplikat ma osobny run (osobny czas startu, osobne wejście/trigger outputs), a kroki wewnątrz pojedynczego runu nie muszą wyglądać na ponawiane ani równoległe — po prostu przepływ został uruchomiony więcej niż raz dla tego samego bodźca.
W skrócie: retry policy rozpoznasz po sekwencji „błąd → ponowienie → sukces” w obrębie jednego runu; równoległość po nakładających się w czasie wykonaniach w jednym runie; wyzwalacz po wielu runach dla tego samego zdarzenia. To rozróżnienie jest kluczowe, bo każda z tych przyczyn wymaga innego miejsca obserwacji: retry i równoległość diagnozuje się w szczegółach runu, a wyzwalacz — na poziomie listy uruchomień i danych wejściowych triggera.
Czym jest idempotencja w automatyzacjach i dlaczego jest lepsza niż „wyłącz retry”?
Idempotencja w automatyzacjach oznacza, że to samo logiczne żądanie/zdarzenie można przetworzyć wielokrotnie, a efekt końcowy pozostanie taki sam jak po pierwszym wykonaniu. Innymi słowy: jeśli przepływ zostanie uruchomiony ponownie (np. po błędzie, timeoutcie, ponownym dostarczeniu komunikatu albo ręcznym wznowieniu), to nie spowoduje to utworzenia drugiego rekordu, wysłania duplikatu maila czy wykonania podwójnej aktualizacji — operacja jest „bezpieczna na powtórzenia”.
To jest lepsze niż podejście „wyłącz retry”, bo retry to mechanizm niezawodności, który ma maskować chwilowe problemy infrastruktury i sieci. Wyłączenie ponowień nie rozwiązuje przyczyny duplikacji, tylko zmniejsza szansę, że system sam spróbuje dokończyć pracę po błędzie przejściowym. W praktyce zwiększasz ryzyko niedokończonych procesów (brak wysłanych powiadomień, nieutworzone rekordy), a duplikaty nadal mogą powstać z innych powodów (np. ponowne dostarczenie triggera, ponowne uruchomienie przepływu, niepewność czy akcja po stronie systemu docelowego zadziałała przed timeoutem).
Idempotencja przenosi kontrolę z „czy próbować ponownie” na „czy wolno wykonać skutek biznesowy drugi raz”. Dzięki temu automatyzacja staje się odporna na naturalne zachowania platform (ponowienia, wznowienia, opóźnienia) i na typowy problem „nie wiem, czy poprzednia próba zadziałała”, bo nawet jeśli zadziałała, powtórka nie zmieni stanu drugi raz.
Jak zaprojektować klucz idempotencji, żeby był stabilny i unikalny dla danej operacji?
Klucz idempotencji musi identyfikować konkretną operację biznesową w sposób, który pozostaje taki sam przy ponowieniu (retry), a jednocześnie nie koliduje z inną operacją. W praktyce oznacza to: klucz ma wynikać z danych wejściowych opisujących „co” robisz, a nie z danych technicznych opisujących „którym uruchomieniem” to robisz.
Projektuj klucz jako deterministyczną kompozycję kilku pól: (1) stabilnego identyfikatora obiektu/rekordu, na którym wykonujesz operację, (2) typu operacji (np. create/update/send), oraz (3) stabilnego identyfikatora zdarzenia/żądania, jeśli dostępny. Następnie te elementy znormalizuj (np. przytnij spacje, ujednolić wielkość liter, uporządkuj pola) i połącz do jednego ciągu. Jeśli klucz byłby zbyt długi lub zawierał wrażliwe dane, stosuj skrót (hash) z tej znormalizowanej reprezentacji.
- Używaj pól stabilnych: ID rekordu (GUID/klucz biznesowy), numer zamówienia, identyfikator wiadomości, identyfikator transakcji, data/czas zdarzenia z systemu źródłowego (o ile jest stała dla zdarzenia).
- Unikaj pól niestabilnych: identyfikator uruchomienia flow, timestamp „teraz”, losowe GUID generowane w trakcie, licznik prób, dynamiczne tokeny sesji — one zmienią klucz przy retry i idempotencja przestanie działać.
- Uwzględnij zakres (scope): jeśli ten sam identyfikator może wystąpić w wielu systemach/środowiskach/tenantach, dodaj do klucza stały prefiks typu
system=...lubenv=..., żeby nie było kolizji. - Zadbaj o jednoznaczność semantyczną: jeśli w ramach jednego obiektu mogą wystąpić różne operacje, zawsze dodaj typ operacji (np.
invoice#123|sendEmailvsinvoice#123|postToERP).
Minimalna reguła kontrolna: dwa ponowienia tej samej operacji muszą generować identyczny klucz, a dwie różne operacje nie powinny móc wygenerować tego samego klucza. Jeśli nie masz stabilnego identyfikatora zdarzenia, buduj klucz z możliwie najmniejszego zestawu pól, które jednoznacznie opisują operację (np. ID obiektu + typ operacji + wersja/znacznik zmian), zamiast dodawać „czas wykonania”, który tylko maskuje problem i psuje stabilność.
Jak zrobić deduplikację w SharePoint, Dataverse lub SQL, gdy nie ma gotowego mechanizmu?
Jeśli źródło danych nie daje twardego zabezpieczenia przed duplikatami (np. unikalnego indeksu/klucza), deduplikację trzeba zbudować jako warstwę kontroli unikalności opartą o stabilny „klucz deduplikacyjny”. To deterministyczna wartość wyliczana z pól, które definiują „ten sam” rekord (np. OrderId, Email+Data, ExternalId). Taki klucz zapisujesz w rekordzie i traktujesz jako jedyne kryterium identyczności.
Minimalny, poprawny mechanizm ma dwa kroki: (1) przed zapisem wyszukujesz istniejący rekord po tym kluczu, (2) jeśli istnieje — aktualizujesz lub kończysz przetwarzanie, a jeśli nie — tworzysz nowy rekord. To jest deduplikacja logiczna, która działa niezależnie od systemu, ale jej niezawodność zależy od tego, czy potrafisz zapewnić atomowość „sprawdź i wstaw”.
Najpewniejsze jest wymuszenie unikalności na poziomie bazy, bo eliminuje wyścigi równoległych uruchomień. W SQL realizuje się to przez unikalny indeks/constraint na kolumnie klucza deduplikacyjnego; wtedy nawet przy równoległym wstawianiu jedna transakcja wygra, a druga dostanie błąd naruszenia unikalności, który obsługujesz jako sygnał „to już jest”. W Dataverse analogicznie służy do tego klucz alternatywny (alternate key) na polach identyfikujących rekord; Power Automate może wtedy używać „Upsert” (wstaw lub zaktualizuj) po kluczu, co praktycznie sprowadza deduplikację do operacji idempotentnej.
W SharePoint jest najtrudniej, bo listy nie mają pełnoprawnych unikalnych indeksów dla dowolnych kombinacji pól i łatwo o wyścig. Najbliższym odpowiednikiem jest kolumna z wymuszeniem unikalności (jeśli da się ją zastosować do Twojego klucza) albo deduplikacja przez osobną „tabelę blokad”/rejestr kluczy: najpierw próbujesz zapisać klucz w miejscu, które wymusza unikalność, a dopiero potem tworzysz właściwy element. Bez takiego twardego punktu (SQL/Dataverse/unikalna kolumna) samo „wyszukaj, a potem utwórz” w SharePoint pozostaje podatne na duplikaty przy równoległych uruchomieniach.
Jak obsłużyć przypadek częściowego wykonania, gdy część akcji poszła, a część nie?
Częściowe wykonanie oznacza, że w ramach jednego uruchomienia przepływu część efektów ubocznych już zaszła (np. utworzono rekord, wysłano wiadomość, zaktualizowano plik), a później wystąpił błąd. Przy ponownym uruchomieniu (retry lub ponowny start) nie wolno „powtórzyć w ciemno” całego zestawu akcji, bo to zwykle generuje duplikaty. Obsługa takiego przypadku polega na tym, aby przepływ potrafił jednoznacznie rozpoznać, które kroki zostały już skutecznie zakończone dla danego obiektu biznesowego, i wykonać tylko brakujące.
Praktycznie sprowadza się to do utrzymywania stanu postępu powiązanego z tym samym kluczem idempotencji (tym samym „przetwarzanym przypadkiem”). Stan może być przechowywany jako rekord kontrolny (np. w tabeli/datasource), jako pola statusu w przetwarzanym obiekcie, albo jako osobny „dziennik” kroków. Każdy krok, który ma efekt uboczny, powinien działać w schemacie: najpierw sprawdź w stanie, czy krok już wykonano, a jeśli nie — wykonaj akcję i dopiero po sukcesie zapisz/oznacz jej wykonanie. Dzięki temu ponowne uruchomienie przechodzi przez te same kroki, ale pomija te, które są już oznaczone jako zakończone.
Jeżeli część wykonanych akcji da się bezpiecznie odwrócić, alternatywą jest kompensacja: po wykryciu błędu wycofujesz już wykonane skutki uboczne, a następnie uruchomienie retry zaczyna „na czysto”. W Power Automate zwykle realizuje się to przez wydzieloną ścieżkę obsługi błędu (np. zakres z konfiguracją „run after”), która wykonuje akcje odwracające tylko wtedy, gdy odpowiadające im kroki zostały wcześniej potwierdzone jako wykonane. Gdy odwrócenie nie jest możliwe, właściwą strategią jest właśnie wznowienie od miejsca przerwania oparte o zapisany stan.
Jak testować i symulować awarie, żeby mieć pewność, że duplikaty nie wrócą?
Testy muszą celowo wprowadzać sytuacje, w których Power Automate najczęściej „powtarza” pracę: timeouty, ponowne próby (retry), błędy po stronie konektora oraz przerwanie przepływu po wykonaniu efektu ubocznego. Celem nie jest sprawdzenie, czy flow kończy się sukcesem, tylko czy po awarii i wznowieniu (lub ponownym uruchomieniu) efekt końcowy pozostaje pojedynczy.
Najpierw zdefiniuj mierzalne kryterium braku duplikatów: dla danego klucza idempotencji (np. ID rekordu, MessageId, BusinessKey) w systemie docelowym ma istnieć dokładnie jeden rezultat (jedno zamówienie, jedna pozycja w SharePoint, jeden wpis w Dataverse). Dopiero potem planuj testy „awaryjne” wokół punktów, gdzie powstaje efekt uboczny (create/update, wysyłka, zapis pliku).
- Symuluj awarię tuż po efekcie ubocznym: dodaj kontrolowany „fail” bezpośrednio po akcji tworzącej/zmieniającej dane (np. Condition/Terminate z Failed). Następnie uruchom ponownie ten sam input. Oczekiwany wynik: brak nowego rekordu, a flow przechodzi ścieżkę „już przetworzone”.
- Wymuś ponowne próby i opóźnienia: testuj zachowanie przy retry, ustawiając w akcjach ryzykownych (konektory HTTP/Dataverse/SharePoint) włączone ponowne próby i/lub sztuczne opóźnienia (Delay) przed zapisem. Oczekiwany wynik: nawet jeśli akcja wykona się dwa razy wskutek retry, drugi przebieg nie tworzy duplikatu (jest zablokowany warunkiem/idempotency key).
- Testuj równoległość i wyścigi: uruchom równolegle kilka instancji flow z tym samym kluczem (np. równoległe wyzwolenie, ręczne uruchomienia, lub batch). Oczekiwany wynik: dokładnie jedna instancja „wygra” stworzenie rezultatu, pozostałe zakończą się bez efektu ubocznego.
- Testuj wznowienia po przerwaniu: przerwij przepływ w połowie (np. anuluj run, spowoduj błąd w kolejnej akcji), a potem doprowadź do ponownego przetworzenia tego samego zdarzenia (ponowny trigger lub replay po stronie źródła). Oczekiwany wynik: idempotencja rozpoznaje, że praca została już wykonana i nie tworzy kolejnego rezultatu.
Dla pewności wykonuj testy na danych „produkcyjnie podobnych” (te same ograniczenia unikalności, te same uprawnienia konektorów) i zawsze weryfikuj wynik w systemie docelowym, a nie tylko po statusie run. Test uznajesz za zaliczony dopiero wtedy, gdy po wielu powtórzeniach tej samej wiadomości/rekordu liczba rezultatów pozostaje stała (1) i w logice przepływu widać deterministyczne „wyjście bez działania” przy kolejnym podejściu.
Jakie ustawienia produkcyjne zmniejszają ryzyko duplikacji bez utraty niezawodności?
W produkcji duplikacje najczęściej wynikają z automatycznych ponowień (retry) i równoległości: gdy akcja wykonała się po stronie systemu zewnętrznego, ale Power Automate nie dostał potwierdzenia (timeout, chwilowy błąd), uruchamia retry i efekt uboczny powtarza się. Celem ustawień produkcyjnych nie jest „wyłączenie niezawodności”, tylko takie ograniczenie ponowień i współbieżności, aby minimalizować powtórne skutki uboczne, a jednocześnie zachować odporność na krótkie awarie.
Po pierwsze, zarządzaj ponowieniami na poziomie akcji. Dla kroków, które mają efekt uboczny (np. utworzenie rekordu, wysłanie wiadomości, płatność, aktualizacja stanu), ustaw Retry policy świadomie: redukuj liczbę prób i wydłużaj odstępy, aby nie wykonywać tej samej operacji kilka razy w krótkim oknie. Gdy dana operacja nie jest bezpieczna do powtarzania, lepszym ustawieniem bywa całkowite wyłączenie retry dla tej konkretnej akcji i obsłużenie błędu inną ścieżką (np. oznaczenie do ręcznej weryfikacji), niż automatyczne powielanie skutków ubocznych.
Po drugie, kontroluj równoległość w miejscach, gdzie ten sam obiekt biznesowy może być przetwarzany równocześnie. Jeśli włączysz przetwarzanie współbieżne (concurrency) na wyzwalaczu lub w pętli, to łatwo o wyścigi: dwa przebiegi podejmują identyczną akcję zanim zobaczą rezultat drugiego. W produkcji ustawiaj współbieżność konserwatywnie (często 1) dla przepływów aktualizujących te same encje lub korzystających z tych samych zasobów, a zwiększaj ją tylko tam, gdzie masz pewność braku konfliktów.
Po trzecie, ustawiaj rozsądne limity czasu (timeout) dla akcji zależnych od API. Zbyt krótki timeout zwiększa prawdopodobieństwo, że operacja zakończy się po stronie usługi, ale przepływ uzna ją za nieudaną i ponowi. Zbyt długi timeout może blokować przetwarzanie i potęgować opóźnienia. W praktyce timeout powinien odzwierciedlać realne SLA/latencję danego API i charakter operacji (inne dla „create”, inne dla „get”).
Po czwarte, projektuj ścieżki błędów tak, by nie generowały ponownego wykonania „create” jako domyślnej reakcji. W produkcji warto rozdzielać błędy przejściowe (gdzie retry ma sens) od błędów logicznych/konfliktów (gdzie retry tylko powieli problem). Jeśli konektor zwraca konflikty lub odpowiedzi wskazujące, że zasób już istnieje, lepiej przejść w gałąź weryfikacji/odczytu niż ponawiać tworzenie.
Te ustawienia nie zastępują idempotencji, ale istotnie zmniejszają liczbę sytuacji, w których Power Automate sam „dubluje” operacje: ograniczają agresywne retry, eliminują wyścigi przez równoległość i redukują fałszywe błędy wynikające z nietrafionych timeoutów.