AWS Data PlatformSession 3 of 10

데이터 저장소 - S3 Data Lake 설계

파일 포맷, 파티셔닝, 스토리지 클래스, Glue Data Catalog

약 40분
Intermediate

목차

  1. S3 Data Lake 아키텍처
  2. 파일 포맷 비교
  3. 파티셔닝 전략
  4. S3 스토리지 클래스 및 수명 주기
  5. AWS Glue Data Catalog
  6. 정리 및 다음 세션 예고

1. S3 Data Lake 아키텍처

"Data Lake는 단순한 저장소가 아니라, 데이터의 가치를 극대화하는 아키텍처 패턴이다."

— AWS Data Lake Best Practices

1.1 Data Lake 계층 구조

효과적인 Data Lake는 데이터의 처리 단계에 따라 계층을 분리합니다. 각 계층은 명확한 목적과 데이터 품질 수준을 가집니다.

3-Layer Data Lake Architecture
┌─────────────────────────────────────────────────────────────────────┐
│                        S3 Data Lake                                  │
│                                                                      │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │                    Raw / Landing Zone                            ││
│  │  s3://data-lake-raw/                                            ││
│  │  ├── database/orders/2024/01/15/                                ││
│  │  ├── logs/clickstream/2024/01/15/                               ││
│  │  └── saas/salesforce/opportunities/                             ││
│  │                                                                  ││
│  │  • 원본 데이터 그대로 저장 (Immutable)                           ││
│  │  • 스키마 검증 없음                                              ││
│  │  • 데이터 리니지 추적용                                          ││
│  └─────────────────────────────────────────────────────────────────┘│
│                              │                                       │
│                              ▼  ETL/ELT                              │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │                    Curated / Processed Zone                      ││
│  │  s3://data-lake-curated/                                        ││
│  │  ├── orders/                    (Parquet, 파티셔닝)             ││
│  │  ├── customers/                 (중복 제거, 정규화)             ││
│  │  └── products/                  (스키마 적용)                   ││
│  │                                                                  ││
│  │  • 정제된 데이터 (Cleansed)                                     ││
│  │  • 표준 스키마 적용                                              ││
│  │  • Parquet/ORC 포맷                                             ││
│  └─────────────────────────────────────────────────────────────────┘│
│                              │                                       │
│                              ▼  Aggregation                          │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │                    Analytics / Consumption Zone                  ││
│  │  s3://data-lake-analytics/                                      ││
│  │  ├── daily_sales_summary/       (집계 테이블)                   ││
│  │  ├── customer_360/              (통합 뷰)                       ││
│  │  └── ml_features/               (피처 스토어)                   ││
│  │                                                                  ││
│  │  • 비즈니스 로직 적용                                            ││
│  │  • 분석 최적화 (Pre-aggregated)                                 ││
│  │  • BI 도구 직접 연결                                             ││
│  └─────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘

1.2 S3 버킷 설계 전략

✅ 권장 패턴

# 계층별 버킷 분리
s3://company-data-lake-raw/
s3://company-data-lake-curated/
s3://company-data-lake-analytics/

# 또는 단일 버킷 + 접두사
s3://company-data-lake/
├── raw/
├── curated/
└── analytics/
  • • 계층별 IAM 정책 분리 용이
  • • 수명 주기 정책 개별 적용
  • • 비용 추적 명확

❌ 피해야 할 패턴

# 날짜를 최상위로
s3://data-lake/2024/01/15/orders/
s3://data-lake/2024/01/15/customers/

# 환경 혼재
s3://data-lake/prod/orders/
s3://data-lake/dev/orders/
  • • 테이블 단위 관리 어려움
  • • 파티션 프루닝 비효율
  • • 환경 분리 불명확

1.3 네이밍 컨벤션

# 권장 S3 경로 구조
s3://{bucket}/{layer}/{domain}/{table}/
    {partition_key}={partition_value}/
    {filename}.{format}

# 예시
s3://acme-data-lake-curated/
    sales/                          # 도메인
        orders/                     # 테이블
            year=2024/              # 파티션
                month=01/
                    day=15/
                        part-00000.parquet

# 파일명 규칙
{table}_{timestamp}_{uuid}.{format}
orders_20240115T103000_abc123.parquet

버킷명

{company}-{purpose}-{env}
acme-data-lake-prod

경로

소문자, 언더스코어
snake_case 권장

파티션

Hive 스타일
key=value 형식

1.4 데이터 계층별 특성

특성RawCuratedAnalytics
데이터 형태원본 그대로정제/표준화집계/변환
포맷JSON, CSV, XMLParquet, ORCParquet
스키마Schema-on-ReadSchema-on-WriteSchema-on-Write
보존 기간장기 (규정 준수)중기단기~중기
스토리지 클래스IA/GlacierStandard/IAStandard
접근 빈도낮음중간높음

2. 파일 포맷 비교

데이터 레이크에서 파일 포맷 선택은 쿼리 성능, 저장 비용, 스키마 진화에 직접적인 영향을 미칩니다. 각 포맷의 특성을 이해하고 적절히 선택해야 합니다.

2.1 주요 파일 포맷

파일 포맷 비교
특성ParquetORCAvroJSON/CSV
저장 방식컬럼형컬럼형행 기반행 기반
압축률⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
분석 쿼리⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
스키마 진화⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
쓰기 속도⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
AWS 지원 최적
주요 사용처분석, DWHive, Presto스트리밍, CDCRaw 데이터

2.2 Parquet 심층 분석

Parquet은 AWS 데이터 레이크의 사실상 표준 포맷입니다. 컬럼형 저장으로 분석 쿼리에 최적화되어 있습니다.

┌─────────────────────────────────────────────────────────────────────┐
│                    Parquet 파일 구조                                 │
│                                                                      │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │                         File                                     ││
│  │  ┌─────────────────────────────────────────────────────────────┐││
│  │  │                    Row Group 1                               │││
│  │  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐           │││
│  │  │  │Column A │ │Column B │ │Column C │ │Column D │           │││
│  │  │  │ Chunk   │ │ Chunk   │ │ Chunk   │ │ Chunk   │           │││
│  │  │  │         │ │         │ │         │ │         │           │││
│  │  │  │ [Stats] │ │ [Stats] │ │ [Stats] │ │ [Stats] │           │││
│  │  │  └─────────┘ └─────────┘ └─────────┘ └─────────┘           │││
│  │  └─────────────────────────────────────────────────────────────┘││
│  │  ┌─────────────────────────────────────────────────────────────┐││
│  │  │                    Row Group 2                               │││
│  │  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐           │││
│  │  │  │Column A │ │Column B │ │Column C │ │Column D │           │││
│  │  │  │ Chunk   │ │ Chunk   │ │ Chunk   │ │ Chunk   │           │││
│  │  │  └─────────┘ └─────────┘ └─────────┘ └─────────┘           │││
│  │  └─────────────────────────────────────────────────────────────┘││
│  │  ┌─────────────────────────────────────────────────────────────┐││
│  │  │                    Footer (Metadata)                         │││
│  │  │  • Schema                                                    │││
│  │  │  • Row Group locations                                       │││
│  │  │  • Column statistics (min/max/null count)                    │││
│  │  └─────────────────────────────────────────────────────────────┘││
│  └─────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘

컬럼형 저장의 장점:
• SELECT col_a, col_b만 읽으면 해당 컬럼 청크만 스캔
• 같은 타입 데이터가 연속 → 압축 효율 극대화
• Column Statistics로 불필요한 Row Group 스킵

Parquet 최적화 설정

  • Row Group Size: 128MB~1GB
  • Page Size: 1MB (기본값)
  • 압축: Snappy (속도), ZSTD (압축률)
  • Dictionary: 카디널리티 낮은 컬럼

Athena 최적 파일 크기

  • 권장: 128MB ~ 512MB
  • • 너무 작으면: S3 요청 오버헤드
  • • 너무 크면: 병렬 처리 비효율
  • • Glue로 파일 병합 가능

2.3 포맷 변환 예시

// PySpark - JSON to Parquet 변환

from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("JSON to Parquet") \
    .config("spark.sql.parquet.compression.codec", "snappy") \
    .getOrCreate()

# JSON 읽기
df = spark.read.json("s3://data-lake-raw/orders/2024/01/15/")

# 스키마 적용 및 변환
df_cleaned = df.select(
    df.order_id.cast("long"),
    df.customer_id.cast("long"),
    df.amount.cast("decimal(10,2)"),
    df.order_date.cast("date"),
    df.status.cast("string")
)

# Parquet로 저장 (파티셔닝 포함)
df_cleaned.write \
    .mode("overwrite") \
    .partitionBy("order_date") \
    .option("compression", "snappy") \
    .parquet("s3://data-lake-curated/orders/")

// AWS Glue - 포맷 변환 Job

import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from awsglue.context import GlueContext
from pyspark.context import SparkContext

sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session

# Glue Catalog에서 읽기
datasource = glueContext.create_dynamic_frame.from_catalog(
    database="raw_db",
    table_name="orders_json"
)

# 스키마 변환
mapped = ApplyMapping.apply(
    frame=datasource,
    mappings=[
        ("order_id", "string", "order_id", "long"),
        ("amount", "string", "amount", "decimal(10,2)"),
        ("order_date", "string", "order_date", "date")
    ]
)

# Parquet로 저장
glueContext.write_dynamic_frame.from_options(
    frame=mapped,
    connection_type="s3",
    connection_options={
        "path": "s3://data-lake-curated/orders/",
        "partitionKeys": ["order_date"]
    },
    format="parquet",
    format_options={"compression": "snappy"}
)

2.4 포맷 선택 가이드

Parquet 선택

분석 쿼리, Athena/Redshift Spectrum, 대부분의 Curated/Analytics 계층

ORC 선택

Hive 기반 워크로드, EMR Hive, 기존 Hadoop 에코시스템

Avro 선택

스트리밍 데이터, Kafka, 스키마 진화가 빈번한 경우

JSON/CSV 유지

Raw 계층 원본 보존, 외부 시스템 연동, 디버깅용

3. 파티셔닝 전략

파티셔닝은 데이터를 논리적으로 분할하여 쿼리 성능을 최적화하는 핵심 전략입니다. 적절한 파티셔닝은 스캔 데이터량을 줄여 비용과 시간을 절약합니다.

3.1 파티셔닝 기본 개념

파티션 프루닝 (Partition Pruning)
┌─────────────────────────────────────────────────────────────────────┐
│                    파티션 프루닝 효과                                 │
│                                                                      │
│  쿼리: SELECT * FROM orders WHERE order_date = '2024-01-15'         │
│                                                                      │
│  파티셔닝 없음 (Full Scan)                                          │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │ ████████████████████████████████████████████████████████████████ ││
│  │ 전체 데이터 스캔: 1TB → 비용: $5.00, 시간: 60초                  ││
│  └─────────────────────────────────────────────────────────────────┘│
│                                                                      │
│  날짜 파티셔닝 (Partition Pruning)                                  │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │ orders/                                                          ││
│  │ ├── year=2024/                                                   ││
│  │ │   ├── month=01/                                                ││
│  │ │   │   ├── day=14/ ░░░░░░░░ (스킵)                             ││
│  │ │   │   ├── day=15/ ████████ (스캔) ← 이것만!                   ││
│  │ │   │   └── day=16/ ░░░░░░░░ (스킵)                             ││
│  │ 스캔 데이터: 3GB → 비용: $0.015, 시간: 2초                       ││
│  └─────────────────────────────────────────────────────────────────┘│
│                                                                      │
│  💰 비용 절감: 99.7% | ⚡ 성능 향상: 30x                             │
└─────────────────────────────────────────────────────────────────────┘

3.2 파티션 키 선택

좋은 파티션 키

  • 날짜/시간: 대부분의 쿼리가 시간 범위 필터
  • 지역/국가: 지역별 분석이 빈번한 경우
  • 카테고리: 카디널리티가 적절한 경우 (10~1000)
  • 테넌트 ID: 멀티테넌트 환경

피해야 할 파티션 키

  • 고유 ID: 파티션 수 폭발 (수백만 개)
  • 타임스탬프: 초/밀리초 단위는 과도함
  • 쿼리에 안 쓰는 컬럼: 프루닝 효과 없음
  • 불균형 분포: 특정 파티션에 데이터 집중

3.3 파티셔닝 패턴

패턴 1: 시간 기반 파티셔닝

# 일별 파티셔닝 (가장 일반적)
s3://bucket/orders/year=2024/month=01/day=15/

# 시간별 파티셔닝 (고빈도 데이터)
s3://bucket/logs/year=2024/month=01/day=15/hour=10/

# 월별 파티셔닝 (저빈도 데이터)
s3://bucket/reports/year=2024/month=01/

대부분의 분석 쿼리가 시간 범위를 포함하므로 가장 효과적

패턴 2: 복합 파티셔닝

# 지역 + 날짜
s3://bucket/sales/region=us-east/year=2024/month=01/day=15/

# 테넌트 + 날짜
s3://bucket/events/tenant_id=acme/year=2024/month=01/day=15/

쿼리 패턴에 따라 2~3개 키 조합. 순서는 카디널리티 낮은 것 먼저

패턴 3: 버킷팅 (Bucketing)

# 고카디널리티 컬럼을 해시로 분산
# customer_id를 100개 버킷으로 분산
s3://bucket/orders/bucket=00/
s3://bucket/orders/bucket=01/
...
s3://bucket/orders/bucket=99/

# Spark에서 버킷팅
df.write.bucketBy(100, "customer_id").saveAsTable("orders")

JOIN 성능 최적화. 같은 버킷 키로 조인하면 셔플 최소화

3.4 파티션 수 관리

⚠️ 파티션 수 주의사항

  • Athena: 테이블당 최대 100만 파티션
  • Glue Catalog: 파티션 메타데이터 조회 오버헤드
  • S3: 파티션당 최소 128MB 파일 권장
파티션 전략연간 파티션 수권장 사용 사례
연도별1아카이브, 연간 보고서
월별12월간 분석, 저빈도 데이터
일별365일반적인 트랜잭션 데이터
시간별8,760로그, 이벤트 스트림
지역(10) + 일별3,650글로벌 서비스

3.5 파티션 추가 자동화

// Glue Crawler vs MSCK REPAIR vs Partition Projection

-- 방법 1: MSCK REPAIR (수동)
MSCK REPAIR TABLE orders;
-- 새 파티션 자동 감지, 대량 파티션 시 느림

-- 방법 2: ALTER TABLE (명시적)
ALTER TABLE orders ADD PARTITION (year='2024', month='01', day='15')
LOCATION 's3://bucket/orders/year=2024/month=01/day=15/';

-- 방법 3: Partition Projection (권장) ⭐
CREATE EXTERNAL TABLE orders (
  order_id BIGINT,
  amount DECIMAL(10,2)
)
PARTITIONED BY (
  year STRING,
  month STRING,
  day STRING
)
STORED AS PARQUET
LOCATION 's3://bucket/orders/'
TBLPROPERTIES (
  'projection.enabled' = 'true',
  'projection.year.type' = 'integer',
  'projection.year.range' = '2020,2030',
  'projection.month.type' = 'integer',
  'projection.month.range' = '1,12',
  'projection.month.digits' = '2',
  'projection.day.type' = 'integer',
  'projection.day.range' = '1,31',
  'projection.day.digits' = '2',
  'storage.location.template' = 
    's3://bucket/orders/year=${year}/month=${month}/day=${day}/'
);
-- 메타스토어 조회 없이 파티션 경로 계산 → 빠름!

4. S3 스토리지 클래스 및 수명 주기

S3는 다양한 스토리지 클래스를 제공하여 접근 빈도와 비용을 최적화할 수 있습니다. 수명 주기 정책으로 자동 전환을 설정하면 비용을 크게 절감할 수 있습니다.

4.1 S3 스토리지 클래스

스토리지 클래스 비교
클래스저장 비용검색 비용최소 보관사용 사례
Standard$0.023/GB없음없음자주 접근하는 데이터
Intelligent-Tiering자동 최적화없음없음접근 패턴 불확실
Standard-IA$0.0125/GB$0.01/GB30일월 1회 접근
One Zone-IA$0.01/GB$0.01/GB30일재생성 가능 데이터
Glacier IR$0.004/GB$0.03/GB90일분기별 접근
Glacier Flexible$0.0036/GB$0.03/GB90일연 1-2회 접근
Glacier Deep Archive$0.00099/GB$0.02/GB180일규정 준수 아카이브

* 가격은 us-east-1 기준, 실제 가격은 리전별로 다름

4.2 Data Lake 계층별 권장 클래스

┌─────────────────────────────────────────────────────────────────────┐
│                    Data Lake 스토리지 전략                           │
│                                                                      │
│  Analytics Zone (자주 접근)                                          │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │  S3 Standard                                                     ││
│  │  • 일일 대시보드 데이터                                          ││
│  │  • 실시간 분석 결과                                              ││
│  │  • ML 피처 스토어                                                ││
│  └─────────────────────────────────────────────────────────────────┘│
│                                                                      │
│  Curated Zone (주기적 접근)                                          │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │  S3 Standard-IA 또는 Intelligent-Tiering                        ││
│  │  • 최근 3개월 데이터: Standard                                   ││
│  │  • 3개월~1년 데이터: Standard-IA                                 ││
│  │  • 1년 이상: Glacier IR                                          ││
│  └─────────────────────────────────────────────────────────────────┘│
│                                                                      │
│  Raw Zone (거의 접근 안 함)                                          │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │  S3 Standard-IA → Glacier                                       ││
│  │  • 최근 30일: Standard (재처리 대비)                             ││
│  │  • 30일~1년: Standard-IA                                         ││
│  │  • 1년 이상: Glacier Flexible                                    ││
│  │  • 7년 이상: Glacier Deep Archive (규정 준수)                    ││
│  └─────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘

4.3 수명 주기 정책 설정

// Terraform - S3 Lifecycle Policy

resource "aws_s3_bucket_lifecycle_configuration" "data_lake" {
  bucket = aws_s3_bucket.data_lake.id

  # Raw Zone 정책
  rule {
    id     = "raw-zone-lifecycle"
    status = "Enabled"

    filter {
      prefix = "raw/"
    }

    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }

    transition {
      days          = 365
      storage_class = "GLACIER"
    }

    transition {
      days          = 2555  # 7년
      storage_class = "DEEP_ARCHIVE"
    }

    # 10년 후 삭제 (규정에 따라 조정)
    expiration {
      days = 3650
    }
  }

  # Curated Zone 정책
  rule {
    id     = "curated-zone-lifecycle"
    status = "Enabled"

    filter {
      prefix = "curated/"
    }

    transition {
      days          = 90
      storage_class = "STANDARD_IA"
    }

    transition {
      days          = 365
      storage_class = "GLACIER_IR"
    }
  }

  # Analytics Zone - Intelligent Tiering
  rule {
    id     = "analytics-zone-lifecycle"
    status = "Enabled"

    filter {
      prefix = "analytics/"
    }

    transition {
      days          = 0
      storage_class = "INTELLIGENT_TIERING"
    }
  }

  # 불완전 멀티파트 업로드 정리
  rule {
    id     = "abort-incomplete-multipart"
    status = "Enabled"

    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }
}

4.4 비용 최적화 계산

💰 비용 절감 예시: 100TB Raw 데이터

수명 주기 없음 (모두 Standard)

  • 100TB × $0.023 = $2,300/월
  • 연간: $27,600

수명 주기 적용

  • 10TB Standard: $230
  • 30TB Standard-IA: $375
  • 60TB Glacier: $216
  • 합계: $821/월
  • 절감: 64%

⚠️ 주의사항

  • 최소 보관 기간 전 삭제 시 조기 삭제 요금 발생
  • Glacier 검색 시 복원 시간 고려 (분~시간)
  • 작은 파일 많으면 요청 비용이 저장 비용보다 클 수 있음
  • Athena는 Glacier 직접 쿼리 불가 (복원 필요)

5. AWS Glue Data Catalog

AWS Glue Data Catalog은 데이터 레이크의 중앙 메타데이터 저장소입니다. Athena, Redshift Spectrum, EMR 등 다양한 서비스가 공유하는 스키마 정보를 관리합니다.

5.1 Data Catalog 구조

Glue Data Catalog 계층 구조
┌─────────────────────────────────────────────────────────────────────┐
│                    AWS Glue Data Catalog                             │
│                                                                      │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │                    Catalog (Account Level)                       ││
│  │                                                                  ││
│  │  ┌─────────────────────────────────────────────────────────────┐││
│  │  │                    Database: raw_db                          │││
│  │  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │││
│  │  │  │Table: orders│ │Table: users │ │Table: logs  │           │││
│  │  │  │             │ │             │ │             │           │││
│  │  │  │• Columns    │ │• Columns    │ │• Columns    │           │││
│  │  │  │• Partitions │ │• Partitions │ │• Partitions │           │││
│  │  │  │• Location   │ │• Location   │ │• Location   │           │││
│  │  │  │• Format     │ │• Format     │ │• Format     │           │││
│  │  │  └─────────────┘ └─────────────┘ └─────────────┘           │││
│  │  └─────────────────────────────────────────────────────────────┘││
│  │                                                                  ││
│  │  ┌─────────────────────────────────────────────────────────────┐││
│  │  │                    Database: curated_db                      │││
│  │  │  ┌─────────────┐ ┌─────────────┐                           │││
│  │  │  │Table: orders│ │Table: users │  (정제된 스키마)          │││
│  │  │  └─────────────┘ └─────────────┘                           │││
│  │  └─────────────────────────────────────────────────────────────┘││
│  │                                                                  ││
│  │  ┌─────────────────────────────────────────────────────────────┐││
│  │  │                    Database: analytics_db                    │││
│  │  │  ┌─────────────┐ ┌─────────────┐                           │││
│  │  │  │Table: daily │ │Table: kpi   │  (집계 테이블)            │││
│  │  │  │_sales       │ │_summary     │                           │││
│  │  │  └─────────────┘ └─────────────┘                           │││
│  │  └─────────────────────────────────────────────────────────────┘││
│  └─────────────────────────────────────────────────────────────────┘│
│                                                                      │
│  연동 서비스: Athena, Redshift Spectrum, EMR, Glue ETL              │
└─────────────────────────────────────────────────────────────────────┘

5.2 테이블 생성 방법

방법 1: Glue Crawler (자동 스키마 추론)

resource "aws_glue_crawler" "orders" {
  name          = "orders-crawler"
  database_name = aws_glue_catalog_database.raw.name
  role          = aws_iam_role.glue.arn

  s3_target {
    path = "s3://data-lake-raw/orders/"
  }

  schema_change_policy {
    delete_behavior = "LOG"
    update_behavior = "UPDATE_IN_DATABASE"
  }

  schedule = "cron(0 1 * * ? *)"  # 매일 새벽 1시
}

S3 데이터를 스캔하여 자동으로 스키마 추론. 새 파티션 자동 등록.

방법 2: DDL (명시적 스키마 정의)

-- Athena에서 실행
CREATE EXTERNAL TABLE curated_db.orders (
  order_id BIGINT,
  customer_id BIGINT,
  amount DECIMAL(10,2),
  status STRING,
  created_at TIMESTAMP
)
PARTITIONED BY (
  year STRING,
  month STRING,
  day STRING
)
STORED AS PARQUET
LOCATION 's3://data-lake-curated/orders/'
TBLPROPERTIES (
  'parquet.compression' = 'SNAPPY',
  'projection.enabled' = 'true',
  'projection.year.type' = 'integer',
  'projection.year.range' = '2020,2030',
  'projection.month.type' = 'integer',
  'projection.month.range' = '1,12',
  'projection.month.digits' = '2',
  'projection.day.type' = 'integer',
  'projection.day.range' = '1,31',
  'projection.day.digits' = '2',
  'storage.location.template' = 
    's3://data-lake-curated/orders/year=${year}/month=${month}/day=${day}/'
);

스키마를 명시적으로 정의. Partition Projection으로 메타스토어 조회 최소화.

방법 3: Terraform (IaC)

resource "aws_glue_catalog_table" "orders" {
  name          = "orders"
  database_name = aws_glue_catalog_database.curated.name

  table_type = "EXTERNAL_TABLE"

  parameters = {
    "classification"      = "parquet"
    "projection.enabled"  = "true"
    "projection.year.type"   = "integer"
    "projection.year.range"  = "2020,2030"
  }

  storage_descriptor {
    location      = "s3://data-lake-curated/orders/"
    input_format  = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat"
    output_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat"

    ser_de_info {
      serialization_library = "org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe"
    }

    columns {
      name = "order_id"
      type = "bigint"
    }
    columns {
      name = "amount"
      type = "decimal(10,2)"
    }
  }

  partition_keys {
    name = "year"
    type = "string"
  }
  partition_keys {
    name = "month"
    type = "string"
  }
}

5.3 스키마 진화 관리

✅ 안전한 스키마 변경

  • • 새 컬럼 추가 (끝에)
  • • 컬럼 타입 확장 (INT → BIGINT)
  • • 새 파티션 키 추가

❌ 위험한 스키마 변경

  • • 컬럼 삭제
  • • 컬럼 순서 변경
  • • 타입 축소 (BIGINT → INT)
  • • 컬럼 이름 변경

// 안전한 스키마 진화 예시

-- 새 컬럼 추가 (Parquet은 컬럼 이름으로 매핑)
ALTER TABLE orders ADD COLUMNS (
  discount DECIMAL(10,2),
  shipping_address STRING
);

-- 기존 데이터: discount, shipping_address는 NULL로 읽힘
-- 새 데이터: 새 컬럼 값 포함

-- Glue Crawler 스키마 업데이트 정책
schema_change_policy {
  delete_behavior = "LOG"        # 컬럼 삭제 감지 시 로그만
  update_behavior = "UPDATE_IN_DATABASE"  # 새 컬럼 자동 추가
}

5.4 Data Catalog 검색

AWS Glue Data Catalog Search

테이블, 컬럼, 파티션을 검색하여 데이터 자산을 발견할 수 있습니다.

// AWS CLI - Data Catalog 검색

# 테이블 검색
aws glue search-tables \
  --search-text "orders" \
  --max-results 10

# 특정 데이터베이스의 테이블 목록
aws glue get-tables \
  --database-name curated_db

# 테이블 상세 정보
aws glue get-table \
  --database-name curated_db \
  --name orders

# 파티션 목록
aws glue get-partitions \
  --database-name curated_db \
  --table-name orders \
  --max-results 100

6. 정리 및 다음 세션 예고

6.1 핵심 요약

3-Layer Architecture

Raw → Curated → Analytics 계층으로 데이터 품질과 접근성을 단계적으로 향상. 각 계층별로 스토리지 클래스와 보존 정책을 다르게 적용.

Parquet 포맷

AWS 데이터 레이크의 사실상 표준. 컬럼형 저장으로 분석 쿼리 최적화. Snappy 압축, 128MB~512MB 파일 크기 권장.

파티셔닝

쿼리 패턴에 맞는 파티션 키 선택이 핵심. 날짜 기반이 가장 일반적. Partition Projection으로 메타스토어 조회 오버헤드 제거.

스토리지 클래스

접근 빈도에 따라 Standard → IA → Glacier 전환. 수명 주기 정책으로 자동화하여 60% 이상 비용 절감 가능.

Glue Data Catalog

중앙 메타데이터 저장소. Athena, Redshift Spectrum, EMR이 공유. Crawler로 자동 스키마 추론 또는 DDL로 명시적 정의.

6.2 Best Practices 체크리스트

📁 버킷 설계

  • ☐ 계층별 버킷/접두사 분리
  • ☐ 일관된 네이밍 컨벤션
  • ☐ Hive 스타일 파티션 경로

📄 파일 포맷

  • ☐ Curated/Analytics는 Parquet
  • ☐ 파일 크기 128MB~512MB
  • ☐ Snappy 또는 ZSTD 압축

🗂️ 파티셔닝

  • ☐ 쿼리 패턴 기반 파티션 키
  • ☐ 파티션 수 100만 이하
  • ☐ Partition Projection 활용

💰 비용 최적화

  • ☐ 수명 주기 정책 설정
  • ☐ Intelligent-Tiering 검토
  • ☐ 불완전 업로드 정리

6.3 다음 세션 예고

Session 4: 배치 처리 - AWS Glue & EMR

대용량 데이터 변환과 ETL 파이프라인 구축 방법을 학습합니다.

  • AWS Glue ETL 심층 분석 (Spark, Python Shell)
  • Glue Job Bookmark와 증분 처리
  • Amazon EMR 아키텍처 및 클러스터 설계
  • EMR Serverless vs EMR on EC2
  • Step Functions를 활용한 워크플로우 오케스트레이션

6.4 실습 과제

과제 1: Data Lake 버킷 설계

3-Layer 구조의 S3 버킷을 생성하고, 각 계층에 맞는 수명 주기 정책을 설정하세요. Terraform 또는 CloudFormation으로 IaC 구현.

과제 2: Parquet 변환 파이프라인

JSON 샘플 데이터를 Parquet으로 변환하는 Glue Job을 작성하세요. 날짜 기반 파티셔닝을 적용하고 Athena에서 쿼리 성능을 비교하세요.

과제 3: 비용 분석

1TB 데이터에 대해 스토리지 클래스별 월간 비용을 계산하세요. 수명 주기 정책 적용 전후의 연간 비용 차이를 분석하세요.