NoSQL 완전 정리 — RDB의 한계, 기업 사례, 보완 전략

Psj·2026년 4월 20일

sql

목록 보기
11/12

NoSQL 완전 정리 — RDB의 한계, 기업 사례, 보완 전략

작성일: 2026-04-20
대상: RDB(MySQL) 중심으로 개발해온 백엔드 개발자
목적: NoSQL이 왜 등장했는지, 언제 써야 하는지, 포기한 것을 어떻게 보완하는지 이해


1. NoSQL이란

Not Only SQL의 약자. SQL(RDB)만이 답이 아니라는 뜻.

RDB가 잘 못하는 특정 상황을 해결하기 위해 나온 DB 계열 전체를 부르는 말.

RDB와 NoSQL은 경쟁 관계가 아니라 역할 분담
RDB → 정합성이 중요한 데이터 (돈, 계약)
NoSQL → 정합성보다 다른 것이 중요한 데이터 (속도, 유연성, 확장성)

2. RDB의 한계 — NoSQL이 나온 진짜 이유

2-1. Lock 경합 — 동시 쓰기가 많을 때

MySQL은 ACID를 보장하기 위해 쓸 때 잠금(Lock)을 걸어요.

카카오톡 점심시간 12:00
동시에 500만 명이 메시지 전송
→ 초당 수만 건 INSERT

Thread 1: INSERT → Lock 획득 → 쓰기 중
Thread 2: INSERT → Lock 대기
Thread 3: INSERT → Lock 대기
Thread N: INSERT → 대기열 수천 개
→ 응답 지연 → Connection Pool 고갈 → 장애

MySQL 안정적인 쓰기 한계: 약 5,000~10,000 TPS
카카오톡 메시지: 초당 약 11만 건
→ MySQL 한계치의 10배 이상

2-2. ALTER TABLE — 스키마 변경이 불가능한 수준

네이버 카페 게시글 테이블: 30억 건 (약 5TB)

기획팀: "해시태그 기능 추가해주세요"
ALTER TABLE posts ADD COLUMN hashtags VARCHAR(500);

MySQL이 하는 일
→ 30억 건 전체를 새 구조로 복사
→ 복사 중 테이블 Lock
→ 게시글 작성 불가
→ 소요 시간: 약 40시간

기능 추가할 때마다 40시간 서비스 중단
→ 개발팀이 기능 추가를 거부하게 됨
→ 서비스 발전이 멈춤

2-3. 수평 확장 — 서버를 늘릴 수가 없음

데이터가 너무 많아서 서버 3대로 분산

[서버1 MySQL] [서버2 MySQL] [서버3 MySQL]

문제 1: JOIN
  user가 서버1, orders가 서버2 → JOIN 불가

문제 2: 트랜잭션
  서버 여러 대에 걸친 분산 트랜잭션 → 구현 극악

문제 3: 라우팅
  쿼리가 들어오면 어느 서버 데이터인지? → 직접 짜야 함

2-4. NULL 투성이 — 카테고리별 다른 속성

쿠팡 상품 테이블: 카테고리마다 속성이 완전히 다름

id  name    inch  resolution  size  expiry  voltage
1   삼성TV  65    4K          NULL  NULL    220
2   나이키  NULL  NULL        270   NULL    NULL
3   감귤    NULL  NULL        NULL  14      NULL

컬럼 200개 × 상품 1억 건
대부분 NULL → 디스크 낭비, 조회 느림
새 카테고리 = 또 ALTER TABLE = 또 수십 시간 중단

3. NoSQL 종류별 개념

3-1. Key-Value (Redis)

가장 단순한 구조. 키: 값 쌍으로 저장.

"session:user:42"  → "{ name: 김대리, role: FORWARDER }"
"exchange:USD:KRW" → "1380.50"
"online:42"        → "true"

특징
  - 메모리 기반 → 극도로 빠름
  - 구조 없음 → 단순한 데이터만 가능
  - TTL 설정 가능 (자동 만료)

적합한 곳
  세션, 캐시, 실시간 랭킹, 분산 락, 임시 데이터

3-2. Document (MongoDB)

JSON 형태의 문서를 저장. 컬렉션 안에 다양한 구조 가능.

{ id: 1, type: "BOOKING", bookingId: 42, title: "새 접수" }
{ id: 2, type: "SHIPMENT", eta: "내일", port: "부산" }
{ id: 3, type: "SYSTEM", severity: "CRITICAL" }

RDB 용어 대응
  테이블 → 컬렉션 (Collection)
  행     → 문서 (Document)
  컬럼   → 필드 (Field)

적합한 곳
  비정형 데이터, 스키마 자주 변경, 중첩 구조

3-3. Column (Cassandra, HBase)

생긴 건 RDB와 비슷하지만 대량 쓰기에 극도로 최적화.
JOIN, 트랜잭션 없음.

특징
  - 처음부터 분산 설계
  - 초당 수십만 건 쓰기 가능
  - 시계열 데이터에 최적

적합한 곳
  IoT 센서, 채팅 메시지, 로그, 시계열 데이터

3-4. Graph (Neo4j)

노드(점)와 엣지(관계선)로 데이터 표현.

(김대리) -[담당]→ (부킹A)
(박과장) -[승인]→ (부킹A)
(김대리) -[소속]→ (국제물류㈜)

적합한 곳
  친구 추천, 물류 경로 최적화, 권한 관리, 사기 탐지

4. ACID vs BASE

RDB — ACID (정합성 보장)
  Atomicity    트랜잭션은 전부 성공 or 전부 실패
  Consistency  항상 일관된 상태 유지
  Isolation    트랜잭션끼리 서로 영향 없음
  Durability   저장되면 절대 안 사라짐

NoSQL — BASE (정합성 일부 포기, 속도/확장성 확보)
  Basically Available   일단 응답은 함
  Soft state            잠깐 데이터가 안맞을 수 있음
  Eventually consistent 나중엔 결국 맞춰짐

5. 기업 도입 사례 — 실제로 겪은 문제와 해결

5-1. 카카오 (2012) — Lock 경합 문제

문제

카카오톡 사용자 폭증
초당 수만 건 메시지 INSERT
MySQL Lock 경합 → 대기열 폭증 → 장애 반복

해결: HBase (Column DB) 도입

메시지 저장을 MySQL에서 HBase로 분리

HBase 저장 방식
  메시지 전송
    ↓
  WAL 로그 (디스크에 먼저 기록)
    ↓
  MemStore (메모리)에 씀 → Lock 없음
    ↓
  백그라운드에서 HFile (디스크)에 flush

서버 꺼져도 WAL 로그로 복구 → 유실 없음

Row Key 설계: room_id + timestamp (역순)
  → 특정 채팅방 최근 메시지 바로 스캔
  → MySQL 인덱스보다 훨씬 빠름

결과

MySQL                    HBase
초당 1만 건 한계   →    초당 수십만 건 가능
Lock 경합 장애     →    Lock 없음
응답 지연 수초     →    응답 수ms

정합성을 포기한 것과 보완

포기한 것
  메모리에 먼저 쓰는 구조 → 서버 장애 시 유실 가능성

보완 방법
  1. WAL (Write Ahead Log): 메모리 쓰기 전 디스크 로그 선기록
  2. 복제 (Replication): 데이터 3벌 복제, 서버 2대 죽어도 살아있음
  3. 메시지 특성 활용: 순서 1~2개 바뀌어도 사용자가 크게 못 느낌
                        → 100% 정합성 포기 가능한 데이터였음

5-2. 네이버 (2015) — ALTER TABLE 문제

문제

카페/블로그 게시글 30억 건 (약 5TB)
기능 추가 요청마다 ALTER TABLE
→ 수십 시간 서비스 중단 반복
→ 개발팀이 기능 추가를 거부하는 상황 발생

해결: MongoDB 도입

MySQL → MongoDB 이전 후

기능 추가 전 게시글
{ title: "오늘 모임", content: "즐거웠어요" }

해시태그 추가 후 (ALTER TABLE 없이)
{ title: "주말 번개", content: "같이가요", hashtags: ["번개", "주말"] }

투표 추가 후 (또 ALTER TABLE 없이)
{ title: "메뉴 투표", content: "뭐먹을까요",
  poll: { question: "어디서?", options: ["치킨", "피자"] } }

기존 문서는 그대로
새 필드가 없어도 에러 없음
서비스 중단 없음
소요 시간 0초

코드 레벨 차이

// MySQL - 기능 추가 시
// 1. ALTER TABLE (수십 시간 중단)
// 2. Entity 컬럼 추가
// 3. Repository 수정
// 4. 배포

// MongoDB - 기능 추가 시
// 1. Entity에 필드만 추가
// 2. 그냥 저장

// 해시태그 추가 전
postRepository.save(new Post(title, content));

// 해시태그 추가 후 - DB 변경 없음
postRepository.save(new Post(title, content, hashtags));

스키마 포기를 보완한 방법

방법 1 — 애플리케이션 레벨 검증
  DB가 막아주지 않으니 코드에서 직접 검증

  @Document(collection = "posts")
  public class Post {
      @NotNull
      private String title;
      @NotBlank
      private String content;
      private List<String> hashtags; // optional
      private Poll poll;             // optional
  }

방법 2 — MongoDB Schema Validation
  필수 필드는 강제, 선택 필드는 자유

  db.createCollection("posts", {
    validator: {
      $jsonSchema: {
        required: ["title", "content", "cafe_id"],
        properties: {
          title: { bsonType: "string", maxLength: 500 }
        }
      }
    }
  })

방법 3 — 이벤트 소싱 패턴
  변경 이력 자체를 저장
  { event: "POST_CREATED", postId: 1, title: "제목" }
  { event: "HASHTAG_ADDED", postId: 1, hashtags: ["여행"] }
  → 이벤트 순서대로 적용하면 최신 상태 재현
  → 넷플릭스, 우버 이 방식 사용

5-3. 쿠팡 (2018) — NULL 투성이 & ALTER TABLE 반복

문제

상품 테이블 1억 건
카테고리마다 속성이 완전히 다름
  TV: inch, resolution, panel, refreshRate, ports
  신발: sizes, colors, material, sole
  식품: weight, origin, expiryDays, storage

MySQL에 억지로 넣으면
  컬럼 200개 × 1억 건 = 대부분 NULL
  디스크 낭비 어마어마 (약 20TB)
  새 카테고리 = ALTER TABLE = 수십 시간 중단 반복

해결: MongoDB 도입 (상품 속성 한정)

TV
{
  id: 1,
  name: "삼성 QLED 65인치",
  price: 1500000,
  category: "TV",
  specs: {
    inch: 65,
    resolution: "4K",
    panel: "QLED",
    refreshRate: 120,
    ports: { hdmi: 4, usb: 2 }
  }
}

신발
{
  id: 2,
  name: "나이키 에어맥스",
  price: 150000,
  category: "신발",
  specs: {
    sizes: [240, 245, 250, 255, 260],
    colors: ["흰색", "검정"],
    material: "메쉬"
  }
}

캠핑 카테고리 추가 시 차이

MySQL
  ALTER TABLE products
    ADD COLUMN tent_capacity INT,
    ADD COLUMN waterproof_rating INT,
    ADD COLUMN pole_material VARCHAR;
  → 1억 건 테이블 수십 시간 중단
  → 또 NULL 컬럼 추가

MongoDB
  그냥 새 문서 넣으면 끝
  { category: "캠핑", specs: { tentCapacity: 4, waterproofRating: 3000 } }
  → 서비스 중단 없음
  → 기존 상품 영향 없음

디스크 절약 효과

MySQL 상품 1억 건
  컬럼 200개 × 1억 건 (대부분 NULL)
  디스크: 약 20TB

MongoDB 상품 1억 건
  필요한 필드만 저장
  디스크: 약 5TB

→ 디스크 75% 절약
→ 조회 속도 향상 (읽어야 할 데이터 자체가 줄어듦)

JOIN 포기를 보완한 방법

방법 1 — Embedding (자주 조회하는 데이터 미리 포함)
  {
    id: 1,
    name: "삼성 QLED",
    seller: {               ← JOIN 대신 embed
      id: 500,
      name: "삼성전자 공식",
      rating: 4.9
    },
    specs: { inch: 65, ... }
  }

방법 2 — 자주 바뀌는 데이터는 별도 조회
  embed: 판매자 이름 (거의 안 바뀜)
  별도 조회: 재고 (매초 바뀜)

방법 3 — CQRS 패턴 (읽기/쓰기 DB 분리)
  쓰기: MySQL (정규화, 정합성)
  읽기: MongoDB (비정규화, JOIN 없음, 빠름)

  MySQL 저장 → 이벤트 발행 → MongoDB 문서 생성
  조회 요청 → MongoDB에서 바로 반환
  → 쿠팡, 네이버쇼핑 이 방식 사용

6. RDB 장점을 포기한 것과 보완 전략 총정리

6-1. ACID 정합성 포기 → 보완

포기한 것
  원자성: 일부 작업만 성공할 수 있음
  내구성: 메모리에 있을 때 서버 꺼지면 유실 가능

보완 방법

1. WAL (Write Ahead Log)
   디스크 로그 선기록 → 서버 재시작 시 복구

2. 복제 (Replication)
   데이터 3벌 복제
   Cassandra: replication_factor = 3
   서버 2대 죽어도 살아있음

3. 데이터 특성 활용
   정합성이 덜 중요한 데이터에만 NoSQL 적용
   좋아요 수, 조회수, 메시지, 로그
   → 잠깐 틀려도 결국 맞춰지면 됨

4. 돈 관련은 무조건 MySQL 유지
   결제, 정산, 포인트 → 어떤 기업도 NoSQL 단독 안 씀

6-2. 고정 스키마 포기 → 보완

포기한 것
  DB가 잘못된 데이터 타입/구조를 막아주지 않음

보완 방법

1. 애플리케이션 레벨 검증
   @NotNull, @NotBlank, @Size 등으로 직접 검증

2. MongoDB Schema Validation
   필수 필드 강제, 선택 필드 자유

3. 이벤트 소싱
   변경 이력 저장 → 언제든 상태 재현 가능

6-3. JOIN 포기 → 보완

포기한 것
  여러 컬렉션 데이터를 한 번에 가져올 수 없음

보완 방법

1. Embedding
   자주 함께 조회되는 데이터를 한 문서에 미리 합침
   단, 자주 바뀌는 데이터는 embed 비적합

2. $lookup (MongoDB의 JOIN)
   성능이 RDB JOIN보다 떨어짐
   자주 쓰면 안 됨, 가끔 쓸 때만

3. CQRS 패턴
   쓰기: MySQL (정규화)
   읽기: MongoDB (비정규화, embed)

6-4. 트랜잭션 포기 → 보완

포기한 것
  여러 컬렉션에 걸친 원자적 처리 보장 어려움

보완 방법

1. Saga 패턴
   트랜잭션을 여러 단계로 분리
   실패 시 보상 트랜잭션 실행

   주문 생성 성공 → 재고 감소 실패
   → 보상: 주문 취소 실행
   → 결국 일관된 상태로 수렴

2. MongoDB 4.0+ 트랜잭션
   멀티 문서 트랜잭션 지원
   단, 샤딩 환경에서 성능 저하 있음
   꼭 필요한 곳에만 사용

3. 단일 문서 설계
   트랜잭션이 필요한 데이터를 한 문서에 embed
   단일 문서 트랜잭션은 MongoDB도 보장

7. NoSQL이 적합한 데이터 vs 부적합한 데이터

적합한 데이터 (정합성이 덜 중요한 곳)

로그 데이터
  API 요청/응답 로그, 에러 로그
  → 구조가 매번 다름, 대량 쌓임, 정합성 불필요

알림 이력
  타입마다 구조가 다름
  → MongoDB 적합 (단, 공통 구조로 담기면 MySQL로 충분)

채팅 메시지
  대화방 단위 저장, 대량 쓰기
  → Cassandra/HBase 적합

상품 속성
  카테고리마다 속성 수십 개가 완전히 다름
  → MongoDB 적합

IoT 센서 데이터
  기기마다 데이터 구조 다름, 초당 수만 건
  → Cassandra 적합

사용자 행동 분석
  클릭, 페이지뷰, 필터 등 이벤트 다양
  → MongoDB 적합

세션/캐시
  임시 데이터, 빠른 속도 필요
  → Redis 적합

부적합한 데이터 (정합성이 중요한 곳)

결제/정산     → 돈이 관련된 모든 것
계약/부킹     → 법적 효력이 있는 데이터
회원 정보     → 로그인, 권한 관련
재고 관리     → 트랜잭션 필수
운임/견적     → 금액 계산
서류 관리     → 법적 문서

→ 이런 데이터는 어떤 기업도 NoSQL 단독 안 씀

8. 실무 아키텍처 — 혼용 패턴

대부분의 대기업은 이렇게 혼용해요.

ILIC 글로벌 확장 시 예시

MySQL (RDB) — 핵심 비즈니스
  부킹, 정산, 회원, 운임, 서류
  → 정합성이 생명

Redis (NoSQL) — 속도
  세션, 환율 캐시, SSE 다중 서버
  → 빠른 속도, 임시 데이터

MongoDB (NoSQL) — 유연성
  API 로그, 알림 이력, 화물 타입별 속성
  → 구조 자유, 스키마 변경 잦음

Cassandra (NoSQL) — 대량 쓰기
  IoT 선박 센서, 시계열 데이터
  → 초당 수십만 건 쓰기

Elasticsearch — 검색
  포워더 검색, 화물 전문 검색
  → 역인덱스 기반 빠른 검색

9. 언제 NoSQL 도입을 고민해야 하나

Lock 경합
  초당 수만 건 이상 쓰기 발생
  MySQL 응답 지연이 시작될 때
  → Redis(캐시), Cassandra(대량 쓰기) 검토

ALTER TABLE
  핵심 테이블이 수억 건 넘었는데
  스키마를 자주 변경해야 할 때
  → MongoDB 검토

수평 확장
  서버 10대 이상 필요한 규모
  MySQL 샤딩이 복잡해질 때
  → MongoDB, Cassandra 샤딩 검토

스키마 다양성
  카테고리/타입마다 속성이 수십 개씩 달라서
  NULL 컬럼이 200개가 넘어갈 때
  → MongoDB 검토

전문 검색
  MySQL LIKE가 느려서
  사용자 검색 UX가 나빠질 때
  → Elasticsearch 검토

10. 핵심 요약

NoSQL이 해결하는 문제 딱 3가지

1. Lock 경합
   RDB  → 동시 쓰기 많으면 Lock 병목
   NoSQL → Lock 없거나 최소화 → 대량 동시 쓰기 가능

2. ALTER TABLE
   RDB  → 수억 건 테이블 스키마 변경 = 수십 시간 중단
   NoSQL → 스키마 없음 → 변경 즉시 가능

3. 수평 확장
   RDB  → 서버 늘리면 JOIN, 트랜잭션 문제
   NoSQL → 처음부터 분산 설계 → 서버 추가 자연스러움

NoSQL은 RDB 대체재가 아니라 역할 분담
  정합성 중요 + 관계 복잡   → MySQL
  빠른 캐시 + 임시 데이터  → Redis
  구조 자유 + 비정형        → MongoDB
  초대량 쓰기 + 시계열     → Cassandra
  텍스트 검색               → Elasticsearch

기업들이 NoSQL로 완전히 갈아탄 게 아님
MySQL로 시작 → 규모가 커져 MySQL이 발목 잡힐 때 → NoSQL 일부 도입
돈 관련은 어떤 기업도 NoSQL 단독으로 안 씀

변경 이력

날짜내용
2026-04-20초안 — NoSQL 개념, 기업 사례, RDB 장점 포기 및 보완 전략
profile
Software Developer

0개의 댓글