개발이군고구마

[Redis/Kafka 이용한 대용량 트래픽 예약 및 쿠폰 발급 서비스] 2.대용량 트래픽을 견디기 위한 방법 고민 본문

SERVER/Kafka

[Redis/Kafka 이용한 대용량 트래픽 예약 및 쿠폰 발급 서비스] 2.대용량 트래픽을 견디기 위한 방법 고민

김구황 2025. 3. 24. 07:41
728x90
 

[Redis/Kafka 이용한 대용량 트래픽 예약 및 쿠폰 발급 서비스] 2.대용량 트래픽을 견디기 위한 방법

🥑 카프카를 사용하는 이유 [성능]

medium.com

서울 우먼 잇츠 스터디 3기 연구자료 



🥑 카프카를 사용하는 이유 [성능]

1. 문제 상황

  • 예약이 완료된 이후 비동기 처리 작업이 상당 수 존재함
  • 해당 비동기 작업들은 클라이언트에게 바로 응답 불필요
  • 비동기 처리 작업들에서 오류가 생기면, 같은 트랜잭션에 묶인 메인 로직에 영향을 미쳐, 정작 중요한 핵심 프로세스가 수행이 되지 않음

2. 어떤 해결 방법들이 있을까

✔️ RabbitMQ : AMQP 프로토콜 구현 메시지 브로커

  • Exchange의 과정이 존재
  • 브로커 중심적인 설계 (= 수신인에게 메세지를 확인, 라우팅, 저장 배달 역할 초점)
  • Consumer가 메세지를 소비하면 메세지를 삭제함

✔️ ActiveMQ : 자바로 만든 오픈 소스 메시지 브로커

  • 적은 수준의 메세지만 처리
  • 신뢰성 거래성 확보, 즉시 ETL 작업

✔️ Kafka

  • 분산 메세징 시스템
  • 메세지를 파일 시스템에 저장하여 영속성 보장
  • Consumer가 broker로부터 ‘직접’ 메세지를 가지고 옴 pull
  • Producer 중심 형태로 ‘많은 양’의 데이터를 파티셔닝 하는데 초점
  • Consumer가 전달 상태를 기억
❔ 브로커란?

- Publisher로 부터 전달 받은 메세지를 Subscriber로 전달해주는 중간 역할. 
- 'Message Queue' : 메세지가 적재되는 공간
- 'Topic' : 메세지의 그룹

❔ MQ의 장점? 

- 비동기 : QUEUE 라는 임시 저장소가 있어 나중에 처리가 가능
- 낮은 결합도 
- 확장성 : PRODUCDER/CONSUMER 서비스를 원하는대로 확장 가능 
- 탄력성 : CONSUMER가 다운되도 어플리케이션이 중단되지 않으며, 메세지는 MQ에 존재
- 보장성 : MQ 에 들어가는 모든 메세지는 CONSUMER 에게 전달 
출처 : https://heodolf.tistory.com/49

3. 그 중 왜 카프카인가

✔️내가 구현한 시스템을 살펴보자

  • 들어온 예약 데이터는 중요한 데이터이므로, consumer가 소비를 실패했다고 해서 삭제되면 안됨
  • 데이터의 ETL은 크게 필요 없음
  • ‘비동기’의 초점을 맞추고 있으며, 대용량 트래픽을 대비해야 함

4. 카프카 원리

✔️ 카프카 동작 원리

[출처] dev_kwonnee.log
  • publisher는 전달하고자 하는 메세지를 topic을 통해 카테고리화 함
  • consumer는 원하는 topic을 ‘구독’ 함으로써 메세지를 읽어옴
  • publisher와 consumer는 오로지 topic 정보만 알 뿐, 서로의 정보를 모름
  • consumer 추가 제거시 consumer들의 partition 재분배가 발생
  • kafka는 broker들이 하나의 클러스터로 구성되어 동작
  • zookeeper가 분산 처리를 담당함

✔️ 용어

  • 주키퍼 : 분산 시스템의 메타 정보를 관리. 분산 어플리케이션을 분산 조율, 조정하는 서비스
  • 토픽과 파티션 : 토픽은 발행된 메세지의 카테고리 명. 토픽은 partition이라는 단위로 쪼개어져 클러스터의 각 서버들에 분산 저장되어 있음.
  • pub/sub 모델 : topic을 구독하는 모든 consumer에게 메세지를 브로드캐스팅 하는 방식

✔️ 장점

  • 분산 시스템 & 실시간 로그 처리 : TPS 가 매우 우수함. 분산 및 복제 구성 쉽게 할 수 있음
  • TCP 기반 프로토콜 & 메세지 전달 방식 개선 : 다수의 메세지를 ‘배치’ 형태로 broker에게 ‘한번에’ 전달할 수 있어, TCP/IP 라운드 트립 횟수를 줄일 수 있음
  • 메세지 파일 시스템 저장 방식 : 메세지를 메모리에 저장하는 시스템과 달리 ‘파일’ 시스템에 저장. 데이터의 영속성이 보장됨. Consumer 에 의해 처리된 메세지가 바로 삭제되지 않고, 파일 시스템에 ‘그대로’ 두었다가 수명이 지나면 삭제함
  • 메세지 pull 방식 : consumer가 직접 broker로부터 메세지를 pull하여 최적의 성능 보장

5. 카프카 기업 사용 예시

 

 

 

🥑레디스를 사용하는 이유 [DISK I/O]

1. 문제 상황

  • 예약 요청 건 별로 호스트에 대한 예약 재고를 확인해야함
  • 트래픽이 몰리는 경우, 계속된 디스크 I/O를 통해 병목 현상이 발생할 수 있음
  • 데이터 서버의 CPU 과부화가 예상되기도 함

2. 어떤 해결 방법들이 있을까

✔️ 인메모리 캐싱

  • @Caheable : Spring 에서 사용하며, 메소드의 결과가 캐싱됨. Id 값으로 구별
  • EhCache : Spring에서 제공하는 자바 기반 오픈소스 캐시 라이브러리.

✔️ 네트워크 캐싱

  • CDN : 네트워크의 엣지에서 정적 에셋 파일을 캐싱하여 엔드 유저에게 빠르게 도달시키는 방법. 콘텐츠 전송 시 서버 로드를 줄여줌
https://akku-dev.tistory.com/21

✔️ 분산 캐싱 (여러 서버에 걸쳐 데이터를 메모리에 저장하는 방식)

  • Redis : 다양한 데이터 구조를 지원 (리스트, 해시, 집합 등). 데이터의 영속성을 제공하며 Pub/Sub 및 트랜잭션 지원 (=메세지 브로커 역할 가능). 싱글 스레드 기반이며 메모리 사용량이 상대적으로 높음
  • Memcached : 문자열 기반 키-값 저장소로 데이터 베이스 부하를 줄일 수 있음. 멀티스레드 지원. 빠르고 단순한 캐싱

✔️ 리버스 프록시 캐싱

  • 클라이언트로부터 전달 받은 요청을 인터셉트하여 서버에 보내고, 리턴한 값을 저장함. 대표적인 예로는 Ngnix 웹서버

✔️ 클라이언트 캐싱

  • 브라우저 캐시 : Etag, Cache-Control의 속성을 활용하여 이전에 한번 불러왔던 데이터들을 브라우저 내에 저장시키는 방법

3. 그 중 왜 레디스인가

✔️내가 구현한 시스템을 살펴보자

  • 인 메모리 캐시 사용 ? 재고 데이터는 큰 데이터는 아니기 때문에 적절할 수 있음. 그러나 대용량으로 요청이 올 땐 WAS의 힙 메모리 차지가 클 수 있다는 단점이 존재함
  • DB 분산 부하가 주된 목적이기 때문에 분산 캐시에 초점을 두고 있음
  • 재고 데이터는 휘발 되서는 안되는 데이터이기 때문에, 읽기 전략 Look Aside 와 쓰기 전략을 적절히 혼합하여 사용해야 함

4. 레디스 원리

💥 분산데이터 ? 데이터 정합성 문제 발생할 수 있음

✔️ 캐시 히트

 읽기 전략

  • Look Aside (캐시 조회 -> (없을 경우) DB 조회 해서 값 가져옴 -> 캐시 업데이트)
  • Read Through (캐시 조회 -> (없을 경우) DB 조회 -> 캐시가 DB 조회 후 업데이트 -> 캐시에서 값 가져옴)

 쓰기 전략

  • Write Back (모든 데이터를 Cache Store 에 저장 -> 일정 시간이 지난 후 DB 저장)
  • Write Through (DB 저장 내용 Cache 저장 -> DB 로 저장)
  • Write Around (모든 데이터는 DB 에 저장 -> Cache miss 가 발생하는 경우만 DB 와 캐시에 데이터 저장)

 읽기 쓰기 전략 조합

  • Look Aside + Write Around

캐시를 조회하고 없으면 DB 조회 후 캐시 업데이트

쓸 때는 DB에만 데이터 저장하고, cache miss 나는 경우만 cache 저장

  • Read Through + Write Around
  • Read Through + Write Through
💥 Redis 사용시 캐시 히트 문제는 3장에서 다시 자세히 다루어야 하는 문제임

5. 레디스 기업 사용 예시

 

🥑레디슨을 사용하는 이유 [동시성과 데이터 정합성]

1. 문제 상황

  • 여러 요청이 한 자원을 공유 할 때 각 분산 DB의 동기화가 여러 요청의 동기화 속도를 따라가지 못하는 상황 발생 (즉, 재고의 수량이 업데이트 되기 이전에 다른 스레드가 재고를 조회하는 상황)
  • Redis를 사용하여 재고를 확인할 예정이므로, 싱글 스레드의 특성을 사용할 수 있음 (이 장점을 사용할 방법)

2. 어떤 해결 방법들이 있을까

✔️ @Transactional

스터디 발표 중 스터디원이 아주 좋은 질문을 주셨다.

“@Transactional 을 쓰는 것으로 락 문제를 해결할 수 없을까?”

[출처] jhkim31.log 빨간색(transactional)과 파란색(lock)
  • Transactional을 사용하면 ‘프록시’ 객체를 사용하여 DB 접근시 락을 얻는데, 문제 상황은 재고 조회를 동시에 하는 것에서 발생 (조회에는 따로 락이 걸리지 않음)
  • 락이 풀리고 그 다음 스레드가 다시 락을 얻은 후에, ‘같은 값’ 으로 update (=재고 차감 x) ❓ 롤백되는 건지 update 되는 건지
  • JPA 의 경우 비관적인 LOCK(읽기 잠금, 공유 잠금) 을 트랜잭션과 함께 설정함으로써 해결할 수 있음

“@Transactional 사용시 스레드는 언제 락을 얻는 걸까? 서비스 실행 시점? 아니면 repository 접근 시점?”

[출처] 당뚱로그 쇼핑몰 재고관리 — 동시성 문제 해결방법 탐구…
  • 트랜잭션 시작 후 DB 접근 시점에 획득함

✔️ DB Lock

  • 공유 자원에 락을 걸음 (레코드)
  • DB 데이터 자체에 Lock을 설정하는 쿼리 (JPA) DB Connection 비용과 Disk I/O 발생
  • 공유 Lock : 데이터를 변경하지 않는 읽기 작업을 위해 잠금
  • 배타 Lock : 데이터를 변경하는 작업을 위해 잠금

✔ 비관적 Lock

  • 충돌은 무조건 발생한다 라는 전제
  • @Lock 어노테이션을 사용하여 구현 가능
  • 데드락에서는 벗어날 수 있으나, API 콜 대기 시간이 길어짐 (배타락 점유 시간 )

✔ 낙관적 Lock

[출처] 당뚱로그 쇼핑몰 재고관리 — 동시성 문제 해결방법 탐구…
  • 충돌은 거의 발생하지 않는다는 전제
  • 어떤 데이터도 락을 걸지 않기 때문에 데드락은 발생하지 않음
  • 버전 충돌로 인해 트랜잭션이 롤백될 가능성

✔️ (분산락) Redisson

  • 임계 구역에 락을 걸음
  • pub/sub 방식으로 동작
  • 간단한 Redis 명령어로 사용할 수 있음
  • 실제로 작업을 처리하는 부분이 ‘싱글 스레드’ 로 동작하여 원자성 보장
  • 분산 환경 (여러 서버에서 요청) 에서의 동시성 제어가 가능 (!= synchronized)
  • 스케일 아웃된 DB 환경에서도 동시성 제어가 가능함

💥 DB 락 오류 정리

  • 데드락 : 격리 수준에 따라서 트랜잭션 select 쿼리로 조회하는 경우 레코드에 공유 락을 획득한 후, 함께 점유한 레코드에 대해서 배타락 (update)를 요청할 때 벌어지는 현상
[출처] 당뚱로그 데드락 “공유 자원에 대한 배타 락”
  • 교착상태 : Lock 간의 경합이 발생하여 특정 트랜잭션이 작업을 진행하지 못하고 대기하는 상태 (예, commit 하지 않은 상태에서 다른 작업에서 무한 대기)
[출처] 성장하는 성하 Blog

3. 그 중 왜 레디슨인가

✔️내가 구현한 시스템을 살펴보자

[출처] koomin1227.log 분산락을 사용하여 동시성 문제 해결하기’
  • 공유 자원을 레디스에 올려둘 예정임 -> Redis 서버를 사용 가능
  • 분산락의 관리자가 Redis가 되면서 MSA 환경에서도 동시성 이슈 제어가 가능함
  • 요청마다 락을 점유하고 데이터를 업데이트 하기 때문에 DB의 동기화를 기다리지 않아도 됨

🚫 주의

트랜잭션이 나중에 락 반납 이후에 반납 되면 동시성 이슈 생김. 트랜잭션 내부에서 분산락 반납하지 않도록 주의. 즉, [락 획득 → 트랜잭션 시작 → 트랜잭션 종료 → 락 해제] 순으로 실행해야 함

4. 레디슨 원리

  • 메소드 블록을 Lock을 잡아줌
  • 레코드에 락을 걸어서 다른 로직들에게 대기를 걸게 됨
  • 락이 해제되면 해당 락을 subscribe 중인 클라이언트들이 신호를 받고 락에 진입할 수 있음

5. 레디슨 기업 사용 예시

 

🌿 참고 문헌

카프카
- https://dev-youngjun.tistory.com/259 (카프카 기본 세팅)
- https://velog.io/@cho876/%EC%B9%B4%ED%94%84%EC%B9%B4kafka-vs-RabbitMQ (카프카 비교)
- https://always-kimkim.tistory.com/entry/kafka101-broker https://heodolf.tistory.com/49 (개념)

레디스
- https://sabarada.tistory.com/178 (레디스 스프링)
- https://inpa.tistory.com/entry/REDIS-📚-캐시Cache-설계-전략-지침-총정리 (캐싱 전략)
- https://velog.io/@ksk0605/요즘-Redis-안하면-신입이-취업을-어케함-ㅋㅋ (레디스 기본 원리)
- https://escapefromcoding.tistory.com/704 (레디스와 memcached 비교)
- https://yozm.wishket.com/magazine/detail/2341/ (캐싱 방법 비교 정리)

레디슨
- https://velog.io/@hgs-study/redisson-distributed-lock (분산락 동시제어)
- https://gengminy.tistory.com/59 (개념)
- https://devoong2.tistory.com/entry/Spring-Redisson-라이브러리를-이용한-Distribute-Lock-동시성-처리-2 (분산락 구현예)

동시성
- https://velog.io/@jhkim31/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-%EB%B6%84%EC%82%B0%EB%9D%BD%EA%B3%BC-Transactional (트랜잭션과 분산락 로직)
- https://ksh-coding.tistory.com/121 (DB 락)
- https://velog.io/@dangddoong/learning-solutions-concurrency-issues-e-commerce-inventory-management-logic (분산락 DB락)
- https://meorimori.tistory.com/156 (락 비교)
- https://oneny.tistory.com/113

대량 트래픽
- https://guti-coding.tistory.com/197?category=1195671