본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 19. · 10 Views
DynamoDB 테이블 생성 완벽 가이드
NoSQL 데이터베이스 DynamoDB의 테이블 생성부터 파티션 키 설계, 용량 모드 선택까지 초급 개발자도 쉽게 따라할 수 있는 실무 중심 가이드입니다. 실제 프로젝트에서 바로 활용할 수 있는 핵심 개념을 스토리텔링으로 풀어냅니다.
목차
1. DynamoDB란?
스타트업에 입사한 지 일주일 된 김개발 씨는 첫 프로젝트 미팅에서 팀장님의 말에 당황했습니다. "이번 서비스는 DynamoDB로 구축할 거예요." 관계형 데이터베이스만 다뤄본 김개발 씨에게 DynamoDB는 완전히 새로운 세계였습니다.
DynamoDB는 AWS에서 제공하는 완전 관리형 NoSQL 데이터베이스입니다. 마치 무한히 확장 가능한 거대한 해시맵처럼, 키-값 쌍으로 데이터를 저장하고 빠르게 조회할 수 있습니다.
수백만 명의 사용자가 동시에 접속해도 밀리초 단위의 빠른 응답 속도를 보장합니다.
다음 코드를 살펴봅시다.
import boto3
# DynamoDB 클라이언트 생성
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-2')
# 테이블 참조
table = dynamodb.Table('Users')
# 데이터 조회 - 매우 빠른 응답 속도
response = table.get_item(
Key={
'user_id': 'user123'
}
)
print(response['Item'])
김개발 씨는 지난 프로젝트에서 MySQL을 사용했습니다. 테이블을 만들 때면 항상 ERD를 그리고, 정규화를 고민하고, 외래키를 설정했습니다.
그런데 팀장님은 "DynamoDB는 그런 거 안 해도 돼요"라고 말했습니다. 어떻게 그럴 수 있을까요?
선배 개발자 박시니어 씨가 설명을 시작했습니다. "DynamoDB는 관계형 데이터베이스와는 완전히 다른 철학으로 만들어졌어요." 그렇다면 DynamoDB는 정확히 무엇일까요?
쉽게 비유하자면, DynamoDB는 마치 거대한 도서관의 자동 보관함 시스템과 같습니다. 각 보관함에는 고유한 번호가 있고, 그 번호만 알면 몇 초 만에 원하는 물건을 찾을 수 있습니다.
도서관 사서가 책을 하나하나 찾아다닐 필요가 없는 것처럼, DynamoDB도 복잡한 조인 없이 키만으로 데이터를 빠르게 찾아냅니다. 기존 관계형 데이터베이스를 사용할 때는 어땠을까요?
서비스 사용자가 늘어나면 데이터베이스 서버를 업그레이드해야 했습니다. CPU를 더 좋은 것으로 바꾸고, 메모리를 증설하는 수직 확장을 해야 했죠.
하지만 한 대의 서버가 감당할 수 있는 용량에는 한계가 있었습니다. 더 큰 문제는 트래픽이 갑자기 폭증했을 때 대응하기 어렵다는 점이었습니다.
서버를 증설하려면 며칠씩 걸리기도 했습니다. 바로 이런 문제를 해결하기 위해 DynamoDB가 등장했습니다.
DynamoDB를 사용하면 자동 확장이 가능해집니다. 사용자가 10명이든 1000만 명이든 AWS가 알아서 처리합니다.
또한 관리 부담이 없다는 것도 큰 장점입니다. 백업, 복제, 패치 같은 운영 작업을 AWS가 대신 해줍니다.
무엇보다 일관된 성능이라는 큰 이점이 있습니다. 데이터가 아무리 많아져도 밀리초 단위의 빠른 응답 속도를 유지합니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 boto3 라이브러리로 DynamoDB 리소스를 생성합니다.
이 부분이 AWS와의 연결을 담당하는 핵심입니다. 다음으로 특정 테이블을 참조하는 객체를 만듭니다.
마지막으로 get_item 메서드로 키를 이용해 데이터를 조회하면, 거의 즉시 결과가 반환됩니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 실시간 게임 순위표 서비스를 개발한다고 가정해봅시다. 수백만 명의 유저가 동시에 점수를 갱신하고 조회하는 상황에서 DynamoDB를 활용하면 안정적인 성능을 얻을 수 있습니다.
넷플릭스, 나이키, 삼성 같은 글로벌 기업들이 실제로 DynamoDB를 적극 사용하고 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 관계형 데이터베이스처럼 복잡한 조인 쿼리를 시도하는 것입니다. DynamoDB는 조인을 지원하지 않기 때문에 애플리케이션 레벨에서 데이터를 조합해야 합니다.
따라서 데이터 모델링을 처음부터 신중하게 설계해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 스타트업에서 많이 쓰는 거군요!" DynamoDB를 제대로 이해하면 확장 가능하고 관리하기 쉬운 서비스를 구축할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 소규모 프로젝트라도 나중의 확장을 고려한다면 DynamoDB를 선택하세요
- AWS 프리티어로 월 25GB까지 무료로 사용할 수 있습니다
2. 테이블 생성하기
첫 미팅이 끝나고 김개발 씨는 본격적으로 DynamoDB 테이블을 만들기 시작했습니다. AWS 콘솔을 켜니 "테이블 생성" 버튼이 보였습니다.
하지만 막상 눌러보니 생소한 용어들이 가득했습니다. 파티션 키?
정렬 키? 이게 뭘까요?
DynamoDB 테이블 생성은 생각보다 간단합니다. 테이블 이름과 기본 키만 정하면 바로 시작할 수 있습니다.
마치 엑셀에서 새 시트를 만드는 것처럼 몇 번의 클릭이면 충분합니다. 하지만 이 간단한 단계에서 내린 결정이 나중에 서비스 전체의 성능을 좌우합니다.
다음 코드를 살펴봅시다.
import boto3
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-2')
# 테이블 생성
table = dynamodb.create_table(
TableName='Users',
KeySchema=[
{
'AttributeName': 'user_id',
'KeyType': 'HASH' # 파티션 키
}
],
AttributeDefinitions=[
{
'AttributeName': 'user_id',
'AttributeType': 'S' # String 타입
}
],
BillingMode='PAY_PER_REQUEST' # 온디맨드 모드
)
# 테이블이 생성될 때까지 대기
table.wait_until_exists()
print(f"테이블 {table.table_name}이 생성되었습니다!")
김개발 씨는 처음에 AWS 콘솔을 사용하려다가, 박시니어 씨의 조언을 듣고 코드로 테이블을 생성하기로 했습니다. "코드로 관리하면 나중에 다른 환경에서도 똑같이 재현할 수 있어요." 테이블을 만드는 과정은 어떻게 진행될까요?
쉽게 비유하자면, 테이블 생성은 마치 새 아파트 단지를 설계하는 것과 같습니다. 먼저 아파트 이름을 정하고, 각 집을 구분할 고유한 주소 체계를 만들어야 합니다.
이 주소 체계가 바로 파티션 키입니다. 한번 정하면 나중에 바꾸기 어렵기 때문에 신중하게 결정해야 합니다.
테이블을 수동으로 관리하던 시절에는 어땠을까요? 개발 환경, 스테이징 환경, 프로덕션 환경마다 콘솔에 접속해서 일일이 똑같은 테이블을 만들어야 했습니다.
설정을 잘못 입력하는 실수도 자주 발생했습니다. 더 큰 문제는 나중에 테이블 구조를 변경해야 할 때 어떤 설정으로 만들었는지 기억이 안 난다는 점이었습니다.
바로 이런 문제를 해결하기 위해 코드로 테이블을 생성하는 방식이 권장됩니다. 코드로 테이블을 생성하면 버전 관리가 가능해집니다.
Git에 코드를 저장하면 언제든 이전 상태로 돌아갈 수 있습니다. 또한 자동화도 쉬워집니다.
CI/CD 파이프라인에 포함시키면 배포할 때 테이블이 자동으로 생성됩니다. 무엇보다 문서화가 된다는 큰 이점이 있습니다.
코드 자체가 테이블 구조를 설명하는 문서가 됩니다. 위의 코드를 단계별로 살펴보겠습니다.
먼저 create_table 메서드를 호출합니다. TableName에는 테이블 이름을 지정하는데, AWS 계정 내에서 고유해야 합니다.
KeySchema에서는 어떤 속성을 파티션 키로 사용할지 정의합니다. KeyType이 'HASH'라는 것은 파티션 키를 의미합니다.
AttributeDefinitions에서는 키로 사용할 속성의 데이터 타입을 지정합니다. 'S'는 String, 'N'은 Number, 'B'는 Binary를 의미합니다.
BillingMode는 과금 방식을 결정하는데, 'PAY_PER_REQUEST'는 사용한 만큼만 비용을 내는 온디맨드 모드입니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 사용자 관리 시스템을 개발한다고 가정해봅시다. user_id를 파티션 키로 하는 Users 테이블을 만들면, 특정 사용자의 정보를 매우 빠르게 조회할 수 있습니다.
대부분의 웹 서비스에서 이런 패턴을 기본으로 사용합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 테이블 이름을 대충 짓는 것입니다. 나중에 테이블이 많아지면 어떤 테이블이 무엇을 저장하는지 헷갈릴 수 있습니다.
따라서 명확하고 일관된 네이밍 규칙을 정해서 사용해야 합니다. 예를 들어 'prod-users', 'dev-users' 처럼 환경을 접두사로 붙이는 방식이 좋습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 코드를 실행한 김개발 씨는 몇 초 후 "테이블이 생성되었습니다!" 메시지를 확인했습니다.
"생각보다 간단하네요!" 테이블 생성의 기본을 제대로 이해하면 더 복잡한 구조도 쉽게 다룰 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 테이블 이름에 환경(dev, prod)을 접두사로 붙이면 관리가 쉬워집니다
- wait_until_exists()를 사용하면 테이블 생성이 완료될 때까지 안전하게 기다릴 수 있습니다
3. 파티션 키와 정렬 키
테이블을 성공적으로 만든 김개발 씨는 이제 실제 데이터를 넣어보려고 했습니다. 그런데 팀장님이 코드 리뷰에서 이런 말을 했습니다.
"user_id만으로는 부족해요. 사용자의 주문 내역을 빠르게 조회하려면 복합 키를 써야 합니다." 복합 키?
또 새로운 개념이 나타났습니다.
파티션 키는 데이터를 여러 서버에 분산 저장하는 기준이고, 정렬 키는 같은 파티션 내에서 데이터를 정렬하는 기준입니다. 마치 아파트 동 번호와 호수 번호처럼, 두 개를 조합하면 더 정교한 데이터 접근이 가능합니다.
이 조합을 복합 기본 키라고 부릅니다.
다음 코드를 살펴봅시다.
import boto3
from datetime import datetime
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-2')
# 파티션 키 + 정렬 키로 테이블 생성
table = dynamodb.create_table(
TableName='Orders',
KeySchema=[
{
'AttributeName': 'user_id',
'KeyType': 'HASH' # 파티션 키
},
{
'AttributeName': 'order_date',
'KeyType': 'RANGE' # 정렬 키
}
],
AttributeDefinitions=[
{'AttributeName': 'user_id', 'AttributeType': 'S'},
{'AttributeName': 'order_date', 'AttributeType': 'S'}
],
BillingMode='PAY_PER_REQUEST'
)
table.wait_until_exists()
# 특정 사용자의 최근 주문 조회
response = table.query(
KeyConditionExpression='user_id = :uid AND order_date > :date',
ExpressionAttributeValues={
':uid': 'user123',
':date': '2025-01-01'
},
ScanIndexForward=False, # 내림차순 정렬
Limit=10
)
김개발 씨는 처음에는 이해가 안 갔습니다. "파티션 키 하나만으로도 충분하지 않나요?" 하지만 실제 서비스를 생각해보니 문제가 보이기 시작했습니다.
박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "파티션 키만 있으면 한 사용자의 데이터를 전부 가져와야 해요.
주문이 1000개 있으면 1000개를 다 읽어야 하죠." 그렇다면 파티션 키와 정렬 키는 정확히 어떻게 다를까요? 쉽게 비유하자면, 파티션 키는 도서관의 서가 번호와 같습니다.
"철학" 서가, "과학" 서가처럼 큰 범주를 나눕니다. 정렬 키는 그 서가 안에서 책을 정렬하는 기준입니다.
출판 연도순으로 정렬할 수도 있고, 저자 이름순으로 정렬할 수도 있습니다. 이렇게 두 가지를 조합하면 원하는 책을 훨씬 빠르게 찾을 수 있습니다.
정렬 키가 없던 시절에는 어땠을까요? 특정 사용자의 최근 주문 10개를 조회하려면 그 사용자의 모든 주문을 읽어온 뒤, 애플리케이션 레벨에서 정렬하고 10개만 추출해야 했습니다.
주문이 수천 개라면 엄청난 데이터 전송량과 처리 시간이 필요했습니다. 더 큰 문제는 비용이었습니다.
DynamoDB는 읽은 데이터량에 비례해서 요금을 부과하기 때문에 불필요한 데이터까지 읽으면 낭비가 심했습니다. 바로 이런 문제를 해결하기 위해 정렬 키가 등장했습니다.
정렬 키를 사용하면 범위 쿼리가 가능해집니다. "2025년 1월 이후 주문만 가져와"라는 조건을 DynamoDB 레벨에서 처리할 수 있습니다.
또한 자동 정렬도 얻을 수 있습니다. 데이터가 정렬 키 순서대로 저장되기 때문에 별도의 정렬 작업이 필요 없습니다.
무엇보다 비용 절감이라는 큰 이점이 있습니다. 필요한 데이터만 정확히 읽어오기 때문입니다.
위의 코드를 자세히 살펴보겠습니다. KeySchema 배열에 두 개의 항목이 있습니다.
첫 번째는 파티션 키(HASH), 두 번째는 정렬 키(RANGE)입니다. query 메서드를 사용할 때 KeyConditionExpression으로 조건을 지정할 수 있습니다.
user_id는 정확히 일치해야 하고(=), order_date는 범위 조건(>)을 사용할 수 있습니다. ScanIndexForward를 False로 설정하면 최신 주문부터 반환됩니다.
Limit으로 최대 몇 개까지 가져올지 제한할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 이커머스 주문 관리 시스템을 개발한다고 가정해봅시다. user_id를 파티션 키로, order_date를 정렬 키로 설정하면 "특정 사용자의 최근 주문 내역"을 밀리초 단위로 조회할 수 있습니다.
쿠팡, 마켓컬리 같은 대규모 쇼핑몰에서 이런 패턴을 필수적으로 사용합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 정렬 키에 랜덤한 값을 넣는 것입니다. UUID 같은 랜덤 값을 정렬 키로 쓰면 정렬의 의미가 없어집니다.
따라서 의미 있는 순서가 있는 값을 정렬 키로 선택해야 합니다. 날짜, 시간, 순번 같은 값이 적합합니다.
또 하나 조심해야 할 점은 핫 파티션 문제입니다. 특정 파티션 키에 데이터가 너무 몰리면 성능이 저하됩니다.
예를 들어 한 명의 사용자가 하루에 수백만 건의 주문을 하는 경우는 드물지만, 만약 그런 상황이 발생한다면 파티션 키 설계를 다시 고민해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 감탄했습니다. "이렇게 하면 정말 빠르겠네요!" 파티션 키와 정렬 키를 제대로 설계하면 DynamoDB의 성능을 최대한 활용할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 정렬 키에는 날짜, 시간, 순번처럼 의미 있는 순서가 있는 값을 사용하세요
- 범위 쿼리가 필요하다면 반드시 정렬 키를 설정해야 합니다
4. 읽기/쓰기 용량
테이블 구조를 완성한 김개발 씨는 이제 성능 테스트를 준비했습니다. 그런데 AWS 콘솔을 보니 "읽기 용량 유닛", "쓰기 용량 유닛"이라는 용어가 눈에 들어왔습니다.
팀장님은 "이걸 잘못 설정하면 비용 폭탄을 맞을 수 있어요"라고 경고했습니다. 도대체 이게 뭘까요?
**읽기 용량 유닛(RCU)**과 **쓰기 용량 유닛(WCU)**은 DynamoDB의 처리 능력을 나타내는 단위입니다. 마치 도로의 차선 수처럼, 용량이 많을수록 더 많은 요청을 동시에 처리할 수 있습니다.
하지만 많이 설정할수록 비용도 올라가기 때문에 적절한 균형이 중요합니다.
다음 코드를 살펴봅시다.
import boto3
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-2')
# 프로비저닝 모드로 테이블 생성
table = dynamodb.create_table(
TableName='Products',
KeySchema=[
{'AttributeName': 'product_id', 'KeyType': 'HASH'}
],
AttributeDefinitions=[
{'AttributeName': 'product_id', 'AttributeType': 'S'}
],
BillingMode='PROVISIONED', # 프로비저닝 모드
ProvisionedThroughput={
'ReadCapacityUnits': 5, # 초당 5개의 강력한 일관성 읽기
'WriteCapacityUnits': 5 # 초당 5개의 1KB 쓰기
}
)
table.wait_until_exists()
# 나중에 용량 변경 가능
table.update(
ProvisionedThroughput={
'ReadCapacityUnits': 10,
'WriteCapacityUnits': 10
}
)
김개발 씨는 처음에 용량 유닛이라는 개념 자체가 낯설었습니다. "그냥 무제한으로 쓸 수 없나요?" 하지만 박시니어 씨의 설명을 듣고 왜 이런 개념이 필요한지 이해하게 되었습니다.
"클라우드 리소스는 무한정 공짜가 아니에요. 사용한 만큼 비용을 내는 게 원칙이죠." 그렇다면 용량 유닛은 정확히 무엇을 의미할까요?
쉽게 비유하자면, 용량 유닛은 마치 식당의 테이블 수와 같습니다. 테이블이 5개면 동시에 5팀의 손님을 받을 수 있습니다.
손님이 더 많이 오면 대기해야 하거나 거절당할 수 있습니다. 마찬가지로 RCU가 5라면 초당 5개의 읽기 요청을 처리할 수 있고, 그 이상 요청이 오면 제한될 수 있습니다.
용량을 제대로 관리하지 않던 시절에는 어땠을까요? 예상보다 트래픽이 많이 들어오면 서비스가 느려지거나 멈췄습니다.
"ProvisionedThroughputExceededException"이라는 무시무시한 에러가 발생했습니다. 반대로 용량을 너무 많이 설정해두면 사용하지도 않는 리소스에 매달 수십만 원씩 비용을 낭비했습니다.
더 큰 문제는 트래픽 패턴을 예측하기 어렵다는 점이었습니다. 평소에는 조용하다가 특정 시간대에만 폭증하는 서비스도 많았습니다.
바로 이런 문제를 해결하기 위해 다양한 용량 관리 옵션이 등장했습니다. 프로비저닝 모드를 사용하면 비용 예측이 가능해집니다.
미리 용량을 지정하면 월 비용을 정확히 계산할 수 있습니다. 또한 Auto Scaling을 설정하면 트래픽에 따라 자동으로 용량이 조절됩니다.
무엇보다 Reserved Capacity를 구매하면 최대 75%까지 비용을 절감할 수 있다는 큰 이점이 있습니다. 위의 코드를 구체적으로 살펴보겠습니다.
BillingMode를 'PROVISIONED'로 설정하면 프로비저닝 모드가 활성화됩니다. ProvisionedThroughput에서 RCU와 WCU를 숫자로 지정합니다.
1 RCU는 초당 1개의 4KB 강력한 일관성 읽기를 의미합니다. 1 WCU는 초당 1개의 1KB 쓰기를 의미합니다.
예를 들어 8KB 크기의 아이템을 읽으려면 2 RCU가 필요합니다. update 메서드로 나중에 언제든지 용량을 변경할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 뉴스 사이트를 운영한다고 가정해봅시다.
대부분의 시간에는 트래픽이 적지만, 속보가 나가면 순간적으로 폭증합니다. 이럴 때 Auto Scaling을 설정해두면 자동으로 용량이 늘어났다가 다시 줄어듭니다.
많은 미디어 기업들이 이런 방식으로 비용을 최적화하고 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 용량을 너무 적게 설정하는 것입니다. 트래픽이 조금만 늘어나도 바로 에러가 발생합니다.
따라서 여유분을 두고 설정해야 합니다. 예상 트래픽의 150% 정도로 설정하는 것이 안전합니다.
또 하나 주의할 점은 버스트 용량입니다. DynamoDB는 사용하지 않은 용량을 최대 5분까지 저장했다가 급증 시 사용할 수 있습니다.
하지만 이것만 믿고 용량을 너무 낮게 설정하면 위험합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 조언대로 Auto Scaling을 설정한 김개발 씨는 안심했습니다. "이제 새벽에 장애 알람 받을 일은 없겠네요!" 읽기/쓰기 용량을 제대로 이해하고 설정하면 안정적인 서비스를 저렴한 비용으로 운영할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 처음에는 Auto Scaling을 설정해두고 모니터링하면서 최적값을 찾으세요
- CloudWatch 알람으로 용량 초과 상황을 실시간 감지하세요
5. 온디맨드 vs 프로비저닝
김개발 씨는 이제 테이블을 실전에 투입할 준비가 되었습니다. 그런데 팀장님이 마지막으로 물었습니다.
"온디맨드로 갈까요, 프로비저닝으로 갈까요?" 김개발 씨는 두 가지의 차이를 제대로 이해하지 못했습니다. 어떤 걸 선택해야 할까요?
온디맨드 모드는 사용한 만큼만 비용을 내는 방식이고, 프로비저닝 모드는 미리 용량을 예약하고 고정 비용을 내는 방식입니다. 마치 휴대폰 요금제에서 데이터 무제한 요금제와 정량 요금제의 차이와 같습니다.
각각 장단점이 뚜렷하기 때문에 서비스 특성에 맞게 선택해야 합니다.
다음 코드를 살펴봅시다.
import boto3
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-2')
# 온디맨드 모드 테이블
on_demand_table = dynamodb.create_table(
TableName='EventLogs',
KeySchema=[
{'AttributeName': 'event_id', 'KeyType': 'HASH'}
],
AttributeDefinitions=[
{'AttributeName': 'event_id', 'AttributeType': 'S'}
],
BillingMode='PAY_PER_REQUEST' # 사용한 만큼만 과금
)
# 프로비저닝 모드 테이블
provisioned_table = dynamodb.create_table(
TableName='UserSessions',
KeySchema=[
{'AttributeName': 'session_id', 'KeyType': 'HASH'}
],
AttributeDefinitions=[
{'AttributeName': 'session_id', 'AttributeType': 'S'}
],
BillingMode='PROVISIONED',
ProvisionedThroughput={
'ReadCapacityUnits': 100,
'WriteCapacityUnits': 100
}
)
# 나중에 모드 변경 가능 (하루 1회 제한)
client = boto3.client('dynamodb', region_name='ap-northeast-2')
client.update_table(
TableName='EventLogs',
BillingMode='PROVISIONED',
ProvisionedThroughput={
'ReadCapacityUnits': 50,
'WriteCapacityUnits': 50
}
)
김개발 씨는 두 가지 모드의 차이를 듣고 고민에 빠졌습니다. "우리 서비스 트래픽이 얼마나 나올지 아직 모르는데, 어떻게 선택하죠?" 박시니어 씨가 경험담을 들려주었습니다.
"제가 지난 프로젝트에서 처음에는 온디맨드로 시작했어요. 트래픽 패턴을 파악한 후 프로비저닝으로 바꿨더니 비용이 절반으로 줄었죠." 그렇다면 두 모드는 정확히 어떻게 다를까요?
쉽게 비유하자면, 온디맨드는 택시를 타는 것과 같습니다. 필요할 때만 타고, 탄 만큼만 요금을 냅니다.
프로비저닝은 자가용을 리스하는 것과 같습니다. 매달 고정 비용을 내지만, 많이 타면 탈수록 킬로미터당 단가가 저렴해집니다.
어느 것이 유리한지는 사용 패턴에 달려 있습니다. 과금 방식이 복잡하던 시절에는 어땠을까요?
예측 불가능한 트래픽을 가진 신규 서비스는 프로비저닝 모드를 쓰기 부담스러웠습니다. 용량을 너무 많이 설정하면 낭비고, 너무 적게 설정하면 장애가 발생했습니다.
반대로 트래픽이 안정적인 서비스도 온디맨드만 쓰면 비용이 비쌌습니다. 더 큰 문제는 예산 수립이 어렵다는 점이었습니다.
다음 달 AWS 청구서가 얼마나 나올지 예측하기 힘들었습니다. 바로 이런 문제를 해결하기 위해 두 가지 모드를 선택할 수 있게 되었습니다.
온디맨드 모드를 사용하면 용량 계획이 불필요해집니다. DynamoDB가 알아서 무한대로 확장합니다.
또한 즉시 시작할 수 있습니다. 복잡한 설정 없이 바로 서비스를 시작할 수 있습니다.
무엇보다 트래픽 급증에 강하다는 큰 이점이 있습니다. 갑자기 사용자가 100배 늘어나도 문제없이 처리됩니다.
반면 프로비저닝 모드는 비용 절감이 가능합니다. 같은 트래픽이라면 온디맨드보다 최대 5배까지 저렴할 수 있습니다.
또한 예산 예측이 쉬워집니다. 매달 고정 비용을 계산할 수 있습니다.
무엇보다 Reserved Capacity를 구매하면 추가 할인을 받을 수 있습니다. 위의 코드를 비교해서 살펴보겠습니다.
온디맨드 모드는 BillingMode만 'PAY_PER_REQUEST'로 설정하면 끝입니다. ProvisionedThroughput을 지정할 필요가 없습니다.
프로비저닝 모드는 BillingMode를 'PROVISIONED'로 설정하고, 추가로 RCU와 WCU를 지정해야 합니다. 중요한 점은 하루에 한 번만 모드를 변경할 수 있다는 제한이 있습니다.
따라서 신중하게 결정해야 합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 스타트업이 신규 서비스를 론칭한다고 가정해봅시다. 처음에는 트래픽을 예측할 수 없으니 온디맨드로 시작합니다.
3개월 정도 운영하면서 CloudWatch로 트래픽 패턴을 분석합니다. 만약 트래픽이 안정적이고 예측 가능하다면 프로비저닝으로 전환해서 비용을 절감합니다.
많은 성공한 스타트업들이 이런 전략을 사용합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 무조건 온디맨드가 편하다고 계속 쓰는 것입니다. 트래픽이 안정화된 후에도 온디맨드를 쓰면 불필요한 비용을 낭비합니다.
따라서 정기적으로 비용을 리뷰해야 합니다. AWS Cost Explorer로 월별 비용 추이를 확인하세요.
또 하나 조심해야 할 점은 개발 환경입니다. 개발/테스트 환경에서는 온디맨드를 쓰는 것이 합리적입니다.
사용량이 적고 불규칙하기 때문입니다. 반면 프로덕션 환경에서는 트래픽이 안정화되면 프로비저닝을 고려해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 팀장님과 상의한 결과, 처음에는 온디맨드로 시작하기로 결정했습니다.
"일단 출시하고, 데이터를 보면서 최적화하는 게 맞겠어요." 온디맨드와 프로비저닝의 차이를 제대로 이해하면 서비스 단계에 맞는 현명한 선택을 할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 신규 서비스는 온디맨드로 시작해서 트래픽 패턴을 파악하세요
- 월 트래픽이 예측 가능하고 안정적이라면 프로비저닝으로 전환해서 비용을 절감하세요
6. 글로벌 보조 인덱스
서비스를 성공적으로 출시한 김개발 씨는 새로운 요구사항을 받았습니다. "사용자 이메일로도 검색할 수 있게 해주세요." 하지만 테이블의 파티션 키는 user_id입니다.
키를 바꾸려면 테이블을 새로 만들어야 할까요? 박시니어 씨가 웃으며 말했습니다.
"GSI를 쓰면 돼요."
**글로벌 보조 인덱스(GSI)**는 기본 키가 아닌 다른 속성으로도 빠르게 조회할 수 있게 해주는 기능입니다. 마치 책의 뒷면에 있는 색인처럼, 여러 방식으로 데이터를 찾을 수 있게 해줍니다.
테이블을 재생성하지 않고도 다양한 접근 패턴을 지원할 수 있습니다.
다음 코드를 살펴봅시다.
import boto3
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-2')
# GSI를 포함한 테이블 생성
table = dynamodb.create_table(
TableName='Users',
KeySchema=[
{'AttributeName': 'user_id', 'KeyType': 'HASH'}
],
AttributeDefinitions=[
{'AttributeName': 'user_id', 'AttributeType': 'S'},
{'AttributeName': 'email', 'AttributeType': 'S'},
{'AttributeName': 'created_at', 'AttributeType': 'S'}
],
GlobalSecondaryIndexes=[
{
'IndexName': 'EmailIndex',
'KeySchema': [
{'AttributeName': 'email', 'KeyType': 'HASH'},
{'AttributeName': 'created_at', 'KeyType': 'RANGE'}
],
'Projection': {
'ProjectionType': 'ALL' # 모든 속성 포함
},
'ProvisionedThroughput': {
'ReadCapacityUnits': 5,
'WriteCapacityUnits': 5
}
}
],
BillingMode='PROVISIONED',
ProvisionedThroughput={
'ReadCapacityUnits': 10,
'WriteCapacityUnits': 10
}
)
table.wait_until_exists()
# GSI를 사용한 쿼리
response = table.query(
IndexName='EmailIndex',
KeyConditionExpression='email = :email',
ExpressionAttributeValues={
':email': 'user@example.com'
}
)
print(response['Items'])
김개발 씨는 처음에 당황했습니다. "user_id로만 조회할 수 있다고 배웠는데, 이메일로도 조회하라니요?" 하지만 GSI라는 강력한 기능이 있다는 것을 알게 되었습니다.
박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "GSI는 원본 테이블과는 별도로 데이터를 다시 정렬해서 저장하는 거예요." 그렇다면 글로벌 보조 인덱스는 정확히 무엇일까요?
쉽게 비유하자면, GSI는 마치 도서관의 여러 검색 시스템과 같습니다. 같은 책을 제목으로도 찾을 수 있고, 저자 이름으로도 찾을 수 있고, ISBN으로도 찾을 수 있습니다.
책은 하나지만 찾는 방법은 여러 가지입니다. GSI도 같은 데이터를 여러 방식으로 조회할 수 있게 만들어줍니다.
GSI가 없던 시절에는 어땠을까요? 이메일로 사용자를 찾으려면 테이블 전체를 Scan해야 했습니다.
사용자가 100만 명이면 100만 개의 레코드를 모두 읽어야 했습니다. 엄청난 시간과 비용이 들었습니다.
더 큰 문제는 이런 Scan 작업이 테이블의 다른 쿼리에도 영향을 준다는 점이었습니다. 성능 저하가 서비스 전체로 퍼졌습니다.
바로 이런 문제를 해결하기 위해 GSI가 등장했습니다. GSI를 사용하면 추가 접근 패턴이 가능해집니다.
한 테이블에 여러 개의 GSI를 만들어서 다양한 방식으로 조회할 수 있습니다. 또한 독립적인 용량을 설정할 수 있습니다.
메인 테이블과 GSI의 RCU/WCU를 따로 관리할 수 있습니다. 무엇보다 빠른 조회 속도라는 큰 이점이 있습니다.
Scan 대신 Query를 사용할 수 있어 밀리초 단위로 응답합니다. 위의 코드를 단계별로 살펴보겠습니다.
GlobalSecondaryIndexes 배열에 인덱스 정의를 추가합니다. IndexName은 쿼리할 때 사용할 이름입니다.
KeySchema에서 이 인덱스의 파티션 키와 정렬 키를 지정합니다. email을 파티션 키로, created_at을 정렬 키로 설정했습니다.
Projection은 인덱스에 어떤 속성을 포함할지 결정합니다. 'ALL'은 모든 속성, 'KEYS_ONLY'는 키만, 'INCLUDE'는 특정 속성만 포함합니다.
쿼리할 때는 IndexName을 지정해서 GSI를 사용합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 회원 가입 시스템을 개발한다고 가정해봅시다. user_id로는 빠르게 조회하지만, 로그인할 때는 이메일을 입력받습니다.
이럴 때 email을 파티션 키로 하는 GSI를 만들면 로그인 속도가 극적으로 빨라집니다. 대부분의 SaaS 서비스가 이런 패턴을 사용합니다.
또 다른 예시로, 전자상거래 사이트를 생각해봅시다. 주문 테이블의 기본 키는 order_id이지만, "특정 날짜의 모든 주문"을 조회해야 할 때가 있습니다.
order_date를 파티션 키로 하는 GSI를 만들면 일별 매출 리포트를 빠르게 생성할 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 GSI를 너무 많이 만드는 것입니다. GSI 하나당 추가 비용이 발생하고, 쓰기 성능도 영향을 받습니다.
따라서 꼭 필요한 접근 패턴만 GSI로 만들어야 합니다. 일반적으로 테이블당 5개 이하로 유지하는 것이 좋습니다.
또 하나 조심해야 할 점은 결과적 일관성입니다. GSI는 기본적으로 결과적 일관성만 제공합니다.
데이터를 쓴 직후에 GSI로 조회하면 아직 반영되지 않을 수 있습니다. 보통 1초 이내에 반영되지만, 실시간성이 중요한 경우 주의해야 합니다.
Projection 설정도 신중해야 합니다. 'ALL'로 설정하면 편하지만 저장 공간과 비용이 늘어납니다.
자주 조회하는 속성만 'INCLUDE'로 지정하면 비용을 절감할 수 있습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
EmailIndex를 만들고 테스트한 김개발 씨는 감탄했습니다. "와, 정말 빠르네요!
Scan보다 수백 배는 빠른 것 같아요." 박시니어 씨가 고개를 끄덕였습니다. "맞아요.
GSI를 제대로 활용하면 DynamoDB의 진가를 발휘할 수 있어요." 글로벌 보조 인덱스를 제대로 이해하고 설계하면 다양한 조회 패턴을 빠르고 효율적으로 지원할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - GSI는 최대 20개까지 만들 수 있지만, 꼭 필요한 것만 만드세요
- Projection을 'KEYS_ONLY'나 'INCLUDE'로 설정하면 비용을 절감할 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
Spring MongoDB 완벽 가이드
MongoDB와 NoSQL의 핵심 개념부터 Spring Data MongoDB를 활용한 실전 CRUD까지, 초급 개발자도 쉽게 따라할 수 있는 완벽한 가이드입니다. JPA와의 비교를 통해 언제 MongoDB를 사용해야 하는지 명확하게 이해할 수 있습니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.