Idempotencja w Mule 4: Object Store v2 bez race’ów na wielu workerach
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: contains → store 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):
- Ustal stabilny klucz biznesowy (np.
paymentId,Idempotency-Keyz nagłówka). os:storezfailIfPresent=true(first-writer-wins).- 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. - 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 clashes — Distributed 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ługaKEY_ALREADY_EXISTS. - Connectorzy z persistent cache w OSv2: jeden worker albo wyłącz persistent cache (per Help dla danego connectora).
- Nie zakładaj, że
containsna workerze A widzistorez 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ść)
- Zdefiniuj klucz biznesowy (stabilny, ≤1024 B, bez
|na CloudHub). os:storezfailIfPresent=true; obsłużOS:KEY_ALREADY_EXISTSjako success-path dedupu.- Usuń
contains→storejako „gwarancję”. - Policz TPS (base 10 / premium 100) i rozmiar wartości (≤10 MB).
- Ustaw TTL świadomie (static vs rolling).
- Multi-worker: zaplanuj brak distributed lock na CH/CH2; zweryfikuj connectory z OSv2 cache.
- App-to-app → MQ, nie OSv2.
- 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
- FAQ: Object Store v2
- Using Object Store v2
- Object Store v2 Overview
- Object Store Connector Reference
- Store and Retrieve Information in an Object Store
- Troubleshooting Object Store Connector
- Distributed Locking
Help (multi-worker adjacency)
- Object already exists… Salesforce Connector Events Listener / multiple workers
- Failed to store schema… Confluent Schema Registry / multiple workers