NoSQL 데이터 모델링: 읽기 경로를 설계하는 패턴

Denormalization, Aggregation, Application Join을 중심으로 NoSQL 데이터 모델링에서 읽기 성능과 확장성을 설계하는 방법을 정리합니다.

2026-08-14 · 최초 발행 2025-10-14

NoSQL 데이터 모델링은 관계형 모델의 정규화 규칙을 그대로 옮기는 일이 아니다. 단건 조회, Top-N, 타임라인 스캔처럼 서비스의 핵심 접근 패턴을 먼저 정하고, 그 쿼리가 짧은 경로로 끝나도록 데이터를 배치한다.

읽기 지연과 조인 비용을 줄이는 대표적인 선택지가 Denormalization, Aggregation, Application Join이다. 세 패턴은 모두 읽기 성능과 수평 확장을 겨냥하지만, 데이터 중복·변경 전파·부분 실패에 대응하는 방식은 다르다.

읽기 경로에 맞춰 데이터를 중복하는 Denormalization

Denormalization은 필요한 값을 의도적으로 중복 저장해, 한 번의 키 조회 또는 하나의 문서·로우에서 응답을 돌려주는 방식이다. 조인을 없애고 읽기 경로를 단순하게 만들 수 있으며, 캐시 효율도 높아진다.

대신 쓰기 증폭(write amplification)이 발생한다. 원본 데이터가 바뀔 때 복제본도 함께 갱신해야 하므로, 중복 데이터의 일관성을 유지하는 비용이 뒤따른다.

함께 저장하거나 미리 계산하는 Aggregation

Aggregation은 두 맥락에서 쓰인다. 하나는 DDD의 Aggregate 경계 안에 관련 데이터를 묶어 단일 단위로 저장하는 임베드 방식이다. 다른 하나는 카운터나 윈도우 집계처럼 사전 집계(pre-aggregation) 값을 별도로 유지해 읽기 시 즉시 반환하는 방식이다.

이 패턴은 트랜잭션 범위를 줄이고 Hot path에서 상수 시간 응답을 만들 수 있다. 반면 집계 갱신은 복잡해지고, 지연 일관성을 어떻게 관리할지 정해야 한다.

애플리케이션에서 조합하는 Application Join

Application Join은 데이터베이스 조인 대신 애플리케이션이 여러 키나 컬렉션을 병렬 조회한 뒤 결과를 조립하는 패턴이다. 데이터 스토어에 덜 종속적이고 수평 확장에도 유리하다.

그 대가로 네트워크 왕복이 늘며 N+1 문제가 생길 수 있다. 소스별 장애가 겹칠 때 부분 실패를 어떻게 다룰지도 설계해야 한다.

접근 패턴에서 시작하는 모델 설계

스키마를 결정하기 전에 Top-N 쿼리, 단건 조회, 타임라인 스캔처럼 중요한 쿼리를 적는다. 이후 읽기 최적화를 기준으로 데이터 배치, 키 구성, 파티셔닝을 역으로 설계한다.

파티션 키는 균등 분포와 쿼리 지역성 사이의 균형이 필요하다. 복합 키와 버킷팅은 hotspot을 완화하는 수단이 되며, 정렬 키(클러스터링 키)는 시간순·랭킹 스캔에 맞출 수 있다.

변경 전파에는 Outbox+CDC, 이벤트 버스, 장기 실행 리트라이, 멱등성 키를 활용한다. 문서 버전 필드, 타임스탬프 기반 우선순위, TTL, 소프트 딜리트도 일관성 관리에 쓰인다.

단일 문서 또는 파티션 안에서는 원자성을 우선하고, 교차 파티션 작업에는 사가나 보상 트랜잭션을 적용한다. 카운터와 집계에는 원자 증가 연산을 사용하며 분산 락은 최소화한다.

읽기 집중 뷰는 별도 컬렉션이나 테이블로 구체화할 수 있다. Cache-aside와 Write-through 중 하나를 택하고, TTL과 불일치 허용 범위(SLA)를 함께 명시한다.

패턴 성능(읽기) 확장성 일관성 안정성 운영 편의
Denormalization 매우 높음 높음 최종 일관성 위주 중복 불일치 리스크 중간(변경 전파 필요)
Aggregation 높음(핫쿼리) 높음 지연 일관성/원자 카운터 집계 충돌 주의 중간(스케줄러/스트림 필요)
Application Join 중간(네트워크 증가) 매우 높음 소스별 상이 부분 실패 취약 낮음~중간(오케스트레이션 필요)

원본 변경을 읽기 뷰까지 전파하는 흐름

Denormalization 사본Aggregation(카운터/윈도우)Application Join 대상 없음입력: 원본 데이터 변경 발생처리1: 단일 파티션/문서원자적 업데이트처리2: Outbox 레코드 동일트랜잭션 기록처리3: CDC/이벤트 소비자구독대상 유형?사본 문서/행 업데이트증분 집계/시간창 재계산스킵에러 처리: 멱등 업데이트,재시도 백오프출력: 최종/지연 일관 상태,읽기 트래픽 안정화

원본 데이터와 Outbox는 로컬 트랜잭션으로 기록하고, 소비자에서는 단건 원자 업데이트를 유지한다. 소비자 재시도가 무한 루프로 번지지 않도록 멱등 키와 최대 재시도를 두며, 처리하지 못한 이벤트는 DLQ로 격리하고 모니터링 알람을 연결한다.

상품 상세처럼 읽기 요청이 집중되는 경우

전자상거래 상품 상세에서는 상품과 베스트 리뷰를 Denormalization으로 임베드하고, 평점 평균과 카운트를 Aggregation으로 사전 집계할 수 있다. 상세 페이지는 1회 조회로 응답하고 평점은 상수 시간에 표시한다. 변경 전파는 Outbox와 소비자 워커로 비동기 처리한다.

간단 예시(환경: MongoDB 6.x, Node.js 18+)

// products collection: denormalized view
{
  _id: "p_123",
  name: "Wireless Headset",
  price: 89000,
  top_reviews: [{user:"u1", rating:5, text:"굿"}],
  rating: {avg: 4.7, cnt: 523} // pre-aggregated
}

// Application Join (fallback: 리뷰 더보기)
const product = await db.collection('products').findOne({_id: "p_123"});
const more = await db.collection('reviews')
  .find({product_id: "p_123"})
  .sort({created_at:-1}).limit(20).toArray(); // 필요 시만 추가 조회

텔레메트리와 타임라인에 적용할 때

IoT 텔레메트리에서는 디바이스×일자 파티션 집계 테이블을 Aggregation으로 두고, 교차 디바이스 분석이 필요할 때 Application Join을 사용할 수 있다. 대시보드의 실시간 지표는 상수 시간으로 조회하고, 롤업은 5m→1h→1d 계층으로 구성한다.

간단 예시(환경: Apache Cassandra 4.1+)

CREATE TABLE metrics_daily (
  device_id text,
  date date,
  metric text,
  bucket int,            -- 0..23 (시간 버킷)
  sum double,
  cnt counter,           -- 카운터 사용 시 별도 테이블 분리 고려
  PRIMARY KEY ((device_id, date), metric, bucket)
);
-- 읽기: 단일 파티션 스캔으로 일별 지표 집계

소셜 피드와 타임라인에서는 작성 시 팔로워 타임라인으로 팬아웃하는 Denormalization, 좋아요·댓글 카운트의 사전 집계, 유저 프로필·블록 리스트를 조합하는 Application Join을 함께 쓸 수 있다. 여기서는 쓰기 증폭과 읽기 상수 시간 타임라인 사이의 교환 조건, 그리고 지연 일관성 허용 범위 SLA가 핵심이다.

워크로드별 선택 기준

Denormalization은 읽기:쓰기가 ≥ 10:1이고 응답 SLA가 ≤ 50ms이며, 조인 의존도가 높은 조회 중심 워크로드에 적합하다. 엔터티 크기가 안정적이고 변경 빈도가 낮아 전파 비용을 예측할 수 있을 때도 선택하기 좋다.

Aggregation은 상시 노출 지표, Top-K·랭킹, 슬라이딩 윈도우 분석에 맞는다. 원자 카운터를 제공하는 스토어와 스트림 처리(CDC/ETL) 인프라가 있는지가 전제 조건이다.

Application Join은 데이터 소스가 이질적이거나 교차 파티션 조회가 필요하고, 소스마다 일관성 모델이 다를 때 고려한다. 병렬 또는 배치 mget, 캐시, 부분 실패를 허용하는 UX 설계를 함께 준비해야 한다.

운영에서 드러나는 비용과 효과

변경 전파는 Outbox+CDC와 업데이트 버전 또는 이벤트ID를 활용한 멱등 업데이트로 관리한다. 읽기 경로에는 Cache-aside 캐시를 두고, 쓰기 경로에서는 배압과 큐를 모니터링한다. 카나리 전파와 데이터 리컨실리에이션 잡을 이용한 양방향 검증도 필요하다.

Denormalization은 저장공간↑, 쓰기 지연↑를 감수하는 대신 읽기 지연↓와 스케일아웃 용이를 얻는다. Aggregation은 정확도와 지연 일관성, 재계산 비용 사이의 선택을 요구한다. Application Join은 확장성↑의 대가로 네트워크 비용과 부분 실패 처리 복잡도가 커진다.

단일 조회 응답시간은 120200ms에서 1540ms로 바뀔 수 있다(99p 기준, 캐시 적중 60% 가정). 조인을 제거하고 키 기반 조회로 전환하면 DB RPS 여유는 25배 확대된다. 머티리얼라이즈드 뷰와 캐시를 사용하면 읽기 노드 수는 3050% 절감될 수 있지만, 쓰기·스트림 처리 비용은 10~30% 증가할 수 있다.

부분 장애는 DLQ 격리로 사용자 영향을 최소화하고, 지연 일관성 범위는 SLA로 관리한다. 읽기 최적화의 이점은 변경 전파, 멱등성, 캐시 전략까지 운영 설계에 포함할 때 유지된다.

NoSQL데이터 모델링역정규화집계파티셔닝