🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Step Functions 기초 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 19. · 6 Views

Step Functions 기초 완벽 가이드

AWS Step Functions의 핵심 개념부터 상태 머신 설계, ASL 언어, 분기 처리, 병렬 실행까지. 초급 개발자도 쉽게 따라할 수 있는 실무 중심 가이드입니다.


목차

  1. Step Functions란?
  2. 상태 머신 개념
  3. ASL 언어 기초
  4. Task 상태 사용
  5. Choice와 분기
  6. 병렬 실행

1. Step Functions란?

어느 날 김개발 씨는 주문 처리 시스템을 개발하고 있었습니다. 결제 확인 → 재고 확인 → 배송 처리 → 알림 발송까지, 여러 단계를 Lambda 함수로 구현했는데 관리가 너무 어려웠습니다.

선배 박시니어 씨가 다가와 말했습니다. "Step Functions를 사용해보는 게 어때요?"

AWS Step Functions는 여러 AWS 서비스를 연결하여 하나의 워크플로우로 만들어주는 서비스입니다. 마치 레고 블록을 조립하듯이 각 단계를 시각적으로 연결할 수 있습니다.

복잡한 비즈니스 로직을 코드 없이 구성할 수 있어 유지보수가 훨씬 쉬워집니다.

다음 코드를 살펴봅시다.

{
  "Comment": "간단한 주문 처리 워크플로우",
  "StartAt": "결제확인",
  "States": {
    "결제확인": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:CheckPayment",
      "Next": "재고확인"
    },
    "재고확인": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:CheckInventory",
      "Next": "배송처리"
    },
    "배송처리": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:ProcessShipping",
      "End": true
    }
  }
}

김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 최근 이커머스 플랫폼의 주문 처리 시스템을 담당하게 되었습니다.

처음에는 간단해 보였지만, 막상 구현하려니 복잡한 문제들이 쏟아져 나왔습니다. 결제가 완료되면 재고를 확인하고, 재고가 있으면 배송을 시작하고, 배송이 시작되면 고객에게 알림을 보내야 합니다.

각 단계마다 Lambda 함수를 만들었는데, 이 함수들을 어떻게 연결해야 할지 막막했습니다. 더 큰 문제는 에러 처리였습니다.

재고가 부족하면 어떻게 해야 할까요? 배송 처리 중에 오류가 발생하면요?

각 Lambda 함수마다 에러 처리 로직을 넣다 보니 코드가 복잡해지고 관리가 어려워졌습니다. 박시니어 씨가 김개발 씨의 고민을 듣고 말했습니다.

"Step Functions를 사용하면 이런 문제를 깔끔하게 해결할 수 있어요." Step Functions란 무엇일까요? 쉽게 비유하자면, Step Functions는 마치 공장의 조립 라인과 같습니다.

자동차 공장에서 각 작업장이 순서대로 배치되어 있듯이, Step Functions도 각 작업(Lambda 함수, API 호출 등)을 순서대로 배치합니다. 컨베이어 벨트가 부품을 다음 작업장으로 옮기듯이, Step Functions도 데이터를 다음 단계로 자동으로 전달합니다.

전통적인 방식에서는 어떤 문제가 있었을까요? 개발자들은 각 Lambda 함수 안에서 다음 함수를 직접 호출해야 했습니다.

코드가 서로 강하게 결합되어 있어서, 한 부분을 수정하면 연관된 다른 부분도 함께 수정해야 했습니다. 더 큰 문제는 워크플로우의 전체 흐름을 파악하기 어렵다는 것이었습니다.

코드를 하나하나 뜯어봐야만 어떤 순서로 실행되는지 알 수 있었습니다. 예를 들어 김개발 씨의 경우, 결제 확인 Lambda에서 재고 확인 Lambda를 호출하고, 재고 확인 Lambda에서 다시 배송 처리 Lambda를 호출했습니다.

만약 중간에 알림 발송 단계를 추가하려면 어떻게 해야 할까요? 여러 Lambda 함수의 코드를 모두 수정해야 했습니다.

바로 이런 문제를 해결하기 위해 Step Functions가 등장했습니다. Step Functions를 사용하면 각 단계를 독립적으로 관리할 수 있습니다.

워크플로우는 JSON 형식의 상태 머신 정의로 선언되어 있어서, 코드 수정 없이 워크플로우만 변경할 수 있습니다. 또한 AWS 콘솔에서 워크플로우를 시각적으로 확인할 수 있어 전체 흐름을 한눈에 파악할 수 있습니다.

무엇보다 에러 처리와 재시도 로직을 워크플로우 레벨에서 관리할 수 있다는 점이 큰 장점입니다. 각 Lambda 함수는 자신의 비즈니스 로직만 처리하면 되고, 복잡한 오케스트레이션은 Step Functions가 담당합니다.

위의 코드를 한 줄씩 살펴보겠습니다. 가장 먼저 Comment 필드로 이 워크플로우가 무엇을 하는지 설명합니다.

그리고 StartAt 필드로 어느 단계부터 시작할지 지정합니다. 여기서는 "결제확인"부터 시작합니다.

States 객체 안에 각 단계를 정의합니다. "결제확인" 상태는 Type이 "Task"로, 실제 작업을 수행하는 단계입니다.

Resource에는 실행할 Lambda 함수의 ARN을 지정하고, Next로 다음에 실행할 단계를 명시합니다. "재고확인"과 "배송처리"도 같은 방식으로 정의됩니다.

마지막 단계인 "배송처리"는 Next 대신 End: true를 사용하여 워크플로우가 여기서 종료됨을 나타냅니다. 실제 현업에서는 어떻게 활용할까요?

많은 기업들이 Step Functions를 ETL 파이프라인, 주문 처리, 승인 워크플로우, 배치 처리 등에 활용하고 있습니다. 예를 들어 넷플릭스는 동영상 인코딩 파이프라인에 Step Functions를 사용합니다.

원본 동영상을 여러 해상도로 변환하고, 썸네일을 생성하고, 메타데이터를 업데이트하는 복잡한 과정을 Step Functions로 관리합니다. 또 다른 예로, 금융 서비스에서는 대출 승인 프로세스에 Step Functions를 활용합니다.

신용 조회 → 서류 검증 → 리스크 평가 → 최종 승인까지의 단계를 자동화하면서도, 각 단계의 실행 상태를 실시간으로 추적할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 모든 로직을 Step Functions에 넣으려고 하는 것입니다. Step Functions는 오케스트레이션에 특화되어 있지, 복잡한 비즈니스 로직을 처리하기 위한 것이 아닙니다.

데이터 변환이나 복잡한 계산은 Lambda 함수에서 처리하고, Step Functions는 흐름 제어만 담당하도록 해야 합니다. 또한 Step Functions는 실행 횟수와 상태 전환 횟수에 따라 과금됩니다.

너무 잦은 상태 전환은 비용을 증가시킬 수 있으니, 적절한 단위로 단계를 나누는 것이 중요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 조언을 듣고 Step Functions를 도입한 김개발 씨는 놀라운 변화를 경험했습니다. 코드가 훨씬 간결해졌고, 새로운 단계를 추가하는 것도 쉬워졌습니다.

무엇보다 AWS 콘솔에서 각 주문이 어느 단계에 있는지 실시간으로 확인할 수 있어서 모니터링이 편해졌습니다. Step Functions를 제대로 이해하면 복잡한 분산 시스템을 더 쉽게 관리할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - AWS 콘솔의 Workflow Studio를 사용하면 드래그 앤 드롭으로 워크플로우를 시각적으로 설계할 수 있습니다

  • Step Functions의 Express 워크플로우는 고빈도 실행에 최적화되어 있고 비용도 저렴합니다
  • CloudWatch Logs와 연동하여 각 단계의 입출력 데이터를 상세하게 로깅할 수 있습니다

2. 상태 머신 개념

김개발 씨가 Step Functions를 도입하고 나서 박시니어 씨에게 질문했습니다. "그런데 이 워크플로우를 왜 '상태 머신'이라고 부르나요?" 박시니어 씨는 화이트보드에 그림을 그리며 설명하기 시작했습니다.

**상태 머신(State Machine)**은 시스템이 여러 상태(State) 중 하나에 있으면서 특정 조건에 따라 다른 상태로 전환되는 모델입니다. 마치 신호등이 빨강-노랑-초록 상태를 순환하듯이, 워크플로우도 각 단계를 상태로 표현합니다.

Step Functions는 이 상태 머신 개념을 기반으로 설계되었습니다.

다음 코드를 살펴봅시다.

{
  "Comment": "주문 상태를 관리하는 상태 머신",
  "StartAt": "주문접수",
  "States": {
    "주문접수": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "ReceiveOrder",
        "Payload.$": "$"
      },
      "Next": "처리중"
    },
    "처리중": {
      "Type": "Pass",
      "Result": "주문이 처리 중입니다",
      "Next": "완료"
    },
    "완료": {
      "Type": "Succeed"
    }
  }
}

박시니어 씨는 화이트보드에 동그라미 세 개를 그렸습니다. "주문접수", "처리중", "완료"라고 각각 적고 화살표로 연결했습니다.

"이게 바로 상태 머신이에요. 주문이 이 상태들을 순서대로 거치면서 처리되는 거죠." 김개발 씨는 고개를 갸우뚱했습니다.

"그냥 순서대로 실행하는 건데 왜 이렇게 복잡하게 생각해야 하나요?" 박시니어 씨는 웃으며 답했습니다. "좋은 질문이에요.

지금은 간단해 보이지만, 실제로는 훨씬 복잡한 상황이 많거든요." 상태 머신이란 정확히 무엇일까요? 쉽게 비유하자면, 상태 머신은 마치 보드게임의 말과 같습니다.

주사위를 던질 때마다 말이 다음 칸으로 이동하듯이, 특정 이벤트가 발생하면 시스템이 다음 상태로 전환됩니다. 각 칸(상태)에서는 정해진 규칙(로직)이 있고, 어느 칸으로 이동할지(전환)도 규칙에 따라 결정됩니다.

상태 머신이 없던 시절에는 어땠을까요? 개발자들은 프로그램의 현재 상태를 여러 변수로 관리했습니다.

"isProcessing", "isCompleted", "hasError" 같은 불린 플래그들이 코드 곳곳에 흩어져 있었습니다. 상태가 복잡해질수록 이런 플래그의 조합이 기하급수적으로 늘어났고, 버그가 발생하기 쉬웠습니다.

예를 들어 주문 시스템에서 "처리중이면서 동시에 취소됨" 같은 모순된 상태가 발생할 수 있었습니다. 각 플래그를 독립적으로 관리하다 보니 일관성을 유지하기 어려웠던 것입니다.

바로 이런 문제를 해결하기 위해 상태 머신 패턴이 등장했습니다. 상태 머신을 사용하면 시스템이 항상 하나의 명확한 상태에만 존재합니다.

"주문접수", "처리중", "완료", "취소됨" 중 정확히 하나의 상태만 가질 수 있습니다. 또한 어떤 상태에서 어떤 상태로 전환 가능한지가 명확하게 정의되어 있어 잘못된 전환을 방지할 수 있습니다.

무엇보다 워크플로우의 현재 위치를 한눈에 파악할 수 있다는 점이 큰 장점입니다. "지금 이 주문은 배송 준비 중입니다"라고 명확하게 말할 수 있게 됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. "주문접수" 상태는 Type이 "Task"로, 실제로 Lambda 함수를 호출합니다.

Parameters에서 함수 이름과 입력 데이터를 지정합니다. **Payload.$**의 "$"는 현재 입력 데이터 전체를 의미하는 JSONPath 표현식입니다.

"처리중" 상태는 Type이 "Pass"입니다. Pass 상태는 실제 작업을 수행하지 않고 데이터를 다음 상태로 전달만 합니다.

여기서는 Result로 간단한 메시지를 추가하고 있습니다. 이는 디버깅이나 로깅 목적으로 유용합니다.

"완료" 상태는 Type이 "Succeed"로, 워크플로우가 성공적으로 종료되었음을 나타냅니다. End: true를 사용하는 것과 비슷하지만, Succeed는 명시적으로 성공 상태를 표현합니다.

실제 현업에서는 어떻게 활용할까요? 상태 머신 개념은 비즈니스 프로세스를 모델링하는 데 매우 유용합니다.

예를 들어 HR 시스템의 휴가 승인 프로세스를 생각해봅시다. "신청됨" → "팀장 검토중" → "승인됨" 또는 "거절됨"으로 상태가 전환됩니다.

각 상태에서 누가 무엇을 할 수 있는지가 명확하게 정의됩니다. 또 다른 예로, IoT 디바이스 관리 시스템이 있습니다.

디바이스는 "등록됨" → "활성화됨" → "비활성화됨" → "폐기됨" 상태를 거칩니다. 각 상태마다 허용되는 작업이 다르고, 잘못된 상태 전환은 자동으로 차단됩니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 너무 많은 상태를 만드는 것입니다.

상태가 많아질수록 관리가 복잡해지고 상태 다이어그램을 이해하기 어려워집니다. 비슷한 성격의 상태는 통합하고, 정말 필요한 상태만 정의하는 것이 좋습니다.

또한 상태 전환 조건을 명확하게 문서화해야 합니다. "어떤 조건에서 이 상태로 전환되는가?"를 팀원 모두가 이해할 수 있어야 합니다.

Step Functions의 경우 상태 머신 정의 자체가 문서 역할을 하므로, Comment 필드를 적극 활용하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 이제 왜 Step Functions가 상태 머신 기반인지 이해하게 되었습니다. "각 주문이 지금 어느 상태에 있는지 명확하게 알 수 있겠네요!" 상태 머신 개념을 제대로 이해하면 복잡한 비즈니스 프로세스를 체계적으로 설계할 수 있습니다.

여러분도 오늘 배운 내용을 프로젝트에 적용해 보세요.

실전 팁

💡 - 상태 다이어그램을 먼저 그려보면 워크플로우 설계가 훨씬 쉬워집니다

  • 각 상태는 단일 책임을 가져야 합니다. 하나의 상태가 너무 많은 일을 하면 안 됩니다
  • AWS 콘솔에서 실행 중인 워크플로우의 현재 상태를 실시간으로 확인할 수 있습니다

3. ASL 언어 기초

Step Functions를 공부하던 김개발 씨는 이상한 점을 발견했습니다. 워크플로우 정의가 JSON인데, 왜 공식 문서에서는 "ASL 언어"라고 부를까요?

선배에게 물어보니 "Amazon States Language의 약자"라며 자세히 설명해주었습니다.

**ASL(Amazon States Language)**은 Step Functions의 워크플로우를 정의하는 JSON 기반 언어입니다. 일반 JSON과 비슷해 보이지만, 상태 머신을 표현하기 위한 특별한 키워드와 규칙이 있습니다.

ASL을 이해하면 복잡한 워크플로우도 체계적으로 작성할 수 있습니다.

다음 코드를 살펴봅시다.

{
  "Comment": "ASL 기본 구조 예제",
  "StartAt": "HelloWorld",
  "States": {
    "HelloWorld": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:Hello",
      "TimeoutSeconds": 300,
      "Retry": [{
        "ErrorEquals": ["States.Timeout"],
        "MaxAttempts": 2
      }],
      "Catch": [{
        "ErrorEquals": ["States.ALL"],
        "Next": "HandleError"
      }],
      "Next": "Goodbye"
    },
    "HandleError": {
      "Type": "Fail",
      "Cause": "Lambda 함수 실행 실패"
    },
    "Goodbye": {
      "Type": "Succeed"
    }
  }
}

김개발 씨는 처음 ASL 코드를 보고 당황했습니다. JSON이라고 하는데 본 적 없는 키워드들이 가득했기 때문입니다.

"Type", "Resource", "Retry", "Catch"... 이게 다 무슨 의미일까요?

박시니어 씨가 코드를 가리키며 설명했습니다. "ASL은 그냥 JSON이 아니라 상태 머신을 표현하기 위한 특수한 JSON이에요.

각 키워드가 정해진 의미를 가지고 있죠." ASL이란 정확히 무엇일까요? 쉽게 비유하자면, ASL은 마치 악보와 같습니다.

악보도 기본적으로는 종이 위의 기호들이지만, 음악가들은 그 기호들을 보고 정확한 음악을 연주할 수 있습니다. 마찬가지로 ASL도 JSON 형식의 텍스트지만, Step Functions 엔진은 그것을 읽고 정확한 워크플로우를 실행합니다.

일반 JSON과 ASL의 차이는 무엇일까요? 일반 JSON은 데이터를 표현하는 포맷입니다.

어떤 구조든 자유롭게 만들 수 있습니다. 반면 ASL은 스펙이 정해진 언어입니다.

반드시 포함해야 하는 필드가 있고, 각 필드의 값도 정해진 형식을 따라야 합니다. 예를 들어 모든 ASL 정의는 StartAtStates 필드를 반드시 가져야 합니다.

StartAt이 없으면 어디서부터 시작할지 알 수 없고, States가 없으면 실행할 상태가 없게 됩니다. 이런 필수 규칙들이 ASL을 일반 JSON과 구분짓습니다.

바로 이런 제약 덕분에 ASL은 검증 가능하고 예측 가능한 언어가 되었습니다. ASL을 사용하면 워크플로우를 선언적으로 정의할 수 있습니다.

"이렇게 실행하라"는 명령형 코드가 아니라 "이런 구조다"라고 선언만 하면 Step Functions가 알아서 실행해줍니다. 또한 ASL은 버전 관리가 가능한 텍스트 파일이므로 Git으로 관리하고 코드 리뷰를 할 수 있습니다.

무엇보다 AWS 콘솔에서 ASL을 시각적 다이어그램으로 자동 변환해주므로 이해하기 쉽습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

맨 위의 Comment는 선택사항이지만 워크플로우의 목적을 설명하는 데 유용합니다. StartAt은 필수 필드로, 첫 번째 상태의 이름을 지정합니다.

"HelloWorld" 상태는 Type이 "Task"이고, Resource로 Lambda 함수를 지정합니다. TimeoutSeconds는 이 상태가 최대 몇 초 동안 실행될 수 있는지 정의합니다.

300초를 초과하면 자동으로 타임아웃 에러가 발생합니다. Retry 배열은 재시도 정책을 정의합니다.

"States.Timeout" 에러가 발생하면 최대 2번까지 자동으로 재시도합니다. Catch 배열은 에러 처리를 정의합니다.

"States.ALL"은 모든 종류의 에러를 의미하며, 에러 발생 시 "HandleError" 상태로 전환됩니다. "HandleError" 상태는 Type이 "Fail"로, 워크플로우를 실패 상태로 종료시킵니다.

Cause 필드에 실패 이유를 명시할 수 있습니다. "Goodbye" 상태는 성공 종료를 나타냅니다.

실제 현업에서는 어떻게 활용할까요? 많은 팀들이 ASL 정의를 Terraform이나 CloudFormation 같은 IaC 도구로 관리합니다.

코드로 관리되므로 변경 이력이 남고, 개발-스테이징-프로덕션 환경에 일관되게 배포할 수 있습니다. 또한 ASL은 JSON이므로 프로그램으로 생성할 수도 있습니다.

예를 들어 비슷한 패턴의 워크플로우를 여러 개 만들어야 한다면, Python이나 Node.js로 ASL을 동적으로 생성하는 스크립트를 작성할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 ASL 스펙을 제대로 확인하지 않고 임의로 필드를 추가하는 것입니다. ASL은 엄격한 스펙을 따르므로, 정의되지 않은 필드를 사용하면 에러가 발생합니다.

공식 문서의 스펙을 반드시 확인하면서 작성해야 합니다. 또한 복잡한 로직을 ASL로 표현하려고 하면 안 됩니다.

ASL은 흐름 제어를 위한 언어이지, 범용 프로그래밍 언어가 아닙니다. 복잡한 데이터 처리는 Lambda 함수에서 하고, ASL은 단순하게 유지하는 것이 좋습니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 이제 ASL이 단순한 JSON이 아니라 체계적인 언어라는 것을 이해하게 되었습니다.

"이제 공식 문서를 보면서 제대로 작성할 수 있을 것 같아요!" ASL의 기본 구조를 제대로 이해하면 복잡한 워크플로우도 자신 있게 작성할 수 있습니다. 여러분도 오늘 배운 내용을 바탕으로 첫 번째 워크플로우를 만들어보세요.

실전 팁

💡 - AWS Step Functions 콘솔에서 제공하는 샘플 워크플로우를 살펴보면 ASL 작성 패턴을 빠르게 익힐 수 있습니다

  • VSCode의 AWS Toolkit 확장을 설치하면 ASL 작성 시 자동완성과 문법 검사를 지원받을 수 있습니다
  • ASL 정의를 작성할 때는 들여쓰기를 일관되게 유지하여 가독성을 높이세요

4. Task 상태 사용

김개발 씨가 워크플로우를 작성하다가 궁금한 점이 생겼습니다. "Task 상태에서 Lambda 말고 다른 것도 호출할 수 있나요?" 박시니어 씨는 고개를 끄덕이며 "당연하죠.

DynamoDB, SNS, ECS 등 수십 가지 AWS 서비스를 직접 호출할 수 있어요"라고 답했습니다.

Task 상태는 실제 작업을 수행하는 상태로, Step Functions에서 가장 많이 사용됩니다. Lambda 함수 호출뿐만 아니라 다양한 AWS 서비스를 통합하여 호출할 수 있습니다.

SDK 통합을 사용하면 Lambda 없이도 S3, DynamoDB, SNS 등을 직접 조작할 수 있어 비용과 복잡도를 크게 줄일 수 있습니다.

다음 코드를 살펴봅시다.

{
  "Comment": "다양한 Task 상태 예제",
  "StartAt": "DynamoDB에_저장",
  "States": {
    "DynamoDB에_저장": {
      "Type": "Task",
      "Resource": "arn:aws:states:::dynamodb:putItem",
      "Parameters": {
        "TableName": "Orders",
        "Item": {
          "orderId": {"S.$": "$.orderId"},
          "status": {"S": "PENDING"}
        }
      },
      "Next": "SNS로_알림"
    },
    "SNS로_알림": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:ap-northeast-2:123456789012:OrderNotification",
        "Message.$": "$.orderId"
      },
      "End": true
    }
  }
}

김개발 씨는 처음에 모든 작업을 Lambda 함수로 만들었습니다. DynamoDB에 데이터를 저장하는 Lambda, SNS로 알림을 보내는 Lambda, S3에서 파일을 읽는 Lambda...

함수가 너무 많아져서 관리하기 힘들었습니다. 박시니어 씨가 코드 리뷰를 하다가 말했습니다.

"이런 단순한 작업들은 Lambda 없이 Step Functions에서 직접 호출할 수 있어요. SDK 통합이라는 기능을 사용하면 됩니다." 김개발 씨는 놀랐습니다.

"Lambda 없이 DynamoDB를 호출할 수 있다고요?" Task 상태의 SDK 통합이란 무엇일까요? 쉽게 비유하자면, Task 상태는 마치 스위스 아미 나이프와 같습니다.

하나의 도구로 여러 가지 작업을 할 수 있습니다. Lambda 함수를 호출할 수도 있고, DynamoDB에 데이터를 쓸 수도 있고, SNS로 메시지를 보낼 수도 있습니다.

상황에 맞는 기능을 골라 사용하면 됩니다. 전통적인 방식의 문제점은 무엇이었을까요?

개발자들은 간단한 작업을 위해서도 Lambda 함수를 만들어야 했습니다. 예를 들어 DynamoDB에 한 줄의 데이터를 쓰는 것만으로도 Lambda 함수를 작성하고, 테스트하고, 배포해야 했습니다.

이는 시간도 오래 걸리고 관리해야 할 리소스도 늘어나는 문제가 있었습니다. 더 큰 문제는 비용이었습니다.

Lambda는 호출 횟수와 실행 시간에 따라 과금됩니다. 단순히 DynamoDB를 호출하는 것만으로도 Lambda 비용이 발생했던 것입니다.

바로 이런 문제를 해결하기 위해 SDK 통합 기능이 등장했습니다. SDK 통합을 사용하면 Step Functions가 AWS 서비스를 직접 호출합니다.

Lambda 함수가 중간에 끼어들 필요가 없으므로 지연시간도 줄고 비용도 절약됩니다. 또한 관리해야 할 Lambda 함수의 수가 줄어들어 아키텍처가 더 단순해집니다.

현재 Step Functions는 200개 이상의 AWS 서비스 API를 지원합니다. DynamoDB, S3, SNS, SQS, EventBridge, ECS 등 거의 모든 주요 서비스를 직접 호출할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. "DynamoDB에_저장" 상태의 Resource를 보면 "arn:aws:states:::dynamodb:putItem"입니다.

이 특별한 ARN 형식이 바로 SDK 통합을 나타냅니다. "states:::" 다음에 서비스 이름과 API 액션이 옵니다.

Parameters 객체는 DynamoDB의 putItem API에 전달될 파라미터입니다. TableName으로 테이블을 지정하고, Item으로 저장할 데이터를 정의합니다.

"S.$": "$.orderId"는 JSONPath 표현식으로, 입력 데이터의 orderId 필드를 가져와 String 타입으로 저장합니다. "SNS로_알림" 상태도 비슷합니다.

Resource가 "arn:aws:states:::sns:publish"로, SNS의 publish API를 호출합니다. TopicArn으로 어느 주제에 발행할지 지정하고, **Message.$**로 메시지 내용을 입력 데이터에서 가져옵니다.

실제 현업에서는 어떻게 활용할까요? 많은 기업들이 데이터 파이프라인에서 SDK 통합을 적극 활용합니다.

예를 들어 S3에서 파일을 읽고, 내용을 파싱하고, DynamoDB에 저장하고, 완료 알림을 SNS로 보내는 전체 과정을 Lambda 없이 구현할 수 있습니다. 물론 복잡한 파싱 로직은 Lambda가 필요하지만, 단순한 CRUD 작업은 SDK 통합으로 충분합니다.

또 다른 예로, 배치 작업 실행이 있습니다. ECS Task를 시작하고, 완료될 때까지 기다리고, 결과를 DynamoDB에 기록하고, CloudWatch Logs에 로그를 남기는 전체 흐름을 Step Functions만으로 구성할 수 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 SDK 통합으로 복잡한 로직을 구현하려는 것입니다.

SDK 통합은 단일 API 호출만 지원합니다. 여러 API를 조합하거나 복잡한 데이터 변환이 필요하다면 Lambda를 사용하는 것이 낫습니다.

또한 Parameters 객체의 형식을 정확히 맞춰야 합니다. 각 AWS 서비스 API가 요구하는 파라미터 형식이 다르므로, AWS SDK 문서를 참고하여 정확하게 작성해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. SDK 통합을 배운 김개발 씨는 불필요한 Lambda 함수들을 제거하기 시작했습니다.

코드가 간결해지고 비용도 줄었습니다. "이렇게 편한 기능이 있었다니!" Task 상태의 SDK 통합을 제대로 활용하면 더 단순하고 효율적인 워크플로우를 만들 수 있습니다.

여러분도 간단한 작업은 Lambda 대신 SDK 통합으로 구현해보세요.

실전 팁

💡 - SDK 통합 사용 시 IAM 역할에 해당 AWS 서비스 호출 권한이 있는지 확인하세요

  • Step Functions 콘솔의 "Optimized integration" 목록에서 지원되는 서비스를 확인할 수 있습니다
  • 복잡한 데이터 변환이 필요하면 Lambda를 사용하고, 단순 API 호출은 SDK 통합을 사용하는 것이 좋습니다

5. Choice와 분기

김개발 씨가 주문 처리 워크플로우를 만들다가 막혔습니다. "재고가 있으면 배송하고, 없으면 입고 대기 상태로 만들어야 하는데 어떻게 하죠?" 박시니어 씨가 웃으며 답했습니다.

"Choice 상태를 사용하면 됩니다. 조건에 따라 다른 경로로 분기할 수 있어요."

Choice 상태는 입력 데이터를 기반으로 조건을 평가하여 워크플로우를 분기시킵니다. 프로그래밍의 if-else 문과 비슷하지만, 선언적 방식으로 표현됩니다.

숫자 비교, 문자열 매칭, 타임스탬프 비교, 존재 여부 확인 등 다양한 조건을 지원하여 복잡한 비즈니스 로직을 구현할 수 있습니다.

다음 코드를 살펴봅시다.

{
  "Comment": "재고 확인 후 분기 처리",
  "StartAt": "재고확인",
  "States": {
    "재고확인": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:CheckInventory",
      "Next": "재고_판단"
    },
    "재고_판단": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.stock",
          "NumericGreaterThan": 0,
          "Next": "배송처리"
        },
        {
          "Variable": "$.stock",
          "NumericEquals": 0,
          "Next": "입고대기"
        }
      ],
      "Default": "에러처리"
    },
    "배송처리": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:Ship",
      "End": true
    },
    "입고대기": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:WaitForStock",
      "End": true
    },
    "에러처리": {
      "Type": "Fail",
      "Cause": "재고 정보 오류"
    }
  }
}

김개발 씨는 고민에 빠졌습니다. 지금까지 배운 상태들은 모두 순차적으로 실행되었습니다.

하지만 실제 비즈니스 로직은 조건에 따라 다른 경로를 타야 하는 경우가 많습니다. 박시니어 씨가 화이트보드에 다이어그램을 그렸습니다.

"재고확인" 상태에서 두 갈래로 화살표가 갈라지는 그림이었습니다. "이렇게 조건에 따라 경로가 나뉘는 걸 분기라고 하죠.

Choice 상태가 바로 이걸 담당해요." Choice 상태란 정확히 무엇일까요? 쉽게 비유하자면, Choice 상태는 마치 교차로의 신호등과 같습니다.

직진 신호가 켜지면 직진하고, 좌회전 신호가 켜지면 좌회전합니다. 마찬가지로 Choice 상태도 조건을 평가하여 어느 경로로 진행할지 결정합니다.

순차적 실행만으로는 왜 부족할까요? 실제 비즈니스 프로세스는 대부분 조건부입니다.

"VIP 고객이면 익일 배송, 일반 고객이면 3일 배송", "결제 금액이 10만원 이상이면 무료 배송, 미만이면 배송비 부과" 같은 로직이 필요합니다. 이런 조건부 로직 없이는 실용적인 워크플로우를 만들 수 없습니다.

예전에는 이런 분기 로직을 Lambda 함수 안에 넣었습니다. Lambda가 조건을 판단하고 다음 Lambda를 호출하는 방식이었죠.

하지만 이렇게 하면 비즈니스 로직과 흐름 제어가 섞여서 코드가 복잡해졌습니다. 바로 이런 문제를 해결하기 위해 Choice 상태가 등장했습니다.

Choice 상태를 사용하면 분기 로직을 워크플로우 레벨에서 선언할 수 있습니다. Lambda 함수는 단순히 데이터를 반환만 하고, 그 데이터를 기반으로 어느 경로로 갈지는 Choice 상태가 결정합니다.

관심사의 분리가 명확해지는 것입니다. 또한 AWS 콘솔에서 워크플로우를 시각적으로 볼 때 분기가 한눈에 보입니다.

코드를 읽지 않아도 "재고가 있으면 이쪽, 없으면 저쪽"이라는 흐름이 직관적으로 이해됩니다. 위의 코드를 한 줄씩 살펴보겠습니다.

"재고확인" Task는 CheckInventory Lambda를 호출합니다. 이 함수는 재고 수량을 확인하고 {"stock": 5} 같은 형태로 결과를 반환합니다.

"재고_판단" 상태의 Type이 "Choice"입니다. Choices 배열에 여러 조건을 정의할 수 있습니다.

첫 번째 조건은 Variable로 "$.stock"을 지정하여 입력 데이터의 stock 필드를 참조합니다. NumericGreaterThan: 0은 stock이 0보다 크면 이 조건이 참이라는 의미입니다.

조건이 참이면 Next로 지정된 "배송처리" 상태로 전환됩니다. 두 번째 조건은 stock이 정확히 0인 경우를 처리합니다.

NumericEquals: 0으로 재고가 없을 때 "입고대기" 상태로 이동합니다. Default는 모든 조건에 맞지 않을 때 가는 기본 경로입니다.

예를 들어 stock 필드가 없거나 음수인 경우 "에러처리" 상태로 갑니다. Default는 선택사항이지만, 예외 상황을 처리하기 위해 지정하는 것이 좋습니다.

실제 현업에서는 어떻게 활용할까요? Choice 상태는 승인 워크플로우에서 자주 사용됩니다.

예를 들어 경비 청구 시스템에서 "10만원 미만은 팀장 승인, 10만원 이상은 임원 승인" 같은 분기를 구현할 수 있습니다. 금액에 따라 자동으로 적절한 승인자에게 라우팅됩니다.

또 다른 예로, A/B 테스트 시스템이 있습니다. 사용자의 ID를 기반으로 무작위 그룹을 할당하고, 그룹에 따라 다른 처리 로직을 실행할 수 있습니다.

Choice 상태의 조건으로 간단한 해시 함수 결과를 판단하면 됩니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 너무 복잡한 조건을 Choice에 넣으려는 것입니다. Choice 상태는 기본적인 비교 연산만 지원합니다.

복잡한 계산이나 정규식 매칭이 필요하다면 Lambda에서 처리하고, 그 결과를 단순한 플래그로 반환하는 것이 좋습니다. 또한 Choices 배열의 순서가 중요합니다.

Step Functions는 위에서부터 순서대로 조건을 평가하고, 첫 번째로 참인 조건의 Next로 이동합니다. 따라서 더 구체적인 조건을 위에, 일반적인 조건을 아래에 배치해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. Choice 상태를 배운 김개발 씨는 복잡한 분기 로직을 깔끔하게 구현할 수 있게 되었습니다.

"이제 Lambda에서 흐름 제어를 할 필요가 없네요!" Choice 상태를 제대로 활용하면 비즈니스 로직을 더 명확하게 표현할 수 있습니다. 여러분도 조건부 로직이 필요할 때 Choice 상태를 적극 활용해보세요.

실전 팁

💡 - 조건이 3개 이상이면 가독성을 위해 각 조건에 주석을 추가하세요(Comment 필드 활용)

  • 문자열 비교 시 StringEquals, 숫자 비교 시 NumericEquals 등 적절한 연산자를 사용하세요
  • 반드시 Default 경로를 지정하여 예상치 못한 입력을 처리하세요

6. 병렬 실행

김개발 씨의 워크플로우가 점점 느려졌습니다. 사용자 정보 조회, 결제 정보 조회, 배송지 정보 조회를 순차적으로 하다 보니 시간이 오래 걸렸습니다.

박시니어 씨가 조언했습니다. "이 작업들은 서로 독립적이니까 병렬로 실행하면 어때요?

Parallel 상태를 사용하면 됩니다."

Parallel 상태는 여러 개의 독립적인 워크플로우를 동시에 실행합니다. 각 브랜치는 독립적으로 실행되며, 모든 브랜치가 완료될 때까지 기다린 후 다음 상태로 진행합니다.

서로 의존성이 없는 작업들을 병렬화하여 전체 실행 시간을 크게 단축할 수 있습니다.

다음 코드를 살펴봅시다.

{
  "Comment": "병렬 데이터 조회 예제",
  "StartAt": "병렬_데이터_조회",
  "States": {
    "병렬_데이터_조회": {
      "Type": "Parallel",
      "Branches": [
        {
          "StartAt": "사용자_정보",
          "States": {
            "사용자_정보": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:GetUser",
              "End": true
            }
          }
        },
        {
          "StartAt": "결제_정보",
          "States": {
            "결제_정보": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:GetPayment",
              "End": true
            }
          }
        },
        {
          "StartAt": "배송_정보",
          "States": {
            "배송_정보": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:GetShipping",
              "End": true
            }
          }
        }
      ],
      "Next": "데이터_통합"
    },
    "데이터_통합": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:123456789012:function:MergeData",
      "End": true
    }
  }
}

김개발 씨는 답답했습니다. 주문 상세 정보를 보여주기 위해 세 개의 Lambda 함수를 순차적으로 호출하는데, 각각 1초씩 걸려 총 3초가 소요되었습니다.

사용자들이 "페이지가 너무 느려요"라고 불평했습니다. 박시니어 씨가 워크플로우 다이어그램을 보더니 말했습니다.

"이 세 작업은 서로 독립적이잖아요? 사용자 정보를 조회하는 데 결제 정보가 필요한 건 아니죠.

동시에 실행하면 1초면 끝나요." 김개발 씨는 눈이 번쩍 뜨였습니다. "어떻게 하면 되나요?" Parallel 상태란 정확히 무엇일까요?

쉽게 비유하자면, Parallel 상태는 마치 레스토랑 주방과 같습니다. 셰프 한 명이 전채-메인-디저트를 순서대로 만들면 시간이 오래 걸립니다.

하지만 전채 담당, 메인 담당, 디저트 담당이 동시에 작업하면 훨씬 빠릅니다. 모든 요리가 완성되면 한 접시에 담아 서빙합니다.

순차 실행의 문제점은 무엇일까요? 전통적인 워크플로우는 한 번에 한 작업만 실행합니다.

A가 끝나야 B가 시작되고, B가 끝나야 C가 시작됩니다. 하지만 실제로는 A, B, C가 서로 독립적인 경우가 많습니다.

이런 경우 순차 실행은 불필요하게 시간을 낭비합니다. 예를 들어 김개발 씨의 경우, 사용자 정보와 결제 정보는 전혀 관련이 없습니다.

사용자 정보 조회가 끝날 때까지 결제 정보 조회를 기다릴 이유가 없는 것입니다. 바로 이런 문제를 해결하기 위해 Parallel 상태가 등장했습니다.

Parallel 상태를 사용하면 여러 작업이 동시에 시작됩니다. 각 브랜치는 완전히 독립적인 워크플로우로, 서로 영향을 주지 않습니다.

Step Functions는 모든 브랜치가 완료될 때까지 기다린 후, 각 브랜치의 결과를 배열로 모아서 다음 상태로 전달합니다. 이를 통해 실행 시간을 크게 단축할 수 있습니다.

세 개의 1초 작업을 병렬로 실행하면 총 1초면 충분합니다. 십 개의 작업도 모두 병렬로 실행하면 가장 오래 걸리는 작업의 시간만큼만 소요됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. "병렬_데이터_조회" 상태의 Type이 "Parallel"입니다.

Branches 배열에 동시에 실행할 워크플로우들을 정의합니다. 각 브랜치는 완전한 상태 머신 정의입니다.

StartAt과 States를 가지고 있고, 자체적으로 여러 상태를 포함할 수도 있습니다. 첫 번째 브랜치는 "사용자_정보" 상태를 실행합니다.

GetUser Lambda 함수를 호출하고 End: true로 이 브랜치를 종료합니다. 두 번째와 세 번째 브랜치도 비슷하게 결제 정보와 배송 정보를 조회합니다.

세 브랜치가 모두 완료되면 Parallel 상태가 종료되고, 각 브랜치의 결과는 배열로 합쳐집니다. 예를 들어 [{"user": "김개발"}, {"payment": "카드"}, {"shipping": "서울"}] 같은 형태가 됩니다.

이 배열이 Next로 지정된 "데이터_통합" 상태로 전달됩니다. MergeData Lambda는 세 개의 결과를 하나의 객체로 합쳐서 최종 결과를 만듭니다.

실제 현업에서는 어떻게 활용할까요? Parallel 상태는 대용량 데이터 처리에서 빛을 발합니다.

예를 들어 1000개의 이미지를 리사이즈해야 한다면, 10개의 브랜치로 나눠서 각각 100개씩 처리할 수 있습니다. 순차로 하면 한 시간 걸릴 작업이 병렬로 하면 10분 안에 끝납니다.

또 다른 예로, 외부 API 호출이 있습니다. 여러 외부 서비스에서 데이터를 가져와야 할 때, 순차적으로 호출하면 각 API의 응답 시간이 누적됩니다.

하지만 병렬로 호출하면 가장 느린 API의 응답 시간만큼만 기다리면 됩니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 의존성이 있는 작업을 병렬로 실행하는 것입니다. 예를 들어 "결제 처리"와 "재고 차감"은 병렬로 실행하면 안 됩니다.

결제가 실패했는데 재고가 차감되는 문제가 생길 수 있습니다. 병렬 실행은 작업들이 완전히 독립적일 때만 사용해야 합니다.

또한 너무 많은 브랜치를 만들면 Step Functions의 실행 제한에 걸릴 수 있습니다. 한 번에 수백 개의 브랜치를 실행하는 것보다는 적절한 배치 크기로 나누는 것이 좋습니다.

예를 들어 Map 상태와 조합하여 동적으로 병렬 처리를 구성할 수 있습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

Parallel 상태를 도입한 김개발 씨는 페이지 로딩 시간을 3분의 1로 줄이는 데 성공했습니다. 사용자들의 불만이 사라지고 오히려 "빨라졌어요!"라는 긍정적인 피드백을 받았습니다.

Parallel 상태를 제대로 활용하면 워크플로우의 성능을 극적으로 개선할 수 있습니다. 여러분도 독립적인 작업들을 병렬화하여 실행 시간을 단축해보세요.

실전 팁

💡 - 각 브랜치는 독립적인 에러 처리를 가질 수 있습니다. 한 브랜치의 실패가 다른 브랜치에 영향을 주지 않도록 설계하세요

  • Parallel 상태에도 Retry와 Catch를 지정할 수 있어, 모든 브랜치에 공통 에러 처리를 적용할 수 있습니다
  • 병렬 실행으로 비용이 증가할 수 있으니, Lambda 동시 실행 제한을 적절히 설정하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#AWS#StepFunctions#StateMachine#Lambda#Workflow

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스