DevLog

엔지니어링 블로그를 한 곳에서 탐색하고, 최근 발행 흐름을 빠르게 파악할 수 있는 서비스 입니다.

Quick Links

  • Latest Feed
  • Engineering Directory

Support

  • 소개
  • 개인정보처리방침

Contribute

  • 원하는 블로그 추가 (준비 중)
  • Feedback

© 2026 DevLog Inc. All rights reserved.

본 사이트는 공개 RSS 피드를 통해 콘텐츠를 수집하며, 모든 콘텐츠의 저작권은 원저작자에게 있습니다.

Back to Feed
Read Original

Contents

Continue Reading

  • More from 우아한형제들
  • Related reads#KotlinCoroutine
#Backend

한꺼번에 짊어지던 배치를 내려놓고, 하나씩 흘려보내는 워크플로로

한꺼번에 짊어지던 배치를 내려놓고, 하나씩 흘려보내는 워크플로로
01

Summary

배치의 무거운 짐을 내려놓다: 배달의민족이 Temporal 대신 직접 만든 경량 워크플로 엔진 'Flowkit' 개발기

코루틴과 MongoDB로 스레드 점유 없이 수천 개의 실시간 주문 지연 이벤트를 우아하게 흘려보내는 법

배달의민족에서 배치 잡으로 처리하던 장기 대기 후처리 비즈니스를 단일 건별 라이프사이클 기반의 워크플로 아키텍처로 탈바꿈한 흥미로운 여정을 다룹니다. 복잡한 글로벌 기술 스택 대신 기존 인프라(Kotlin 코루틴, MongoDB)를 백분 활용해 경량 엔진을 자체 구현하고, 배치의 물리적 한계를 정교하게 해결해 낸 실무적인 설계와 극복 과정을 상세히 공유합니다.

  • 01피크타임 전체 DB 스캔 성능 병목을 해결하기 위해 '1개 주문 = 1개 지속가능한 워크플로'로 모델을 세련되게 전환
  • 02Temporal의 핵심 철학을 차용하여 복잡한 분산 인프라 없이 Kotlin Coroutine과 Reactive MongoDB만으로 초경량 엔진 구현
  • 03대기(await) 진입 시 스레드와 메모리를 완전 반납하고 데이터를 직렬화하여 영속화했다가 정밀하게 복구하는 아키텍처 설계
  • 04호출 시퀀스를 활용한 캐싱 메커니즘을 통해 워크플로가 처음부터 재실행되어도 실제 외부 호출은 중복 방지 처리
  • 05시각적인 Replay 이력 및 상태 변수 변화를 모니터링할 수 있는 독립 어드민 시스템 구축으로 관제 가시성 정점 확보

+RECOMMENDATION

피크타임 대규모 디비 스캔이나 주기적 폴링 기반 배치를 걷어내고 싶지만, Temporal과 같은 복잡한 분산 기술을 프로덕션에 직접 얹는 인프라 러닝커브와 유지비용이 걱정되는 시니어 백엔드 아키텍트에게 필독을 권장합니다.

The Problem

배달의민족 포스 시스템에서 일정 시간이 흐른 뒤 처리해야 하는 주문 접수 지연 알림 등을 대형 배치 잡으로 일괄 처리하고 있었습니다. 그러나 피크타임 시 대량의 데이터를 매 사이클마다 전체 스캔해야 해 DB 부하가 컸고, 정밀도 저하와 여러 모듈 분산으로 인한 이력 추적의 어려움이 존재했습니다.

The Solution

대형 워크플로 엔진인 Temporal의 높은 러닝커브와 독자적인 인프라 구축 부담을 피하기 위해, Kotlin 코루틴과 MongoDB 기반의 자체 경량 워크플로 라이브러리인 'Flowkit'을 개발하였습니다. 이를 도입해 '주문 1건 = 워크플로 1개' 모델로 아키텍처를 전면 전환하고, 대기 발생 시 스레드 점유 없이 코루틴 상태를 DB에 직렬화해두었다가 깨어날 시점에만 히스토리를 재실행하는 방식을 적용했습니다.

The Result

피크타임 배치 처리에 따르던 전체 DB 대상 재스캔 연산 부하를 완벽히 걷어내고 대기 중 리소스 소모를 '0'에 수렴하게 만들었습니다. 또한 각 주문별 상태 변경 변수 스냅샷, 진행 단계 로그, 신호 목록 등을 직관적으로 보여주는 전용 어드민 대시보드를 구축해 운영 가시성을 획기적으로 확보했습니다.

Trade-off

완성도 높은 기성 엔진과 달리 액티비티 실행 단위마다 영속화하지 않고 대기(await), 완료, 실패 시점에만 주기적 체크포인트를 찍기 때문에, 서버 장애 시 일부 액티비티 구간이 재실행될 수 있어 비즈니스 멱등성 보장이 필수적입니다. 아울러 안전한 재실행(Replay)을 위해 본문 내에 절대로 비결정론적 로직을 작성해선 안 된다는 개발 제한 규칙을 직접 코드 리뷰 등으로 검증해야 하는 관리 비용이 존재합니다.

03

Key Concepts

Concept · 01

지속 가능한 실행 (Durable Execution)

가상 프로세스의 실행 상태를 스토리지에 견고하게 백업하여, 하드웨어 중단이나 애플리케이션 재배포 같은 크래시 상황이 발생하더라도 마지막 연산 상태 그대로 정확히 복구하여 실행 흐름을 이어가게 돕는 특성입니다.

  • Flowkit은 await과 같이 대기하는 시점을 체크포인트 삼아 가상 메모리 변수와 상태를 MongoDB에 문서 형태로 직렬화합니다.
  • 서버가 재시작되어도 저장된 복원 지점에서 중단 없이 동기적으로 코드가 흘러가도록 보증합니다.
Concept · 02

결정론적 제약 (Deterministic Code Constraint)

매번 코드를 재실행할 때마다 외적 요인 없이 항상 같은 분기, 같은 흐름, 동일한 연산 순서가 무조건 재현되도록 제한하는 규칙입니다.

  • execute() 함수 본문 안에서는 절대로 직접적인 현재 시간 호출(System.currentTimeMillis())이나 랜덤 추첨, 무제한적인 I/O를 수행해선 안 됩니다.
  • 모든 비결정론적 행위는 Flowkit이 제어하는 Activity 클래스 내부로 캡슐화해 격리시킴으로써 재실행 시 무결성을 확보합니다.
Concept · 03

히스토리 재생 (History Replay)

대기 상태에서 깨어난 워크플로를 중단 직후부터 구동하기 위해, 함수를 처음부터 다시 정상 구동하되 이미 완료된 외부 호출 결과는 캐시에서 바로 인출하여 건너뛰는 방식입니다.

  • 각 Activity 호출 및 타이머 대기 순간마다 0부터 고유한 일련번호를 매겨 상태 캐시와 직접 매핑합니다.
  • 재작동 시 일치하는 캐시 결과를 즉각 반환하고 도달하지 못했던 새로운 영역에서만 실제 연산을 개시합니다.
Continue reading · same source

우아한형제들More from 우아한형제들

View all posts from 우아한형제들
  • 문서로만 지키던 아키텍처 규칙, 테스트 코드로 강제하기

    ArchUnitHexagonal ArchitectureCI/CD
    1일 전
  • AI에게 만드는 법 대신 실패하는 법을 묻다: 포토그래퍼의 수천 명 동시 접속 게임 만들기

    Server-Sent EventsRedis PubSubKubernetes
    1주 전
  • 옆 팀은 AI를 어떻게 쓸까? 첫 사내 기술 콘퍼런스 우아한테크데이

    Developer RelationsKnowledge SharingAI Transformation
    2주 전
  • 기술블로그 세 번째 책 《요즘 우아한 백엔드 개발》 출간

    KotlinApache KafkaElasticsearch
    1개월 전
  • 배포 없이 앱과 로컬 웹을 잇다

    ProxyTunnelingWebSocket
    1개월 전

Related reads#KotlinCoroutine

Explore #KotlinCoroutine
Flex·Hexagonal Architecture

[코드가 환경을 모르는 구조 4/7] 타임머신 — 시간 축을 교체한다

#KotlinCoroutine4개월 전

Source

우아한형제들
우아한형제들
Engineering Blog

Published · September 8, 2026

Topics

Kotlin CoroutineTemporalMongoDBBatch ProcessingDurable Execution