내구성 있는 실행의 세계: 마샬 가능한 스택의 환상과 멱등성의 현실

On the way to step functions: dreams of marshalable stacks

작성자
발행일
2026년 01월 16일

핵심 요약

  • 1 내구성 있는 실행은 전자 결제 등 특정 분야에서 필수적이나, 코드의 일시 중지 및 재개에 있어 런타임의 근본적인 한계에 직면합니다.
  • 2 Temporal.io와 같은 현대 내구성 실행 시스템은 마샬 가능한 스택의 부재를 멱등성으로 대체하며, 이는 개발자에게 복잡한 고려사항을 안겨줍니다.
  • 3 진정한 스냅샷 기능의 부재는 핸들 직렬화, 코드 변경, 비결정적 요소 등으로 인한 문제를 야기하며, 멱등성 기반의 해결책 또한 자체적인 한계를 가집니다.

도입

최근 내구성 있는 실행(durable execution)은 웹 애플리케이션에서는 흔치 않지만, 전자 결제, 송금, 인증 등 특정 분야에서 그 중요성이 부각되고 있습니다. 이는 코드의 특정 지점에서 실행을 중단하고 나중에 정확히 그 지점에서 재개할 수 있는 능력을 요구합니다. Temporal.io와 Restate와 같은 선두 주자들은 사가(saga) 패턴을 기반으로 이러한 문제를 해결하려 하지만, 근본적인 런타임의 한계에 직면합니다. 본 글은 이러한 내구성 있는 실행의 개념과 이를 구현하는 데 따르는 난제들을 탐구합니다.

내구성 있는 실행의 핵심 아이디어는 함수가 자신을 ‘스냅샷’하여 ‘깊은 잠’에 빠뜨리고, 외부 스케줄러에 의해 다시 활성화되어 중단된 호출 스택 지점부터 실행을 재개하는 것입니다. 그러나 실제 런타임에서는 이러한 ‘마샬 가능한 스택(marshalable stack)’이 불가능합니다.

마샬 가능한 스택의 한계

  • 핸들 문제: 파일, 소켓, 데이터베이스, GPU 등 외부 리소스에 대한 ‘핸들’은 대부분 직렬화할 수 없습니다. 이는 운영 체제 수준의 깊은 통합을 요구하며, Java의 Serializable 인터페이스와 유사한 문제를 야기합니다.

  • 코드 변경: 스냅샷된 호출이 재개될 때, 시스템의 코드(예: PaymentInterfaceV1PaymentInterfaceAdapterFactory로 변경)가 변경되면 오류가 발생합니다. 이는 ActiveJob과 같은 백그라운드 작업에서 클래스 정의가 사라지는 경우와 유사합니다.

  • 비결정적 요소: Process.clock_gettime(Process::CLOCK_MONOTONIC)와 같은 단조로운 시간 측정은 스냅샷 시점과 재개 시점 사이에 일관성을 보장하기 어렵습니다. 이는 추가적인 상태 저장과 복잡한 동기화 메커니즘을 요구합니다.

  • 자원 비용: VM 스냅샷은 OS, 라이브러리, 전체 메모리 내용을 포함하므로 크기가 매우 커서, 모든 결제마다 16GB의 Docker 컨테이너를 보관하는 것은 비현실적입니다.

멱등성(Idempotency)을 통한 대체

마샬 가능한 스택의 부재를 해결하기 위해 현재 시스템들은 ‘멱등성’을 활용합니다. 이는 작업이 실패하거나 중단되더라도, 동일한 작업을 여러 번 수행해도 결과가 동일하다는 가정을 기반으로 합니다.

  • run_remotely_and_asynchronously와 같은 함수 호출 시, 이전에 저장된 체크포인트가 있는지 확인하여 해당 호출을 건너뛰고 캐시된 결과를 사용합니다.

  • 멱등성의 한계:

    • SecureRandom.uuid와 같이 비결정적인 인수를 사용하면 매번 새로운 값이 생성되어 체크포인트가 무용지물이 됩니다. 이를 해결하기 위해 ‘일시적인 컨텍스트’와 ‘영구적인 컨텍스트’를 구분하는 등 추가적인 복잡성이 발생합니다.
    • 소스 코드 라인 수준의 체크포인트는 코드 변경에 매우 취약합니다.
    • 인증 토큰과 같이 시간 제한이 있거나 재사용할 수 없는 요소들은 run_remotely_and_asynchronously 블록 내에서 생성되어야 하며, 이는 ‘외부 프로그램’(오케스트레이터)과 ‘내부 프로그램’(태스크) 간의 책임 분리를 복잡하게 만듭니다.

예외 복구 및 롤백

서버 크래시나 OOM killer에 의한 컨테이너 종료 등으로 작업이 중단될 경우, 적절한 복구를 위해 프로그램의 어느 지점에서 복구를 시작해야 할지 아는 것이 중요합니다. 부분적으로 완료된 작업의 롤백은 복잡하며, 내구성 있는 실행의 중요한 부분입니다.

결론

내구성 있는 실행의 많은 '정신'은 재활용 가능한 호출 스택이나 저비용 VM 스냅샷에 대한 열망에서 비롯됩니다. 그러나 현재의 클라우드 기반 내구성 실행 엔진들은 이러한 기능을 제공하는 척하며, 실제로는 강제된 멱등성으로 이를 대체합니다. 이 멱등성은 대부분 개발자의 책임으로 전가되며, 이는 본질적으로 나쁜 것은 아니지만 명확한 한계입니다. 필자는 Go나 Java가 이러한 목적에 적합하다고 가장하기보다는, 진정으로 이 문제를 해결할 수 있는 새로운 언어/VM 개발에 투자해야 한다고 주장합니다. 다음 글에서는 '워크플로우' 코드와 '활동' 코드를 분리하여 이 문제를 어떻게 관리할 수 있는지 탐구할 예정입니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!