내구성 있는 실행의 핵심 아이디어는 함수가 자신을 ‘스냅샷’하여 ‘깊은 잠’에 빠뜨리고, 외부 스케줄러에 의해 다시 활성화되어 중단된 호출 스택 지점부터 실행을 재개하는 것입니다. 그러나 실제 런타임에서는 이러한 ‘마샬 가능한 스택(marshalable stack)’이 불가능합니다.
마샬 가능한 스택의 한계
-
핸들 문제: 파일, 소켓, 데이터베이스, GPU 등 외부 리소스에 대한 ‘핸들’은 대부분 직렬화할 수 없습니다. 이는 운영 체제 수준의 깊은 통합을 요구하며, Java의
Serializable인터페이스와 유사한 문제를 야기합니다. -
코드 변경: 스냅샷된 호출이 재개될 때, 시스템의 코드(예:
PaymentInterfaceV1이PaymentInterfaceAdapterFactory로 변경)가 변경되면 오류가 발생합니다. 이는 ActiveJob과 같은 백그라운드 작업에서 클래스 정의가 사라지는 경우와 유사합니다. -
비결정적 요소:
Process.clock_gettime(Process::CLOCK_MONOTONIC)와 같은 단조로운 시간 측정은 스냅샷 시점과 재개 시점 사이에 일관성을 보장하기 어렵습니다. 이는 추가적인 상태 저장과 복잡한 동기화 메커니즘을 요구합니다. -
자원 비용: VM 스냅샷은 OS, 라이브러리, 전체 메모리 내용을 포함하므로 크기가 매우 커서, 모든 결제마다 16GB의 Docker 컨테이너를 보관하는 것은 비현실적입니다.
멱등성(Idempotency)을 통한 대체
마샬 가능한 스택의 부재를 해결하기 위해 현재 시스템들은 ‘멱등성’을 활용합니다. 이는 작업이 실패하거나 중단되더라도, 동일한 작업을 여러 번 수행해도 결과가 동일하다는 가정을 기반으로 합니다.
-
run_remotely_and_asynchronously와 같은 함수 호출 시, 이전에 저장된 체크포인트가 있는지 확인하여 해당 호출을 건너뛰고 캐시된 결과를 사용합니다. -
멱등성의 한계:
SecureRandom.uuid와 같이 비결정적인 인수를 사용하면 매번 새로운 값이 생성되어 체크포인트가 무용지물이 됩니다. 이를 해결하기 위해 ‘일시적인 컨텍스트’와 ‘영구적인 컨텍스트’를 구분하는 등 추가적인 복잡성이 발생합니다.- 소스 코드 라인 수준의 체크포인트는 코드 변경에 매우 취약합니다.
- 인증 토큰과 같이 시간 제한이 있거나 재사용할 수 없는 요소들은
run_remotely_and_asynchronously블록 내에서 생성되어야 하며, 이는 ‘외부 프로그램’(오케스트레이터)과 ‘내부 프로그램’(태스크) 간의 책임 분리를 복잡하게 만듭니다.
예외 복구 및 롤백
서버 크래시나 OOM killer에 의한 컨테이너 종료 등으로 작업이 중단될 경우, 적절한 복구를 위해 프로그램의 어느 지점에서 복구를 시작해야 할지 아는 것이 중요합니다. 부분적으로 완료된 작업의 롤백은 복잡하며, 내구성 있는 실행의 중요한 부분입니다.