기준: SAA-C03 시험 가이드오류 제보
Lambda 함수 하나가 하나의 작업을 처리하는 것은 단순하다. 그런데 주문 처리처럼 검증 → 재고 확인 → 결제 → 배송 요청 → 알림이라는 여러 단계가 순서와 조건을 가지고 연결될 때, Lambda 안에서 이 흐름을 직접 코딩하면 어떻게 될까. 각 Lambda가 다음 Lambda를 호출하고, 실패 시 이전 단계를 취소하고, 어디까지 완료됐는지 추적하는 코드가 Lambda 비즈니스 로직과 뒤엉킨다. Step Functions는 이 흐름 제어(Flow Control)를 코드 밖으로 꺼내서 시각적 상태 머신으로 정의한다.
이런 오케스트레이션 문제는 마이크로서비스 아키텍처에서 특히 심각하다. 서비스가 늘어날수록 서비스 간 조율 로직이 각 서비스에 분산되어 버린다. 한 서비스가 다음 서비스를 직접 호출하는 코레오그래피(Choreography) 방식은 이벤트 기반으로 느슨하게 결합되지만, 전체 흐름을 한눈에 파악하기 어렵다. Step Functions의 오케스트레이션(Orchestration) 방식은 중앙에서 흐름을 정의하고 시각화할 수 있어서 복잡한 비즈니스 프로세스 추적에 유리하다.
Step Functions는 Amazon States Language(ASL)라는 JSON 기반 언어로 워크플로를 정의한다. ASL에서 모든 것은 "상태(State)"이고, 상태 간에 전이(Transition)가 있다. 실행 중인 워크플로의 각 단계가 어디에 있는지, 어떤 데이터를 가지고 있는지, 무엇이 실패했는지를 Step Functions 서비스가 추적하고 영속화한다. Lambda는 이 상태 머신에서 호출되는 "작업자"일 뿐이다.
| 항목 | Standard | Express |
|---|---|---|
| 최대 실행 시간 | 1년 | 5분 |
| 실행 의미론 | Exactly-once | Async: At-least-once / Sync: At-most-once |
| 실행 이력 | AWS 콘솔에서 상세 추적 가능 | CloudWatch Logs로 별도 저장 필요 |
| 비용 | 상태 전환당 과금 | 실행 수 + 실행 시간 과금 |
| 처리량 | 초당 2,000 실행 | 초당 100,000 이상 실행 |
| 적합한 워크로드 | 장기 비즈니스 프로세스, 인간 승인 | 대량 짧은 워크플로 (IoT, 스트리밍, ETL) |
Standard는 각 상태 전환이 "exactly-once" 의미론을 보장한다. 결제 처리나 이메일 발송처럼 중복 실행이 치명적인 작업에 적합하다. Express는 초당 100,000개 이상의 실행을 처리할 수 있는 높은 처리량이 필요한 경우에 쓴다. 짧은 IoT 이벤트 처리, 스트리밍 데이터 변환에 적합하다.
Exactly-once의 분산 시스템 이론 — "Exactly-once"는 분산 시스템에서 가장 어려운 보장 중 하나다. 네트워크 실패가 발생했을 때 재시도를 하면 at-least-once가 되고(중복 가능), 재시도를 안 하면 at-most-once가 된다(손실 가능). Step Functions Standard는 내부적으로 분산 로그와 멱등성(Idempotency) 메커니즘을 통해 exactly-once를 구현한다. 이 구현은 Leslie Lamport의 Paxos(1989)와 유사한 합의 알고리즘을 기반으로 한다. 이 보장이 성능 비용으로 이어지는 것이 Standard가 Express보다 비싸고 처리량이 낮은 이유다. Express의 at-least-once는 중복 처리가 가능한 멱등적 작업(Idempotent Operation)에서만 안전하다.
Task 상태: Lambda 호출, ECS 태스크 시작, DynamoDB 쓰기, SQS 메시지 전송, SageMaker 훈련 등 실제 작업을 수행한다. SDK Integration으로 200개 이상의 AWS 서비스를 직접 호출 가능해서 Lambda 없이도 AWS 서비스를 조합할 수 있다.
Choice 상태: 조건 분기. 특정 값에 따라 다른 상태로 전이한다. if-else의 선언적 표현.
Parallel 상태: 여러 브랜치를 동시에 실행하고 모두 완료될 때까지 기다린다. 신용 조회와 신원 확인을 동시에 하고 둘 다 완료 후 다음 단계로.
Map 상태: 배열의 각 항목에 대해 동일한 처리를 반복한다. Distributed Map은 최대 10,000개의 병렬 실행을 지원하고, S3 객체 배열을 처리하는 데 최적화되어 있다.
Wait 상태: 지정된 시간 또는 특정 타임스탬프까지 대기. 결제 처리 3초 후 결제 상태 확인, 예약 24시간 후 리마인더 발송 등.
waitForTaskToken: 장기 대기 패턴. Task 상태에서 태스크 토큰을 외부 시스템(사람, 외부 API, 레거시 시스템)에 전달하고, 그 시스템이 토큰을 포함해서 콜백을 보낼 때까지 Step Functions가 대기한다. 대기 중에는 비용이 발생하지 않고 최대 1년까지 대기 가능.
// waitForTaskToken 예시 (인간 승인 — 결제 처리)
{
"Type": "Task",
"Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
"Parameters": {
"QueueUrl": "https://sqs.../approval-queue",
"MessageBody": {
"orderId.$": "$.orderId",
"amount.$": "$.amount",
"taskToken.$": "$$.Task.Token",
"approvalUrl.$": "States.Format('https://app.example.com/approve?token={}', $$.Task.Token)"
}
},
"HeartbeatSeconds": 86400,
"Next": "ApprovedState",
"Catch": [
{
"ErrorEquals": ["States.HeartbeatTimeout"],
"Next": "ApprovalExpiredState"
}
]
}승인자가 SQS 메시지를 받아 확인 후 SendTaskSuccess(taskToken) 또는 SendTaskFailure(taskToken)를 호출하면 워크플로가 재개된다.
Intuit TurboTax Step Functions 사례 — 2021년 Intuit(TurboTax, QuickBooks)는 세금 신고 처리 워크플로를 Step Functions Standard로 구현한 사례를 AWS re:Invent에서 발표했다. 세금 신고 프로세스는 데이터 검증 → 계산 → 정부 기관 제출 → 확인 대기(최대 수 시간) → 결과 통보의 단계를 가진다. 특히 정부 기관 확인 대기 단계에서 waitForTaskToken을 사용해서 실제로 정부 응답이 올 때까지 무상태로 대기했다. 이 패턴으로 월 수백만 건의 세금 신고를 처리한다. 과거에는 Lambda 안에서 폴링 루프로 구현했지만, Step Functions + waitForTaskToken으로 전환하면서 비용이 90% 절감됐다고 발표했다.
Step Functions에서 모든 Task 상태는 Retry와 Catch를 가질 수 있다.
Retry: 특정 오류 유형에 대해 재시도 횟수, 초기 지연, 백오프 배율을 설정한다.
"Retry": [
{
"ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2,
"MaxDelaySeconds": 30
}
]2초 → 4초 → 8초 간격으로 최대 3회 재시도(지수 백오프). 모두 실패하면 Catch로 넘어간다.
Catch: 특정 오류를 잡아서 다른 상태로 라우팅한다. 사가(Saga) 패턴의 보상 트랜잭션을 여기서 구현한다.
"Catch": [
{
"ErrorEquals": ["PaymentFailedException"],
"ResultPath": "$.error",
"Next": "ReleaseInventoryState"
},
{
"ErrorEquals": ["States.ALL"],
"ResultPath": "$.unexpectedError",
"Next": "HandleUnexpectedError"
}
]결제 실패 시 재고를 다시 릴리즈하는 보상 트랜잭션으로 연결. States.ALL은 모든 오류를 잡는 와일드카드.
🔍 Saga 패턴의 학문적 기원과 Step Functions 구현 — Step Functions의 사가 패턴은 Hector Garcia-Molina와 Kenneth Salem의 1987년 논문 "Sagas"(ACM SIGMOD)에서 제안된 분산 트랜잭션 패턴을 구현한다. 마이크로서비스 환경에서 여러 서비스에 걸친 트랜잭션을 2PC(2-Phase Commit)로 원자적으로 처리하면 성능 저하와 가용성 문제가 발생한다. Saga 패턴은 각 단계의 "보상 트랜잭션(Compensating Transaction)"을 정의해서 실패 시 이전 단계를 되돌린다. Step Functions의 Catch + 보상 Lambda 함수 호출이 이 패턴의 클라우드 구현이다. 완전한 ACID 트랜잭션이 아니라 "Eventually Consistent" 상태를 보상 트랜잭션으로 달성하는 것이 핵심이다.
⚠️ 256KB 데이터 한도 — Step Functions 상태 간에 전달되는 입출력 데이터는 256KB 한도가 있다. Lambda 실행 결과가 큰 경우(수 MB의 데이터) Lambda에서 S3에 결과를 저장하고 Step Functions에는 S3 URI만 전달하는 패턴을 써야 한다. 256KB를 초과하면 실행이 실패한다. 대용량 데이터 처리 시 반드시 S3를 중간 스토리지로 활용해야 한다.
Distributed Map은 S3의 수백만 개 객체를 병렬로 처리하거나, 대규모 배열의 각 항목을 동시에 처리할 때 사용한다. 최대 10,000개의 동시 실행을 지원한다.
실제 사용 예: S3 버킷에 있는 1만 개의 CSV 파일을 각각 Lambda로 처리해서 집계. Distributed Map이 없다면 Lambda를 순차적으로 호출하거나 복잡한 병렬 처리 로직을 직접 구현해야 한다.
{
"Type": "Map",
"ItemReader": {
"Resource": "arn:aws:states:::s3:listObjectsV2",
"Parameters": {
"Bucket": "my-data-bucket",
"Prefix": "raw-data/"
}
},
"MaxConcurrency": 1000,
"ToleratedFailurePercentage": 10,
"Iterator": {
"StartAt": "ProcessFile",
"States": {
"ProcessFile": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:process-csv",
"Retry": [{"ErrorEquals": ["States.ALL"], "MaxAttempts": 2}],
"End": true
}
}
},
"ResultWriter": {
"Resource": "arn:aws:states:::s3:putObject",
"Parameters": {
"Bucket": "results-bucket",
"Prefix": "results/"
}
}
}ToleratedFailurePercentage: 일부 실패를 허용하고 전체 Map을 성공으로 간주. 100% 완벽보다 80% 성공도 허용되는 데이터 처리에 유용.
GraphQL은 Facebook이 2012년 내부에서 개발하고 2015년 오픈 소스로 공개한 API 쿼리 언어다. REST API의 Over-fetching(필요 이상의 데이터 반환)과 Under-fetching(여러 번 요청 필요) 문제를 해결하기 위해 설계됐다.
GraphQL의 핵심 차이: 클라이언트가 필요한 필드를 명시한다.
# REST API 방식: 여러 엔드포인트 호출 필요
# GET /users/123 → { id, name, email, age, address, ... } # Over-fetching
# GET /users/123/orders → 별도 호출 # Under-fetching (N+1 문제)
# GraphQL 방식: 한 번에, 필요한 것만
query {
user(id: "123") {
name # name만 요청 (Over-fetching 없음)
recentOrders(limit: 5) { # 한 쿼리에서 관계 데이터도 함께 (Under-fetching 없음)
orderId
totalAmount
status
}
}
}AppSync는 이 GraphQL을 관리형으로 제공한다. 스키마 정의 → Resolver 구성 → 백엔드 연결이 선언적으로 이루어진다.
AppSync는 다양한 데이터 소스를 Resolver를 통해 연결한다.
Pipeline Resolver: 하나의 GraphQL 필드 해석에 여러 함수를 체인으로 연결. "사용자 조회 → 권한 확인 → 데이터 반환"같은 복합 로직을 코드 없이 구성.
Pipeline Resolver 실행 순서:
Before Mapping Template
→ Function 1 (예: 권한 확인)
→ Function 2 (예: 데이터 조회)
→ Function 3 (예: 결과 변환)
→ After Mapping Template
→ 클라이언트에 반환
AppSync의 강력한 기능 중 하나는 GraphQL Subscription이다. 특정 Mutation이 발생했을 때 구독자에게 자동으로 Push한다.
# Mutation: 새 메시지 보내기
mutation SendMessage {
sendMessage(roomId: "room1", text: "안녕하세요") {
messageId
text
timestamp
}
}
# Subscription: 새 메시지 수신 (다른 클라이언트가 등록)
subscription OnNewMessage {
onCreateMessage(roomId: "room1") {
messageId
text
sender { name }
timestamp
}
}클라이언트가 Subscription을 시작하면 WebSocket 연결이 열리고, 누군가 sendMessage Mutation을 실행할 때마다 구독자에게 자동으로 Push된다. 채팅, 협업 도구, 실시간 피드에 완벽한 패턴이다.
GraphQL N+1 문제와 DataLoader 해결 — GraphQL의 N+1 문제는 관계형 데이터를 처리할 때 발생한다. 예를 들어 100명의 사용자 목록을 가져오고(1번 쿼리), 각 사용자의 최근 주문을 가져오면 100번의 추가 쿼리가 발생한다(N번 쿼리, 총 1+N번). Facebook은 이를 해결하기 위해 Dataloader라는 배치 로딩 라이브러리를 개발했다. 개별 요청을 모아서 배치로 처리한다 — 100명의 userId를 모아서 BatchGetItem으로 한번에 조회. AppSync도 DynamoDB BatchGetItem 통합과 Pipeline Resolver로 이 문제를 완화한다. Lambda Resolver를 사용할 때는 직접 Dataloader를 구현하거나, AppSync의 배치 처리 기능을 활용한다.
AppSync Caching — AppSync는 per-resolver 캐싱을 지원한다. 특정 쿼리의 응답을 TTL 동안 캐싱하면 DynamoDB/Lambda 호출을 줄일 수 있다. 캐시 크기는 t2.micro부터 r4.8xlarge까지 선택 가능하다. REST API의 전체 응답 캐싱과 달리, AppSync는 필드 단위로 캐시를 제어할 수 있어서 자주 변경되는 필드와 그렇지 않은 필드를 다르게 캐싱할 수 있다. Subscription 데이터는 캐싱되지 않는다(항상 실시간).
| 기준 | AppSync | API Gateway |
|---|---|---|
| 쿼리 언어 | GraphQL | REST/HTTP |
| 필드 선택 | 클라이언트가 결정 | 서버가 결정 |
| 다중 데이터 소스 | 한 쿼리에서 여러 소스 | 엔드포인트당 하나 |
| 실시간 Push | Subscription (WebSocket) | WebSocket API (직접 구현) |
| N+1 문제 해결 | DataLoader + Pipeline Resolver | 없음 |
| 캐싱 | per-resolver 캐싱 | 전체 응답 캐싱 (REST만) |
| 타입 시스템 | GraphQL Schema (강타입) | OpenAPI/Swagger |
| 주요 사용 사례 | 모바일/웹 앱, 다양한 클라이언트 | 마이크로서비스 REST API |
AppSync가 적합한 시나리오:
API Gateway가 적합한 시나리오:
다른 클라우드와의 비교:
| 항목 | AWS AppSync | GCP Firebase/GraphQL | Azure API Management |
|---|---|---|---|
| 관리형 GraphQL | O (AppSync) | X (직접 구현) | X (직접 구현) |
| 실시간 구독 | O (WebSocket) | O (Realtime DB, Firestore) | X |
| 오프라인 동기화 | O (Amplify DataStore) | O (Firestore 오프라인) | X |
| 백엔드 통합 | DDB/Lambda/RDS/OpenSearch/HTTP | Firestore/Cloud Functions | 자체 API |
# Step Functions Standard 상태 머신 생성 (주문 처리 워크플로)
aws stepfunctions create-state-machine \
--name OrderProcessingFlow \
--role-arn arn:aws:iam::111:role/stepfunctions-role \
--type STANDARD \
--definition '{
"Comment": "주문 처리 워크플로",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-2:111:function:validate-order",
"Retry": [
{
"ErrorEquals": ["Lambda.ServiceException"],
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Catch": [{"ErrorEquals": ["ValidationError"], "Next": "OrderFailed"}],
"Next": "CheckInventory"
},
"CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:getItem",
"Parameters": {
"TableName": "Inventory",
"Key": {"productId": {"S.$": "$.productId"}}
},
"Next": "ProcessPayment"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
"Parameters": {
"QueueUrl": "https://sqs.ap-northeast-2.amazonaws.com/111/payment-queue",
"MessageBody": {
"orderId.$": "$.orderId",
"amount.$": "$.amount",
"taskToken.$": "$$.Task.Token"
}
},
"HeartbeatSeconds": 300,
"Catch": [{"ErrorEquals": ["PaymentFailedException"], "Next": "ReleaseInventory"}],
"Next": "NotifyCustomer"
},
"ReleaseInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-2:111:function:release-inventory",
"Next": "OrderFailed"
},
"NotifyCustomer": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "arn:aws:sns:ap-northeast-2:111:order-notifications",
"Message.$": "States.Format('"'"'주문 {} 처리 완료'"'"', $.orderId)"
},
"End": true
},
"OrderFailed": {
"Type": "Fail",
"Error": "OrderProcessingFailed"
}
}
}'
# Express 상태 머신 생성 (IoT 이벤트 처리 — 고처리량)
aws stepfunctions create-state-machine \
--name IoTEventProcessor \
--role-arn arn:aws:iam::111:role/sfn-role \
--type EXPRESS \
--logging-configuration '{
"level": "ALL",
"includeExecutionData": true,
"destinations": [{"cloudWatchLogsLogGroup": {"logGroupArn": "arn:aws:logs:ap-northeast-2:111:log-group:sfn-iot"}}]
}' \
--definition file://iot-flow.json
# 상태 머신 실행
aws stepfunctions start-execution \
--state-machine-arn arn:aws:states:ap-northeast-2:111:stateMachine:OrderProcessingFlow \
--input '{"orderId": "ord-001", "productId": "prod-001", "amount": 29900}'
# 실행 이력 조회
aws stepfunctions get-execution-history \
--execution-arn arn:aws:states:ap-northeast-2:111:execution:OrderProcessingFlow:exec-uuid
# AppSync API 생성 (Cognito + API Key 인증)
aws appsync create-graphql-api \
--name prod-graphql-api \
--authentication-type AMAZON_COGNITO_USER_POOLS \
--user-pool-config userPoolId=ap-northeast-2_xxx,awsRegion=ap-northeast-2,defaultAction=ALLOW \
--additional-authentication-providers '[{"authenticationType": "API_KEY"}]'
# AppSync DynamoDB 데이터 소스 연결
aws appsync create-data-source \
--api-id api-id \
--name OrdersTable \
--type AMAZON_DYNAMODB \
--service-role-arn arn:aws:iam::111:role/appsync-ddb-role \
--dynamodb-config tableName=Orders,awsRegion=ap-northeast-2
# AppSync 캐싱 활성화 (per-resolver TTL)
aws appsync update-graphql-api \
--api-id api-id \
--caching-config ttl=300,cachingKeys="\$context.arguments.id"
# AppSync API Key 생성 (개발/테스트용)
aws appsync create-api-key \
--api-id api-id \
--expires 1800000000Step Functions는 Lambda 함수들을 코드가 아닌 상태 머신 정의로 오케스트레이션하는 서비스다. Standard는 1년까지 exactly-once, Express는 5분 안에 대량 처리. waitForTaskToken으로 인간 승인이나 외부 콜백을 기다리고, Distributed Map으로 1만 개 병렬 처리를 선언적으로 구성한다. Retry + Catch는 Saga 패턴으로 보상 트랜잭션을 구현한다.
AppSync는 GraphQL API를 관리형으로 제공하고, Subscription으로 실시간 Push를 쉽게 구현한다. 다양한 클라이언트(모바일/웹)가 각자 필요한 데이터만 가져가야 하는 시나리오, Over-fetching/Under-fetching 문제, 실시간 협업이 AppSync의 자연스러운 사용 사례다.
내일은 컨테이너의 세계로 간다. ECS, EKS, Fargate, ECR이 어떻게 다르고, 컨테이너에서 IAM 권한을 올바르게 관리하는 방법을 다룬다.
선택지를 클릭하면 정답·해설이 펼쳐집니다.
문제 1
대출 심사 워크플로를 구현한다. 신청서 접수 → 신용 조회(자동) → 심사위원 검토(인간, 최대 3일) → 승인/거절 알림. 이 워크플로에 가장 적합한 Step Functions 구성은?
문제 2
S3 버킷에 매일 100만 개의 JSON 파일이 업로드된다. 각 파일을 Lambda로 처리해서 DynamoDB에 집계 데이터를 저장해야 한다. 가장 효율적인 방법은?
문제 3
모바일 앱과 웹 앱이 같은 백엔드를 사용하는데, 모바일은 화면이 작아서 사용자 이름과 프로필 사진만 필요하고, 웹은 전체 프로필 + 최근 주문 10개 + 친구 목록이 필요하다. 같은 API로 두 클라이언트의 요구를 효율적으로 처리하려면?
문제 4
Step Functions 워크플로에서 Lambda 함수가 PaymentDeclinedException을 throw했다. 이미 처리된 재고 예약을 되돌려야 한다. 어떻게 설정하는가?
문제 5
협업 문서 편집 앱에서 여러 사용자가 같은 문서를 동시에 편집할 때, 한 사용자의 변경 사항이 다른 모든 사용자에게 실시간으로 반영되어야 한다. 가장 적합한 AWS 서비스 조합은?
문제 6
Step Functions와 Lambda를 조합해서 이미지 처리 파이프라인을 만든다. 각 단계(리사이징, 워터마크 추가, S3 업로드)는 Lambda로 구현되어 있다. 이 파이프라인에 Standard와 Express 중 어느 것이 더 적합하며, 이유는?