Skip to content
Published on

데이터베이스 완전 비교 2025: PostgreSQL vs MySQL vs MongoDB vs Redis — 선택 기준과 실전 가이드

Authors

들어가며

2025년 데이터베이스 생태계는 그 어느 때보다 다양하고 복잡합니다. AI/ML 워크로드의 폭발적 증가로 벡터 검색이 필수가 되었고, 실시간 스트리밍 처리, 멀티모델 지원, 서버리스 아키텍처 등 새로운 요구사항이 데이터베이스 선택을 더욱 어렵게 만들고 있습니다.

이 글에서는 PostgreSQL, MySQL, MongoDB, Redis를 중심으로 DynamoDB, Cassandra/ScyllaDB까지 8개 주요 데이터베이스의 아키텍처, 성능, 비용, 유스케이스를 체계적으로 비교합니다. 단순한 기능 나열이 아닌, 실제 프로덕션 환경에서의 의사결정에 도움이 되는 실전 가이드를 제공합니다.


1. 2025년 데이터베이스 랭킹과 트렌드

DB-Engines 랭킹 (2025년 3월 기준)

순위데이터베이스유형점수전년 대비
1OracleRDBMS1,245-3.2%
2MySQLRDBMS1,142-1.8%
3PostgreSQLRDBMS685+12.4%
4MongoDBDocument397+2.1%
5RedisKey-Value168-5.3%
6ElasticsearchSearch155+1.7%
7SQLiteRDBMS142+8.9%
8CassandraWide-Column112-4.1%

2025년 주요 트렌드

1. AI가 데이터베이스 선택을 바꾼다

RAG(Retrieval-Augmented Generation) 파이프라인이 보편화되면서 벡터 검색 기능이 데이터베이스 선택의 핵심 기준이 되었습니다. PostgreSQL의 pgvector, MongoDB Atlas Vector Search, Redis Stack이 이 경쟁에서 두각을 나타내고 있습니다.

2. 서버리스 데이터베이스의 부상

Neon(PostgreSQL 서버리스), PlanetScale(MySQL 서버리스), MongoDB Atlas Serverless, DynamoDB on-demand 등 사용량 기반 과금 모델이 표준이 되어가고 있습니다.

3. 멀티모델 데이터베이스

하나의 데이터베이스에서 관계형, 문서, 그래프, 시계열 데이터를 모두 처리하는 멀티모델 접근이 증가하고 있습니다. PostgreSQL이 JSONB, pgvector, PostGIS 등 확장으로 이 영역에서 가장 앞서가고 있습니다.

4. Valkey의 등장

Redis Labs의 라이선스 변경(SSPL)에 대응하여 Linux Foundation이 Valkey 포크를 출시했습니다. AWS ElastiCache, Google Cloud Memorystore 등이 Valkey를 채택하면서 Redis 생태계에 큰 변화가 일어나고 있습니다.


2. RDBMS vs NoSQL vs NewSQL — 언제 무엇을 써야 하는가

CAP 정리의 실전 이해

분산 시스템에서 Consistency(일관성), Availability(가용성), Partition Tolerance(분할 내성) 세 가지를 동시에 완벽하게 달성할 수 없다는 이론입니다.

         Consistency (C)
            /\
           /  \
          /    \
    CP   / CAP  \ CA
   시스템 / 불가능 \ 시스템
        /________\
  Partition(P)   Availability(A)
유형조합데이터베이스특징
CA일관성 + 가용성전통 RDBMS (단일 노드)네트워크 파티션 시 불가용
CP일관성 + 분할내성MongoDB, HBase가용성 일부 포기
AP가용성 + 분할내성Cassandra, DynamoDB최종적 일관성

RDBMS를 선택해야 할 때

  • 트랜잭션 무결성이 필수 (금융, 결제, 재고 관리)
  • 복잡한 JOIN과 집계 쿼리가 빈번
  • 데이터 스키마가 명확하고 안정적
  • ACID 준수가 법적/규제적 요구사항

NoSQL을 선택해야 할 때

  • 스키마가 자주 변경되거나 비정형 데이터
  • 수평 확장(horizontal scaling)이 핵심 요구사항
  • 초저지연(sub-millisecond) 읽기/쓰기 필요
  • 대용량 데이터의 빠른 쓰기 처리

NewSQL을 선택해야 할 때

CockroachDB, TiDB, YugabyteDB 같은 NewSQL 데이터베이스는 RDBMS의 트랜잭션 보장과 NoSQL의 수평 확장성을 모두 제공합니다.

  • 글로벌 분산이 필요한 트랜잭션 시스템
  • RDBMS 호환성을 유지하면서 수평 확장이 필요
  • 다만, 성숙도와 커뮤니티 크기에서 전통 RDBMS에 비해 아직 부족

3. PostgreSQL 딥 다이브

아키텍처 개요

PostgreSQL은 프로세스 기반 아키텍처를 사용합니다. 각 클라이언트 연결마다 별도의 백엔드 프로세스가 할당됩니다.

┌─────────────────────────────────────────────┐
PostgreSQL│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ Backend  │  │ Backend  │  │ Backend  │  │
│  │ Process  │  │ Process  │  │ Process  │  │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  │
│       │             │             │         │
│  ┌────┴─────────────┴─────────────┴────┐    │
│  │          Shared Memory              │    │
│  │  ┌──────────┐  ┌──────────────┐    │    │
│  │  │ Shared   │  │    WAL       │    │    │
│  │  │ Buffers  │  │  Buffers     │    │    │
│  │  └──────────┘  └──────────────┘    │    │
│  └─────────────────────────────────────┘    │
│                                             │
│  ┌─────────────────────────────────────┐    │
│  │     Background Workers              │    │
│  │  WAL Writer | Checkpointer |        │    │
│  │  Autovacuum | Stats Collector       │    │
│  └─────────────────────────────────────┘    │
└─────────────────────────────────────────────┘

MVCC (Multi-Version Concurrency Control)

PostgreSQL의 MVCC는 각 행의 여러 버전을 유지하여 읽기/쓰기 간 잠금 충돌을 최소화합니다.

-- 트랜잭션 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- MVCC 동작 확인
BEGIN;
SELECT xmin, xmax, ctid, * FROM orders WHERE id = 1;
-- xmin: 생성 트랜잭션 ID, xmax: 삭제 트랜잭션 ID
-- ctid: 물리적 위치 (page, offset)
COMMIT;

JSONB — 관계형 + 문서형의 최강 조합

-- JSONB 컬럼 생성
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    metadata JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- GIN 인덱스 생성 (JSONB 검색 최적화)
CREATE INDEX idx_products_metadata ON products USING GIN (metadata);

-- JSONB 쿼리 예시
SELECT name, metadata->>'category' AS category,
       (metadata->'specs'->>'weight')::numeric AS weight
FROM products
WHERE metadata @> '{"category": "electronics"}'
  AND (metadata->'specs'->>'weight')::numeric < 500;

-- JSONB 집계
SELECT metadata->>'category' AS category,
       COUNT(*),
       AVG((metadata->'price'->>'amount')::numeric) AS avg_price
FROM products
GROUP BY metadata->>'category';

pgvector — AI/RAG 시대의 게임 체인저

-- pgvector 확장 설치
CREATE EXTENSION IF NOT EXISTS vector;

-- 벡터 컬럼이 있는 테이블
CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    embedding vector(1536)  -- OpenAI text-embedding-3-small 차원
);

-- HNSW 인덱스 (빠른 근사 최근접 이웃 검색)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- IVFFlat 인덱스 (메모리 효율적)
CREATE INDEX ON documents
USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);

-- 코사인 유사도 기반 검색
SELECT id, title,
       1 - (embedding <=> query_embedding) AS similarity
FROM documents
ORDER BY embedding <=> query_embedding
LIMIT 10;

-- 하이브리드 검색 (벡터 + 전문 검색)
SELECT id, title,
       ts_rank(to_tsvector('korean', content), plainto_tsquery('korean', '검색어')) AS text_score,
       1 - (embedding <=> query_embedding) AS vector_score
FROM documents
WHERE to_tsvector('korean', content) @@ plainto_tsquery('korean', '검색어')
ORDER BY vector_score * 0.7 + text_score * 0.3 DESC
LIMIT 10;

pg_trgm — 퍼지 검색과 유사도 매칭

CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- 트라이그램 인덱스
CREATE INDEX idx_products_name_trgm ON products
USING GIN (name gin_trgm_ops);

-- 유사도 검색 (오타 허용)
SELECT name, similarity(name, 'PostgreSLQ') AS sim
FROM products
WHERE name % 'PostgreSLQ'
ORDER BY sim DESC;

파티셔닝

-- 범위 파티셔닝 예시
CREATE TABLE orders (
    id BIGSERIAL,
    order_date DATE NOT NULL,
    customer_id INTEGER NOT NULL,
    total_amount NUMERIC(10,2),
    PRIMARY KEY (id, order_date)
) PARTITION BY RANGE (order_date);

-- 파티션 생성
CREATE TABLE orders_2025_q1 PARTITION OF orders
    FOR VALUES FROM ('2025-01-01') TO ('2025-04-01');
CREATE TABLE orders_2025_q2 PARTITION OF orders
    FOR VALUES FROM ('2025-04-01') TO ('2025-07-01');

-- 자동 파티션 관리 (pg_partman)
CREATE EXTENSION pg_partman;
SELECT partman.create_parent(
    p_parent_table => 'public.orders',
    p_control => 'order_date',
    p_type => 'native',
    p_interval => '3 months'
);

PostgreSQL 17 주요 기능

  • Incremental Backup: pg_basebackup에서 증분 백업 지원
  • SQL/JSON 표준 지원 강화: JSON_TABLE, JSON_QUERY 등
  • 성능 향상: 병렬 해시 조인 개선, 벌크 로딩 최적화
  • 보안 강화: LDAP/SCRAM 인증 개선

4. MySQL 딥 다이브

InnoDB 아키텍처

┌─────────────────────────────────────────────┐
MySQL Server│  ┌───────────────────────────────────────┐  │
│  │          Connection Pool              │  │
│  │   Thread 1 | Thread 2 | Thread N     │  │
│  └───────────────────────────────────────┘  │
│  ┌───────────────────────────────────────┐  │
│  │          Query Cache (제거됨 8.0+)     │  │
│  │          Parser -> Optimizer          │  │
│  └───────────────────────────────────────┘  │
│  ┌───────────────────────────────────────┐  │
│  │            InnoDB Engine              │  │
│  │  ┌──────────┐  ┌──────────────────┐  │  │
│  │  │ Buffer   │  │  Change Buffer   │  │  │
│  │  │ Pool     │  │                  │  │  │
│  │  └──────────┘  └──────────────────┘  │  │
│  │  ┌──────────┐  ┌──────────────────┐  │  │
│  │  │ Redo Log │  │  Undo Log        │  │  │
│  │  └──────────┘  └──────────────────┘  │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘

MySQL은 스레드 기반 아키텍처로, PostgreSQL의 프로세스 기반과 대비됩니다. 이로 인해 연결 오버헤드가 낮고 메모리 효율이 높습니다.

MySQL vs PostgreSQL 핵심 차이

-- MySQL: UPSERT (INSERT ... ON DUPLICATE KEY)
INSERT INTO products (id, name, stock)
VALUES (1, 'Widget', 100)
ON DUPLICATE KEY UPDATE stock = stock + VALUES(stock);

-- PostgreSQL: UPSERT (INSERT ... ON CONFLICT)
INSERT INTO products (id, name, stock)
VALUES (1, 'Widget', 100)
ON CONFLICT (id)
DO UPDATE SET stock = products.stock + EXCLUDED.stock;

HeatWave ML — MySQL에서 직접 ML

-- HeatWave ML로 모델 학습
CALL sys.ML_TRAIN('my_schema.customer_data',
    'churn_label',
    JSON_OBJECT('task', 'classification'),
    @model_handle);

-- 예측 실행
CALL sys.ML_PREDICT_ROW(
    JSON_OBJECT('age', 35, 'tenure', 24, 'monthly_charge', 79.99),
    @model_handle,
    @prediction);

SELECT @prediction;

Group Replication (그룹 복제)

MySQL Group Replication은 Paxos 기반 합의 프로토콜을 사용하여 고가용성을 제공합니다.

-- Group Replication 상태 확인
SELECT MEMBER_ID, MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE
FROM performance_schema.replication_group_members;

-- 단일 프라이머리 모드 (기본, 권장)
-- 하나의 쓰기 노드, 나머지는 읽기 전용
SET GLOBAL group_replication_single_primary_mode = ON;

ProxySQL로 읽기/쓰기 분리

-- ProxySQL 라우팅 룰 설정
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup)
VALUES
    (1, 1, '^SELECT .* FOR UPDATE', 0),   -- 쓰기 그룹
    (2, 1, '^SELECT', 1);                  -- 읽기 그룹

-- 서버 그룹 설정
INSERT INTO mysql_servers (hostgroup_id, hostname, port, weight)
VALUES
    (0, 'primary.mysql.local', 3306, 1),    -- 쓰기
    (1, 'replica1.mysql.local', 3306, 50),  -- 읽기
    (1, 'replica2.mysql.local', 3306, 50);  -- 읽기

MySQL 9.0 주요 기능

  • JavaScript Stored Programs: GraalVM 기반 JavaScript 저장 프로시저
  • Vector Data Type: 벡터 데이터 타입 네이티브 지원
  • EXPLAIN 개선: FORMAT=TREE 기본값, 더 직관적인 실행 계획
  • 보안: OpenID Connect 인증 지원

5. MongoDB 딥 다이브

문서 모델의 강점

// MongoDB 문서 구조 — 임베딩 패턴
db.orders.insertOne({
  orderId: 'ORD-2025-001',
  customer: {
    name: '김개발',
    email: 'kim@example.com',
    address: {
      street: '강남대로 123',
      city: '서울',
      zipCode: '06123',
    },
  },
  items: [
    {
      productId: 'PROD-001',
      name: '무선 키보드',
      price: 89000,
      quantity: 2,
    },
    {
      productId: 'PROD-002',
      name: '모니터 스탠드',
      price: 45000,
      quantity: 1,
    },
  ],
  payment: {
    method: 'card',
    status: 'completed',
    paidAt: ISODate('2025-03-15T10:30:00Z'),
  },
  totalAmount: 223000,
  createdAt: ISODate('2025-03-15T10:25:00Z'),
})
// 벡터 검색 인덱스 생성
db.collection.createSearchIndex({
  name: 'vector_index',
  definition: {
    mappings: {
      dynamic: true,
      fields: {
        embedding: {
          type: 'knnVector',
          dimensions: 1536,
          similarity: 'cosine',
        },
      },
    },
  },
})

// 벡터 검색 쿼리
db.documents.aggregate([
  {
    $vectorSearch: {
      index: 'vector_index',
      path: 'embedding',
      queryVector: queryEmbedding, // 1536차원 벡터
      numCandidates: 100,
      limit: 10,
      filter: { category: 'tech' },
    },
  },
  {
    $project: {
      title: 1,
      content: 1,
      score: { $meta: 'vectorSearchScore' },
    },
  },
])

Aggregation Pipeline 고급 예시

// 월별 매출 분석 파이프라인
db.orders.aggregate([
  // 기간 필터
  {
    $match: {
      createdAt: {
        $gte: ISODate('2025-01-01'),
        $lt: ISODate('2026-01-01'),
      },
      'payment.status': 'completed',
    },
  },
  // 주문 항목 분해
  { $unwind: '$items' },
  // 월별 그룹핑
  {
    $group: {
      _id: {
        year: { $year: '$createdAt' },
        month: { $month: '$createdAt' },
        category: '$items.category',
      },
      totalRevenue: { $sum: { $multiply: ['$items.price', '$items.quantity'] } },
      orderCount: { $sum: 1 },
      avgOrderValue: { $avg: { $multiply: ['$items.price', '$items.quantity'] } },
    },
  },
  // 정렬
  { $sort: { '_id.year': 1, '_id.month': 1 } },
  // 출력 형태 변환
  {
    $project: {
      _id: 0,
      period: { $concat: [{ $toString: '$_id.year' }, '-', { $toString: '$_id.month' }] },
      category: '$_id.category',
      totalRevenue: 1,
      orderCount: 1,
      avgOrderValue: { $round: ['$avgOrderValue', 0] },
    },
  },
])

MongoDB 샤딩

// 샤드 키 선택 — 카디널리티, 분산도, 쿼리 패턴 고려
sh.enableSharding('ecommerce')

// 해시 샤딩 (균등 분산)
sh.shardCollection('ecommerce.orders', { customerId: 'hashed' })

// 범위 샤딩 (범위 쿼리 최적화)
sh.shardCollection('ecommerce.logs', { timestamp: 1 })

// 존 샤딩 (지역 기반 데이터 분리)
sh.addShardTag('shard-kr', 'ASIA')
sh.addShardTag('shard-us', 'AMERICAS')
sh.addTagRange('ecommerce.users', { region: 'KR' }, { region: 'KS' }, 'ASIA')

MongoDB 8.0 주요 기능

  • Queryable Encryption: 암호화된 상태에서 쿼리 가능
  • Cluster-to-Cluster Sync: 멀티 클라우드 동기화
  • Time Series Collection 개선: 더 효율적인 시계열 데이터 처리
  • 성능: 읽기/쓰기 성능 20-35% 향상

6. Redis 딥 다이브

데이터 구조와 활용

-- Strings: 캐시, 세션, 카운터
SET user:session:abc123 "session_data" EX 3600
INCR page:views:homepage

-- Hashes: 객체 저장
HSET user:1001 name "김개발" email "kim@dev.com" login_count 42
HINCRBY user:1001 login_count 1

-- Sorted Sets: 리더보드, 랭킹
ZADD leaderboard 9500 "player:alice"
ZADD leaderboard 8700 "player:bob"
ZADD leaderboard 9200 "player:charlie"
ZREVRANGE leaderboard 0 9 WITHSCORES  -- Top 10

-- Streams: 이벤트 스트리밍
XADD mystream * sensor_id "temp-01" temperature "23.5" humidity "45"
XREAD COUNT 10 BLOCK 5000 STREAMS mystream 0

-- HyperLogLog: 고유 카운트 (근사치)
PFADD unique:visitors:2025-03-23 "user1" "user2" "user3"
PFCOUNT unique:visitors:2025-03-23

Redis Stack — Search, JSON, TimeSeries

-- Redis JSON
JSON.SET product:001 $ '{"name":"무선 키보드","price":89000,"tags":["electronics","keyboard"],"specs":{"weight":350,"wireless":true}}'
JSON.GET product:001 $.specs.weight

-- Redis Search 인덱스 생성
FT.CREATE idx:products ON JSON PREFIX 1 product:
  SCHEMA
    $.name AS name TEXT SORTABLE
    $.price AS price NUMERIC SORTABLE
    $.tags[*] AS tags TAG
    $.specs.weight AS weight NUMERIC

-- 전문 검색
FT.SEARCH idx:products "@name:키보드 @price:[50000 100000]"

-- 벡터 검색 (Redis Stack)
FT.CREATE idx:docs ON HASH PREFIX 1 doc:
  SCHEMA
    title TEXT
    embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE

-- KNN 검색
FT.SEARCH idx:docs "*=>[KNN 10 @embedding $query_vec AS score]"
  PARAMS 2 query_vec "\x00\x00..."
  SORTBY score
  RETURN 2 title score

-- Redis TimeSeries
TS.CREATE temperature:sensor1 RETENTION 86400000 LABELS sensor_id temp-01 location office
TS.ADD temperature:sensor1 * 23.5
TS.RANGE temperature:sensor1 - + AGGREGATION avg 60000  -- 1분 평균

영속성: RDB vs AOF

특성RDB (스냅샷)AOF (Append Only File)
동작 방식주기적 메모리 덤프모든 쓰기 명령 로깅
데이터 손실마지막 스냅샷 이후fsync 정책에 따라 다름
복구 속도빠름 (바이너리 로드)느림 (명령 재실행)
파일 크기작음 (압축)큼 (모든 명령 기록)
CPU 영향fork() 시 높음everysec 시 낮음
# redis.conf 권장 설정
save 900 1       # 900초 동안 1개 이상 변경 시 RDB
save 300 10      # 300초 동안 10개 이상 변경 시 RDB
appendonly yes   # AOF 활성화
appendfsync everysec  # 1초마다 fsync (성능/안전 균형)

Redis Cluster

# 6노드 클러스터 생성 (3 마스터 + 3 레플리카)
redis-cli --cluster create \
  node1:6379 node2:6379 node3:6379 \
  node4:6379 node5:6379 node6:6379 \
  --cluster-replicas 1

Redis Cluster는 16384개의 해시 슬롯을 사용하여 키를 분산합니다. 키 공간은 CRC16 해시를 사용하여 슬롯에 매핑됩니다.

Valkey 포크 — Redis의 미래?

2024년 Redis Labs가 SSPL 라이선스로 전환한 후, Linux Foundation 주도로 Valkey가 탄생했습니다.

항목RedisValkey
라이선스SSPL (2024~)BSD-3-Clause
지원 클라우드Redis CloudAWS, GCP, Azure
호환성원본Redis 7.2 호환
개발 주체Redis Ltd.Linux Foundation
커뮤니티제한적 기여오픈 기여

7. DynamoDB — 서버리스 NoSQL의 대표

핵심 개념

┌───────────────────────────────────────────┐
DynamoDB Table│  ┌─────────────────────────────────────┐  │
│  │  Partition Key (PK)  Sort Key (SK)  │  │
│  ├─────────────────────────────────────┤  │
│  │  USER#001            PROFILE        │  │
│  │  USER#001            ORDER#001      │  │
│  │  USER#001            ORDER#002      │  │
│  │  USER#002            PROFILE        │  │
│  │  USER#002            ORDER#001      │  │
│  └─────────────────────────────────────┘  │
│                                           │
GSI: OrderDate-Index│  ┌─────────────────────────────────────┐  │
│  │  OrderDate (PK)     UserId (SK)     │  │
│  └─────────────────────────────────────┘  │
└───────────────────────────────────────────┘

Single-Table Design 패턴

import boto3

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

# 사용자 프로필 저장
table.put_item(Item={
    'PK': 'USER#001',
    'SK': 'PROFILE',
    'name': '김개발',
    'email': 'kim@dev.com',
    'type': 'USER'
})

# 같은 테이블에 주문 저장
table.put_item(Item={
    'PK': 'USER#001',
    'SK': 'ORDER#2025-03-15#ORD001',
    'items': [{'name': '키보드', 'price': 89000}],
    'total': 89000,
    'status': 'completed',
    'type': 'ORDER'
})

# 사용자의 모든 주문 조회 (Query)
response = table.query(
    KeyConditionExpression='PK = :pk AND begins_with(SK, :sk)',
    ExpressionAttributeValues={
        ':pk': 'USER#001',
        ':sk': 'ORDER#'
    }
)

DynamoDB 가격 모델

모드읽기 비용쓰기 비용적합한 경우
On-Demand0.25 USD/100만 RRU1.25 USD/100만 WRU트래픽 예측 불가
Provisioned0.00065 USD/RCU/시간0.00065 USD/WCU/시간안정적 트래픽
Reserved최대 77% 할인최대 77% 할인1년/3년 약정

8. Cassandra / ScyllaDB — 대규모 쓰기의 왕

Cassandra 아키텍처

Cassandra는 Dynamo 스타일의 분산 해시 테이블과 BigTable 스타일의 데이터 모델을 결합합니다.

┌────────┐  ┌────────┐  ┌────────┐
Node A │──│ Node B │──│ Node CToken: │  │ Token: │  │ Token:0-33   │  │ 34-66  │  │ 67-100└───┬────┘  └───┬────┘  └───┬────┘
    │           │           │
    └───────────┼───────────┘
            Gossip Protocol
-- 키스페이스 생성 (복제 전략 포함)
CREATE KEYSPACE ecommerce WITH replication = {
    'class': 'NetworkTopologyStrategy',
    'us-east': 3,
    'eu-west': 3
};

-- 테이블 생성 (파티션 키 + 클러스터링 키)
CREATE TABLE ecommerce.user_activity (
    user_id UUID,
    activity_date DATE,
    activity_time TIMESTAMP,
    activity_type TEXT,
    details MAP<TEXT, TEXT>,
    PRIMARY KEY ((user_id, activity_date), activity_time)
) WITH CLUSTERING ORDER BY (activity_time DESC)
  AND compaction = {'class': 'TimeWindowCompactionStrategy',
                    'compaction_window_size': 1,
                    'compaction_window_unit': 'DAYS'};

ScyllaDB — Cassandra 호환 고성능 대안

ScyllaDB는 C++로 재작성된 Cassandra 호환 데이터베이스입니다. Shard-per-core 아키텍처로 동일 하드웨어에서 3-10배 높은 성능을 제공합니다.

비교 항목CassandraScyllaDB
언어JavaC++ (Seastar)
아키텍처스레드 기반Shard-per-core
GC 영향GC pause 존재GC 없음
같은 하드웨어 성능기준3-10x
Cassandra 호환원본CQL 호환

9. 성능 벤치마크 비교

YCSB 벤치마크 (Yahoo Cloud Serving Benchmark)

다음은 동일 하드웨어(8 vCPU, 32GB RAM, NVMe SSD)에서의 비교입니다.

작업PostgreSQLMySQLMongoDBRedisDynamoDBScyllaDB
읽기 지연 (p99)2.1ms1.8ms1.5ms0.3ms4.2ms0.9ms
쓰기 지연 (p99)3.5ms2.9ms2.1ms0.4ms5.8ms1.2ms
읽기 TPS45K52K58K250K25K120K
쓰기 TPS28K35K42K200K20K85K
혼합 (50/50) TPS35K42K48K220K22K100K

주의: 벤치마크 결과는 워크로드 패턴, 데이터 크기, 인덱스 구성 등에 따라 크게 달라집니다.

확장성 비교

확장 방식PostgreSQLMySQLMongoDBRedisDynamoDBCassandra
수직 확장우수우수좋음좋음N/A좋음
읽기 확장복제본복제본복제본 세트클러스터자동노드 추가
쓰기 확장제한적제한적샤딩클러스터자동노드 추가
최대 데이터 크기수 TB수 TB수 PB수백 GB무제한수 PB

10. 유스케이스별 선택 매트릭스

최적 데이터베이스 선택 가이드

유스케이스1순위2순위이유
이커머스PostgreSQLMySQL트랜잭션 + JSONB 유연성
소셜 미디어MongoDBCassandra비정형 데이터 + 높은 쓰기
IoT 센서 데이터ScyllaDBDynamoDB대용량 시계열 쓰기
실시간 분석PostgreSQLClickHouse복잡한 집계 쿼리
RAG/AI 검색PostgreSQL (pgvector)MongoDB (Atlas Vector)벡터 + 전문 검색
캐시RedisDragonflyDB초저지연, 풍부한 데이터 구조
세션 저장소RedisDynamoDB빠른 읽기/쓰기, TTL
채팅/메시징MongoDBCassandra유연한 스키마, 시간순 정렬
게임 리더보드RedisDynamoDBSorted Set, 빠른 갱신
로그 관리ElasticsearchMongoDB전문 검색, 시계열
지리 공간PostgreSQL (PostGIS)MongoDBGIS 기능, 공간 인덱스
그래프 데이터Neo4jPostgreSQL (AGE)관계 탐색 최적화

11. 멀티 DB 아키텍처 패턴

Polyglot Persistence (다중 데이터베이스 전략)

┌─────────────────────────────────────────────────┐
Application Layer└───────┬──────────┬──────────┬──────────┬────────┘
        │          │          │          │
   ┌────▼────┐ ┌──▼───┐ ┌───▼───┐ ┌───▼────┐
   │PostgreSQL│ │Redis │ │MongoDB│ │Elastic │
   (주문/결제)(캐시)(상품)(검색)   └─────────┘ └──────┘ └───────┘ └────────┘

CQRS 패턴 (Command Query Responsibility Segregation)

쓰기 (Command):                   읽기 (Query):
┌──────────┐                     ┌──────────┐
Client   │                     │  Client└────┬─────┘                     └────┬─────┘
     │                                │
┌────▼─────┐                     ┌────▼─────┐
Command  │                     │  QueryService  │                     │ Service└────┬─────┘                     └────┬─────┘
     │                                │
┌────▼─────┐  ──이벤트──▶  ┌─────▼─────┐
│PostgreSQL│              │  Redis / (Source  │              │  MongoDBof Truth) (Read Model)└──────────┘              └───────────┘

Cache-Aside 패턴

import redis
import psycopg2
import json

r = redis.Redis(host='localhost', port=6379)

def get_product(product_id):
    # 1. 캐시 확인
    cache_key = f"product:{product_id}"
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)

    # 2. 캐시 미스 — DB 조회
    conn = psycopg2.connect("dbname=ecommerce")
    cur = conn.cursor()
    cur.execute("SELECT * FROM products WHERE id = %s", (product_id,))
    product = cur.fetchone()

    if product:
        # 3. 캐시에 저장 (TTL 1시간)
        r.setex(cache_key, 3600, json.dumps(product))

    return product

def update_product(product_id, data):
    # 1. DB 업데이트
    conn = psycopg2.connect("dbname=ecommerce")
    cur = conn.cursor()
    cur.execute("UPDATE products SET name=%s WHERE id=%s",
                (data['name'], product_id))
    conn.commit()

    # 2. 캐시 무효화
    r.delete(f"product:{product_id}")

12. 비용 비교 (매니지드 서비스)

월간 비용 예상 (중간 규모 서비스: 100GB 데이터, 5000 TPS)

서비스제공자인스턴스월 비용 (USD)
RDS PostgreSQLAWSdb.r6g.xlarge~350
Cloud SQL PostgreSQLGCPdb-custom-4-16384~320
RDS MySQLAWSdb.r6g.xlarge~350
Atlas MongoDBMongoDBM40~480
ElastiCache RedisAWScache.r6g.xlarge~450
DynamoDB On-DemandAWS-~180-600
Neon PostgreSQLNeonScale~69-189
PlanetScale MySQLPlanetScaleScaler Pro~39-299

비용 최적화 전략

  1. 예약 인스턴스: 1-3년 약정으로 40-70% 할인
  2. 서버리스 옵션: 트래픽 변동이 큰 경우 Neon, PlanetScale, DynamoDB on-demand
  3. 읽기 전용 복제본: 읽기 부하 분산으로 메인 인스턴스 스펙 절약
  4. 적절한 인스턴스 크기: CloudWatch/모니터링으로 실제 사용률 확인 후 조정
  5. 데이터 아카이빙: 오래된 데이터를 S3/Glacier로 이전

13. 면접 질문 20선

기본 개념 (1-5)

Q1: ACID와 BASE의 차이를 설명하세요.

ACID (Atomicity, Consistency, Isolation, Durability): 전통적 RDBMS의 트랜잭션 속성. 모든 작업이 완전히 성공하거나 완전히 실패하며, 데이터 일관성을 보장합니다.

BASE (Basically Available, Soft state, Eventually consistent): NoSQL의 접근 방식. 항상 사용 가능하지만 즉각적 일관성 대신 최종적 일관성을 보장합니다.

선택 기준: 금융/결제 시스템에는 ACID, SNS 피드/좋아요 카운터 같은 곳에는 BASE가 적합합니다.

Q2: CAP 정리를 실무 예시와 함께 설명하세요.
  • CP 시스템 (MongoDB): 네트워크 파티션 발생 시 일관성을 위해 일부 노드의 가용성을 포기. 프라이머리 선출 중 쓰기 불가.
  • AP 시스템 (Cassandra, DynamoDB): 네트워크 파티션에서도 모든 노드가 응답하지만, 노드 간 데이터가 일시적으로 다를 수 있음.
  • CA 시스템: 네트워크 파티션이 없는 단일 노드 시스템. 분산 환경에서는 사실상 존재하지 않음.

실무 팁: PACELC 정리로 확장하면, 파티션이 없을 때(Else) 지연(Latency)과 일관성(Consistency) 사이의 트레이드오프도 고려합니다.

Q3: 인덱스의 종류와 각각의 적합한 사용 사례를 설명하세요.
  • B-Tree 인덱스: 범위 검색, 정렬에 최적. 대부분의 기본 인덱스. O(log N)
  • Hash 인덱스: 동등 비교(=)에만 사용. O(1). PostgreSQL에서는 WAL 미지원 문제로 잘 안 쓰임
  • GIN (Generalized Inverted Index): 배열, JSONB, 전문 검색에 적합
  • GiST (Generalized Search Tree): 공간 데이터(PostGIS), 범위 타입에 적합
  • BRIN (Block Range Index): 물리적으로 정렬된 대용량 테이블(시계열)에 효과적. 메모리 효율적
  • Bloom 인덱스: 많은 컬럼에 대한 동등 비교 필터링
Q4: N+1 쿼리 문제가 무엇이고 어떻게 해결하나요?

N+1 문제: 1번의 쿼리로 N개의 결과를 가져온 후, 각 결과에 대해 추가 쿼리를 N번 실행하는 비효율적 패턴.

해결 방법:

  • JOIN: 관계형 DB에서 한 번의 쿼리로 데이터 조인
  • Eager Loading: ORM에서 관련 데이터를 미리 로드 (Django: select_related, prefetch_related)
  • DataLoader 패턴: GraphQL에서 배치 요청으로 묶어 처리
  • MongoDB: 임베딩 패턴으로 관련 데이터를 하나의 문서에 저장
Q5: 데이터베이스 정규화와 비정규화의 트레이드오프를 설명하세요.

정규화 (1NF-BCNF):

  • 장점: 데이터 중복 최소화, 일관성 유지 용이, 저장 공간 절약
  • 단점: JOIN 증가로 읽기 성능 저하, 복잡한 쿼리

비정규화:

  • 장점: 읽기 성능 향상 (JOIN 감소), 단순한 쿼리
  • 단점: 데이터 중복, 갱신 이상(anomaly) 위험, 저장 공간 증가

실무 가이드: OLTP는 3NF 이상 정규화, OLAP/읽기 집중 워크로드는 의도적 비정규화 검토.

심화 문제 (6-10)

Q6: PostgreSQL의 MVCC와 MySQL InnoDB의 MVCC 차이를 설명하세요.

PostgreSQL MVCC:

  • 각 행의 여러 버전을 테이블에 직접 저장 (xmin, xmax)
  • 오래된 버전은 VACUUM으로 정리
  • 장점: 읽기 시 잠금 없음, 높은 동시성
  • 단점: 테이블 팽창(bloat), VACUUM 오버헤드

MySQL InnoDB MVCC:

  • Undo Log에 이전 버전 저장
  • 현재 행에는 항상 최신 버전
  • Purge 스레드가 불필요한 Undo 정리
  • 장점: 테이블 팽창 없음, 간단한 관리
  • 단점: Undo 로그 크기 증가 시 성능 저하
Q7: 데이터베이스 샤딩 전략의 종류와 선택 기준은?
  1. 해시 샤딩: 키를 해시하여 균등 분산. 범위 쿼리 비효율적.
  2. 범위 샤딩: 키 범위로 분할. 핫스팟 위험. 시계열에 적합.
  3. 지리 기반 샤딩: 사용자 위치 기반 분할. 지역별 규제 준수에 유용.
  4. 디렉토리 기반 샤딩: 매핑 테이블로 유연한 라우팅. 관리 복잡도 높음.

샤드 키 선택 기준: 높은 카디널리티, 균등 분산, 쿼리 패턴과 일치.

Q8: Redis가 싱글 스레드인데 어떻게 빠른가요?
  1. 인메모리 처리: 모든 데이터가 RAM에 있어 디스크 I/O 없음
  2. I/O 멀티플렉싱: epoll/kqueue로 수천 연결을 단일 스레드로 처리
  3. 잠금 불필요: 싱글 스레드이므로 동시성 제어 오버헤드 없음
  4. 최적화된 자료 구조: SDS, ziplist, intset 등 메모리 효율적 구조
  5. Redis 6.0+: I/O 스레딩 도입 (명령 처리는 여전히 싱글 스레드)

참고: Redis 7.0+에서는 멀티스레드 I/O가 더 개선되어 네트워크 처리 성능이 향상되었습니다.

Q9: MongoDB에서 임베딩과 레퍼런싱 중 어떤 것을 선택해야 하나요?

임베딩 (하위 문서):

  • 함께 조회되는 데이터
  • 1:1 또는 1:소수 관계
  • 16MB 문서 크기 제한 내
  • 예: 주문과 주문 항목, 사용자와 주소

레퍼런싱 (별도 컬렉션):

  • 독립적으로 조회/업데이트되는 데이터
  • 1:다수 또는 다:다 관계
  • 문서 크기가 빠르게 증가하는 경우
  • 예: 블로그 글과 댓글, 상품과 리뷰

하이브리드 패턴: 자주 쓰는 데이터는 임베딩, 나머지는 레퍼런싱.

Q10: 커넥션 풀링이 왜 중요하고 어떻게 설정하나요?

DB 연결 생성 비용: TCP 핸드셰이크 + 인증 + 세션 할당 등으로 수~수십 ms 소요. 매 요청마다 연결을 생성하면 성능이 크게 저하됩니다.

설정 예시 (HikariCP):

  • maximumPoolSize: 일반적으로 CPU 코어 수 * 2 + 디스크 수
  • minimumIdle: 최소 유휴 연결 수
  • connectionTimeout: 풀에서 연결을 기다리는 최대 시간
  • idleTimeout: 유휴 연결 유지 시간

PostgreSQL은 프로세스 기반이므로 PgBouncer를 앞에 두어 연결 풀링을 관리하는 것이 모범 사례입니다.

실전/시스템 설계 (11-15)

Q11: 대규모 트래픽 전자상거래 서비스의 DB 아키텍처를 설계하세요.
  1. 상품 카탈로그: MongoDB (유연한 속성, 빠른 읽기)
  2. 주문/결제: PostgreSQL (ACID 트랜잭션)
  3. 장바구니/세션: Redis (TTL, 빠른 읽기/쓰기)
  4. 상품 검색: Elasticsearch (전문 검색, 패싯)
  5. 추천 시스템: PostgreSQL pgvector (벡터 유사도)
  6. 분석/리포팅: ClickHouse 또는 BigQuery

캐싱 전략: Redis에서 Cache-Aside 패턴으로 상품 정보 캐싱. CDN으로 정적 콘텐츠 캐싱.

Q12: 데이터베이스 마이그레이션 전략을 설명하세요.
  1. Dual-Write 패턴: 양쪽 DB에 동시 쓰기, 점진적 읽기 전환
  2. CDC (Change Data Capture): Debezium 등으로 실시간 데이터 동기화
  3. Shadow Traffic: 새 DB에 트래픽 미러링하여 성능 검증
  4. Blue-Green: 두 환경을 준비하고 한 번에 전환

핵심 포인트: 롤백 계획 필수, 데이터 일관성 검증 자동화, 점진적 트래픽 이전.

Q13: 읽기 복제본과 쓰기 마스터의 복제 지연(replication lag) 문제를 어떻게 해결하나요?
  1. Read-your-writes 일관성: 쓰기 후 짧은 시간 동안 마스터에서 읽기
  2. 최소 복제 확인: PostgreSQL의 synchronous_commit 설정
  3. 지연 모니터링: pg_stat_replicationreplay_lag 체크
  4. GTID 기반 라우팅: MySQL GTID로 특정 시점까지 반영된 복제본으로 라우팅
  5. 캐시 활용: 쓰기 직후 캐시에 최신 값 저장, 복제본 대신 캐시에서 읽기
Q14: 대용량 테이블에서 스키마 변경을 어떻게 안전하게 수행하나요?

PostgreSQL:

  • ALTER TABLE ADD COLUMN (DEFAULT 없이): 즉시 완료
  • ALTER TABLE ADD COLUMN DEFAULT value: pg 11+ 즉시 완료
  • CREATE INDEX CONCURRENTLY: 테이블 잠금 없이 인덱스 생성

MySQL:

  • pt-online-schema-change (Percona): 트리거 기반 온라인 변경
  • gh-ost (GitHub): 바이너리 로그 기반 온라인 변경
  • MySQL 8.0+ INSTANT DDL: 메타데이터만 변경하는 작업 지원

MongoDB:

  • 스키마리스이므로 대부분 마이그레이션 불필요
  • 인덱스 생성: createIndex (백그라운드 빌드 기본)
Q15: DB 성능 모니터링에서 가장 중요한 지표는?
  1. 쿼리 성능: 슬로우 쿼리 로그, p95/p99 응답 시간
  2. 연결 수: 활성/유휴 연결, 커넥션 풀 사용률
  3. 캐시 히트율: Buffer Pool/Shared Buffer 히트율 (목표: 99% 이상)
  4. 복제 지연: 마스터-레플리카 간 지연 시간
  5. 디스크 I/O: IOPS, 대기 시간, 사용률
  6. 잠금 대기: 행 잠금/테이블 잠금 대기 시간과 빈도
  7. 트랜잭션 처리량: TPS, 커밋/롤백 비율

도구: pg_stat_statements(PostgreSQL), Performance Schema(MySQL), Atlas(MongoDB), Redis INFO.

심화 실전 문제 (16-20)

Q16: Hot Partition 문제란 무엇이고 어떻게 해결하나요?

특정 파티션/샤드에 트래픽이 집중되어 성능 병목이 발생하는 문제입니다.

DynamoDB 예시: 특정 파티션 키에 쓰기가 집중되면 해당 파티션만 스로틀링 발생.

해결 방법:

  1. Write Sharding: 키에 랜덤 접미사 추가 (예: USER#001#3)
  2. Composite Key: 날짜/시간 등을 키에 포함하여 분산
  3. 캐싱 레이어: 읽기 핫스팟은 Redis로 흡수
  4. 분산 카운터: 하나의 카운터를 N개로 분할하여 합산
Q17: 데이터베이스 백업 전략의 종류와 RPO/RTO를 설명하세요.
  • RPO (Recovery Point Objective): 허용 가능한 최대 데이터 손실 시간
  • RTO (Recovery Time Objective): 허용 가능한 최대 복구 시간

백업 종류:

  1. Full Backup: 전체 데이터. RTO 짧음, 저장 공간 큼
  2. Incremental Backup: 마지막 백업 이후 변경분. 저장 공간 적음, 복구 복잡
  3. Continuous Archiving (WAL): PostgreSQL WAL 아카이빙으로 특정 시점 복구(PITR) 가능. RPO 거의 0
  4. 스냅샷: EBS 스냅샷, RDS 자동 백업. 빠른 복구
Q18: Slow Query를 분석하고 최적화하는 프로세스를 설명하세요.
  1. 식별: 슬로우 쿼리 로그 활성화 (MySQL: slow_query_log, PostgreSQL: log_min_duration_statement)
  2. 분석: EXPLAIN ANALYZE로 실행 계획 확인
  3. 최적화 순서:
    • 인덱스 추가/개선
    • 쿼리 리팩토링 (불필요한 JOIN 제거, 서브쿼리를 CTE로)
    • 파티셔닝 적용
    • 데이터 아카이빙
    • 비정규화 검토 (읽기 최적화)
  4. 검증: 변경 전후 성능 비교
  5. 모니터링: pg_stat_statements, Performance Schema 지속 관찰
Q19: Eventually Consistent 시스템에서 데이터 정합성을 어떻게 보장하나요?
  1. Read Repair: 읽기 시 불일치 발견하면 최신 값으로 수정
  2. Anti-Entropy (Merkle Tree): 백그라운드에서 노드 간 데이터 비교/수정
  3. Conflict Resolution: 최종 쓰기 승리(LWW), 벡터 클록, 커스텀 병합 로직
  4. Quorum 읽기/쓰기: R + W > N 조건으로 강한 일관성 확보
  5. Saga 패턴: 분산 트랜잭션 대신 보상 트랜잭션으로 일관성 유지
Q20: 2025년 DB 선택 시 가장 중요하게 고려해야 할 요소 3가지는?
  1. AI/벡터 검색 지원: RAG 파이프라인이 표준이 되면서, 벡터 검색 기능이 필수. pgvector, Atlas Vector Search, Redis Vector Search 중 선택.

  2. 운영 복잡도 vs 비용: 매니지드 서비스(RDS, Atlas, ElastiCache)의 편리함 vs 셀프 호스팅의 비용 절감. 팀 규모와 DevOps 역량에 따라 결정.

  3. 데이터 지역성과 규제: GDPR, 한국 개인정보보호법 등 데이터 주권 규제. DynamoDB Global Tables, CockroachDB의 지역 기반 분산이 해답이 될 수 있음.


14. 퀴즈

퀴즈 1: PostgreSQL에서 JSONB와 JSON 타입의 주요 차이점 2가지는?
  1. 저장 방식: JSON은 텍스트 그대로 저장, JSONB는 바이너리 분해하여 저장. JSONB가 쓰기는 약간 느리지만 읽기/검색이 훨씬 빠름.
  2. 인덱싱: JSONB는 GIN 인덱스를 지원하여 키/값 검색이 O(log N). JSON은 인덱스 불가능.

추가: JSONB는 키 순서를 보장하지 않고 중복 키를 제거합니다.

퀴즈 2: Redis의 Sorted Set이 내부적으로 사용하는 자료구조는?

Skip List + Hash Table 조합입니다.

  • Skip List: 정렬된 순서로 범위 검색 O(log N)
  • Hash Table: 점수 조회 O(1)

원소가 128개 이하이고 값이 64바이트 이하이면 ziplist(7.0+에서는 listpack)를 사용하여 메모리를 절약합니다.

퀴즈 3: DynamoDB에서 GSI(Global Secondary Index)와 LSI(Local Secondary Index)의 차이는?
항목GSILSI
파티션 키테이블과 다를 수 있음테이블과 동일
정렬 키자유롭게 선택자유롭게 선택
용량 제한없음파티션당 10GB
생성 시점테이블 생성 후에도 가능테이블 생성 시에만
일관성최종적 일관성만강한 일관성 가능
프로비저닝별도 RCU/WCU테이블 공유

실무 팁: 대부분 GSI가 유연하므로 GSI를 우선 사용합니다.

퀴즈 4: MongoDB에서 트랜잭션을 언제 사용하고 언제 피해야 하나요?

사용해야 할 때:

  • 여러 문서/컬렉션에 걸친 원자적 업데이트 (결제와 재고 동시 변경)
  • 데이터 일관성이 비즈니스적으로 필수

피해야 할 때:

  • 단일 문서 업데이트 (MongoDB는 단일 문서 수준에서 이미 원자적)
  • 높은 처리량이 필요한 작업 (트랜잭션은 성능 오버헤드 발생)
  • 샤딩된 환경에서 빈번한 멀티 샤드 트랜잭션

설계 원칙: 가능한 한 단일 문서에 관련 데이터를 임베딩하여 트랜잭션 필요성을 줄이세요.

퀴즈 5: VACUUM이 PostgreSQL에서 왜 중요한가요?

PostgreSQL의 MVCC는 업데이트/삭제 시 이전 버전을 즉시 제거하지 않습니다. VACUUM은 이 "죽은 튜플(dead tuples)"을 정리합니다.

하지 않으면:

  1. 테이블 팽창: 디스크 공간 낭비, 순차 스캔 성능 저하
  2. 인덱스 팽창: 인덱스 크기 증가, 검색 성능 저하
  3. 트랜잭션 ID 래핑: 약 20억 트랜잭션 후 데이터 손실 위험 (XID wraparound)

Autovacuum이 기본 활성화되어 있지만, 쓰기 부하가 높은 테이블은 Autovacuum 파라미터 튜닝이 필요합니다.


참고 자료


이 글에서 다룬 8개 데이터베이스는 각각 고유한 강점을 가지고 있습니다. "최고의 데이터베이스"는 없으며, "최적의 데이터베이스"만 있을 뿐입니다. 워크로드 패턴, 확장 요구사항, 팀 역량, 비용 제약을 종합적으로 고려하여 선택하세요. 그리고 하나의 데이터베이스에 모든 것을 맡기려 하지 말고, Polyglot Persistence 전략으로 각 데이터베이스의 강점을 조합하는 것이 2025년의 모범 사례입니다.