Przejdź do treści

ERROR_BLOCKED_TOKEN: limit API BaseLinkera dzielony z ERP — jak ułożyć crony sklepu i ponawianie eksportu zamówień

Sklep B2B na Magento wymieniał dane z BaseLinkerem, a system ERP zasilał tam stany przez to samo API. W godzinach pracy w logach pojawiał się kod ERROR_BLOCKED_TOKEN: zamówienie złożone rano nie trafiało do BaseLinkera, synchronizacja stanów urywała się przed końcem katalogu, a harmonogram Magento pokazywał przy niej „success”. Opisujemy diagnozę w 10 minut, nowy podział cronów, ponawianie eksportu bez duplikatów i „okno ciszy”, które podpięte w złym miejscu wyłączało nam na kilka godzin dziennie maile z potwierdzeniami i indeksy.

Blokada nie jest tu błędem, tylko sygnałem

BaseLinker odrzuca zapytania, gdy limit jest wyczerpany. Tego sklep nie zmieni. Problemem było to, jak sklep na blokadę reagował: eksport zamówienia próbował raz i się poddawał, pełna synchronizacja stanów startowała w godzinach, w których limit zużywał już ERP, przerwany przebieg zaczynał od nowa, a harmonogram Magento raportował sukces. Każdą z tych rzeczy da się naprawić po stronie sklepu, bez zmian w ERP.

Objaw

Kod ERROR_BLOCKED_TOKEN w odpowiedziach API rano i po południu, w nocy żadnego. Zamówienie bez odpowiednika w BaseLinkerze, synchronizacja stanów przerwana, w harmonogramie „success”.

Pierwszy test

Rozkład błędów z logu integracji na godziny doby i obok niego lista systemów, które używają tego samego tokenu, z godzinami ich wysyłek.

01

Objaw: błędy BaseLinkera tylko w godzinach pracy

Ten sam problem dawał trzy różne objawy, zgłaszane przez różne osoby, więc długo nie wyglądał na jeden.

  • Zamówienie nie dotarło do BaseLinkera. Klient złożył je rano, w Magento było poprawne, w BaseLinkerze go nie było. Jedyny ślad: komentarz w historii zamówienia z treścią błędu. Eksport uruchamiany zaraz po złożeniu zamówienia próbował jeden raz. Zamówienie dosłano ręcznie, gdy ktoś zauważył brak.
  • Synchronizacja stanów urywała się w trakcie. Przebieg stronicował katalog BaseLinkera i przerywał się na którejś stronie przed końcem. Produkty z dalszych stron zostawały ze stanami z poprzedniego przebiegu.
  • Harmonogram pokazywał sukces. W cron_schedule ten sam przebieg miał status success, a w logu Magento obok siebie stały wpis „Cron Job … is successfully finished” i wpis naszej synchronizacji o błędzie API.

Sama odpowiedź BaseLinkera jest jednoznaczna: dokumentacja API opisuje, że przy błędzie pole status ma wartość ERROR, a szczegóły są w polach error_code i error_message. W naszych logach error_code miał wartość ERROR_BLOCKED_TOKEN. Błędy wypadały rano i późnym popołudniem, czyli w godzinach pracy biura. Okno nocne było czyste.

02

Jeden token, wiele integracji: skąd bierze się blokada

Dokumentacja BaseLinkera podaje jedną liczbę: limit to 100 zapytań na minutę. Pisze też, że token jest przypisany do konta użytkownika. Nie ma tam mowy o osobnej puli dla każdej integracji. W tym koncie z jednego tokenu korzystały jednocześnie:

  • system ERP, który rano wysyłał hurtowe aktualizacje stanów i dodawał produkty do katalogu,
  • skrypty biurowe, które dokładały produkty do katalogu,
  • sklep: stany, ceny, treści katalogu i eksport zamówień,
  • narzędzie marketingowe podłączone do tego samego konta.

Każdy z tych systemów osobno mieścił się w limicie. Razem, rano, już nie. Sklep dokładał do tego najgorszy możliwy kształt ruchu: pełna synchronizacja stanów przechodziła cały katalog strona po stronie, czyli serię kilkuset zapytań w ciągu kilku minut, i odpalała się także w godzinach, w których ERP wysyłał stany. Eksport zamówienia trafiał na ten sam wyczerpany limit i po jednej odmowie kończył pracę.

Czego dokumentacja nie mówi: jak długo trwa blokada i czy osobny token dla ERP dostaje osobną pulę. U nas późniejsze próby z tym samym tokenem przechodziły bez żadnej zmiany po naszej stronie, więc blokadę traktujemy jako stan przejściowy. Jeśli planujesz rozdzielenie tokenów jako rozwiązanie, potwierdź najpierw w Base, jak liczony jest limit. Na tym założeniu nie budowaliśmy niczego.

Z perspektywy sklepu widać tylko własne zapytania. Kto zużył resztę limitu, wiesz dopiero po rozmowie z osobą, która zarządza ERP i skryptami. Bez tej rozmowy każda zmiana harmonogramu sklepu jest strzałem.
03

Diagnoza: kto zużywa limit API

  1. Rozkład błędów na godziny. Z logu integracji wyciągnij linie z ERROR_BLOCKED_TOKEN i policz je w podziale na godziny doby. Skupienie w godzinach pracy przy czystej nocy oznacza konkurencję o limit. Błędy w nocy, gdy pracuje tylko sklep, oznaczają, że sklep sam przekracza limit i trzeba zacząć od jego pętli.
  2. Własne zapytania na minutę. Sprawdź, czy integracja zapisuje w logu każde wywołanie API z czasem i nazwą metody. Jeśli nie zapisuje, to jest pierwsza poprawka, bo bez tego nie policzysz własnego udziału. Jeśli zapisuje, policz zapytania w najgorszej minucie pełnej synchronizacji.
  3. Lista użytkowników tokenu. Tokeny generuje się w panelu BaseLinkera w sekcji, którą dokumentacja opisuje jako „Account & other → My account → API”. Spisz, który system używa którego tokenu, i zapytaj administratora ERP, o której i jak często ERP wysyła stany.
  4. Zamówienia bez odpowiednika. Wypisz zamówienia z ostatnich dni, które nie mają zapisanego identyfikatora zamówienia BaseLinkera albo mają w historii komentarz z błędem eksportu.
  5. Harmonogram kontra log. Dla minut z błędem w logu integracji sprawdź status tego samego zadania w cron_schedule. Jeśli jest tam success, monitoring oparty na harmonogramie nie pokaże Ci tej awarii nigdy.

Dwie pułapki przy czytaniu danych. Pierwsza: w naszej instalacji znaczniki czasu w cron_schedule (scheduled_at, executed_at) są w UTC, a wyrażenia cron w konfiguracji zadań liczą się w czasie lokalnym. Wpis z godziną 03:00 w tabeli to zadanie zaplanowane na 05:00 czasu polskiego latem. Szukaliśmy przyczyny w dzienniku systemowym o złej godzinie. Druga: zanim uznasz problem za systemowy, policz przebiegi z błędem na tle wszystkich przebiegów. Po zmianie harmonogramu blokady okazały się u nas sporadyczne, z dniami bez żadnego przypadku, choć wcześniej opisywaliśmy je jako codzienne.

04

Nowy harmonogram: stany, ceny, treści — co, jak często i kiedy

Zasada jest prosta: każdy rodzaj danych dostaje częstotliwość wynikającą z tego, jak szybko się zmienia i ile kosztuje nieaktualna wartość, a ciężkie przebiegi idą tam, gdzie limitu nikt inny nie zużywa.

Harmonogram zadań sklepu korzystających z API BaseLinkera przed zmianą i po niej
DaneWcześniejPo zmianieDlaczego
stanyco 30 minutco godzinęsklep pokazuje kopię stanów z BaseLinkera; o rezerwacji przy zamówieniu decyduje BaseLinker, nie ta kopia
cenykilka razy na godzinęraz na dobę, w nocycenniki B2B zmieniają się rzadko, a noc była jedynym oknem bez blokad
treści kataloguw nocy, część przebiegów wcześnie ranow nocy i późnym wieczoremporanne przebiegi wypadły w oknie ciszy (sekcja 05)
zamówieniaraz, zaraz po złożeniuzaraz po złożeniu plus osobne ponawianiezamówienie nie może czekać na nocne okno
zadania wewnętrzne Magentobez zmianbez zmiannie wołają API BaseLinkera

Druga zmiana dotyczyła samego crona. Zadania integracji siedziały w tej samej grupie co maile z potwierdzeniami, indeksy i kolejki. Magento w jednym uruchomieniu cron:run wykonuje dane zadanie najwyżej raz, a wielominutowa synchronizacja trzymała wspólną blokadę procesu, więc lekkie zadania traciły kolejne uruchomienia. Integracja dostała własną grupę crona z osobnym timerem i osobną blokadą. Lekkie zadania chodzą swoim torem, a w torze integracji można robić rzeczy, których w głównym robić nie wolno, na przykład okno ciszy.

Jak podzielić odpowiedzialność za stany między ERP, BaseLinker i sklep, opisujemy w poradniku o synchronizacji stanów między ERP, sklepem i BaseLinkerem. Co musi zawierać pozycja zamówienia, żeby BaseLinker w ogóle zdjął stan, jest w poradniku zamówienie z Magento nie zmniejsza stanu w BaseLinkerze.

Nowy harmonogram zmniejsza udział sklepu w limicie, ale nie usuwa blokad całkowicie. Dopóki ERP wysyła hurtowe aktualizacje w godzinach pracy, część minut będzie zajęta. Dlatego reszta tego tekstu dotyczy tego, jak sklep ma się zachować, gdy blokada i tak przyjdzie.
05

„Okno ciszy” tylko dla integracji, nie dla całego crona

Okno ciszy to kilka porannych godzin, w których tor integracji nie startuje, bo wtedy ERP zasila stany do BaseLinkera. Warunek sprawdzany przed każdym przebiegiem mówi „teraz nie” i przebieg jest pomijany. Zamówienia nie wypadają przez to z obiegu: eksport zaraz po złożeniu dalej próbuje, a ponawianie podejmie je po końcu okna.

Pułapka konfiguracji: warunek pod złym timerem. W tym wdrożeniu warunek okna ciszy był podpięty pod oba timery, integracji i główny. Główny cron z definicji wyklucza grupę integracji, więc nie wysyła do BaseLinkera ani jednego zapytania; okno ciszy niczego tam nie chroniło. Wyłączało za to na kilka godzin każdego dnia maile z potwierdzeniami zamówień, aktualizację siatki zamówień w panelu, indeksy i konsumentów kolejek. Trwało to sześć dni. Nie wykrył tego żaden alarm, bo pominięte uruchomienie nie jest błędem: nic się nie wywraca, nic nie trafia do logu wyjątków. Widać to dopiero w danych o pominiętych uruchomieniach w przeglądzie zadań. Naprawa: warunek został tylko na timerze integracji.

Ten sam przegląd pokazał drugą pułapkę. Zadanie, którego godzina wypada w oknie ciszy, nie wykona się nigdy i nie zostawi śladu. Magento generuje wpisy harmonogramu tylko kilka minut do przodu, w trakcie uruchomień crona. Gdy całe uruchomienie jest pomijane, wpisów nie ma, więc zapytanie o to zadanie w cron_schedule zwraca zero wierszy w każdym statusie: ani missed, ani error, ani pending. Jedno z naszych zadań dobowych nie wykonało się ani razu przez te same sześć dni. Licznik missed też myli: przy kilkugodzinnej przerwie pokazuje tylko wpisy z pierwszych kilkunastu minut, bo dalej nie ma czego oznaczyć.

Po wprowadzeniu okna ciszy wypisz wszystkie zadania toru integracji i sprawdź, czy któreś ma godzinę uruchomienia wewnątrz okna. Takie zadanie przenieś przed okno albo po nim. Nie licz na to, że brak wpisów w harmonogramie zwróci czyjąś uwagę.
06

Zamówienie, które nie przeszło: ponawianie bez duplikatów

Eksport zaraz po złożeniu zamówienia zostaje, bo obsługa i magazyn chcą widzieć zamówienie w BaseLinkerze od razu. Brakowało drugiej połowy: mechanizmu, który zajmie się zamówieniem po odmowie. Kierunek naprawy:

  • Jeden rekord na zamówienie. Każda próba eksportu aktualizuje wiersz w tabeli historii kluczowanej identyfikatorem zamówienia w sklepie: status, liczba prób, ostatni kod błędu, a po sukcesie identyfikator zamówienia w BaseLinkerze. Kolejna próba nie dopisuje nowego wiersza, tylko aktualizuje ten sam.
  • Osobne zadanie ponawiania. Bierze tylko zamówienia ze statusem błędu i bez identyfikatora BaseLinkera, z ograniczonego okna czasu i z limitem prób. Po wyczerpaniu prób zamówienie ma trafić do człowieka, a nie krążyć w nieskończoność.
  • Idempotencja po identyfikatorze zamówienia sklepu. Zamówienie, które ma już identyfikator BaseLinkera, nie jest wysyłane automatycznie drugi raz. Ręczne ponowne wysłanie wymaga świadomego wymuszenia.
  • Rozróżnienie rodzajów błędu. Odmowa z ERROR_BLOCKED_TOKEN to odpowiedź API: w naszych danych takie zamówienie nie istniało w BaseLinkerze i ponowienie go nie dublowało. Timeout albo zerwane połączenie to brak wiedzy: zamówienie mogło powstać, a odpowiedź się zgubiła. Zanim ponowisz zamówienie po timeoucie, sprawdź, czy nie ma go już w BaseLinkerze.
  • Backoff zamiast natychmiastowej powtórki. Powtórka w tej samej sekundzie trafia na ten sam wyczerpany limit i dokłada zapytań. Po odmowie z powodu limitu klient API czeka, a przy kolejnych odmowach czeka dłużej. Klient musi czytać pole status w treści odpowiedzi, nie tylko kod HTTP.
  • Ścieżka ręczna dla jednego zamówienia, z podglądem tego, co zostanie wysłane, bez wysyłki. Przydaje się przy reklamacji klienta i przy teście po zmianie mapowania.

Zadanie ponawiania jest w torze integracji, więc okno ciszy obejmuje także je. To dobrze: ponawianie w minucie, w której ERP wysyła stany, nic nie da.

07

Nie ufaj „success” w cron_schedule: własny stan zadania

Magento oznacza przebieg jako success, gdy metoda zadania zakończy się bez wyjątku. Integracja, która łapie błąd API, zapisuje go we własnym logu i kończy pracę normalnie, dostaje w harmonogramie sukces. Tak było z naszą synchronizacją stanów: przerwała się na limicie w trakcie stronicowania katalogu, a harmonogram pokazał success. Awarię zauważyliśmy na przypadkowym zrzucie ekranu, nie dzięki monitoringowi.

Kierunek naprawy: każde zadanie integracji zapisuje własny stan, czyli początek i koniec przebiegu, liczbę przetworzonych stron na tle wszystkich, liczbę błędów i ostatni kod błędu. Przegląd zadań łączy dwa źródła i ma jedną regułę pierwszeństwa: błąd wewnętrzny wygrywa z success z harmonogramu. Kolejność warunków ma znaczenie, bo w odwrotnej sukces z harmonogramu zasłoni błąd.

Trzy rzeczy, które warto wiedzieć, zanim zbudujesz kontrolę zdrowia na cron_schedule:

  • Magento domyślnie trzyma historię udanych przebiegów przez 60 minut (history_success_lifetime). Liczenie przebiegów zadania dobowego z tej tabeli da fałszywy alarm. Grupa integracji może mieć dłuższą historię, ale do kontroli i tak lepszy jest własny log.
  • Przerwany przez limit przebieg u nas startował przy następnym uruchomieniu od pierwszej strony. Produkty z końca katalogu mogą przez to dłużej żyć na starych stanach niż te z początku. Wznawianie od strony, na której przebieg się przerwał, to nasz następny krok, nie gotowe rozwiązanie.
  • Tabela stanów źródeł w Magento (inventory_source_item) nie ma znacznika czasu aktualizacji. Jeśli chcesz wiedzieć, jak stary jest stan produktu, musisz zapisywać to sam.
08

Limit API: kiedy zrobisz to sam, a kiedy nie

Sam

Diagnoza: rozkład ERROR_BLOCKED_TOKEN na godziny, lista systemów na jednym tokenie, rozmowa z administratorem ERP o godzinach wysyłek, lista zamówień bez identyfikatora BaseLinkera. Jeśli integrację robi gotowa wtyczka z ustawianym harmonogramem, przeniesienie cen i treści na noc zrobisz w jej konfiguracji.

Z kimś, kto to robił

Własna integracja: osobna grupa crona z własnym timerem i blokadą, okno ciszy tylko dla toru integracji z przeglądem zadań wypadających w oknie, ponawianie z idempotencją po identyfikatorze zamówienia sklepu i rozróżnieniem odmowy od timeoutu, backoff, własny stan zadań zamiast statusu z harmonogramu.

Kolejność, która u nas się sprawdziła: najpierw pomiar, kto i kiedy zużywa limit, potem harmonogram, potem ponawianie zamówień, na końcu kontrola stanu zadań. Okno ciszy dopiero wtedy, gdy wiesz, które zadania obejmie. Jeśli chcesz, żebyśmy przejrzeli Twoją integrację pod tym kątem, zobacz integrację BaseLinker z Magento, BaseLinker z WooCommerce, BaseLinker z Shoperem albo eksport zamówień do ERP.

FAQ: ERROR_BLOCKED_TOKEN i limit API BaseLinkera

To kod błędu w polu error_code odpowiedzi ze statusem ERROR. W sprawdzonym wdrożeniu pojawiał się w godzinach pracy, gdy z tego samego tokenu korzystało naraz kilka systemów, w tym system ERP wysyłający hurtowe aktualizacje stanów. Dokumentacja podaje limit 100 zapytań na minutę, ale nie opisuje, jak długo trwa blokada; u nas późniejsze próby przechodziły bez zmiany tokenu.
Dokumentacja podaje limit i mówi, że token jest przypisany do konta użytkownika, ale nie wspomina o osobnej puli dla każdej integracji. W opisanym koncie sklep, ERP, skrypty i narzędzie marketingowe pracowały na jednym tokenie i dzieliły limit. Planując harmonogram, licz zapytania wszystkich systemów, a czy osobny token da osobną pulę, potwierdź w Base.
Osobnym zadaniem ponawiania, które bierze tylko zamówienia z błędem i bez identyfikatora zamówienia BaseLinkera, z limitem prób i odstępem między nimi. Historia prób jest kluczowana identyfikatorem zamówienia w sklepie, więc zamówienie z zapisanym identyfikatorem BaseLinkera nie wychodzi drugi raz. Po timeoucie najpierw sprawdź, czy zamówienie już nie powstało.
Magento oznacza przebieg jako success, gdy metoda zadania kończy się bez wyjątku. Integracja, która łapie błąd API i zapisuje go we własnym logu, dostaje sukces w harmonogramie. Potrzebny jest własny stan zadania: przetworzone strony, liczba błędów, ostatni kod błędu, i reguła, że błąd wewnętrzny wygrywa ze statusem z harmonogramu.
Nie warto. Główny cron, który wyklucza grupę integracji, nie wysyła zapytań do BaseLinkera, więc okno ciszy niczego tam nie chroni, a wyłącza maile z potwierdzeniami, indeksy i kolejki. W opisanym wdrożeniu taki błąd trwał sześć dni. Okno ciszy dotyczy tylko toru integracji, a zadania zaplanowane w jego godzinach trzeba przenieść.