스칼라 Akka Stream과 Akka Actor를 활용하여 비동기 방식으로 대량의 데이터를 병렬로 처리한 후 동시에 상태를 관리하여 중복으로 액션이 실행되는 현상을 방지했고, 그 결과를 응답할 수 있도록 구현했습니다.
대량의 데이터를 비동기 방식으로 처리하고 중복 실행을 방지하기 위해 Akka Streams와 Akka Actor를 결합한 아키텍처를 구현하였습니다. 전체적인 구조는 아래와 같습니다.
MongoDB 데이터 조회
- MongoDB에서 데이터를 조회할 때, reactive stream Java driver를 매핑한 Scala driver를 활용하여 데이터를 조회했습니다.
- FindObservable(Publish 상속) 타입을 리턴하는 batchSize 함수를 활용해서 Akka Stream의 source로 연결해주었습니다.
- Akka Streams의 기본 backpressure 메커니즘 덕분에 downstream(데이터 처리 단계)이 처리 가능한 만큼만 데이터를 요청하게 되어, 안정적인 데이터 흐름과 메모리 관리가 이루어집니다.
ExecutionManagerActor를 통한 상태 관리 및 중복 처리
- 소스로부터 전달받은 데이터는 ExecutionManagerActor에서 처리됩니다.
- 이 Actor는 싱글 스레드로 동작하기 때문에, 내부에 상태를 저장할 수 있는 Map을 두어, 동일한 TID가 이미 처리된 경우 중복 실행을 방지하는 로직을 구현했습니다.
SendKafkaActor를 통한 Kafka 전송 및 장애 복구
- ExecutionManagerActor의 자식 액터를 생성하여 별도의 Kafka 전송을 담당하도록 하였습니다.
- 이렇게 Kafka 전송을 담당하는 자식 액터를 생성한 이유는 IO 바운드 작업을 별도 디스패처로 할당해서 수행할 수 있도록 하기 위함입니다.
- 또한 부모-자식 액터로 구조를 만들면 장애 복구에 용이합니다.
- BackoffSupervisor로 감싸서 네트워크 환경 오류의 경우 자동으로 재시도하도록 구성했습니다.
- BackoffSupervisor의 기본 계산식은 delay = minBackoff * 2^(n)로 이루어지므로 minBackoff는 2초, maxBackoff는 10초로 설정하여 2초, 4초, 8초(2의 지수)와 같은 재시도 간격으로 자동 재시도하도록 구성하였습니다.
추후 확장 계획
- 현재는 단일 인스턴스의 액터를 사용하고 있지만, Akka Actor의 라우터 개념을 도입하여 동일한 역할을 하는 SendKafkaActor 인스턴스를 여러 개 운영하며 병렬 처리 능력이 개선되어 upstream의 데이터를 더 빠르게 소모할 수 있습니다.
[scala mongodb 드라비어에서 제공해주는 batchSize 설정과 동작 원리]
드라이버에서 제공하는 batchSize 설정은 MongoDB 서버에 한 번에 가져올 문서의 개수를 지정합니다.
예를 들어, batchSize를 10으로 설정하면, 내부 커서를 통해 MongoDB 서버는 한 번에 최대 10건의 데이터를 전송합니다.
단, 이 데이터는 즉시 애플리케이션으로 모두 push 되는 것이 아니라, Reactive Streams의 pull 메커니즘에 따라 downstream(구독자)이 실제 요청한 양만큼 전달됩니다.