ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 데이터 배치 AWS 서버리스 전환하며 배운 것들: EC2 크론탭에서 Lambda + Step Functions로
    엔지니어가 되자/Product Building 2026. 3. 25. 12:21

    EC2 crontab + 쉘스크립트 6개로 운영하던 데이터 수집 배치를 AWS Step Functions + Lambda 기반 서버리스 파이프라인으로 전환했다.

    작업하면서 생소한 개념들을 꽤 많이 만났는데, 그때그때 물어보고 받은 답변들을 모아두었다가 이번에 한 번에 정리해본다.


    왜 전환했나

    기존 구조의 문제는 크게 세 가지였다.

    첫째, EC2 고정 IP 문제. 같은 IP에서 반복 요청하면 외부 데이터 소스에서 IP 기반으로 요청을 제한할 수 있다. Lambda는 실행마다 IP가 바뀌므로 이 문제가 해결된다.

    둘째, 쉘스크립트 기반 배치의 에러 추적과 재시도 한계. 어디서 실패했는지 추적이 어렵고, 부분 재시도도 불가능하다.

    셋째, 서버 관리 부담. 메모리 부족, 디스크 관리, OS 패치 등 신경 쓸 게 너무 많다.

    여하튼 이 문제들을 해결하려면 서버리스로 가야 했고, 그래서 Lambda + Step Functions + EventBridge 조합으로 전환하기로 했다.


    Lambda -- "필요할 때만 켜지는 코드 실행기"

    EC2가 24시간 켜져 있는 PC방 컴퓨터라면, Lambda는 편의점 전자레인지라고 보면 된다. 음식 넣을 때만 돌아가고, 안 쓰면 돈이 안 나간다. 그리고 매번 다른 전자레인지를 사용하기 때문에 IP가 바뀐다.

    # 기존 구조
    EC2 ──(고정IP)──▶ 외부 데이터 소스
                       IP 기반 요청 제한에 걸림
    
    # 전환 후 구조
    Lambda A ──(IP 1)──▶ 외부 데이터 소스 OK
    Lambda B ──(IP 2)──▶ 외부 데이터 소스 OK
    Lambda C ──(IP 3)──▶ 외부 데이터 소스 OK
    # 각각 다른 IP라서 제한에 안 걸림

    이게 전환의 가장 큰 이유였다.

    Lambda 핵심 제약

    항목 제한
    최대 실행시간 15분
    메모리 128MB ~ 10GB 선택
    ZIP 크기 50MB (직접) / 250MB (S3 경유)
    비용 월 100만건 무료, 이후 건당 $0.0000002

    우리는 월 1.2만건 정도니까 완전 무료 범위다.


    EventBridge -- "AWS 크론탭"

    한마디로 EC2 없이 돌아가는 크론탭이라고 보면 된다.

    비교 EC2 (기존) EventBridge (전환 후)
    스케줄 등록 crontab -e AWS 콘솔에서 등록
    서버 의존 EC2 꺼지면 배치 안 돌아감 EC2 없어도 돌아감
    호출 방식 셸 스크립트 실행 Lambda 직접 호출
    관리 서버 관리 필요 서버리스, 관리할 것 없음

    동작 방식은 단순하다.

    EventBridge 스케줄 (cron 표현식)
      ▶ Lambda 함수 직접 호출 (payload 전달)
      ▶ 끝

    기존에는 EC2 crontab이 start_api.sh ▶ Python ▶ boto3 ▶ Lambda 호출 이런 식으로 중간 단계가 있었는데, EventBridge는 Lambda를 직접 호출한다. 중간 단계가 싹 사라진다.

    비용은 사실상 무료다. 프리티어가 1,400만 건/월인데, 우리는 월 24건 정도밖에 안 쓴다.


    Step Functions -- "순서표"

     

    여러 Lambda를 순서대로 연결하는 워크플로우 엔진이다. 중간에 실패하면 자동 재시도하거나 에러 처리로 분기할 수 있다. AWS 콘솔에서 플로우차트처럼 시각적으로 그릴 수도 있다.

    1단계: 데이터 수집 (Lambda A)
       ▼ 성공하면
    2단계: STG▶RAW 정제 (Lambda B)
       ▼ 성공하면
    3단계: Slack 알림 (Lambda C)
       ▼ 실패하면
    에러처리: Slack 에러 알림

    그래서 전체 구조를 정리하면 이렇게 된다.

    # 기존
    EC2 크론탭 ▶ Python 스크립트 직접 실행
    
    # 전환 후
    EventBridge(알람시계) ▶ Step Functions(순서표) ▶ Lambda(실제 일꾼)

    이렇게 되면 EC2를 제거할 수 있다.

    Step Functions vs Airflow

    처음에 Step Functions을 보고 "이거 Airflow랑 비슷한 건가?" 싶었는데, 비슷한 개념이긴 하지만 포지션이 다르다.

    둘 다 워크플로우 오케스트레이터로, 여러 작업을 정의된 순서/조건에 따라 실행하고 성공/실패 분기, 재시도, 병렬 처리를 관리한다는 점은 같다.

    비교 Step Functions Airflow
    실행 환경 서버리스 (AWS 관리형) 서버 필요 (스케줄러, 워커, DB)
    워크플로우 정의 JSON (ASL) Python (DAG)
    과금 실행 횟수당 (상태 전환 건수) 서버 유지 비용
    규모 소~중규모 이벤트 파이프라인 대규모 데이터 파이프라인
    생태계 AWS 서비스 통합 특화 다양한 오퍼레이터 (DB, Spark, K8s 등)

    Step Functions을 선택한 이유는 단순하다. 이미 Lambda로 각 단계가 만들어져 있었고, EC2 t3.micro에서 Airflow를 돌리기엔 리소스가 턱없이 부족했다. 주 2회 실행이라 서버리스 과금이 훨씬 저렴하기도 하고.

    쉽게 말하면, Airflow는 "데이터 엔지니어링 팀용 풀스택 오케스트레이터"이고, Step Functions는 "AWS Lambda 체이닝용 경량 오케스트레이터"다.


    Inline Map vs Distributed Map

    이 부분이 좀 재밌었다.

    Step Functions 안에서 "같은 작업을 여러 대상에 반복"할 때 쓰는 방식이 두 가지가 있다.

    Inline Map

    부모 Step Functions 실행 1개 안에서 직접 반복 처리한다. 소규모(수십 개)에 적합하다.

    [부모 Step Functions 실행]
      ├▶ Lambda 호출: 카테고리 A 목록 수집
      ├▶ Lambda 호출: 카테고리 B 목록 수집
      ├▶ Lambda 호출: 카테고리 C 목록 수집  (최대 4개 동시)
      └▶ ...28개 완료될 때까지
      ▼
      결과를 메모리(JSON)로 받음

    Lambda를 28번 따로 호출하긴 하지만, 그 호출을 부모 실행이 직접 관리한다. 결과는 메모리(JSON)로 부모에게 리턴된다. 최대 동시 실행은 40개.

    Distributed Map

    자식 실행을 따로 만들어서 독립적으로 돌린다. 대규모(수백~수만 개)에 적합하다.

    부모 실행 (1개)
      ├▶ 자식 실행 #1: 아이템 A 상세 처리 (Lambda)
      ├▶ 자식 실행 #2: 아이템 B 상세 처리 (Lambda)
      ├▶ 자식 실행 #3: 아이템 C 상세 처리 (Lambda)
      │   ... (최대 5개 동시)
      └▶ 자식 실행 #1500: 아이템 ZZ 상세 처리

    핵심 차이는 "자식 실행이 독립적인 Step Functions 실행으로 생성된다"는 점이다. 즉, 부모 프로세스 1개 안에서 처리되는 Inline Map과 달리, Distributed Map은 프로세스 여러 개가 나뉘어서 분산 처리된다.

    그래서 각각이 별도 프로세스이므로, 각각 state transition이 과금된다. 이게 프리티어를 빠르게 잡아먹는 원인이었다.

    비교 Inline Map Distributed Map
    우리 파이프라인 카테고리 28개 목록 수집 아이템 1,500건 상세 처리
    실행 위치 부모 안에서 직접 자식 실행을 따로 생성
    동시 실행 최대 40개 최대 10,000개
    데이터 입력 JSON 배열 S3 파일에서 직접 읽기
    결과 저장 메모리 (부모에 전달) S3에 자동 저장

    여기서 재밌는 포인트가 있는데, Distributed Map은 S3에서 직접 데이터를 읽고 결과도 S3에 저장한다. 대량 데이터를 메모리에 올리지 않고 처리하는 구조라서, S3를 일종의 데이터레이크처럼 활용하게 된다.

    우리 파이프라인에서의 데이터 흐름

    1. Inline Map (카테고리 28개)
       ▶ Lambda 28번 호출
       ▶ 각 카테고리 목록을 S3에 저장
       ▶ 결과 요약(카테고리코드, 건수, S3키)을 메모리로 부모에게 리턴
    
    2. MergeItems Lambda
       ▶ S3에서 28개 파일 읽어서 병합
       ▶ merged_items.json을 S3에 저장
    
    3. Distributed Map (아이템 1,500건)
       ▶ S3에서 merged_items.json 직접 읽기 (메모리 안 거침)
       ▶ Lambda 1,500번 호출
       ▶ 결과를 S3에 자동 저장

    테스트 2회만으로 Distributed Map 자식 실행이 약 3,400 transition을 발생시켜서, 프리티어 4,000의 85%를 잡아먹었다. 돈을 내도 큰 금액은 아닌데, 이런 과금 구조를 미리 알아두는 게 중요하다.


    IAM 개념 정리

    IAM도 처음에는 좀 헷갈렸는데 정리하면 이렇다.

    개념 설명
    User 사람/프로그램이 AWS에 접근하는 계정. Access Key 발급 가능
    Role AWS 서비스가 다른 서비스를 호출할 때 쓰는 "임시 신분증"
    Policy "누가 뭘 할 수 있는지" 적힌 권한 문서 (JSON)

    처음에는 "Role을 미리 만들어두고 유저에게 그 Role을 부여해서 작업하게 해주는 건가?" 하고 이해했는데, 약간 다르다.

    User는 "누가 호출하느냐"이고, Role은 "AWS 서비스가 다른 서비스를 호출할 때 쓰는 권한"이다. EC2에 Role을 부여할 수도 있지만, 우리는 EC2에 User 키를 .env에 넣어서 쓰고 있었다.

    # User 예시
    s3-uploader (User)
    ▶ EC2의 .env에 이 유저의 Access Key가 있음
    ▶ boto3가 이 키로 AWS API 호출
    
    # Role 예시
    detail-worker-role
    ▶ Lambda 함수가 실행될 때 이 Role을 자동으로 입고 S3, CloudWatch 등 접근
    
    stepfunctions-role
    ▶ Step Functions가 실행될 때 이 Role을 입고 Lambda 호출

    Attach vs Inline Policy

    Attach Policy는 AWS가 미리 만들어둔 정책을 가져다 붙이는 것이다. AmazonS3FullAccess 같은 것들. 여러 Role/User에 공유해서 붙일 수 있다.

    Inline Policy는 해당 Role 전용으로 직접 JSON을 작성해서 붙이는 것이다. 재사용이 안 되는, 말 그대로 1회용이다. 정확히 필요한 권한만 주려면 Inline으로 작성하는 게 맞다.


    전환 후 아키텍처

    전환한 전체 구조는 이렇다.

    EventBridge Scheduler (주말 14:00 KST)
      ▶ Step Functions (data-collection-pipeline)
          ▶ list-collector Lambda x28 카테고리 (병렬 4개씩)
          ▶ merge-items Lambda (병합 + 중복 제거)
          ▶ detail-worker Lambda x수백건 (Distributed Map, 병렬 5개씩)
          ▶ snapshot Lambda (stg ▶ raw 정제)
          ▶ summary-notifier Lambda (Slack 알림)

    전체 배치 스케줄

    스케줄 시간 (KST) 대상 방식
    메인 데이터 수집 (토) 토 14:00 Step Functions EventBridge ▶ SFN
    메인 데이터 수집 (일) 일 14:00 Step Functions EventBridge ▶ SFN
    스냅샷 정제 목~일 04:01 Lambda EventBridge ▶ Lambda
    보조 데이터 수집 A 토 04:30 Lambda EventBridge ▶ Lambda
    보조 데이터 수집 B 토 05:00 Lambda EventBridge ▶ Lambda

    Lambda 함수 구성

    Lambda 메모리 타임아웃 역할
    list-collector 256MB 300s 카테고리별 목록 수집
    merge-items 512MB 120s 수집 결과 병합 + 중복 제거
    detail-worker 512MB 180s 건별 상세 데이터 수집
    snapshot 256MB 600s stg ▶ raw 정제
    summary-notifier 128MB 30s Slack 완료/실패 알림
    sub-collector-a 256MB 600s 보조 데이터 수집 + 정제
    sub-collector-b 256MB 900s 보조 데이터 수집 + 정제

    IAM 구성

    Role/User 용도
    detail-worker-role Lambda 7개 공유 실행 Role
    stepfunctions-role Step Functions 실행 Role
    s3-uploader (User) EC2 배포용 (Lambda 생성/업데이트, SFN 관리)
    EventBridge Scheduler Role EventBridge ▶ Lambda/SFN 트리거 Role

    비용 분석

    이건 궁금해서 꽤 꼼꼼하게 따져봤다.

    Step Functions

    프리티어는 월 4,000 transitions(영구 무료)이고, 초과분은 1,000 transitions당 $0.025다.

    비용의 핵심은 역시 Distributed Map이다. 아이템 1건 = 자식 실행 1개 = 최소 1 transition이므로, 아이템 수에 비례해서 transition이 증가한다.

    시나리오 토 아이템 일 아이템 월간 transitions 초과 비용
    보수적 2,000 2,000 16,312 $0.31
    중간 3,000 3,000 24,312 $0.51
    최대 5,000 5,000 40,312 $0.91

    그 정도면 뭐 돈 내도 상관없는 수준이다.

    Lambda

    프리티어는 월 1M 요청 + 400,000 GB-seconds(영구 무료)다. 요청 수는 월 1~2만건으로 여유롭지만, GB-seconds가 변수다.

    사실상 Lambda가 비용의 진짜 이슈다. detail-worker의 평균 실행 시간이 핵심인데, 30초면 프리티어 내, 60초 넘어가면 초과분이 발생해서 약 $3.47/월이 추가된다. 첫 자동 실행 후 CloudWatch에서 실제 duration을 확인해봐야 정확한 숫자가 나온다.

    EventBridge

    프리티어 1,400만 건/월. 우리는 월 24건. 사실상 무료다.

    총 월간 비용

    서비스 최소 최대
    Step Functions $0.31 $0.91
    Lambda $0 $3.47
    EventBridge $0 $0
    S3 $0.15 $0.30
    CloudWatch $0 $1.00
    합계 ~$0.50 ~$5.68

    EC2 t3.micro가 월 $8.5인 것과 비교하면, 비용 절감은 물론이고 IP 제한 해결, 에러 추적 개선, 서버 관리 부담 제거까지 덤으로 얻은 셈이다.


    테스트 결과

    환경 입력 결과
    DEV {"env":"dev","group":"test"} 카테고리 3개 1,488건 수집, 3,749건 정제, 12분 28초
    PRD {"env":"prd","group":"test"} 카테고리 3개 1,488건 수집 성공, Slack 알림 정상

    EC2 후속 처리

    crontab은 전체 비활성화했다. 각 항목에 [DISABLED 2026-03-24] 태그를 달아뒀고, 주석 해제하면 즉시 원복 가능하다. 2~3주 안정화 모니터링 후 EC2 stop 예정.

    모니터링 체계

    정상 운영 시에는 Slack 알림으로 수집 완료/실패를 확인한다. 문제 발생 시에는 Step Functions 콘솔에서 실패 단계를 클릭하고 CloudWatch Logs를 확인하면 된다. 스케줄 확인은 EventBridge 콘솔의 Schedules에서.

    남은 후속 작업

    이번 주 토요일 첫 자동 실행 확인, 2~3주 안정화 모니터링, Slack 알림에 상세 수집 건수 표시 개선, stg 90일 정리 Lambda화, 그리고 안정 확인 후 EC2 stop이 남아있다.


    S3를 데이터 아카이브로

    현재 Supabase 용량이 500MB로 제한되어 있어서 stg 데이터를 90일 단위로 삭제하고 있다. 근데 과거 데이터는 분석이나 서비스에 다 필요하다.

    Distributed Map 구조 덕분에 수집 데이터가 이미 S3에 저장되고 있으니, S3를 장기 데이터 아카이브로 활용하는 게 자연스러운 다음 단계다. S3 저장 비용은 GB당 $0.025/월 수준이라, 수 GB 규모면 월 $0.10 정도에 불과하다. 나중에 꺼내서 쓸 수도 있으니 좋은 구조가 된 것 같다.


    정리

    서비스 무료 범위 난이도
    EventBridge 완전 무료 (1,400만 건/월) 쉬움 -- 콘솔에서 크론식 입력하면 끝
    Step Functions 월 4,000 transitions 무료 보통 -- JSON으로 워크플로우 정의, 콘솔 드래그앤드롭 가능
    Lambda 월 100만건 + 400,000 GB-seconds 보통 -- 기존 Python 코드를 핸들러 형태로 감싸면 됨

    EC2 crontab + 쉘스크립트에서 EventBridge + Step Functions + Lambda로 전환하면, 서버 관리 없이 자동으로 돌아가는 파이프라인을 만들 수 있다. 비용도 월 $1~6 수준으로 EC2보다 저렴하다.

    이번 작업에서 가장 큰 수확은 비용 절감보다는, 이런 서버리스 서비스들의 개념과 과금 구조를 직접 겪으면서 이해하게 된 것이라고 생각한다. 특히 Inline Map과 Distributed Map의 차이, transition 기반 과금 구조 같은 건 실제로 프리티어가 빠르게 줄어드는 걸 보면서 체감했다.

    반응형
data.pelican.joe@gmail.com