0Pricing
Cloud & IT Cert Prep · 강의

상태 저장 워크플로를 위한 Durable Functions

Durable Functions 오케스트레이터 패턴(분할-집계, 연결, 모니터)을 사용해 장기 실행 워크플로를 오케스트레이션하고, 상태가 체크포인트로 저장되는 방식을 이해합니다.

상태 저장 워크플로를 위한 Durable Functions은(는) CoddyKit의 무료 Cloud & IT Cert Prep 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Cloud & IT Cert Prep 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Cloud & IT Cert Prep 강의에는 총 4개의 강의가 포함되어 있습니다.

Durable 함수를 사용하는 이유

일반 Azure 함수는 상태 비저장 방식입니다. 즉, 각 호출이 독립적으로 실행되며 이전 호출에 대한 기억이 없습니다. Durable 함수는 일반적인 async/await 코드로 상태 저장 장기 실행 워크플로를 작성할 수 있도록 Azure 함수를 확장합니다. Durable Task Framework는 각 단계가 끝날 때마다 상태를 Azure Storage에 자동으로 체크포인트로 저장하므로, 서버 재시작, 제한 시간 초과 또는 계획된 유지 관리가 발생해도 워크플로가 중단된 지점에서 정확히 재개될 수 있습니다.

세 가지 함수 유형

Durable 함수에는 세 가지 함수 유형이 있습니다. 오케스트레이터 함수는 전체 워크플로를 조정합니다. 즉, 실제로 스레드를 차단하지 않고 yield 또는 await를 사용해 활동 함수를 호출하고 결과를 기다립니다. 활동 함수는 단일 작업 단위(API 호출, 데이터베이스 쓰기 등)를 수행하며 부수 효과가 발생해야 하는 유일한 위치입니다. 엔터티 함수는 호출 간에 작은 Durable 상태(카운터, 플래그)를 유지 관리합니다.

// Client function (HTTP trigger) — starts the orchestration
module.exports = async function (context, req) {
  const client = df.getClient(context);
  const orderId = req.body.orderId;
  const instanceId = await client.startNew('OrderOrchestrator', undefined, { orderId });
  return client.createCheckStatusResponse(context.bindingData.req, instanceId);
};

체이닝 패턴

체이닝 패턴은 활동 함수를 순차적으로 실행하며 한 함수의 출력을 다음 함수의 입력으로 전달합니다. 오케스트레이터는 각 활동을 차례로 기다립니다. 어느 활동이든 실패하면 워크플로가 중지되고 실패한 단계부터 다시 시작할 수 있습니다. 이는 가장 단순한 Durable 함수 패턴으로, 주문 처리 파이프라인처럼 각 단계가 이전 단계의 결과에 의존하는 워크플로에 적합합니다.

// Orchestrator: chaining pattern
const df = require('durable-functions');
module.exports = df.orchestrator(function* (context) {
  const orderId = context.df.getInput().orderId;

  const validated = yield context.df.callActivity('ValidateOrder', orderId);
  const charged   = yield context.df.callActivity('ChargePayment', validated);
  const shipped   = yield context.df.callActivity('ShipOrder', charged);

  return { status: 'shipped', trackingId: shipped.trackingId };
});

팬아웃/팬인 패턴

팬아웃/팬인 패턴은 여러 활동 함수를 병렬로 시작하고, 계속 진행하기 전에 모든 함수가 완료되기를 기다립니다. 오케스트레이터는 callActivity를 기다리지 않고 사용해 모든 작업을 동시에 시작하고, 작업 객체를 배열에 수집한 다음 Task.all()에서 양보합니다. 여러 파일 처리, 여러 API 호출 또는 일괄 작업처럼 서로 독립적인 작업 항목을 처리할 때 순차 처리보다 훨씬 빠릅니다.

// Orchestrator: fan-out / fan-in
module.exports = df.orchestrator(function* (context) {
  const items = context.df.getInput().items;

  // Fan-out: start all tasks in parallel
  const tasks = items.map(item => context.df.callActivity('ProcessItem', item));

  // Fan-in: wait for all tasks to complete
  const results = yield context.df.Task.all(tasks);

  return results;
});

모니터 패턴

모니터 패턴은 조건이 충족될 때까지 일정한 간격으로 외부 시스템을 폴링합니다. 폴링 루프와 비슷하지만 완전히 Durable합니다. 오케스트레이터는 상태를 확인하는 활동을 호출하고, createTimer를 사용해 구성 가능한 간격만큼 기다린 다음 반복합니다. 각 폴링 사이에 상태가 저장소에 체크포인트로 저장되므로, 기다리는 동안 오케스트레이터가 컴퓨팅 리소스를 사용하지 않습니다. 따라서 대기 타이머 방식보다 훨씬 효율적입니다.

// Orchestrator: monitor pattern (poll until job completes)
module.exports = df.orchestrator(function* (context) {
  const jobId = context.df.getInput().jobId;
  const expiry = new Date(context.df.currentUtcDateTime);
  expiry.setHours(expiry.getHours() + 24); // 24-hour timeout

  while (context.df.currentUtcDateTime < expiry) {
    const status = yield context.df.callActivity('GetJobStatus', jobId);
    if (status === 'completed') return { jobId, status };
    if (status === 'failed') throw new Error('Job failed');
    // Wait 30 seconds before next poll
    const nextCheck = new Date(context.df.currentUtcDateTime);
    nextCheck.setSeconds(nextCheck.getSeconds() + 30);
    yield context.df.createTimer(nextCheck);
  }
  throw new Error('Workflow timed out');
});

사용자 상호 작용 패턴

사용자 상호 작용 패턴은 오케스트레이션을 일시 중지하고 관리자 승인과 같은 외부 이벤트를 기다립니다. 오케스트레이터는 waitForExternalEvent에서 양보하며, 컴퓨팅 리소스를 사용하지 않고 며칠 또는 몇 주 동안 기다릴 수 있습니다. 외부 시스템(이메일 승인 링크, 모바일 앱, 웹훅)은 Durable 함수 HTTP API를 호출해 이벤트를 발생시키고 오케스트레이션의 대기를 해제합니다. 응답이 없을 때 자동으로 제한 시간을 적용하고 에스컬레이션하려면 타이머와 함께 사용하세요.

// Orchestrator: wait for human approval with timeout
module.exports = df.orchestrator(function* (context) {
  const request = context.df.getInput();

  yield context.df.callActivity('SendApprovalEmail', request);

  const timeout = df.Task.createTimer(context, new Date(Date.now() + 48 * 3600 * 1000));
  const approval = context.df.waitForExternalEvent('ApprovalResponse');

  const winner = yield context.df.Task.any([approval, timeout]);

  if (winner === approval) {
    const approved = winner.result;
    return approved ? 'Approved' : 'Rejected';
  } else {
    return 'Timed out — escalated';
  }
});

오케스트레이터 제약 조건

오케스트레이터 함수는 상태를 다시 구성하기 위해 기록에서 여러 번 재생될 수 있으므로 중요한 제약 조건이 있습니다. 오케스트레이터 함수는 결정적이어야 합니다. Date.now()와 Math.random()을 사용하거나 직접 I/O 호출을 수행해서는 안 됩니다. 대신 타임스탬프에는 context.df.currentUtcDateTime을 사용하고, 모든 I/O는 활동 함수를 호출해 수행하세요. 오케스트레이터 본문에서 로깅을 수행하면 재생 중 로그 항목이 중복으로 생성되므로 로깅에는 활동 함수를 사용하세요.

// WRONG — non-deterministic, will cause replay bugs
module.exports = df.orchestrator(function* (context) {
  const now = new Date();          // Don't use Date()
  const rand = Math.random();       // Don't use Math.random()
  const data = await fetch('/api'); // Don't make HTTP calls directly
});

// CORRECT
module.exports = df.orchestrator(function* (context) {
  const now = context.df.currentUtcDateTime; // OK
  const data = yield context.df.callActivity('FetchData', null); // OK
});

인스턴스 관리: 상태 및 종료

각 오케스트레이션 실행에는 고유한 인스턴스 ID가 있으며, 이를 사용해 상태를 조회하고 이벤트를 보내거나 오케스트레이션을 종료할 수 있습니다. Durable 함수 HTTP 관리 API는 상태 확인(GET /instances/{id}), 이벤트 전송(POST /instances/{id}/raiseEvent/{name}), 종료(POST /instances/{id}/terminate)를 위한 엔드포인트를 제공합니다. 함수에서 Durable 클라이언트 바인딩을 사용하면 이러한 작업에 프로그래밍 방식으로 액세스할 수 있습니다.

// Client function: check orchestration status
module.exports = async function (context, req) {
  const client = df.getClient(context);
  const instanceId = req.params.instanceId;

  const status = await client.getStatus(instanceId, true, true, true);
  return {
    status: 200,
    body: {
      instanceId,
      runtimeStatus: status.runtimeStatus,
      customStatus: status.customStatus,
      output: status.output
    }
  };
};

스토리지 백엔드 및 성능

Durable 함수는 오케스트레이션 기록, 인스턴스 상태 및 함수 간 메시지 큐를 Azure Storage 계정에 저장합니다. 더 높은 처리량이 필요한 경우 Azure SQL 또는 Netherite 백엔드도 사용할 수 있습니다. 각 체크포인트는 Azure Table Storage와 Azure Queue Storage에 기록됩니다. 높은 처리량이 필요한 시나리오(동시에 실행되는 오케스트레이션 수천 개)에서는 Netherite 스토리지 백엔드가 Azure Event Hubs를 사용해 훨씬 뛰어난 성능을 제공합니다. 병목 현상을 감지하려면 오케스트레이션 큐 깊이를 모니터링하세요.

// host.json: configure the Durable Task storage provider
{
  'version': '2.0',
  'extensions': {
    'durableTask': {
      'hubName': 'MyTaskHub',
      'storageProvider': {
        'type': 'azure',
        'connectionStringName': 'AzureWebJobsStorage',
        'controlQueueBatchSize': 32,
        'maxQueuePollingInterval': '00:00:02'
      }
    }
  }
}

오류 처리 및 재시도

활동 함수는 예외를 발생시킬 수 있으며, 이러한 예외는 TaskFailedException으로 오케스트레이터에 전달됩니다. 오케스트레이터에서 try-catch 블록을 사용해 실패를 적절히 처리하세요. 일시적인 오류에는 callActivityWithRetry를 사용해 백오프가 적용된 자동 재시도를 구성하고, 최대 시도 횟수, 첫 번째 재시도 간격 및 백오프 계수를 지정하세요. 이는 외부 API 또는 데이터베이스를 호출하는 활동에 권장되는 패턴입니다.

// Orchestrator: retry an activity with exponential backoff
module.exports = df.orchestrator(function* (context) {
  const retryOptions = new df.RetryOptions(
    5000,  // firstRetryIntervalInMilliseconds
    3      // maxNumberOfAttempts
  );
  retryOptions.backoffCoefficient = 2; // 5s, 10s, 20s

  try {
    const result = yield context.df.callActivityWithRetry(
      'CallExternalAPI',
      retryOptions,
      context.df.getInput()
    );
    return result;
  } catch (e) {
    yield context.df.callActivity('SendFailureAlert', e.message);
    throw e;
  }
});

Durable 엔터티

Durable 엔터티(엔터티 함수)는 ID를 통해 액세스할 수 있는 작은 Durable 상태를 구현하며, 가상 액터와 비슷합니다. 엔터티에는 호출 간에 유지되는 ID와 상태가 있습니다. 오케스트레이터 또는 클라이언트에서 엔터티의 작업을 호출하면 엔터티가 해당 작업을 한 번에 하나씩 순차적으로 처리합니다. 일반적인 용도에는 카운터, 승인 상태 머신, 속도 제한기 및 장바구니가 있으며, 데이터베이스 없이 유지 관리 및 업데이트 가능한 상태가 필요한 모든 시나리오에 사용할 수 있습니다.

// Counter entity function
const df = require('durable-functions');
module.exports = df.entity(function (context) {
  let count = context.df.getState(() => 0);
  const operation = context.df.operationName;

  if (operation === 'add') count += context.df.getInput();
  if (operation === 'reset') count = 0;
  if (operation === 'get') context.df.return(count);

  context.df.setState(count);
});

// From orchestrator, increment counter entity
// const entityId = new df.EntityId('Counter', 'myCounter');
// yield context.df.callEntity(entityId, 'add', 1);

빠른 확인

이 레슨에서 배운 Microsoft Azure 기초(AZ-900) 개념에 대한 이해도를 확인해 보세요.

레슨 요약

이 레슨에서는 다음을 배웠습니다. Durable 함수는 오케스트레이터 상태를 Azure Storage에 체크포인트로 저장하여 상태 저장 장기 실행 워크플로를 지원하고, 주요 패턴으로는 체이닝, 팬아웃/팬인, 모니터 및 사용자 상호 작용이 있으며, 오케스트레이터는 결정적이어야 합니다. 모든 I/O와 비결정적 호출은 활동 함수를 통해 수행해야 합니다. 다음으로 Azure Logic Apps를 살펴보겠습니다.

자주 묻는 질문

“상태 저장 워크플로를 위한 Durable Functions” 강의는 무료인가요?

네 — “상태 저장 워크플로를 위한 Durable Functions” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Cloud & IT Cert Prep 강의 전체를 잠금 해제할 수 있습니다. Cloud & IT Cert Prep 강의에는 총 4개의 강의가 포함되어 있습니다.

“상태 저장 워크플로를 위한 Durable Functions”에서 뭘 배우나요?

Durable Functions 오케스트레이터 패턴(분할-집계, 연결, 모니터)을 사용해 장기 실행 워크플로를 오케스트레이션하고, 상태가 체크포인트로 저장되는 방식을 이해합니다. 브라우저에서 직접 실행하는 실습 코드로 Cloud & IT Cert Prep을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Cloud & IT Cert Prep을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Cloud & IT Cert Prep은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“상태 저장 워크플로를 위한 Durable Functions” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Cloud & IT Cert Prep 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Cloud & IT Cert Prep 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Azure Functions 트리거 및 바인딩
  2. 상태 저장 워크플로를 위한 Durable Functions
  3. Azure Logic Apps
  4. Event Grid 및 이벤트 기반 아키텍처
← Cloud & IT Cert Prep(으)로 돌아가기