Kafka 동시성 문제

최혜성·2024년 1월 17일
0

특정 데이터 저장 요청 -> 카프카 메시지 -> 서버저장
이 과정을 거치는데 카프카 메시지 도착전 쿼리를 날리고 요청을 하면 동시성 문제로 중복 삽입 문재.
물론 최종 저장전 확인이 가능하지만 유저는 모름

-> 레디스 사용
내일 수정예정

===========================================

이.. 이게뭐고

어제 곰곰히 생각해보니 MSA기반 서버에서는 동시성 문제가 필연적일 수 밖에 없는것 같다.

  • Request A
    A에서 B를 조회 했더니 B의 마지막 자리가 남아있네?
    그래서 B한테 저장해줘~라고 Kafka를 통해 메시지를 보냄.
  • Request B
    얘도 A에서 B를 조회했더니 아직 kafka를 통해 보낸 메시지가 db에 도달하지 않아서
    db는 마지막 자리가 남아있다고 말해주네? 그래서 저장해달라고 보내줌

-> 동시성 문제 발생..

이 문제를 트랜잭션 격리 수준이나 - DB Lock등의 기법으로 저장이야 해결할 수 있는데, 유저는 이게 어캐된건지 모름?

Request를 했는데 Response는 됐다매!!!!!!!!!!!!!!!!!!!!!!!!!!!
왜 내가 구매한게 사라져있는데!!!!!

그래서.. Request와 Response의 Sync는 맞춰줘야 하는데 보통 java에서는 synchronized 예약어를 사용해서 하나씩 접근할 수 있게 하는데 얘는 어캐 해야 되나 막막했음.

https://upcurvewave.tistory.com/369
https://brewagebear.github.io/concurrency-distributed-transaction-with-tcc/

이러한 글들을 찾아 봤는데, 트랜잭션 상에서 오류가 발생했을때 해당 서버와 DB간의 상호작용을 핸들링 하고 순차적으로 처리할 수 있게 하는 구조로 설명되어 있다.

즉, 그쪽 서버랑 db만 알고 클라이언트는 아직 모른다는것이다.
나는 '실시간 선착순'시스템을 기반으로 유저가 확실한 여부를 알아야 한다고 생각해 더 찾아봤다.

https://upcurvewave.tistory.com/482
!!!
위 글을 토대로 kafka와 redis를 같이 사용해야만 이를 해결할 수 있다고 봤다.
예전에 redis 한번 써보긴 했었는데, synchronized를 건것처럼 하나의 쓰레드에서 순차적으로 명령들을 처리해 주기 때문에 동시성을 해결해 줄 수는 있는데,

문제는 싱글쓰레드라는 점이다. 너무 많은 연산을 해버리는 순간 지연시간이 엄청 오래 걸리는 문제도 있고, pub sub또한 확실한 메시지의 전달을 보증해줄 수 없다.

그래서 kafka와 함께 사용해서 안정성을 높이려 한다.

적용되는 경우

  1. 남아있는 좌석을 볼때 (포도알 갯수 확인)
  2. 좌석을 예약할때 (실제 클릭해서)

맨 처음 생각했을때 1에서 확인한 결과를 토대로 2를 요청하면 확실하게 요청도 될거고 동시성 문제도 안생길거다~ 라고 생각했는데, 좌석을 보고 예약까지 이어진다는 flow가 100% 확신할 수 도 없고, 이를 블로킹 할 수도 없었다.

실제 선착순 예약 시스템도 남아있는 좌석을 클릭해도 그 사이에 누가 예약하면 예약할 수 없다고 뜨기 때문.
그래서 실제 동기화는 2에서 수행하되, 서버의 부담이 없다면 1또한 동기화를 걸어주고자 한다.

어캐함

일단 2. 좌석 예약 을 먼저 고려하고자 한다.
좌석 예약 end point로 좌석의 id를 넘겨준다 가정하자.
해당 좌석 id를 event 서버로 넘겨 좌석이 비어있는지 확인한다. (OpenFeign)
해당 event 서버는 DB에서 find로 정보를 가져오는데, 'Redis'를 확인해서 현재 예약이 이루어졌는지 확인한다.
만약 Redis 캐시에도 없으면 예약 가능하다 반환한다.
좌석 예약 api 서버는 좌석 예약했다고 kafka에 메시지를 보내고, Redis에다 좌석값을 넣는다. (Order, Event 갱신)

-> Redis를 통해 확실하게 원자성을 보장해줄 수 있으므로 신청한 예약은 확실히 통과된다.

1, 남아있는 좌석 조회는
db에서 findall로 좌석 데이터를 가져온다.
이를 즉시 반환해도 되지만, 만약 kafka의 데이터가 도달하지 못했을경우를 고려한다고 하면
redis의 키값이 존재하는지 여부를 cross-check 한 뒤 반환해주면 되겠다.
근데, 이 redis의 키 값을 가져오는게 getNX등을 이용하면 O(1)정도로 끝낼 수 있지만, 전체 리스트를 가져온다고 하면 O(N)까지 늘어나기 때문에 이 부분은 추후에 고려를 해봐야겠다.

추가정보?

https://www.baeldung.com/cs/cache-write-policy
그래서 이와 비슷한 개념으로 Cache 쓰기 정책이 있다.

  • Write-Back
    캐시 메모리를 사실상 주 기억장치로 사용하고, 일정 주기로 실제 DB에 데이터를 저장하는 기법
  • Write-Through
    캐시 메모리와 DB에 동시에 저장하는 기법

예전에 이거 관련해서 머리가 좀 아팠던 기억이 있는데 현재 구상하는 방식은 Write-Through와 일치하는것 같다.
다만, 이거 잘못 구성하면 Redis의 부담이 매우 커지기 때문에 최적화가 가장 중요하다.

profile
KRW 채굴기

0개의 댓글