코딩관계론

컨슈머 랙을 줄이기 위해서 파티션을 늘렸더니 동시성 이슈가 발생해버렸다 본문

개발/Hot-Stock

컨슈머 랙을 줄이기 위해서 파티션을 늘렸더니 동시성 이슈가 발생해버렸다

개발자_티모 2025. 2. 6. 21:52
반응형

1. 문제 상황

현재 뉴스 패치 시스템은 AWS SQS(메시지 큐)를 통해 3개의 Lambda 함수가 뉴스 데이터를 처리하고 있습니다.
그런데 뉴스 분석 과정에서 하나의 파티션에서 모든 작업을 처리하다 보니, 컨슈머 랙(Consumer Lag)이 급격히 증가하는 문제가 발생했습니다.

컨슈머 랙
초기 구조

해결 시도

가장 먼저 고려한 방법은 파티션을 늘리는 것이었습니다. 하지만 동시성이 증가하면 자연스럽게 동일 데이터 접근 충돌 문제가 발생할 가능성이 커지므로, 이를 해결할 방법도 함께 고민해야 했습니다.

해결 구조

3. 동시성 이슈 해결 방법 검토

파티션을 늘린다면 여러 개의 Lambda가 동시에 같은 데이터에 접근할 가능성이 증가합니다. 이를 해결하기 위해 여러 가지 방법을 검토했습니다.

1) 분산 락 사용

  • RedisZookeeper를 이용한 분산 락을 고려했습니다.
  • 그러나 추가적인 인프라 구성 비용이 증가하고, 서버 부하가 발생할 수 있다는 단점이 있었습니다.

2) 데이터베이스 락 사용

  • 비관적 락(Pessimistic Lock) 또는 **낙관적 락(Optimistic Lock)**을 적용하는 방법도 고려했습니다.
  • 하지만 데이터베이스의 부하가 증가하고, Scale-Out을 고려하지 않은 현재 아키텍처에서는 최선의 방법이 아니라는 결론을 내렸습니다.

3) 애플리케이션 레벨 락 - ReentrantLock + ConcurrentMap

  • scale-out이 됐을 때 동시성 문제를 해결할 수 없다.

선택한 해결 방법: ReentrantLock + ConcurrentMap

자바에서 자체적으로 제공하는 ReentrantLockConcurrentMap을 활용하여 문제를 해결하기로 결정했습니다.

이 두가지 조합을 선택한 이유는 각 객체마다 락을 관리해야 하기 떄문에 이 객체들을 저장하기 위해서 Map이 필요했고, 해당 객체의 락을 생성하거나 저장할 때 동시성 이유가 발생할 수 있기 때문에 ConcurrentMap을 사용하기로 결정했습니다.

4. 고려해야 할 주요 사항

1) 언제 락을 획득하고 해제할 것인가?

  • 락을 너무 오래 유지하면 처리 속도가 느려져 성능 저하가 발생할 수 있음.
  • 따라서 공유 데이터에 읽기와 쓰기를  한 순간에만 락을 걸고, 최소한의 범위에서만 유지하는 전략을 적용.

2) ConcurrentMap에서 언제 ReentrantLock을 제거할 것인가?

  • 락 객체가 무한정 쌓이면 메모리 누수(Memory Leak) 문제가 발생할 가능성이 있음.
  • 따라서 일정 시간이 지나거나, 특정 조건을 만족하면 락 객체를 제거하도록 설정.

3) 락 해제 조건

  • 경쟁 조건(Race Condition)은 공유 변수를 읽고 동시에 갱신할 때 발생함.
  • 따라서 뉴스 패치 작업이 끝난 후 JOB ID에 대한 락을 삭제하면 동시성 이슈 없이 메모리 관리가 가능함.

 

5. 추가적인 문제 발생 - 메시지 중복 처리

이론적으로는 동시성 문제가 해결된 것처럼 보였지만, 실제 실행 결과에서 패치한 뉴스 개수보다 분석한 뉴스 개수가 더 많아지는 문제가 발생했습니다.

원인 분석

  • Kafka의 At-Least-Once 메시지 전송 특성 때문에 한 개의 뉴스 데이터가 여러 번 분석될 가능성이 있었음.
  • Kafka는 기본적으로 "최소 한 번의 메시지 전송"을 보장하지만, 정확히 한 번만 전송한다는 보장은 없음.
  • 따라서 동일한 뉴스 데이터가 여러 번 분석되면서 중복 카운트가 발생하는 현상이 나타남.

해결 방안

  • Kafka의 Exactly-Once Semantics(EOS) 옵션을 활성화하여 중복 처리를 방지할 수 있음.
  • 하지만 EOS는 추가적인 오버헤드가 발생할 수 있으므로, 비즈니스 로직에 따라 선택적으로 적용해야 함.

6. 결론

  • SQS 기반 뉴스 패치 시스템에서 동시성 문제를 해결하기 위해 ReentrantLock + ConcurrentMap을 활용하여 안정적인 데이터 처리를 구현.
  • 락 해제 시점을 명확히 설정하여 메모리 누수를 방지하고, 성능 저하 없이 동시성 문제를 해결.
  • Kafka 메시지 중복 처리 문제는 At-Least-Once의 특성 때문이며, 이를 해결하기 위해 Exactly-Once Semantics 옵션 적용을 고려.

이번 해결 과정에서 애플리케이션 레벨에서 락을 활용한 동시성 제어, 메시지 큐 시스템의 특성에 대한 이해가 중요함을 다시 한번 확인할 수 있었습니다.
향후 시스템을 더욱 확장하기 위해선 Kafka 메시지 처리 방식을 보다 정교하게 다듬어야 할 것입니다.

 

 

후기 

그래도 컨슈머 랙의 값이 커져서 문제가 생긴다. 따라서 디비 락을 낙관적 락으로 변경하고, 충돌에 따른 메세지 관리를 수행해야겠다.더 좋은 방법은 람다로 이전하는 것이 베스트다

 

최종적으로 DB의 낙관적 락으로 변경하고, 분석 요청을 람다로 이전하여 최대 동시성을 확보했다

 

 

[용어설명]

컨슈머 랙(Consumer Lag)이란?

컨슈머 랙이란 메시지 큐에 쌓인 데이터와 이를 처리하는 컨슈머 간의 시간 차이를 의미합니다.
이 값이 커지면 메시지 처리가 지연되어 아직 읽지 않은 메세지가 유실될 위험이 있습니다.

반응형