Wpis z mikrobloga

#programowanie #java #systemdesign
Mirki, czytam sobie różne pytania z system design i natrafiłem na coś takiego:

System do wydawania kuponów gdzie miałem się skupić na funkcjonalności do zapewnienie, że jeden kupon może być użyty tylko określoną liczbę razy. Z takich wymagań konkretnych to wysokie availability 99,999, response poniżej 100ms, założenie że będzie 2000tps


Jakbyście do tego podeszli? ChatGPT sugeruje

@Transactional
@lock(LockModeType.PESSIMISTIC_WRITE)
@query("SELECT k FROM Kupon k WHERE k.kod = :kod")
Optional<Kupon> findByKodForUpdate(String kod);

i

@Transactional
public boolean wykorzystajKupon(String kod) {
return kuponRepository.findByKodForUpdate(kod).map(kupon -> {
if (kupon.getWykorzystania() < kupon.getLimitUzyc()) {
kupon.setWykorzystania(kupon.getWykorzystania() + 1);
kuponRepository.save(kupon);
return true;
} else {
return false;
}
}).orElse(false);
}

już pomijając polskie nazwy i save na już persisted encji
Czy ma to sens, czy jest to wystarczające?
Zakładając, że tak - to czy byłoby to wydajne i skalowane? W NoSQLowej bazie byłby pewnie łatwiej o wydajność, ale czy da się wtedy zrobić takiego locka?
  • 29
  • Odpowiedz
  • 0
Dzięki wszystkim za dyskusję! To ja to widzę tak:
1. Relacyjność raczej musi być - wątpię, aby w docelowym systemie nie chcielibyśmy powiązać kuponu z użytkownikiem i zamówieniem
2. Jednak aby zachować szybki response przechowywałbym użyte kupony w jakiejś NoSQL bazie - pewnie jakiś cache (Redis).
3. Gdy dostaniemy odpowiedź, że kupon jest/był dostępny to łączyłbym się już z transakcyjną bazą danych i w update tak jak sugerował @VlIN inkrementowałbym kupon
  • Odpowiedz
Jednak aby zachować szybki response przechowywałbym użyte kupony w jakiejś NoSQL bazie - pewnie jakiś cache (Redis).


@Patres: No ale tutaj też z kolei miałbyś kolejny narzut na synchronizację bazy z cache. Cache nie zawsze jest spójny z tym co jest na bazie. I to też nie byłoby takie trywialne jak to ugryźć.
  • Odpowiedz
  • 0
@exori_vis: Niekoniecznie, w cache chcę trzymać informację czy kupon może być jeszcze użyty, więc jak np. mam 100 kuponów i 200 requestów to dopiero po setnym kuponie dodaje do cache (tylko raz, a jakby się stało, że już tam jest ten kupon to żeby nie dodało drugi raz tego kuponu do tej bazy).

100 kuponów | 200 requestów:
1..99 - brak reakcji z cache -> obsługą przez relacyjną bazę danych
  • Odpowiedz
  • 1
@Patres jeśli chcesz żeby kupon mógł być wycofany gdy anulujesz zamówienie to w ramach jednej transakcji:
- weź kupon z licznika kuponów przez ten UPDATE
- jak się udało to dodaj do osobnej tabeli COUPON_USES event że kupon został wykorzystany w danym czasie i ew. jakiś stan wykorzystania kuponu, Reserved, used, cancelled
- Powiąż jakoś ten event z rekordem zamówienia
- commit transakcji
  • Odpowiedz
1. Relacyjność raczej musi być - wątpię, aby w docelowym systemie nie chcielibyśmy powiązać kuponu z użytkownikiem i zamówieniem


@Patres: a co niby z tym ma wspólnego relacyjność? Bazy relacyjne nie nazywają się relacyjne dlatego że między tabelami są związki (klucz obcy, klucz główny). W bazach NoSQL też takie związki między użytkownikiem a zamówieniem zamodelujesz.

2. Zdajesz sobie sprawę z tego że cały system bankowy / płatniczy zbudowano bez ACID?
  • Odpowiedz
@exori_vis:a co do poziomu izolacji transakcji to pytanie jest bez sensu bo poziomy izolacji dotyczą tylko baz relacyjnych. W ogólności poziomów spójności danych istnieje o wiele więcej niż to co potrafią bazy relacyjne.
  • Odpowiedz