Wróć do bloga

Idempotencja w Mule 4: Object Store v2 bez race’ów na wielu workerach

2026-09-17

Idempotencja w integracji oznacza: ten sam request (ten sam klucz biznesowy) przetworzony dwa razy nie tworzy dwóch skutków ubocznych — drugiego płatności, drugiego zamówienia, drugiego side-effectu w systemie docelowym. Na Mule 4 / CloudHub wielu leadów sięga po Object Store v2 jako „już widziałem ten ID”. Problem: wzorzec contains → potem store nie jest atomowy pod współbieżnością, a na CloudHub / CloudHub 2.0 distributed locking nie jest dostępne dla OSv2.

Czego ten tekst nie jest: tutorialem „jak kliknąć Object Store w Studio”, zamiennikiem kolejki wiadomości ani obietnicą, że OSv2 zastąpi Anypoint MQ między aplikacjami. Jest przewodnikiem race-safe wzorców i limitów dla developerów / architektów, którzy chronią hotspoty (płatności, zamówienia, webhooki) na multi-replica CloudHub.

Poniżej: dlaczego contains+store psuje się pod loadem, karty pułapek (objaw → przyczyna → docs → wzorzec), limity TPS/rozmiaru, kiedy wybrać MQ, checklista oraz FAQ pod AEO.


Co OSv2 robi dobrze (i czego nie obiecuje)

Object Store v2 przechowuje stan w obrębie jednej aplikacji (CloudHub workers / repliki tej samej app). Domyślnie włączony na Mule 4 w CloudHub; wartości do 10 MB; TTL max 30 dni; rolling vs static TTL zależnie od konfiguracji entryTtl (OSv2 FAQ, Using OSv2, OSv2 Overview).

Docs mówią wprost: OSv2 nie jest zaprojektowany do komunikacji app-to-app. Do dzielenia danych między dwiema aplikacjami Mule 4 użyj kolejki w Anypoint MQ (OSv2 FAQ — Can an app access another app’s store?).


Pułapka #1: containsstore pod concurrency

Objaw: duplikaty płatności / zamówień mimo „idempotency key” w Object Store; sporadycznie tylko na ≥2 workerach / replica.

Przyczyna: między contains (false) a store drugi wątek / druga replika robi to samo. To klasyczny check-then-act — nie atomowy.

Docs: operacja Store z failIfPresent=true rzuca OS:KEY_ALREADY_EXISTS, gdy klucz już istnieje; domyślnie failIfPresent=false nadpisuje wartość (Object Store Connector Reference — Store; Store and Retrieve example). Troubleshooting: OS:KEY_ALREADY_EXISTS = The Mule app tries to store an object, but the object store already has a value for that key (Troubleshooting Object Store Connector).

Wzorzec (race-safer):

  1. Ustal stabilny klucz biznesowy (np. paymentId, Idempotency-Key z nagłówka).
  2. os:store z failIfPresent=true (first-writer-wins).
  3. Na OS:KEY_ALREADY_EXISTS → traktuj jako already processed (On Error Continue / dedykowany handler): zwróć wcześniejszy wynik albo 200/409 zgodnie z kontraktem API — bez ponownego side-effectu.
  4. Nie buduj ścieżki „najpierw contains, potem store” jako gwarancji idempotencji.

Uwaga uczciwości: sam failIfPresent=true nie magicznie dodaje distributed lock na CloudHub (pułapka #2). Zmniejsza okno race względem check-then-act i daje czytelny sygnał „klucz zajęty”.


Pułapka #2: Multi-worker CloudHub bez distributed lock

Objaw: Object already exists for the key / niespójne wartości przy ≥2 workerach; błędy w connectorach, które cache’ują stan w OSv2 (np. Salesforce Replay Listener, Confluent Schema Registry — Help articles).

Przyczyna: Using Object Store v2 with multi-worker CloudHub applications might result in data discrepancies or key clashesDistributed Locking nie jest dostępne na CloudHub i CloudHub 2.0 przy OSv2 (OSv2 Overview; Using OSv2 — Synchronize Access).

Connector docs mówią, że Store jest synchronizowany na poziomie klucza i w cluster mode między nodami (Store reference). To nie anuluje ograniczenia CloudHub dla OSv2 — overview jest tu źródłem prawdy dla CH/CH2.

Docs sugerują: użyj distributed key-value store jako locka do synchronizacji dostępu do OSv2; odsyłają do Distributed Locking (LockFactory / custom extension / scripting) — przydatne na klastrze on-prem / modelach, gdzie Mule lock factory działa cross-node. Na CloudHub z OSv2 planuj tak, jakby nie było platformowego distributed locka: single-writer path, zewnętrzny lock (Redis itp.), albo wzorzec first-writer-wins + idempotent downstream.

Wzorzec:

  • Hot path idempotencji: failIfPresent=true + obsługa KEY_ALREADY_EXISTS.
  • Connectorzy z persistent cache w OSv2: jeden worker albo wyłącz persistent cache (per Help dla danego connectora).
  • Nie zakładaj, że contains na workerze A widzi store z workera B w tej samej milisekundzie bez race.

Pułapka #3: Limity TPS i rozmiaru (cichy 429)

Objaw: sporadyczne błędy pod szczytem; HTTP 429; „Object Store wolny”; wartości > limit.

Docs (OSv2 FAQ):

Limit Wartość
Rozmiar wartości 10 MB (brak limitu liczby kluczy / całkowitego rozmiaru store)
TPS base 10 TPS per app
TPS premium add-on 100 TPS per app
Key size max 1024 bajtów UTF-8
TTL max 2592000 s (30 dni)
CloudHub keys `

Każde API call (connector lub REST) liczy się do TPS. Przekroczenie base bez SKU → requesty mogą być wstrzymane z 429.

Wzorzec: nie używaj OSv2 jako cache’a na każdy request w hot path bez budżetu TPS; batchuj / lokalny cache z TTL tam, gdzie spójność pozwala; monitoruj Usage Reports; premium, gdy realnie potrzebujesz 100 TPS.


Pułapka #4: OSv2 jako „szyna” między aplikacjami

Objaw: app A zapisuje, app B czyta przez REST API „bo da się”; coupling, TTL niespodzianki, brak semantyki kolejki (ack, DLQ, competing consumers).

Docs: możesz użyć Object Store REST API do store/retrieve z innej app, ale OSv2 nie jest do app-to-app — do share data między Mule 4 apps użyj Anypoint MQ (OSv2 FAQ).

Wzorzec: OSv2 = idempotency / watermark / stan wewnątrz app. MQ / broker = komunikacja między appami i trwała kolejka (szczególnie po CH2 bez persistent VM queues — patrz artykuł o migracji CH2).


Pułapka #5: TTL i „znikający” klucz idempotencji

Objaw: po ~30 dniach (lub wcześniej przy static TTL) ten sam biznesowy ID przechodzi ponownie jako „nowy”.

Docs: max TTL 30 dni; rolling TTL (pominięty entryTtl, Mule ≥4.2.1) vs static TTL (ustawiony entryTtl); entryTtl="0" to nie rolling — zmienia zachowanie na static (Connector reference — TTL; Configure custom TTL).

Wzorzec: dla kluczy idempotencji ustaw świadomy static TTL zgodny z oknem biznesowym (np. 7–30 dni); dokumentuj, że po TTL ponowne przetworzenie jest możliwe; długoterminowy audit → baza / data lake, nie OSv2.


Mini decision tree

Potrzeba Wybór
Dedup / „już przetworzyłem ten ID” w jednej app OSv2 + failIfPresent=true + handler KEY_ALREADY_EXISTS
Multi-replica + silna serializacja zapisu Załóż brak CH distributed lock; zewnętrzny lock lub single-writer + idempotent target
Komunikacja między appami / competing consumers Anypoint MQ (nie OSv2)
Cache odpowiedzi pod wysokim RPS Nie OSv2 base 10 TPS — Cache scope / zewnętrzny cache
Audit > 30 dni Persist poza OSv2

Checklista (kolejność)

  1. Zdefiniuj klucz biznesowy (stabilny, ≤1024 B, bez | na CloudHub).
  2. os:store z failIfPresent=true; obsłuż OS:KEY_ALREADY_EXISTS jako success-path dedupu.
  3. Usuń containsstore jako „gwarancję”.
  4. Policz TPS (base 10 / premium 100) i rozmiar wartości (≤10 MB).
  5. Ustaw TTL świadomie (static vs rolling).
  6. Multi-worker: zaplanuj brak distributed lock na CH/CH2; zweryfikuj connectory z OSv2 cache.
  7. App-to-app → MQ, nie OSv2.
  8. Test: równoległe requesty z tym samym kluczem na ≥2 replica.

FAQ

1. Czy contains a potem store wystarczy do idempotencji?

Nie pod współbieżnością — to check-then-act. Preferuj store z failIfPresent=true i obsługę OS:KEY_ALREADY_EXISTS.

2. Co oznacza OS:KEY_ALREADY_EXISTS?

Store z failIfPresent=true, gdy klucz już istnieje. Użyj tego jako sygnału „już przetworzone”, nie jako niespodziewanego crasha.

3. Czy Object Store v2 ma distributed lock na CloudHub?

Docs overview: distributed locking nie jest dostępne na CloudHub / CloudHub 2.0 przy OSv2 — możliwe rozjazdy / key clashes na multi-worker.

4. Jakie są limity TPS i rozmiaru?

Wartość ≤10 MB; base 10 TPS/app; premium add-on 100 TPS/app; key ≤1024 B; TTL ≤30 dni (OSv2 FAQ).

5. Kiedy wybrać Anypoint MQ zamiast OSv2?

Gdy potrzebujesz komunikacji między aplikacjami, semantyki kolejki, competing consumers — OSv2 jest store’em stanu w jednej app, nie szyną.

6. Czy OSv2 nadaje się na cache wysokiego RPS?

Przy base 10 TPS — zwykle nie. Przekroczenie limitu → 429. Rozważ Cache scope / zewnętrzny cache.

7. Co z TTL przy kluczach idempotencji?

Max 30 dni. Po wygaśnięciu ten sam ID może wejść ponownie. Dopasuj static TTL do okna biznesowego; długi audit trzymaj poza OSv2.

8. Czy REST API OSv2 pozwala czytać store innej app?

Technicznie tak, ale docs odradzają app-to-app przez OSv2 — użyj Anypoint MQ.

9. Jak testować race?

Wyślij równolegle N requestów z tym samym idempotency key na app z ≥2 replica; oczekuj jednego side-effectu i kontrolowanej ścieżki KEY_ALREADY_EXISTS.


Soft CTA

Projektujesz idempotencję płatności / zamówień na CloudHub (multi-replica) i chcesz przejrzeć OSv2 vs MQ oraz limity TPS zanim produkcja złapie race? Solita to nordycki partner MuleSoft z dostawą z Polski (EU-shoring) — pomagamy ułożyć wzorzec first-writer-wins i granice OSv2. Bez obietnic „#1” i bez checklisty marketingowej.


Źródła

Dokumentacja

Help (multi-worker adjacency)