Czy serializable będzie dobrym trybem izolacji w tym wypadku?

0

Kontekst:
SMSApi uderza pod /przykladowy/url z rezultatem odpowiedzi na SMS. Ten SMS jest wysyłany do kilku ludzi, którzy mają odpowiedzieć TAK lub NIE i w myśl zasady kto pierwszy ten lepszy coś tam się dzieje. Oczywiście każda kolejna osoba nie może nadpisać tego co się wykona, gdy pierwsza osoba odpowie TAK. Każda kolejna osoba jest po prostu ignorowana.

Problem:
Do tej pory ktoś to tak zaimplementował, że przetwarzanie całej logiki biznesowej (która czasami może trochę pomielić, w tym wypadku juz ponad 1 sek to długo) upchnął do tej samej akcji, która przechwytuje response od SMSApi. Problem pojawił się ostatnio, jak 3 osoby odpowiedziały na sms w przeciągu 5 sek i ostatnia osoba stała się tak jakby pierwszą.

Do tej pory cała logika biznesowa nie była nawet owinięta transakcją. Sprawdzenie, czy ktoś jest pierwszy odbywa się na samej górze, a aktualizacja stanu na samym końcu, więc jak w międzyczasie ktoś się wepchnie, to oczywiście nadpisze poprzednika.

Wcześniej nie miałem takich problemów, więc nie bardzo wiem jakie rozwiązanie wybrać. Widzę dwa:

  • SET TRANSACTION ISOLATION LEVEL SERIALIZABLE - to chyba załatwi sprawę, bo każde kolejne query się zawiesi do momentu aż pierwszy request się nie przemieli. Pytanie jakie to może nieść konsekwencje. Rozumiem, że zawieszę tylko te zapytania, które dotykają tych samych rekordów co moja transakcja, a nie np. całą tabelę z zamówieniami?

  • Zamiast procesować to w momencie, w którym SMSApi do nas uderza, to w tym miejscu wrzuce to tylko do jakiejś tabeli pośredniej. Dodam cron'a, który będzie sobie chodził co 5min i przetwarzał rekordy pojedynczo i wtedy nie będzie problemu, bo weźmie kolejny dopiero jak skończy poprzedni.

0

Może się przydać (mimo że to PostgreSQL):
https://www.postgresql.org/files/developer/concurrency.pdf

0

Hmmm, z tego co widzę, to źle zrozumiałem co robi serializable. Wychodzi na to, ze albo muszę zrobić locka na cała tabele, albo wybrać rozwiązanie drugie.

Czy jest jakieś inne?

2

podejrzewam (bo nie napisałeś), że sprawdzenie, czy ktoś jest pierwszym to coś na zasadzie SELECT ... FROM ... WHERE .... Jeśli tak to sprawdzenie trzeba objąć transakcją i dodać SELECT ... FROM ... WHERE ... FOR UPDATE. Każde następne sprawdzenie albo się wywali albo zawiesi do zakończenia pierwszej transakcji (w zależności czy za FOR UPDATE będzie NOWAIT czy nie). Jednocześnie musisz pamiętać, że zawsze musisz taką (znaczy rozpoczętą) transakcję albo zatwierdzić albo odrzucić niezależnie czy ten ktoś będzie pierwszy czy nie.

A i poziom izolacji powinien być READ COMMITED w tym przypadku

0

Dzięki @abrakadaber, a co myślisz o drugim rozwiązaniu?

Zastanawiam się czy takie kombinowanie to nie overkill w tym przypadku. Nie mamy tu do czynienia z tysiącem RPS, ale bardziej z występującym raz na jakis czas zbiegiem okoliczności i wydaje mi się, ze uzbierane w przeciągu 5 min (zakładam ze cron by co tyle chodził) requesty spokojnie można przetworzyć w mniej niż minutę jeden po drugim. Wtedy wszystko będzie synchroniczne i sprawa załatwiona.

1 użytkowników online, w tym zalogowanych: 0, gości: 1