NoSQL 데이터베이스

NoSQL 데이터베이스

1. NoSQL 개요

1.1 NoSQL vs RDBMS

항목 RDBMS NoSQL
스키마 엄격한 스키마 유연한 스키마
확장성 수직 확장 수평 확장
트랜잭션 ACID BASE (일부 ACID)
쿼리 SQL 다양한 API
사용 사례 트랜잭션, 복잡한 관계 대용량, 유연한 데이터

1.2 서비스 비교

유형 AWS GCP
Key-Value / Document DynamoDB Firestore
Wide Column - Bigtable
In-Memory Cache ElastiCache Memorystore
Document (MongoDB) DocumentDB MongoDB Atlas (마켓플레이스)

2. AWS DynamoDB

2.1 DynamoDB 개요

특징: - 완전 관리형 Key-Value / Document DB - 밀리초 지연 시간 - 무한 확장 - 서버리스 (온디맨드 용량)

핵심 개념: - 테이블: 데이터 컨테이너 - 항목 (Item): 레코드 - 속성 (Attribute): 필드 - Primary Key: 파티션 키 + (선택적) 정렬 키

2.2 테이블 생성

# 테이블 생성 (파티션 키만)
aws dynamodb create-table \
    --table-name Users \
    --attribute-definitions \
        AttributeName=userId,AttributeType=S \
    --key-schema \
        AttributeName=userId,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST

# 테이블 생성 (파티션 키 + 정렬 키)
aws dynamodb create-table \
    --table-name Orders \
    --attribute-definitions \
        AttributeName=customerId,AttributeType=S \
        AttributeName=orderId,AttributeType=S \
    --key-schema \
        AttributeName=customerId,KeyType=HASH \
        AttributeName=orderId,KeyType=RANGE \
    --billing-mode PAY_PER_REQUEST

# 테이블 목록
aws dynamodb list-tables

# 테이블 정보
aws dynamodb describe-table --table-name Users

2.3 CRUD 작업

# 항목 추가 (PutItem)
aws dynamodb put-item \
    --table-name Users \
    --item '{
        "userId": {"S": "user-001"},
        "name": {"S": "John Doe"},
        "email": {"S": "john@example.com"},
        "age": {"N": "30"}
    }'

# 항목 조회 (GetItem)
aws dynamodb get-item \
    --table-name Users \
    --key '{"userId": {"S": "user-001"}}'

# 항목 업데이트 (UpdateItem)
aws dynamodb update-item \
    --table-name Users \
    --key '{"userId": {"S": "user-001"}}' \
    --update-expression "SET age = :newAge" \
    --expression-attribute-values '{":newAge": {"N": "31"}}'

# 항목 삭제 (DeleteItem)
aws dynamodb delete-item \
    --table-name Users \
    --key '{"userId": {"S": "user-001"}}'

# 스캔 (전체 테이블)
aws dynamodb scan --table-name Users

# 쿼리 (파티션 키 기반)
aws dynamodb query \
    --table-name Orders \
    --key-condition-expression "customerId = :cid" \
    --expression-attribute-values '{":cid": {"S": "customer-001"}}'

2.4 Python SDK (boto3)

import boto3
from decimal import Decimal

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Users')

# 항목 추가
table.put_item(Item={
    'userId': 'user-002',
    'name': 'Jane Doe',
    'email': 'jane@example.com',
    'age': 25
})

# 항목 조회
response = table.get_item(Key={'userId': 'user-002'})
item = response.get('Item')

# 쿼리 (GSI 사용 시)
response = table.query(
    IndexName='email-index',
    KeyConditionExpression='email = :email',
    ExpressionAttributeValues={':email': 'jane@example.com'}
)

# 배치 쓰기
with table.batch_writer() as batch:
    for i in range(100):
        batch.put_item(Item={'userId': f'user-{i}', 'name': f'User {i}'})

2.5 글로벌 보조 인덱스 (GSI)

# GSI 추가
aws dynamodb update-table \
    --table-name Users \
    --attribute-definitions \
        AttributeName=email,AttributeType=S \
    --global-secondary-index-updates '[
        {
            "Create": {
                "IndexName": "email-index",
                "KeySchema": [{"AttributeName": "email", "KeyType": "HASH"}],
                "Projection": {"ProjectionType": "ALL"}
            }
        }
    ]'

2.6 DynamoDB Streams

변경 데이터 캡처 (CDC)를 위한 스트림입니다.

# 스트림 활성화
aws dynamodb update-table \
    --table-name Users \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

# Lambda 트리거 연결
aws lambda create-event-source-mapping \
    --function-name process-dynamodb \
    --event-source-arn arn:aws:dynamodb:...:table/Users/stream/xxx \
    --starting-position LATEST

3. GCP Firestore

3.1 Firestore 개요

특징: - 문서 기반 NoSQL DB - 실시간 동기화 - 오프라인 지원 - 자동 확장

핵심 개념: - 컬렉션: 문서 그룹 - 문서: JSON과 유사한 데이터 - 하위 컬렉션: 계층 구조

3.2 Firestore 설정

# Firestore API 활성화
gcloud services enable firestore.googleapis.com

# 데이터베이스 생성 (Native 모드)
gcloud firestore databases create \
    --location=asia-northeast3 \
    --type=firestore-native

3.3 Python SDK

from google.cloud import firestore

db = firestore.Client()

# 문서 추가 (자동 ID)
doc_ref = db.collection('users').add({
    'name': 'John Doe',
    'email': 'john@example.com',
    'age': 30
})

# 문서 추가/업데이트 (지정 ID)
db.collection('users').document('user-001').set({
    'name': 'Jane Doe',
    'email': 'jane@example.com',
    'age': 25
})

# 문서 조회
doc = db.collection('users').document('user-001').get()
if doc.exists:
    print(doc.to_dict())

# 부분 업데이트
db.collection('users').document('user-001').update({
    'age': 26
})

# 문서 삭제
db.collection('users').document('user-001').delete()

# 쿼리
users = db.collection('users').where('age', '>=', 25).stream()
for user in users:
    print(f'{user.id} => {user.to_dict()}')

# 복합 쿼리 (인덱스 필요)
users = db.collection('users') \
    .where('age', '>=', 25) \
    .order_by('age') \
    .limit(10) \
    .stream()

3.4 실시간 리스너

# 문서 변경 감지
def on_snapshot(doc_snapshot, changes, read_time):
    for doc in doc_snapshot:
        print(f'Received document snapshot: {doc.id}')

doc_ref = db.collection('users').document('user-001')
doc_watch = doc_ref.on_snapshot(on_snapshot)

# 컬렉션 변경 감지
col_ref = db.collection('users')
col_watch = col_ref.on_snapshot(on_snapshot)

3.5 보안 규칙

// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 인증된 사용자만 자신의 문서 접근
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }

    // 공개 읽기
    match /public/{document=**} {
      allow read: if true;
      allow write: if request.auth != null;
    }
  }
}
# 보안 규칙 배포
firebase deploy --only firestore:rules

4. 인메모리 캐시

4.1 AWS ElastiCache

지원 엔진: - Redis - Memcached

# Redis 클러스터 생성
aws elasticache create-cache-cluster \
    --cache-cluster-id my-redis \
    --engine redis \
    --cache-node-type cache.t3.micro \
    --num-cache-nodes 1 \
    --cache-subnet-group-name my-subnet-group \
    --security-group-ids sg-12345678

# 복제 그룹 생성 (고가용성)
aws elasticache create-replication-group \
    --replication-group-id my-redis-cluster \
    --replication-group-description "Redis cluster" \
    --engine redis \
    --cache-node-type cache.t3.micro \
    --num-node-groups 1 \
    --replicas-per-node-group 1 \
    --automatic-failover-enabled \
    --cache-subnet-group-name my-subnet-group

# 엔드포인트 확인
aws elasticache describe-cache-clusters \
    --cache-cluster-id my-redis \
    --show-cache-node-info

Python 연결:

import redis

# 단일 노드
r = redis.Redis(
    host='my-redis.xxx.cache.amazonaws.com',
    port=6379,
    decode_responses=True
)

# SET/GET
r.set('key', 'value')
value = r.get('key')

# 해시
r.hset('user:1000', mapping={'name': 'John', 'email': 'john@example.com'})
user = r.hgetall('user:1000')

# TTL
r.setex('session:abc', 3600, 'user_data')

4.2 GCP Memorystore

지원 엔진: - Redis - Memcached

# Redis 인스턴스 생성
gcloud redis instances create my-redis \
    --region=asia-northeast3 \
    --tier=BASIC \
    --size=1 \
    --redis-version=redis_6_x

# 인스턴스 정보 확인
gcloud redis instances describe my-redis \
    --region=asia-northeast3

# 연결 정보 (호스트/포트)
gcloud redis instances describe my-redis \
    --region=asia-northeast3 \
    --format='value(host,port)'

연결:

import redis

# Memorystore Redis (Private IP)
r = redis.Redis(
    host='10.0.0.3',  # Private IP
    port=6379,
    decode_responses=True
)

r.set('hello', 'world')
print(r.get('hello'))

5. 용량 모드

5.1 DynamoDB 용량 모드

모드 특징 적합한 경우
온디맨드 자동 확장, 요청당 과금 트래픽 예측 불가
프로비저닝 용량 사전 지정 안정적 트래픽
# 온디맨드 모드
aws dynamodb update-table \
    --table-name Users \
    --billing-mode PAY_PER_REQUEST

# 프로비저닝 모드
aws dynamodb update-table \
    --table-name Users \
    --billing-mode PROVISIONED \
    --provisioned-throughput ReadCapacityUnits=100,WriteCapacityUnits=100

# Auto Scaling 설정
aws application-autoscaling register-scalable-target \
    --service-namespace dynamodb \
    --resource-id "table/Users" \
    --scalable-dimension "dynamodb:table:ReadCapacityUnits" \
    --min-capacity 5 \
    --max-capacity 1000

5.2 Firestore 용량

Firestore는 완전 서버리스로 자동 확장됩니다.

과금: - 문서 읽기: $0.06 / 100,000 - 문서 쓰기: $0.18 / 100,000 - 문서 삭제: $0.02 / 100,000 - 스토리지: $0.18 / GB / 월


6. 비용 비교

6.1 DynamoDB

항목 온디맨드 프로비저닝
읽기 $0.25 / 100만 RRU | $0.00013 / RCU / 시간
쓰기 $1.25 / 100만 WRU | $0.00065 / WCU / 시간
스토리지 $0.25 / GB / 월 | $0.25 / GB / 월

6.2 Firestore

항목 비용
문서 읽기 $0.06 / 100,000
문서 쓰기 $0.18 / 100,000
스토리지 $0.18 / GB / 월

6.3 ElastiCache / Memorystore

서비스 노드 타입 시간당 비용
ElastiCache cache.t3.micro ~$0.02
ElastiCache cache.r5.large ~$0.20
Memorystore 1GB Basic ~$0.05
Memorystore 1GB Standard (HA) ~$0.10

7. 사용 사례별 선택

사용 사례 권장 서비스
세션 관리 ElastiCache / Memorystore
사용자 프로필 DynamoDB / Firestore
실시간 채팅 Firestore (실시간 동기화)
게임 리더보드 ElastiCache Redis
IoT 데이터 DynamoDB / Bigtable
장바구니 DynamoDB / Firestore
캐싱 ElastiCache / Memorystore

8. 설계 패턴

8.1 DynamoDB 단일 테이블 설계

PK              | SK              | 속성
----------------|-----------------|------------------
USER#123        | USER#123        | name, email
USER#123        | ORDER#001       | product, quantity
USER#123        | ORDER#002       | product, quantity
PRODUCT#A       | PRODUCT#A       | name, price
PRODUCT#A       | REVIEW#001      | rating, comment

8.2 캐시 패턴

Cache-Aside (Lazy Loading):

def get_user(user_id):
    # 캐시 확인
    cached = cache.get(f'user:{user_id}')
    if cached:
        return cached

    # DB에서 조회
    user = db.get_user(user_id)

    # 캐시 저장
    cache.setex(f'user:{user_id}', 3600, user)
    return user

Write-Through:

def update_user(user_id, data):
    # DB 업데이트
    db.update_user(user_id, data)

    # 캐시 업데이트
    cache.set(f'user:{user_id}', data)

9. 다음 단계


참고 자료

to navigate between lessons