이 한 가지 기능 때문에 AWS Lambda에서 Azure Functions로 완전히 옮겼습니다
Source: Dev.to
Let me paint you a picture of what my life looked like before the switch.
I was building an order‑processing system. Nothing too crazy—receive an order, validate inventory, process payment, update the database, send notifications. The kind of stuff that sounds simple until you try to make it reliable.
With AWS Lambda, every function is stateless. That’s by design, and for simple use cases it’s actually fine. But here’s where things got messy:
상태 관리 악몽
파이프라인에서 주문이 어느 단계에 있는지 추적하려면 DynamoDB가 필요했습니다. 함수 간에 데이터를 전달하려면 SQS가 필요했습니다. 전체를 오케스트레이션하려면 Step Functions가 필요했습니다. 갑자기 제 “간단한” 워크플로우는 다음을 포함하게 되었습니다:
- Lambda 함수 5개
- DynamoDB 테이블 2개 (하나는 상태용, 하나는 데드‑레터 추적용)
- SQS 큐 3개
- Step Functions 상태 머신 1개
- 배나무에 앉은 메추라기 한 마리 (음, 마지막 건 아닐 수도 있겠네요)
무언가가 실패할 때마다 여러 서비스에 걸친 로그를 추적해야 했습니다. 단계 하나를 추가하고 싶을 때마다 자체 JSON‑기반 Amazon States Language를 사용하는 Step Functions 정의를 업데이트해야 했습니다. 마치 기능을 만드는 대신 인프라를 구축하고 있는 느낌이었습니다.
인간 상호작용 문제
바로 여기서 문제가 크게 발생했습니다. 우리는 승인 워크플로우가 필요했습니다. 일정 금액 이상의 주문은 관리자가 승인해야만 처리될 수 있었습니다.
Lambda 환경에서는 이것이 다음을 의미했습니다:
- 보류 중인 상태를 어딘가에 저장한다.
- 승인을 받기 위한 API Gateway 엔드포인트를 설정한다.
- 워크플로우를 재개하기 위해 폴링하거나 콜백을 사용한다.
- 타임아웃을 처리한다 (응답이 없을 경우는 어떻게 할까?).
- 모든 엣지 케이스를 다룬다.
이 하나의 기능만을 위해 400줄에 달하는 코드를 작성했습니다. 솔직히 말해서, 매우 취약했습니다. 언제든 뭔가가 깨질까봐 늘 걱정했습니다.
Durable Functions 발견 (모든 것이 바뀐 순간)
대안을 조사하던 중 Azure Durable Functions을 우연히 발견했습니다. 처음엔 회의적이었죠—또 다른 벤더가 마법을 약속한다는 말이죠? 물론이었습니다.
하지만 코드 예제를 보고는 제 턱이 떨어질 정도로 놀랐습니다.
Durable Functions에서 인간‑상호작용 워크플로우
[FunctionName("ApprovalWorkflow")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var order = context.GetInput();
// Check if approval is needed
if (order.Amount > 1000)
{
// Wait for external event – could be hours or days
var approved = await context.WaitForExternalEvent("ApprovalReceived");
if (!approved)
{
return false;
}
}
// Continue with processing
await context.CallActivityAsync("ProcessOrder", order);
await context.CallActivityAsync("SendNotification", order);
return true;
}
그게 전부입니다. 프레임워크가 상태, 타임아웃, 그리고 영속성을 처리합니다. 함수 호스트가 재시작되더라도 정확히 중단된 지점부터 다시 시작합니다.
400줄의 AWS 코드가 약 20줄의 Azure 코드가 되었습니다. 이것은 과장이 아닙니다.
나를 설득한 다섯 가지 패턴
Durable Functions은 내가 겪고 있던 고통 포인트를 정확히 해결해 주는 다섯 가지 핵심 패턴을 지원합니다:
-
Function Chaining – 함수를 순차적으로 호출하고, 하나의 출력값을 다음 함수의 입력값으로 전달합니다. 오케스트레이터가 상태를 자동으로 관리합니다.
-
Fan‑Out/Fan‑In – 100개의 항목을 병렬로 처리하고 결과를 집계해야 하나요? Durable Functions은 이를 기본적으로 지원합니다—큐도 없고, 수동 추적도 없습니다.
var tasks = new List>(); foreach (var item in items) { tasks.Add(context.CallActivityAsync("ProcessItem", item)); } var results = await Task.WhenAll(tasks); var total = results.Sum(); -
Async HTTP APIs – 상태 폴링이 내장된 장기 실행 작업. 프레임워크가 상태 엔드포인트를 자동으로 제공합니다.
-
Monitor Pattern – 조건을 주기적으로 확인하고 무언가 발생할 때까지 대기하는 폴링 워크플로우. 외부 시스템을 기다릴 때 완벽합니다.
-
Human Interaction – 시간 제한 및 에스컬레이션이 포함된 외부 이벤트 대기. 이것이 나에게 모든 것을 바꾼 기능이었습니다.
나의 마이그레이션 여정
| 단계 | 기간 | 수행한 작업 |
|---|---|---|
| 1‑2주: 학습 곡선 | 2주 | 오케스트레이터 모델을 공부했습니다. 오케스트레이터 함수는 재개될 때마다 처음부터 재생된다는 것을 알게 되었으며, 런타임은 결정론적 실행을 보장합니다. |
| 3‑4주: 파일럿 마이그레이션 | 2주 | 가장 간단한 워크플로를 Durable Functions로 다시 작성하고, AWS 버전과 함께 배포한 뒤 결과를 비교했습니다. |
| 5‑6주: 복잡한 워크플로 | 2주 | 전체 주문 처리 시스템을 마이그레이션했습니다. Durable Functions 버전은 더 짧고 디버깅이 쉬웠으며, 오케스트레이션 히스토리를 확인하고, 실패한 인스턴스를 재생하며, 각 단계에서 정확히 무슨 일이 일어났는지 볼 수 있었습니다. |
| 7‑8주: 전체 전환 | 2주 | AWS 인프라(스텝 함수, DynamoDB 테이블, SQS 큐)를 폐기하고 복잡함에 작별을 고했습니다. |
실제 결과
실제로 개선된 점을 솔직히 말씀드리자면:
- 코드 크기 60 % 감소 (약 400줄에서 ~150줄로).
- 개발 주기 40 % 가속 (여러 서비스를 관리할 필요가 없어졌기 때문).
- 신뢰성 향상: 상태 지속성 및 자동 재시도가 내장되어 있습니다.
- 디버깅 간소화: Azure 포털에서 시각적 오케스트레이션 타임라인을 보여주고 실패한 인스턴스를 재생할 수 있습니다.
- 운영 비용 절감: 실행 및 모니터링해야 할 서비스가 줄어듭니다.
만약 아직도 간단한 승인 흐름을 구현하기 위해 Lambdas, Step Functions, SQS, DynamoDB와 같은 얽힌 서비스를 다루고 있다면, Azure Durable Functions을 한 번 살펴보세요. 몇 달 동안의 아키텍처 고민과 늦은 밤 콘솔 탐색을 크게 줄일 수 있습니다.
# Benefits of Moving to Azure Durable Functions
코드 규모 감소
주문 처리 워크플로는 여러 서비스에 걸쳐 약 1,200줄에서 Durable Functions의 약 450줄로 줄어들었습니다.
코드가 적을수록 버그가 적고 유지보수가 쉬워집니다.
Simplified Architecture
- Before: 5 AWS 서비스
- After: 2 Azure 서비스 (Functions + Storage)
작은 정신 모델은 디버깅을 더 빠르게 합니다.
더 나은 가시성
Durable Functions는 모든 오케스트레이션에 대한 내장 히스토리를 제공합니다.
각 단계가 언제 실행되었는지, 입력/출력은 무엇인지, 그리고 실패가 발생한 위치를 정확히 확인할 수 있습니다.
비용 절감
Step Functions(상태 전환당 요금 부과)를 없애고 DynamoDB 사용량을 줄이면 월별 청구서가 ≈ 40 % 감소했습니다.
결과는 상황에 따라 다를 수 있지만, 상태를 유지하는 워크플로우의 경우 이 가격 모델이 더 유리하게 작동하는 경우가 많습니다.
더 빠른 개발
이전에는 일주일이 걸리던 기능들이 이제는 하루면 됩니다.
추상화는 적절한 수준에 위치합니다—보일러플레이트를 피할 만큼 충분히 높고, 제어권을 유지할 만큼 충분히 낮습니다.
모든 것이 완벽한가요?
아니오. 여기에는 트레이드오프가 있습니다:
- Replay 모델: 규율이 필요합니다; 오케스트레이터 내부에서 비결정적 코드(예:
DateTime.Now)를 피하세요. - 마이그레이션 비용: AWS 생태계에 깊이 들어가 있고 Step Functions에 대한 전문 지식이 있다면, 단순한 사용 사례에 대해 이동하는 것이 가치가 없을 수 있습니다.
- 단순 워크로드: 완전히 무상태 함수의 경우, 일반 Lambda 또는 Durable이 없는 Azure Functions도 충분히 괜찮습니다.
핵심: 복잡한 워크플로, 인간 상호작용, 장기 실행 프로세스 또는 다단계 조정의 경우, Durable Functions는 게임 체인저입니다.
최종 생각
저는 저 자신에게, 그리고 불필요한 복잡함과 씨름하며 새벽 2시에 깨어 있는 모든 분들에게 이야기하고 있습니다.
때로는 올바른 도구가 모든 차이를 만들죠. 저에게 Azure Durable Functions은 인프라 파이프라인 대신 비즈니스 로직에 집중할 수 있게 해줍니다.
Lambda와 같은 문제에 부딪히고 있다면, Durable Functions을 한 번 살펴보세요:
- 최악의 경우: 새로운 것을 배우게 됩니다.
- 최상의 경우: 왜 더 일찍 전환하지 않았는지 궁금해집니다.
이게 지금 제 상황입니다. 솔직히 말해서, 뒤돌아볼 생각이 없습니다.