Chapter 05

S3 스토리지 + DynamoDB

"리뷰 사진은 S3에, 리뷰 데이터는 DynamoDB에 저장하자"

학습 목표

  • S3 버킷을 생성하고 이미지 업로드용으로 설정할 수 있다
  • S3 버킷 정책과 CORS를 이해하고 설정할 수 있다
  • DynamoDB 테이블을 생성하고 키 스키마를 설계할 수 있다
  • API 서버에서 S3와 DynamoDB를 연동할 수 있다
  • RDS와 DynamoDB의 차이점을 이해하고 적절한 사용 사례를 구분할 수 있다
Chapter 05 - S3 + DynamoDB 아키텍처
사용자
리뷰 작성 + 이미지 첨부
EC2 (API 서버)
Node.js :5000
↓ IAM Role 권한으로 접근
S3 Bucket
shopeasy-images-{ACCOUNT_ID}
리뷰 이미지 저장
DynamoDB
Reviews 테이블
리뷰 데이터 저장
RDS MySQL
상품/주문/회원
(기존 데이터)

왜 S3와 DynamoDB가 필요한가?

지금까지 ShopEasy의 상품, 주문, 회원 데이터는 RDS MySQL에 저장했습니다. 하지만 리뷰 기능을 추가하려면 두 가지 새로운 요구사항이 생깁니다.

문제 1: 리뷰 이미지를 어디에 저장할까?

  • 사용자가 리뷰에 사진을 첨부하면, 이미지 파일을 서버 어딘가에 저장해야 합니다
  • EC2 서버의 로컬 디스크에 저장하면? - 서버가 죽으면 이미지도 사라집니다
  • RDS에 이미지를 BLOB으로 저장하면? - DB가 느려지고 비용이 폭증합니다
  • 해결: S3 - 이미지 같은 파일은 S3에 저장하고, URL로 접근합니다

문제 2: 리뷰 데이터는 어떤 DB에 저장할까?

  • 리뷰는 상품/주문과 달리 구조가 유연합니다 (별점만 있는 리뷰, 사진이 있는 리뷰, 긴 텍스트 리뷰...)
  • 리뷰는 제품별로 독립적입니다 (다른 테이블과 복잡한 JOIN이 필요 없음)
  • 리뷰는 읽기가 매우 빈번합니다 (상품 페이지를 열 때마다 리뷰 조회)
  • 해결: DynamoDB - 유연한 구조 + 초고속 읽기에 최적화된 NoSQL DB
핵심 포인트

모든 데이터를 하나의 DB에 넣을 필요가 없습니다. 데이터의 성격에 맞는 저장소를 선택하는 것이 클라우드의 핵심입니다.

  • RDS: 상품, 주문, 회원 (관계가 있는 정형 데이터)
  • DynamoDB: 리뷰 (제품별 독립적, 유연한 구조)
  • S3: 이미지, 파일 (대용량 바이너리 데이터)

S3 핵심 개념

비유로 이해하기: S3 = 무한대 크기의 USB 드라이브

USB 드라이브에 파일을 넣고 다른 컴퓨터에서 꺼내 쓰는 것처럼, S3에 파일을 넣으면 인터넷 어디서든 URL로 접근할 수 있습니다. 단, USB와 달리 용량 제한이 없고, 파일이 절대 유실되지 않습니다 (내구성 99.999999999%).

S3 = 무제한 객체 스토리지

Amazon S3 (Simple Storage Service)는 이미지, 동영상, 문서 등 모든 종류의 파일을 저장하는 객체 스토리지 서비스입니다. 저장 용량에 제한이 없으며, 저장한 만큼만 비용을 지불합니다.

핵심 용어

용어 설명 비유
버킷 (Bucket) 파일을 담는 최상위 컨테이너. 전 세계에서 유일한 이름이 필요 폴더의 최상위 디렉토리
객체 (Object) 버킷 안에 저장되는 개별 파일. 키(경로)로 식별 파일 하나하나
키 (Key) 버킷 안에서 객체를 식별하는 경로 (예: uploads/review-001.jpg) 파일 경로
버킷 정책 (Bucket Policy) 버킷과 객체에 대한 접근 권한을 JSON으로 정의 출입증 규칙

CORS란?

CORS (Cross-Origin Resource Sharing)

브라우저가 다른 도메인의 리소스에 접근할 때 필요한 허가증입니다.

예를 들어, ShopEasy 프론트엔드(http://shopeasy-frontend.s3-website.ap-northeast-2.amazonaws.com)에서 S3 이미지 버킷(shopeasy-images-xxxx.s3.amazonaws.com)에 이미지를 업로드하려면, S3 버킷이 "이 도메인에서 오는 요청을 허용합니다"라고 설정해야 합니다.

CORS가 없으면 브라우저가 보안상 요청을 차단합니다. 서버 간 통신(API 서버에서 S3로)은 CORS와 무관하지만, 브라우저에서 직접 S3로 업로드하는 경우 반드시 설정해야 합니다.

S3 객체 URL 구조

text
# S3 객체 URL 형식
https://{버킷이름}.s3.{리전}.amazonaws.com/{키}

# ShopEasy 리뷰 이미지 예시
https://shopeasy-images-123456789012.s3.ap-northeast-2.amazonaws.com/uploads/review-001.jpg

DynamoDB 핵심 개념

비유로 이해하기: DynamoDB = 초고속 서류함

일반 서류함(RDS)은 서류를 꺼내려면 서랍을 하나씩 열어서 찾아야 합니다. 하지만 DynamoDB는 파티션 키(서랍 라벨)만 알면 즉시 해당 서랍으로 이동해서 원하는 서류를 꺼냅니다. 데이터가 아무리 많아져도 검색 속도가 일정합니다 (밀리초 단위 응답).

DynamoDB = 초고속 NoSQL 데이터베이스

Amazon DynamoDB는 어떤 규모에서든 한 자릿수 밀리초 성능을 제공하는 완전관리형 NoSQL 데이터베이스입니다. 서버를 관리할 필요가 없고, 테이블을 만들면 바로 사용할 수 있습니다.

핵심 용어

용어 설명 비유
테이블 (Table) 데이터를 저장하는 단위 (RDS의 테이블과 유사) 서류함
항목 (Item) 테이블의 각 데이터 행 (RDS의 Row와 유사) 서류 한 장
파티션 키 (Partition Key) 데이터를 분산 저장하는 기준. 필수 서랍 라벨
정렬 키 (Sort Key) 같은 파티션 안에서 데이터를 정렬하는 기준. 선택 서랍 안 서류 순서
온디맨드 용량 요청한 만큼만 과금 (사전 용량 설정 불필요) 사용한 만큼 후불

ShopEasy Reviews 테이블 키 설계

키 설계 이유
  • 파티션 키: productId (String) - 상품별로 리뷰를 모아서 조회하므로, 상품 ID가 파티션 키
  • 정렬 키: createdAt#userId (String) - 같은 상품의 리뷰를 날짜순으로 정렬하고, 같은 시간에 작성된 리뷰를 사용자 ID로 구분

이 설계로 "특정 상품의 모든 리뷰를 최신순으로 조회"하는 쿼리를 매우 빠르게 처리할 수 있습니다.

json
# Reviews 테이블 - 데이터 예시
{
  "productId": "PROD-001",
  "createdAt#userId": "2024-12-01T10:30:00Z#USER-042",
  "rating": 5,
  "content": "배송이 빠르고 품질이 좋습니다!",
  "imageUrl": "https://shopeasy-images-xxxx.s3.ap-northeast-2.amazonaws.com/uploads/review-001.jpg",
  "userName": "김철수"
}

RDS vs DynamoDB 비교

항목 RDS (MySQL) DynamoDB
데이터 모델 관계형 (테이블, 행, 열) 키-값 / 문서형 (NoSQL)
스키마 고정 스키마 (ALTER TABLE 필요) 유연한 스키마 (속성 자유 추가)
쿼리 SQL (복잡한 JOIN 가능) 키 기반 조회 (JOIN 불가)
성능 데이터 증가 시 느려질 수 있음 데이터량 무관하게 일정한 성능
관리 인스턴스 크기, 패치 관리 필요 완전 관리형 (서버리스)
비용 인스턴스 가동 시간 기준 과금 온디맨드: 요청량 기준 과금
ShopEasy 용도 상품, 주문, 회원 (관계 있는 데이터) 리뷰 (제품별 독립적, 유연한 구조)
실무 Best Practice: 적합한 DB 선택
  • RDS 사용: 데이터 간 관계가 중요하고, 복잡한 쿼리(JOIN, 트랜잭션)가 필요한 경우
  • DynamoDB 사용: 키 기반 단순 조회가 주 패턴이고, 데이터 구조가 유연해야 하며, 대규모 트래픽을 처리해야 하는 경우
  • 하나의 서비스에서 여러 DB를 함께 사용하는 것은 매우 일반적인 패턴입니다 (Polyglot Persistence)

실습: S3 + DynamoDB 구성

사전 준비
  • Chapter 04에서 만든 IAM Role (ShopEasy-EC2-Role)에 S3와 DynamoDB 권한이 포함되어 있어야 합니다
  • EC2 인스턴스에 해당 IAM Role이 연결되어 있어야 합니다
  • AWS 계정 ID를 확인해 두세요 (콘솔 우측 상단 클릭 또는 aws sts get-caller-identity)
Hands-on Lab
  1. S3 버킷 생성

    리뷰 이미지를 저장할 S3 버킷을 생성합니다.

    • 버킷 이름: shopeasy-images-{ACCOUNT_ID} (예: shopeasy-images-123456789012)
    • 리전: ap-northeast-2 (서울)
    • 퍼블릭 액세스 차단: 모두 해제 (이미지를 브라우저에서 볼 수 있어야 함)
    퍼블릭 액세스 차단 해제가 필요한 이유

    S3는 기본적으로 모든 퍼블릭 액세스를 차단합니다. 하지만 리뷰 이미지는 사용자의 브라우저에서 직접 접근해야 하므로 퍼블릭 읽기가 필요합니다. "모든 퍼블릭 액세스 차단" 설정을 해제한 뒤, 버킷 정책에서 특정 경로만 공개합니다.

    AWS 콘솔 → S3 → 버킷 만들기에서 버킷 이름을 입력하고, "모든 퍼블릭 액세스 차단" 체크박스를 해제합니다. 확인 문구를 입력한 후 버킷을 생성합니다.

  2. S3 버킷 CORS 설정

    브라우저에서 S3 버킷에 이미지를 업로드할 수 있도록 CORS를 설정합니다.

    json
    [
      {
        "AllowedOrigins": ["*"],
        "AllowedMethods": ["GET", "PUT", "POST"],
        "AllowedHeaders": ["*"],
        "MaxAgeSeconds": 3000
      }
    ]

    S3 버킷 → 권한 탭 → CORS(Cross-origin resource sharing) 섹션 → 편집 → 위 JSON을 붙여넣고 저장합니다.

  3. S3 버킷 정책 설정

    uploads/ 경로의 객체를 누구나 읽을 수 있도록 버킷 정책을 설정합니다.

    json
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "PublicReadUploads",
          "Effect": "Allow",
          "Principal": "*",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::shopeasy-images-{ACCOUNT_ID}/uploads/*"
        }
      ]
    }
    보안 포인트

    uploads/* 경로만 퍼블릭으로 공개합니다. 버킷 전체(*)를 공개하면 다른 파일도 노출될 수 있으므로 반드시 필요한 경로만 제한적으로 공개합니다.

    S3 버킷 → 권한 탭 → 버킷 정책 섹션 → 편집 → 위 JSON에서 {ACCOUNT_ID}를 본인의 계정 ID로 바꿔 넣고 저장합니다.

  4. DynamoDB 테이블 생성

    리뷰 데이터를 저장할 DynamoDB 테이블을 생성합니다.

    • 테이블 이름: Reviews
    • 파티션 키: productId (String 타입)
    • 정렬 키: createdAt#userId (String 타입)
    • 용량 모드: 온디맨드 (사용한 만큼 과금, 프리 티어 포함)
    주의: 키 이름에 # 포함

    정렬 키 이름이 createdAt#userId입니다. 이것은 속성 이름 자체에 #이 포함된 것입니다. DynamoDB의 속성 이름에는 특수문자를 사용할 수 있으며, 이 경우 날짜와 사용자 ID를 결합한 복합 키로 활용합니다.

    AWS 콘솔 → DynamoDB → 테이블 만들기 → 테이블 이름: Reviews, 파티션 키: productId (문자열), 정렬 키 추가: createdAt#userId (문자열), 테이블 설정: 사용자 지정, 용량 모드: 온디맨드 → 테이블 생성

  5. API 서버 .env 수정

    EC2에 SSH 접속 후, API 서버의 .env 파일에 S3와 DynamoDB 설정을 추가합니다.

    bash
    # EC2 접속
    ssh -i your-key.pem ec2-user@{EC2_PUBLIC_IP}
    
    # API 서버 디렉토리로 이동
    cd ~/shopeasy/api-server
    
    # .env 파일 편집
    nano .env

    다음 환경변수를 .env 파일에 추가합니다:

    env
    # S3 설정
    STORAGE_TYPE=s3
    S3_BUCKET=shopeasy-images-{ACCOUNT_ID}
    S3_REGION=ap-northeast-2
    
    # DynamoDB 설정
    REVIEW_STORE=dynamodb
    DYNAMODB_TABLE=Reviews
    DYNAMODB_REGION=ap-northeast-2
    Access Key가 필요 없는 이유

    Chapter 04에서 EC2에 IAM Role을 연결했기 때문에, AWS SDK가 자동으로 인스턴스 메타데이터에서 임시 자격 증명을 가져옵니다. .envAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY를 넣지 않아도 됩니다.

    nano 편집기에서 파일 끝에 위 내용을 추가합니다. {ACCOUNT_ID}를 본인의 AWS 계정 ID로 교체하세요. 저장: Ctrl+OEnter, 종료: Ctrl+X

  6. API 서버 재시작

    환경변수를 적용하기 위해 API 서버를 재시작합니다.

    bash
    # pm2를 사용하는 경우
    pm2 restart all
    
    # pm2를 사용하지 않는 경우 (직접 실행)
    # 기존 프로세스 종료 후
    cd ~/shopeasy/api-server
    node server.js &

    pm2 status로 서버 상태를 확인하세요. 재시작 후 pm2 logs로 에러가 없는지 확인합니다.

  7. 리뷰 작성 테스트

    curl로 리뷰를 작성하여 이미지 업로드와 데이터 저장이 정상 동작하는지 테스트합니다.

    bash
    # 테스트용 이미지 생성 (1x1 PNG)
    echo -e '\x89PNG\r\n\x1a\n' > test-image.png
    
    # 리뷰 작성 API 호출 (이미지 포함)
    curl -X POST http://{EC2_PUBLIC_IP}:5000/api/reviews \
      -F "productId=PROD-001" \
      -F "userId=USER-042" \
      -F "userName=testuser" \
      -F "rating=5" \
      -F "content=배송이 빠르고 품질이 좋습니다!" \
      -F "image=@test-image.png"

    성공하면 JSON 응답에 imageUrl이 S3 URL로 포함됩니다.

    bash
    # 리뷰 조회 테스트
    curl http://{EC2_PUBLIC_IP}:5000/api/reviews/PROD-001

    {EC2_PUBLIC_IP}를 EC2의 퍼블릭 IP로 교체하세요. 응답이 에러인 경우 pm2 logs로 서버 로그를 확인하세요.

  8. S3에 이미지 저장 확인

    S3 버킷에 이미지가 정상적으로 업로드되었는지 확인합니다.

    bash
    # AWS CLI로 S3 버킷 내용 확인
    aws s3 ls s3://shopeasy-images-{ACCOUNT_ID}/uploads/
    
    # 또는 콘솔에서: S3 > shopeasy-images-xxxx > uploads/ 폴더 확인

    업로드된 이미지 파일이 목록에 보이면 성공입니다. 브라우저에서 이미지 URL을 직접 열어 이미지가 표시되는지도 확인하세요.

    파일이 보이지 않으면: (1) API 서버 로그 확인, (2) IAM Role에 s3:PutObject 권한이 있는지 확인, (3) 버킷 이름이 .env와 일치하는지 확인

  9. DynamoDB에 리뷰 데이터 확인

    DynamoDB 테이블에 리뷰 데이터가 저장되었는지 확인합니다.

    bash
    # AWS CLI로 DynamoDB 테이블 스캔
    aws dynamodb scan \
      --table-name Reviews \
      --region ap-northeast-2

    출력에 리뷰 데이터 (productId, rating, content 등)가 보이면 성공입니다.

    bash
    # 특정 상품의 리뷰만 조회 (Query)
    aws dynamodb query \
      --table-name Reviews \
      --key-condition-expression "productId = :pid" \
      --expression-attribute-values '{":pid": {"S": "PROD-001"}}' \
      --region ap-northeast-2

    데이터가 없으면: (1) API 서버 로그에서 DynamoDB 관련 에러 확인, (2) IAM Role에 dynamodb:PutItem 권한이 있는지 확인, (3) 테이블 이름과 리전이 .env와 일치하는지 확인

확인 사항

아래 항목을 모두 완료했는지 체크하세요:

  • S3 버킷 shopeasy-images-{ACCOUNT_ID}가 생성되었다
  • S3 버킷의 퍼블릭 액세스 차단이 해제되었다
  • S3 버킷 CORS 설정이 완료되었다
  • S3 버킷 정책에서 uploads/* 경로가 퍼블릭 읽기 허용되었다
  • DynamoDB Reviews 테이블이 생성되었다 (파티션 키: productId, 정렬 키: createdAt#userId)
  • API 서버 .env에 S3, DynamoDB 관련 환경변수가 추가되었다
  • API 서버가 재시작되었다
  • 리뷰 작성 API가 정상 동작한다 (이미지 업로드 포함)
  • S3 버킷에 업로드된 이미지를 브라우저에서 URL로 접근할 수 있다
  • DynamoDB에 리뷰 데이터가 저장되어 있다