Temporal Ruby SDK: 견고한 파이버로 크래시 없는 소프트웨어 구현

Temporal Ruby – crash-proof fibers

작성자
HackerNews
발행일
2025년 10월 16일

핵심 요약

  • 1 Temporal Ruby SDK가 GA(General Availability)를 달성하여 Ruby 개발자들이 견고한(durable) 소프트웨어를 작성할 수 있게 되었습니다.
  • 2 Rust Core와 커스텀 내구성 파이버 스케줄러를 활용하여 결정론적 비동기 워크플로우를 구현한 기술적 배경을 설명합니다.
  • 3 비결정론적 호출 추적, 표준 라이브러리 동기화 문제 해결, 런타임 타입 힌트 지원 등 개발 과정의 주요 난제를 다룹니다.

도입

Temporal Ruby SDK가 오랜 노력 끝에 GA(General Availability)를 달성하며 Ruby 개발자들이 견고하고 안정적인 소프트웨어를 구축할 수 있는 길을 열었습니다. 이 SDK는 Ruby 언어의 고유한 특성을 살리면서도 Temporal의 핵심 기능인 크래시에도 견딜 수 있는 장기 실행 코드를 작성할 수 있도록 지원합니다. 본 글에서는 Temporal Ruby의 전반적인 개념을 소개하고, Rust Core 활용, 내구성 있는 파이버 구현, 불법 호출 방지 메커니즘, 그리고 개발 과정에서 직면했던 흥미로운 기술적 도전 과제들에 대한 심층적인 내용을 다룹니다.

Temporal 및 Temporal Ruby 소개

Temporal은 크래시에도 살아남아 장기 실행될 수 있는 견고한 코드를 작성하기 위한 시스템 및 프로그래밍 모델입니다. Temporal 워크플로우는 결정론적 코드 집합으로, 사이드 이펙트가 있는 동작을 이벤트로 기록하여 크래시 발생 시 중단된 지점부터 재개할 수 있도록 합니다. Ruby SDK는 이러한 Temporal의 기능을 Ruby 개발자들이 네이티브 Ruby 방식으로 활용할 수 있도록 지원합니다.

예제 코드 설명

본문에서는 간단한 원클릭 구매 워크플로우를 예시로 들어 Activity와 Workflow 구현 방법을 제시합니다.

  • Activity 구현: Purchase Activity는 HTTP POST 요청을 통해 구매를 처리하며, HTTP 응답 코드에 따라 재시도 가능 여부를 결정하는 예외 처리를 포함합니다.

  • Workflow 구현: OneClickBuy Workflow는 10초 이내에 취소되지 않으면 구매를 확정하는 로직을 포함합니다. Temporalio::Workflow.sleep을 통한 내구성 있는 타이머, 워크플로우 취소 및 업데이트 기능을 활용합니다.

  • Worker 및 Client 실행: 구현된 Activity와 Workflow는 Worker를 통해 Temporal 서버와 통신하며 실행됩니다. Client는 워크플로우 시작, 업데이트, 취소, 상태 쿼리 및 결과 대기 등 다양한 상호작용을 수행할 수 있습니다.

고급 SDK 구현 세부 사항

### Rust Core + Ruby C 확장 Temporal Ruby SDK는 TypeScript, Python, .NET SDK와 마찬가지로 Temporal의 공통 Rust Core를 활용하여 gRPC 클라이언트 및 Worker 상태 머신 등 복잡한 로직을 처리합니다. 이는 의존성을 줄이고 일관된 기능을 제공하는 이점을 가집니다. Ruby와 Rust 간의 브릿지는 Magnusrb-sys를 사용하며, Ruby의 GVL(Global VM Lock) 문제를 해결하기 위해 별도의 Ruby 스레드에서 Rust 코드를 실행하고 콜백을 통해 비동기적으로 Ruby 큐에 응답을 전달하는 방식을 사용합니다.

내구성 있는 파이버 스케줄러

Temporal 워크플로우 코드는 결정론적이어야 하므로, Ruby SDK는 커스텀 Fiber::Scheduler를 구현합니다. 이 스케줄러는 모든 비동기 작업이 결정론적으로 작동하도록 보장하며, run_until_all_yielded 메서드를 통해 이벤트 루프를 구성하여 파이버를 실행하고 외부 자극(Activity 완료 등)을 기다립니다. kernel_sleeptimeout_after를 구현하여 sleepTimeout.timeout을 내구성 있게 만들지만, 기본적으로 비활성화됩니다.

추가 비동기 구성 요소

Ruby 표준 라이브러리의 부족한 부분을 보완하기 위해 다음과 같은 고수준 비동기 구성 요소를 제공합니다.

  • Temporalio::Cancellation: 계층적이고 보호될 수 있으며 비동기 호출을 중단할 수 있는 취소 메커니즘을 제공합니다.

  • Temporalio::Workflow::Future: 여러 동시 작업을 쉽게 기다릴 수 있는 고수준의 동시성 제어 기능을 제공합니다.

  • Temporalio::Workflow.wait_condition: 블록이 참(truthy)이 될 때까지 기다리는 강력한 기본 요소로, 워크플로우 상태 변화에 따라 대기하는 데 사용됩니다.

불법 호출 추적

워크플로우 코드의 결정론성을 유지하기 위해 Time.now, 스레드, 시스템 랜덤 등 비결정론적 호출은 금지됩니다. Ruby SDK는 TracePoint를 활용하여 워크플로우 스레드에서 발생하는 모든 호출을 검사하고, 설정된 illegal_workflow_calls 목록에 해당하는 호출을 탐지하여 예외를 발생시킵니다. 특정 경우(예: Time.new의 매개변수 유무)에는 TracePoint 바인딩을 통해 호출 매개변수에 접근하여 안전성을 판단합니다.

개발 중 배운 점

### 암시적으로 사용되는 동기화 구조 초기에는 sleep, Timeout.timeout, Queue, Mutex, Logger 등 표준 라이브러리 동기화 구조를 워크플로우에서 직접 사용하는 것을 허용했습니다. 그러나 Mutex와 같은 경우, 실제 블록킹 여부가 파이버 스케줄러에 정확히 전달되지 않아 드문 경쟁 조건과 교착 상태를 유발할 수 있음이 발견되었습니다. 이에 따라 SDK는 이러한 표준 라이브러리 구조의 암시적 사용을 금지하고, Temporalio::Workflow 모듈 내에서 워크플로우에 안전한 대안을 명시적으로 제공하도록 변경했습니다.

IO 대기 파이버 스케줄러

워크플로우에서는 IO가 비결정론적이기 때문에 `Fiber::Scheduler

io_wait는 처음에는 NotImplementedError를 발생시켰습니다. 하지만 텔레메트리, 디버거와 같이 결정론성을 위반해도 되는 특정 시나리오를 위해, IO.select를 재사용하는 비내구성/블록킹 형태의 io_wait 구현을 Temporalio::Workflow::Unsafe.io_enabled` 블록을 통해 옵트인 방식으로 제공합니다.

변환 및 런타임 타입 힌트

Ruby는 다른 정적/동적 타입 언어와 달리 런타임에 접근 가능한 명확한 타입 힌트가 부족합니다. 기본적으로 JSON 페이로드는 Hash로 역직렬화되어 사용자에게 불편함을 줄 수 있습니다. 이를 해결하기 위해 workflow_arg_hint, workflow_result_hint와 같은 “힌트” 개념을 도입했습니다. 이 힌트는 워크플로우 매개변수, 결과, 업데이트 매개변수 등 변환이 발생할 수 있는 모든 곳에서 제공될 수 있으며, 커스텀 컨버터가 JSON 데이터를 원하는 타입으로 변환하는 데 활용될 수 있습니다. 이는 사용자에게 유연하고 강력한 타입 변환 제어 기능을 제공합니다.

결론

Temporal Ruby SDK의 GA 출시는 Ruby 개발자들에게 견고한 소프트웨어 개발을 위한 강력하고 개발자 친화적인 도구를 제공합니다. 본 글에서는 Temporal의 기본적인 개념부터 Rust Core를 활용한 C 확장, 결정론적 파이버 스케줄러, `TracePoint`를 이용한 불법 호출 방지 등 SDK의 고급 구현 세부 사항과 개발 중 마주했던 주요 도전 과제들을 상세히 살펴보았습니다. `Temporalio::Workflow` 모듈을 통해 제공되는 안전한 대안들과 타입 힌트 지원은 Ruby 환경에서 안정적인 분산 시스템 구축에 기여합니다. Ruby 개발자들은 이제 Temporal SDK를 활용하여 복잡한 비즈니스 로직을 보다 쉽게 구현하고 관리할 수 있습니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

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