NoSQL 모델링: 액세스 패턴에서 시작하는 데이터 설계

NoSQL 모델링을 도메인, 쿼리 결과, 파티션 설계와 워크로드 검증 흐름으로 정리하고 주요 패턴과 운영 고려사항을 다룹니다.

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

쿼리 결과가 데이터 구조를 이끈다

관계형 모델의 정규화 규칙만으로는 트래픽 변화와 다양한 데이터 형태를 다루기 어려운 경우가 있다. NoSQL 모델링은 스키마 유연성과 수평 확장을 전제로, 애플리케이션이 실제로 수행할 액세스 패턴에 맞춰 데이터를 배치하는 방법이다.

목표는 일관성, 확장성, 비용, 속도 사이의 균형이다. 조인을 피하고 쓰기 증폭을 줄이며, 핫파티션과 불필요한 운영 복잡도를 피하는 방향으로 설계를 검토한다.

도메인에서 후보 모델까지 이어지는 흐름

도메인 모델 파악쿼리 결과 디자인패턴 모델링기능 최적화후보 선정 테스트선정 모델 최적화엔터티·관계·이벤트 도출액세스 패턴 정의(키·필터·정렬·페이지네이션)임베드 vs 레퍼런스단일 테이블/컬렉션 설계인덱스·TTL·샤드 키·캐시부하·코스트·일관성 테스트 재해시·보강 인덱스·CQRS

도메인을 이벤트 흐름으로 파악하고, 먼저 필요한 쿼리 결과 형태를 고정한다. 그 결과에 맞는 패턴을 선택한 뒤 인덱스, TTL, 샤딩 같은 기능으로 비기능 요구를 충족한다. 후보 모델은 부하와 비용, 일관성 조건에서 검증하고 최종 모델을 다듬는다.

설계 초기에 확인할 질문

도메인 모델에서는 엔터티, 관계, 이벤트 흐름을 도출한다. 이와 함께 데이터 볼륨, 카디널리티, 핫스팟도 추정해야 한다.

이후 화면, 보고서, API가 반환해야 할 응답을 정확한 JSON으로 정의한다. 필수 키와 필터 필드, 정렬 필드, 페이지네이션 전략을 명시한다. 페이지네이션은 limit/lastEvaluatedKey 또는 skip/limit처럼 실제 접근 경로에 맞춰 결정한다.

패턴 단계에서는 임베딩과 레퍼런싱, 단일 테이블 또는 컬렉션 설계, 다큐먼트 덴ORMALIZATION, Time-series, 인버티드 인덱스, CQRS와 보상 트랜잭션을 검토한다.

기능 최적화에서는 파티션 키나 샤드 키, GSI와 세컨더리 인덱스, TTL, Change Stream 또는 CDC, Write-through와 Aside 캐시, JSON Schema나 Pydantic 기반 스키마 검증을 연결한다.

후보를 비교할 때는 R/W 비율, P95/P99, 데이터 크기, 핫키 분포를 포함한 워크로드 프로파일이 필요하다. 쓰기와 읽기 지연, 재시도와 백오프 전략, 요금 모델을 반영한 코스트 프로파일링도 함께 확인한다. 최종 모델에서는 해시 솔팅과 bucketization을 통한 키 재설계, 핫파티션 분산, 보조 인덱스 정비, TTL과 보존주기 튜닝, 운영지표 대시보드화를 수행한다.

데이터 배치 패턴의 선택 기준

패턴 설명 적용 사례 장점 주의점
임베딩(Embedding) 읽기 최적화 위해 하위 엔터티 포함 상품+옵션, 주문+항목 조인 회피, 단일 IO 중복/쓰기 증폭, 부분 업데이트 비용
레퍼런싱(Reference) _id 또는 외래키 유사 참조 유저↔주문 중복 최소화 조인 필요, N+1 가능성
단일 컬렉션(One-Collection) 액세스 패턴별 파티션키 정규화 DynamoDB, MongoDB 단일 뷰 확장성·쿼리 단순화 설계 초기에 패턴 고착 위험
타임시리즈(Time-series) bucket per time, TTL IoT 센서, 로그 파티션 균형, 수명관리 범위쿼리·압축 설계 중요
인버티드 인덱스 역색인 도큐먼트 태그/키워드 검색 다대다 질의 단순화 색인 유지 비용

임베딩은 관련 데이터를 한 번의 읽기로 반환하기 좋지만, 자주 변하는 필드를 함께 넣으면 쓰기 증폭과 부분 업데이트 비용이 커질 수 있다. 레퍼런싱은 중복을 줄이지만 조회 시 조인과 N+1 가능성을 고려해야 한다.

단일 컬렉션 설계는 접근 패턴별 키 구조를 초기에 명확히 해야 한다. 타임시리즈는 시간 단위 버킷과 TTL로 수명 관리를 지원하지만, 범위 쿼리와 압축 방식이 핵심 설계 항목이다. 태그나 키워드 검색에는 인버티드 인덱스를 둘 수 있으나, 인덱스를 유지하는 비용이 따른다.

데이터베이스별로 달라지는 제약

항목 MongoDB DynamoDB Cassandra
파티션/샤드 키 shardKey(다중필드), zone PK(Partition+Sort), GSI/LSI Partition key + clustering
트랜잭션 멀티도큐먼트(일부) 단일 파티션 원자성, Transact APIs 파티션 범위 경량
인덱스 복합/TTL/부분/와일드카드 GSI/LSI, TTL 세컨더리 제한적
스트림/CDC Change Streams Streams CDC 도구 연계
쿼리 스타일 필터·집계 파이프라인 키 기반·조건 파티션 범위 스캔 중심

DynamoDB에서는 키와 액세스 패턴의 결합이 절대적이다. MongoDB는 집계와 보조 인덱스를 활용하는 유연성이 있으며, Cassandra는 쓰기, 시계열, 대량 파티션 처리에 강점이 있다.

주문 조회에서 시작한 모델

전자상거래 주문 도메인에서 먼저 정의할 것은 저장 형식이 아니라 필요한 응답이다.

{
  "orderId": "ORD#2025-08-12-0001",
  "userId": "USR#8392",
  "status": "SHIPPED",
  "items": [{ "sku": "SKU-RED-01", "qty": 2, "price": 19000 }],
  "placedAt": "2025-08-12T05:21:00Z",
  "shipping": { "carrier": "CJ", "invoice": "123-456" }
}

DynamoDB 단일 테이블에서는 사용자별 최근 주문과 날짜별 주문 조회를 지원하는 키 구조를 다음처럼 둘 수 있다.

PK SK GSI1PK GSI1SK attrs
USER#8392 ORDER#2025-08-12-0001 ORDER#2025-08-12 USER#8392 status, total, ...
ORDER#2025-08-12-0001 ITEM#SKU-RED-01 USER#8392 ORDER#2025-08-12-0001 qty, price
ORDER#2025-08-12-0001 ITEM#SKU-BLU-02 USER#8392 ORDER#2025-08-12-0001 qty, price

PK = USER#8392 범위에서 최근 주문을 정렬하고, GSI1로 특정 날짜의 모든 주문을 스캔할 수 있다.

MongoDB에서는 주문과 주문 항목을 하나의 문서에 임베딩하는 방식이 가능하다.

// collection: orders
{
  _id: ObjectId("...") ,
  orderId: "ORD#2025-08-12-0001",
  userId: "USR#8392",
  status: "SHIPPED",
  items: [ { sku: "SKU-RED-01", qty: 2, price: 19000 } ],
  placedAt: ISODate("2025-08-12T05:21:00Z"),
  shipping: { carrier: "CJ", invoice: "123-456" }
}
// index
// 1) { userId:1, placedAt:-1 } 2) { placedAt:1 } with TTL if needed

키 설계를 반복 가능한 작업으로 만들기

Query-First 접근은 상위 액세스 패턴별로 반환 JSON, 필터, 정렬, 페이지네이션을 먼저 적은 뒤 파티션 키와 정렬 키를 고르는 흐름이다.

Input: top_k_access_patterns
For each pattern p:
  define result_json(p)
  identify filters, sort_fields, pagination
  choose partition_key = hash(critical_dimension)
  choose sort_key = compose(range_fields)
  if hotspot_risk: add salt_bucket or time_bucket
  select secondary_indexes if unavoidable
Output: candidate_models

스키마 검증은 애플리케이션 모델에서도 유지할 수 있다.

from pydantic import BaseModel, Field
from typing import List

class Item(BaseModel):
    sku: str
    qty: int
    price: int

class Order(BaseModel):
    orderId: str
    userId: str
    status: str
    items: List[Item]
    placedAt: str

집계 요구가 있을 때는 데이터가 수행해야 할 조회를 집계 파이프라인으로 구체화한다.

// 최근 30일 유저별 매출 Top5 SKU
db.orders.aggregate([
  { $match: { placedAt: { $gte: new Date(Date.now() - 30 * 86400000) } } },
  { $unwind: "$items" },
  {
    $group: {
      _id: { userId: "$userId", sku: "$items.sku" },
      sum: { $sum: { $multiply: ["$items.qty", "$items.price"] } },
    },
  },
  { $sort: { sum: -1 } },
  {
    $group: {
      _id: "$_id.userId",
      top: { $push: { sku: "$_id.sku", amount: "$sum" } },
    },
  },
  { $project: { top: { $slice: ["$top", 5] } } },
]);

키 기반 조회도 사전에 정의한 응답과 일치해야 한다.

-- 사용자 최근 주문 20건
SELECT * FROM ecommerce WHERE PK = 'USER#8392' ORDER BY SK DESC LIMIT 20;

운영에서 드러나는 모델의 비용

핫파티션을 피하려면 (userId % N) 버킷이나 시간과 솔트를 결합한 복합키를 검토한다. 임베딩은 읽기 경로를 줄이지만 자주 변하는 필드는 레퍼런싱으로 분리해 쓰기 증폭을 낮출 수 있다.

읽기 이득보다 인덱스 유지 비용이 크다면 인덱스를 제거한다. 시계열 컬렉션에서는 TTL과 Bucket 압축으로 저장비를 줄일 수 있으며, 읽기 치중 워크로드에는 read-through, 쓰기 치중 워크로드에는 write-back을 고려하되 내고장성에 주의한다.

결국적 일관성 창구에는 보상 트랜잭션을 적용하고, 멱등 키(idempotency key)로 재시도를 안전하게 만든다. CDC나 Change Stream은 ES 또는 DW와의 비동기 동기화 경로가 된다.

모델을 확정하기 전에는 다음 항목을 점검한다.

  • 읽기/쓰기 RPS, P95/P99 지연 목표 정의
  • 상위 5개 액세스 패턴의 키·필터·정렬 정의
  • 핫키/핫파티션 발생 확률과 분산 전략(솔팅, 버킷) 수립
  • TTL·보존주기(Compliance 포함) 설계
  • 보조 인덱스 비용/일관성 리스크 검토
  • 데이터 중복 허용 한도 및 보상 트랜잭션 설계
  • 스키마 검증·마이그레이션·버저닝 전략 수립

이 모델링 절차는 주문과 결제 로그, IoT 시계열 수집, 앱 이벤트 분석, 알림과 피드 타임라인, 권한과 감사 로그에 적용할 수 있다. 액세스 패턴을 기준으로 설계하면 쿼리 지연과 비용을 최적화하고, 파티션 전략으로 수평 확장과 핫스팟 최소화를 함께 다룰 수 있다. 스키마, 보존주기, 인덱스 관리를 운영 표준으로 연결하는 것도 이 절차의 목적이다.

NoSQL데이터 모델링액세스 패턴파티션 키DynamoDB