Wpis z mikrobloga

Siema mirki.
poproszę o gotowe rozwiązanie albo link do materiału z którego mógłbym skorzystać. Mam następujący problem:
Jest aplikacja napisana w #php #symfony która to publikuje eventy w kolejce #rabbitmq. Są też 2 consumery, które to konsumują te eventy. Problem jest taki, że w pewnym miejscu mam race condition. Są opublikowane rożne eventy, które podczas konsumpcji tworzą lub aktualizują rekord w bazie danych. Problem jest w tym, że konsumpcja może nastąpić w bardzo krótkim czasie przez co 2 consumery będą próbowały stworzyć rekord w bazie danych. Dodałem kod, który sprawdza czy rekord istnieje w bazie danych czy nie, a następnie rzucam wyjątek, żeby wykonała się aktualizacja rekordu, ale czasami różnice są takie minimalne, że tego nie wystarcza.

Poproszę o odpowiedź inną niż - zostaw sobie jednego consumera i problem rozwiązany - to nie zadziała, bo czasami ktoś chce wygenerować ogromny raport, np. za pół roku czy za rok i consumer będzie zablokowany przez cały czas generowania raportu, więc reszta systemu nie będzie działać poprawnie, a z dwoma consumerami można sobie pozwolić na blokadę jednego przez dłuższy czas, ponieważ drugi będzie dalej obsługiwał eventy z kolejki.

Myślałem nad wykorzystaniem cache (redis, memcached czy coś innego) i umieszczenie tam flagi - obsługa tego rekordu jest zablokowana w tym momencie, spróbuj później, ale nie wiem czy takie rzeczy się stosuje czy jest na to lepsze rozwiązanie.

#webdev #programowanie
  • 8
@mariecziek: utwórz sobie rekord z samym ID tworząc event, a potem załóż na ten rekord lock przez konsumenta gdy zacznie obsługiwać event. drugi będzie chciał pozyskać lock i dostanie wyjątek, więc możesz założyć, że ten rekord i event z nim związany są w tym momencie obsługiwane przez inny proces.
@Ernest_: Oho, Lock wygląda ciekawie, poczytam sobie. Nie miałem jeszcze okazji z tym pracować.
Dziękuję.

Problem z doctrine jest następujący - błąd The EntityManager is closed. Jest to spowodowane błędem po stronie Mysql - dziś wreszcie udało mi się namierzyć co się dzieje.
Otóż to wygląda tak:
1. z powodu wyżej opisanej sytuacji pierwszy consumer tworzy rekord w bazie danych
2. drugi consumer sprawdza czy rekord istnieje - a rekord nie
@mariecziek: Ciężko powiedzieć czego potrzebujesz, ale zgadując na podstawie tego co napisałeś, to nie istnieje żadne gotowe rozwiązanie pod twój problem, bo to twój kod jest problemem. Potrzebujesz po prostu poprawnego designu i tyle.
Na race condition aktualizacji może pomóc transakcja + SELECT FOR UPDATE, żeby zrobić locka na wierszu dopóki nie skończysz swojej operacji update'u.
Co do race condition przy tworzeniu wierszy, to już musiałbyś dokładnie napisać co tam
@zakopiak: Tam nie ma nic skomplikowanego. Po prostu rekord posiada flagi. Jak coś się dzieje z zamówieniem - mam policzyć wartości flag i ustawić w bazie danych true lub false.
Tutaj jest klasa o której mowa: https://pastebin.com/Z9a7DVB6
dałem tutaj funkcję rekurencyjną, żeby w razie złapania wyjątku kod wykonał się jeszcze raz, ale no tak jak pisałem poprzednio - tego nie wystarcza i nadal mam mnóstwo błędów z powodu EntityManager is closed.
@mariecziek: Tak jak mówiłem - design kodu. Sterowanie logiką biznesową przez wyjątki to pierwszy klasyczny błąd. Tracisz przez to możliwość zrobienia tej operacji atomowo, jak zresztą widać.
Zamiast tego zrób albo tak jak pisałem - transakcja, select for update, update/insert, koniec transakcji. Albo jeśli jesteś w stanie to zrobić w sposób idempotentny to po prostu INSERT ... ON DUPLICATE KEY UPDATE albo REPLACE.

Ewentualnie zapytaj po prostu jakiegoś seniora w
Problem jest w tym, że konsumpcja może nastąpić w bardzo krótkim czasie przez co 2 consumery będą próbowały stworzyć rekord w bazie danych.


nie wiem czy dobrze zrozumialem problem, ale to co mozesz uzyc to UPSERT, a jak musisz cos sprawdzic przed to optimistic locking + retry

Myślałem nad wykorzystaniem cache (redis, memcached czy coś innego) i umieszczenie tam flagi - obsługa tego rekordu jest zablokowana w tym momencie


overengineering

@mariecziek:
Integrity constraint violation: 1062 Duplicate entry '202430' for key


@mariecziek: Już kilka lat nie siedzę w symfony, ale kiedyś miałem podobny problem. Doctrine po utworzeniu encji i dodaniu jej do em nie zapisuje tego od razu do bazy, ale możesz to wymuśić bodajrze przez flush (czy jakoś tak). To powinno pozwolić zmniejszyć ilość konfliktów w tym miejscu.