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.
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”.
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.
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_scheduleten sam przebieg miał statussuccess, 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.
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.
Diagnoza: kto zużywa limit API
- Rozkład błędów na godziny. Z logu integracji wyciągnij linie z
ERROR_BLOCKED_TOKENi 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. - 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.
- 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.
- 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.
- Harmonogram kontra log. Dla minut z błędem w logu integracji sprawdź status tego samego zadania w
cron_schedule. Jeśli jest tamsuccess, 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.
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.
| Dane | Wcześniej | Po zmianie | Dlaczego |
|---|---|---|---|
| stany | co 30 minut | co godzinę | sklep pokazuje kopię stanów z BaseLinkera; o rezerwacji przy zamówieniu decyduje BaseLinker, nie ta kopia |
| ceny | kilka razy na godzinę | raz na dobę, w nocy | cenniki B2B zmieniają się rzadko, a noc była jedynym oknem bez blokad |
| treści katalogu | w nocy, część przebiegów wcześnie rano | w nocy i późnym wieczorem | poranne przebiegi wypadły w oknie ciszy (sekcja 05) |
| zamówienia | raz, zaraz po złożeniu | zaraz po złożeniu plus osobne ponawianie | zamówienie nie może czekać na nocne okno |
| zadania wewnętrzne Magento | bez zmian | bez zmian | nie 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.
„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ć.
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_TOKENto 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
statusw 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.
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.
Limit API: kiedy zrobisz to sam, a kiedy nie
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.
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.