DBOS와 복잡한 시간적 작업(Temporal Tasks)의 소요 시간을 단축하는 방법
요약
분산 시스템 환경에서 복잡한 다단계 작업(Temporal Tasks) 수행 중 발생하는 프로세스 중단과 데이터 불일치 문제를 다룹니다. 기존의 Saga 패턴이나 Kafka를 활용한 복잡한 아키텍처의 운영 비용과 한계를 분석합니다.
핵심 포인트
- 분산 환경에서 프로세스 중단 시 데이터 일관성 유지의 어려움
- Saga 패턴, 멱등성, 조정 작업 등 기존 해결 방식의 높은 운영 비용
- 비즈니스 프로세스를 안정적으로 관리하기 위한 워크플로우 엔진의 필요성
한 고객이 금융 상품에 가입합니다. 시스템은 자격 요건을 확인하고, 계좌를 생성하며, 금액을 청구하거나 예약하고, 구독을 활성화하며, 이벤트를 발생시키고, 후속 점검을 예약합니다. 세 번째 단계 이후 어딘가에서 프로세스가 중단됩니다.
깔끔하게 실패하는 것이 아닙니다. 그냥 죽어버리는 것입니다.
컨테이너가 종료됩니다. 배포가 교체됩니다. 워커(Worker)가 연결을 잃습니다. 프로세스는 이미 외부 세계를 변경한 상태에서 재시작됩니다.
이제 질문은 "이 코드를 어떻게 실행할 것인가?"가 아닙니다.
질문은 이것입니다:
우리가 이미 돈을 옮겼는가?
바로 이 지점에서 멋진 작은 플로우차트(Flowchart)가 프로덕션 시스템(Production system)이 됩니다. 만약 세 번째 단계에서 카드를 청구했거나, 원장 항목(Ledger entry)을 예약했거나, 잔액을 예약했거나, 파트너를 호출했다면, 처음부터 전체 작업을 재시도하는 것은 복구(Recovery)가 아닙니다.
우리는 이것을 직접 구축하곤 했습니다
미국 대륙의 한 핀테크 기업에서 그 해답은 대부분 사가(Sagas), Kafka, 멱등성 키(Idempotency keys), 조정 작업(Reconciliation jobs), 그리고 규율이었습니다.
그 아키텍처는 이제 대중적으로 알려져 있습니다. 비즈니스 프로세스를 로컬 트랜잭션(Local transactions)으로 모델링합니다. 각 서비스는 자신의 데이터를 소유합니다. 이벤트가 프로세스를 앞으로 진행시킵니다. 무언가 잘못되면, 다른 단계가 보상(Compensate)하거나, 재시도하거나, 나중에 수리할 수 있도록 케이스를 발생시킵니다. 결과적 일관성(Eventual consistency)과 타협한 뒤, 운영 비용을 지불하게 됩니다.
그리고 그 비용은 실재합니다.
모든 경계에는 멱등성(Idempotency)이 필요합니다. 모든 컨슈머(Consumer)는 중복된 메시지가 무해한지 여부를 알아야 합니다. 모든 사가(Saga)에는 진행 상황을 기록할 장소가 필요합니다. 모든 타임아웃(Timeout)에는 의미론(Semantics)이 필요합니다. 모든 보상(Compensation)은 되돌리는 것이 가능한지, 아니면 유일하게 정직한 옵션이 수동 검토인지 이해해야 합니다.
이 메커니즘은 화려하지 않습니다. 결국 워크플로우 상태 테이블(Workflow state tables), 중복 제거 제약 조건(Dedupe constraints), Kafka 토픽(Topics), 데드 레터 큐(Dead-letter queues), 대시보드, 재생 스크립트(Replay scripts), 그리고 금융 소프트웨어에서 가장 중요한 질문을 던지는 조정 작업(Reconciliation jobs)을 갖게 됩니다:
"무엇이 참이어야 하며, 무엇이 실제로 참인가?"
그 모델은 효과적입니다. 저는 그 모델을 존중합니다. 그것은 엔지니어들이 비즈니스 프로세스를 제대로 이해하도록 강제합니다. 또한 회사 내부에 조용한 플랫폼을 만들어 줍니다. 어느 시점이 되면, "우리는 그냥 이벤트를 사용합니다"라는 말은 "우리는 관습(conventions)으로 만들어진 분산 워크플로우 엔진(distributed workflow engine)을 소유하고 있습니다"로 변하게 됩니다.
회사가 충분히 크다면 그것은 괜찮습니다. 하지만 원시 기능(primitive)이 엔지니어링 플랫폼 내부에서 하나의 제품(product)으로 자리 잡지 못해, 모든 팀이 동일한 실패 모드(failure modes)를 매번 새로 발견해야 한다면 그것은 그리 괜찮지 않습니다.
동일한 문제가 어디에서나 나타납니다
제가 현재 협업하고 있는 걸프 지역의 한 은행은 다른 옷을 입고 있을 뿐 유사한 형태를 띠고 있습니다: 구독 활성화(subscription activation), 예정된 스윕(scheduled sweeps), 주기적 점검, 파트너 호출, 지연된 작업, 장애 후 재시도 등이 그것입니다. 이름은 바뀌지만, 질문은 지루하고도 잔혹하게 유지됩니다:
"이 다단계 작업(multi-step job)이 이미 일어난 일에 대해 거짓을 말하지 않고, 충돌(crash) 상황에서도 살아남을 수 있는가?"
이것이 바로 DBOS가 제 관심을 끈 이유입니다. 모든 팀이 DBOS를 사용해야 한다고 생각해서가 아닙니다. 이것은 DBOS 리뷰가 아닙니다. 흥미로운 부분은 더 일반적입니다.
내구적 실행(Durable execution)은 "모든 진지한 백엔드 팀이 결국 이것을 직접 구축한다"에서 "제어 평면(control-plane) 속성을 제공하는 무언가를 설치할 수 있다"로 이동했습니다.
이것은 큰 변화입니다.
DBOS와 Temporal이 실제로 제공하는 것
DBOS와 Temporal은 문제에 접근하는 방식은 다르지만, 개발자 수준에서는 유사한 약속을 판매합니다: 워크플로우(workflow)를 일반적인 코드로 작성하고, 의미 있는 단계들을 표시하면, 런타임(runtime)이 장애 발생 후 재개할 수 있을 만큼 충분한 상태(state)를 기록한다는 것입니다.
DBOS Transact는 그 아이디어를 더 가벼운 Postgres 형태의 버전으로 구현한 것입니다. 현재 DBOS의 포지셔닝은 여전히 Transact가 오픈 소스이며, 애플리케이션과 함께 실행되고, 워크플로우 또는 큐(queue) 상태를 Postgres 호환 데이터베이스에 저장한다는 것입니다. DBOS는 그 데이터베이스가 여러분의 애플리케이션 데이터베이스가 될 수 있다고 말합니다. 앱이 재시작되면, 완료되지 않은 워크플로우는 마지막으로 완료된 단계부터 계속됩니다.
그것이 중요한 이유는 도입 경로가 작기 때문입니다. 시작하기 위해 별도의 워크플로우 클러스터(workflow cluster)를 요구하지 않습니다. Postgres는 이미 많은 팀이 영구적인 진실(durable truth)을 보관하는 곳입니다. 이들의 유료 모델은 Pro 및 Teams 툴링, 지원, Conductor 워크플로우 관리, 체크포인트 허용량, 그리고 관리형(managed) 또는 셀프 호스팅(self-hosted) 옵션 등을 중심으로 구성됩니다.
Temporal은 더 큰 플랫폼입니다. Temporal 워크플로우(Workflow)는 Temporal 서비스에 이벤트 이력(event history)을 기록합니다. 액티비티(Activities)가 스케줄링되고, 시작되고, 완료되고, 실패하고, 타임아웃되거나 취소되면, 이러한 사실들이 영구적인 이력에 추가됩니다. 워커(worker)가 충돌(crash)하면, 리플레이(replay)를 통해 워크플로우 상태를 재구성할 수 있습니다. Temporal Cloud의 가격 책정은 월간 요금제에 액션(Actions) 및 스토리지(Storage) 기반의 사용량 요금이 추가되는 방식입니다.
여기에는 취향의 멋진 차이가 있습니다. DBOS는 대략적으로 "당신은 이미 Postgres를 가지고 있으니, 우리가 그곳에서 실행을 영구적(durable)으로 만들어 주겠다"라고 말합니다.
Temporal은 대략적으로 "당신의 비즈니스 프로세스를 전용 영구 실행(durable execution) 플랫폼에서 실행하라"라고 말합니다. 둘 다 합리적입니다. 더 나은 선택은 오케스트레이션(orchestration)의 비중, 운영에 대한 의지(operational appetite), 워크플로우 수명, 그리고 제어 평면(control plane)으로서의 Postgres가 단순함으로 느껴지는지 아니면 결합(coupling)으로 느껴지는지에 따라 달라집니다.
하지만 두 경우 모두 전략적 변화는 동일합니다. 질문이 "어떻게 충돌 안전한(crash-safe) 워크플로우 인프라를 구축할 것인가?"에서 "영구 실행(durable execution)이 어디에 존재해야 하며, 재개될 때의 의미론(semantics)은 누가 소유하는가?"로 바뀝니다.
영구성이 정확성을 결정하지는 않는다
여기에 함정이 있습니다.
사람들은 "영구 실행(durable execution)"이라는 말을 들으면 머릿속으로 "어려운 부분은 해결되었다"라고 번역해 버립니다.
그렇지 않습니다.
짜증 나는 부분은 작아졌을 뿐입니다. 어려운 부분은 더 명확하게 드러났습니다. 프레임워크는 3단계가 완료되었다는 것을 기록할 수 있습니다. 3단계를 다시 실행하는 것을 방지할 수 있습니다. 4단계를 재시도(retry)할 수 있습니다. 이력, 로그, 상태를 노출할 수 있고, 어쩌면 워크플로우를 리플레이하거나 포크(fork)할 수 있게 해줄 수도 있습니다.
하지만 3단계가 적절한 경계(boundary)였는지 여부는 알려줄 수 없습니다.
실패한 파트너 호출을 2분 동안 재시도해야 할지, 2일 동안 해야 할지, 아니면 영원히 하지 말아야 할지 알려줄 수 없습니다. 결제를 취소해야 하는지, 원장 기입(ledger posting)에 보정 분개(compensating entry)가 필요한지, 고객에게 "대기 중(pending)" 또는 "실패(failed)"를 보여줘야 하는지, 아니면 사람이 해당 케이스를 검토해야 하는지도 알려줄 수 없습니다.
이것들은 도메인 결정(domain decisions)입니다. 핀테크(fintech)에서는 회계적 결정(accounting decisions)입니다.
이것이 제가 개념으로서의 내구적 실행(durable execution)은 좋아하지만, 그 주변의 마법 같은 마케팅은 싫어하는 이유입니다. 실패는 사라지지 않습니다. 실패는 설계 단계에서 고려할 수 있을 만큼 명시적으로 드러날 뿐입니다.
경계(boundary)에서는 여전히 멱등성(idempotency)이 필요합니다. 워크플로(workflow)가 결제 제공업체, 원장(ledger), 알림 서비스 또는 파트너 API를 호출한다면, 그 경계는 중복 요청과 모호한 결과(ambiguous outcomes)를 허용할 수 있어야 합니다. 외부 세계는 여전히 키(keys), 참조(references), 상관관계 ID(correlation IDs), 고유 제약 조건(unique constraints) 및 대조(reconciliation)를 필요로 합니다.
여전히 보정 의미론(compensation semantics)이 필요합니다. "실행 취소(Undo)"는 거의 실제 취소가 아닙니다. 금융 시스템은 과거를 삭제하지 않습니다. 대신 수정 사항을 기입합니다. 그들은 되돌리고(reverse), 환불하고(refund), 만료시키고(expire), 해제하고(release), 취소하거나(cancel), 검토 대상으로 표시(mark for review)합니다. 이 단어들은 동의어가 아닙니다.
여전히 무엇이 하나의 단계(step)인지 결정해야 합니다. 너무 작으면 이력(history)이 노이즈로 가득 차고 비용이 많이 듭니다. 너무 크면 크래시(crash)가 발생했을 때, 원자적(atomic)이어야 할 단계 내부의 숨겨진 부분적 진행 상황(partial progress)에 대해 추론해야 하는 상황에 직면합니다. 유용한 경계는 대개 비즈니스 사실(business fact)이 변하는 지점입니다.
이것이 실제 엔지니어링 작업입니다. 내구적 실행(durable execution)은 이 작업을 제거하지 않습니다. 단지 숨기기 어렵게 만들 뿐입니다.
지루한 보장 사항들의 제품화 (the productization of boring guarantees)
어떤 속성은 처음에는 어렵게 얻은 내부 엔지니어링 규율로 시작됩니다. 그다음에는 프레임워크 기능이 됩니다. 그다음에는 플랫폼 기본 요소(platform primitive)가 됩니다. 결국 팀들은 그것을 구축할 수 있는지 묻는 것을 멈추고, 얼마나 많은 소유권(ownership)을 계속 유지하고 싶은지를 묻기 시작합니다.
우리는 관측 가능성(observability), 비밀 관리(secrets management), CI/CD, 피처 플래그(feature flags), 정책 엔진(policy engines), ID(identity), 롤아웃 제어(rollout controls), 인프라 드리프트 탐지(infrastructure drift detection)에서 이를 목격했습니다. 내구적 실행(durable execution)도 동일한 과정을 거치고 있습니다.
지루한 컨트롤 플레인(control-plane) 속성인 "이 작업은 충돌(crash) 발생 후 이미 무엇이 일어났는지 알고 있다"는 기능이 구매 가능한 영역으로 들어오고 있습니다.
이는 좋은 현상입니다. 저는 모든 제품 팀이 Kafka와 cron을 사용하여 미니 Temporal을 직접 구축하기를 원치 않습니다. 또한, 단 하나의 활성화 흐름(activation flow)에 5단계가 포함되어 있다는 이유로 모든 스타트업이 워크플로 플랫폼(workflow platform)을 운영하는 것도 원치 않습니다.
유용한 미래는 혼합된 형태가 될 것입니다. 어떤 워크플로(workflows)는 Postgres 기반의 내구적 라이브러리(durable library)와 함께 애플리케이션 코드 내부에 속해야 합니다. 어떤 것들은 전용 오케스트레이션 플랫폼(orchestration platform)에 속해야 합니다. 어떤 것들은 단순한 데이터베이스 상태 머신(database state machines)으로서 존재해야 합니다. 어떤 것들은 스케줄링된 조정 작업(scheduled reconciliation jobs)으로 남아 있어야 하는데, 왜냐하면 실제 비즈니스 프로세스가 그렇지 않은 척하기에는 충분히 동기적(synchronous)이지 않기 때문입니다.
성숙한 대응은 실패의 책임자(failure owner)를 지정하는 것입니다.
자금이 이동한 후 워크플로가 충돌한다면, 재시도(retry)할지, 되돌릴지(reverse), 기다릴지, 아니면 에스컬레이션(escalate)할지를 누가 결정합니까? 3시간의 다운타임 이후 스케줄링된 스윕(scheduled sweep)이 재개된다면, 누락된 윈도우(windows)를 처리할까요, 건너뛸까요, 아니면 하나로 합칠까요? 파트너가 모호한 타임아웃(timeout)을 반환한다면, 로컬 상태(local state)를 신뢰할까요, 그들의 최종 콜백(eventual callback)을 신뢰할까요, 아니면 조정 풀(reconciliation pull)을 신뢰할까요?
DBOS와 Temporal은 진행 상황(progress)을 보존하는 데 도움을 줄 수 있습니다. 하지만 진행 상황이 무엇을 의미하는지는 결정할 수 없습니다. 그것은 여전히 여러분의 몫입니다. 그것은 언제나 여러분의 몫이었습니다.
그 부분이 바로 주의를 기울일 가치가 있는 부분입니다.
참고 문헌
- DBOS, durable execution by default
- DBOS pricing
- Temporal Event History documentation
- Temporal Cloud pricing
제 프로젝트를 테스트하기 위해 저는 Railway를 사용합니다. 시작하기 위해 20달러(USD)를 받고 싶다면, 이 링크를 사용하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기