
코루틴과 MongoDB로 스레드 점유 없이 수천 개의 실시간 주문 지연 이벤트를 우아하게 흘려보내는 법
배달의민족에서 배치 잡으로 처리하던 장기 대기 후처리 비즈니스를 단일 건별 라이프사이클 기반의 워크플로 아키텍처로 탈바꿈한 흥미로운 여정을 다룹니다. 복잡한 글로벌 기술 스택 대신 기존 인프라(Kotlin 코루틴, MongoDB)를 백분 활용해 경량 엔진을 자체 구현하고, 배치의 물리적 한계를 정교하게 해결해 낸 실무적인 설계와 극복 과정을 상세히 공유합니다.
피크타임 대규모 디비 스캔이나 주기적 폴링 기반 배치를 걷어내고 싶지만, Temporal과 같은 복잡한 분산 기술을 프로덕션에 직접 얹는 인프라 러닝커브와 유지비용이 걱정되는 시니어 백엔드 아키텍트에게 필독을 권장합니다.
배달의민족 포스 시스템에서 일정 시간이 흐른 뒤 처리해야 하는 주문 접수 지연 알림 등을 대형 배치 잡으로 일괄 처리하고 있었습니다. 그러나 피크타임 시 대량의 데이터를 매 사이클마다 전체 스캔해야 해 DB 부하가 컸고, 정밀도 저하와 여러 모듈 분산으로 인한 이력 추적의 어려움이 존재했습니다.
대형 워크플로 엔진인 Temporal의 높은 러닝커브와 독자적인 인프라 구축 부담을 피하기 위해, Kotlin 코루틴과 MongoDB 기반의 자체 경량 워크플로 라이브러리인 'Flowkit'을 개발하였습니다. 이를 도입해 '주문 1건 = 워크플로 1개' 모델로 아키텍처를 전면 전환하고, 대기 발생 시 스레드 점유 없이 코루틴 상태를 DB에 직렬화해두었다가 깨어날 시점에만 히스토리를 재실행하는 방식을 적용했습니다.
피크타임 배치 처리에 따르던 전체 DB 대상 재스캔 연산 부하를 완벽히 걷어내고 대기 중 리소스 소모를 '0'에 수렴하게 만들었습니다. 또한 각 주문별 상태 변경 변수 스냅샷, 진행 단계 로그, 신호 목록 등을 직관적으로 보여주는 전용 어드민 대시보드를 구축해 운영 가시성을 획기적으로 확보했습니다.
Trade-off
완성도 높은 기성 엔진과 달리 액티비티 실행 단위마다 영속화하지 않고 대기(await), 완료, 실패 시점에만 주기적 체크포인트를 찍기 때문에, 서버 장애 시 일부 액티비티 구간이 재실행될 수 있어 비즈니스 멱등성 보장이 필수적입니다. 아울러 안전한 재실행(Replay)을 위해 본문 내에 절대로 비결정론적 로직을 작성해선 안 된다는 개발 제한 규칙을 직접 코드 리뷰 등으로 검증해야 하는 관리 비용이 존재합니다.
가상 프로세스의 실행 상태를 스토리지에 견고하게 백업하여, 하드웨어 중단이나 애플리케이션 재배포 같은 크래시 상황이 발생하더라도 마지막 연산 상태 그대로 정확히 복구하여 실행 흐름을 이어가게 돕는 특성입니다.
매번 코드를 재실행할 때마다 외적 요인 없이 항상 같은 분기, 같은 흐름, 동일한 연산 순서가 무조건 재현되도록 제한하는 규칙입니다.
대기 상태에서 깨어난 워크플로를 중단 직후부터 구동하기 위해, 함수를 처음부터 다시 정상 구동하되 이미 완료된 외부 호출 결과는 캐시에서 바로 인출하여 건너뛰는 방식입니다.




