[MongoDB] NoSQL 메인 DB 사용기 1

RDB 위주로 개발하다가 MongoDB를 메인 데이터베이스로 도입하며 고민한 것들을 회고해 보려고 한다.

1. 정규화 위주의 설계 방식 탈피하기

RDB를 사용할 때 설계의 출발점은 데이터 중복을 없애고 테이블을 쪼개 외래 키 (꼭FK가 아니더라도..)로 연결하는 거였다.

하지만 MongoDB에서는 접근법을 반대로 생각해야 했다.

데이터를 어떻게 저장할까가 아니라 어떻게 읽을까를 먼저 고민해야 한다. 특정 데이터가 항상 같이 읽힌다면 같은 문서에 넣는 것이 유리하다. 이 순서를 바꾸지 않으면 MongoDB를 쓰면서도 RDB처럼 쓰게 된다. (내가 그랬음 컬렉션을 잘게 쪼개고 그러다가 정신차리고…)

2. 데이터 구조 결정 기준

설계 시 가장 먼저 던지는 질문은 이 데이터를 읽을 때 다른 데이터도 항상 함께 필요한가였다. 항상 같이 읽히는 데이터라면 내부에 포함시키고, 독립적으로 읽히거나 수정되는 빈도가 높다면 별도 컬렉션으로 분리했다.

3. 내부 포함과 컬렉션 분리의 판단 기준

내부에 넣을지 밖으로 뺄지 결정할 때 적용한 세 가지 기준이다.

고려 사항내부 포함 (Embedding)컬렉션 분리 (Referencing)
독립적 생성/수정 여부부모 데이터 없이 단독 생성 불가부모 데이터 없이 단독으로 생성/수정 가능
수정 빈도/패턴부모 데이터와 함께 수정되거나 거의 없음부모 데이터와 무관하게 독립적인 수정이 잦음
데이터 증가량하위 데이터의 크기가 제한적임배열이나 하위 데이터 크기가 무한히 증가함

리포트 도메인의 조회 패턴 예시를 가져와봤다.

// RDB 방식으로 사용시 조회를 위해 N번의 추가 쿼리 발생
survey { questionIds: ["Q_001", "Q_002", "Q_003"] }
// 이후 Q_001, Q_002 등을 개별 조회해야 

// MongoDB 방식으로 사용시 1번의 쿼리로 전체 데이터 획득 가능
survey {
  categories: [
    {
      title: "공사 만족도",
      items: [
        { question: "응답 속도", answer: "매우 만족", score: 5 }
      ]
    }
  ]
}

MongoDB에는 조인이 없으므로 데이터를 분리해두면 조회할 때마다 N번의 통신 비용이 발생한다. 데이터를 쓸 때 미리 복사해 두고 읽을 때는 1번의 쿼리로 가져오는 것을 기본으로 생각하자.

4. 스냅샷 패턴을 활용한 과거 이력 보존

특정 문서를 작성한 직원이 나중에 퇴사하거나 부서를 이동했다고 가정해 보자.

과거 문서를 열었을 때는 현재 소속이 아닌 당시 작성 시점의 소속 정보가 노출되어야 하는 요구사항이 흔히 존재한다. RDB였다면 문서에 직원 식별자만(변하지 않는) 남겨두고 별도의 이력 테이블을 설계해서 정합성을 맞췄을 것이다.

// 스냅샷 패턴을 적용해 생성 당시 데이터를 통째로 복사함
document.author = {
  empId: "EMP_001",
  name: "홍길동",
  deptName: "개발본부",
  teamName: "백엔드팀"
}

하지만 MongoDB에서는 데이터 생성 시점에 필요한 외부 정보를 문서 내부에 그대로 복사해서 저장하는 방식이 더 자연스럽다. 이렇게 스냅샷 패턴을 쓰면 읽기 시점에서 외부 인사 시스템이나 ERP에 대한 의존성을 완전히 끊어낼 수 있다. 한 번 저장된 문서는 외부 시스템이 변하더라도 영향을 받지 않는다.

5. 기본 양식과 산출물 데이터의 분리

관리자용 기본 양식 템플릿을 제공하고, 이를 바탕으로 일반 사용자들의 개별 문서가 여러개 파생되는 구조를 설계할 때의 사례다.

이 두 가지를 한 컬렉션에 넣으면 데이터를 조회할 때마다 문서가 빈 양식인지 실제 작성된 문서인지 구분하는 필터 조건을 매번 달아야 한다. 구조와 목적이 명확히 다르므로 물리적인 컬렉션 자체를 분리하는 것이 타당하다.

구분기본 양식 (Schema/Template)개별 문서 (Instance/Data)
대상 타겟 정보OX
사용자 입력 데이터XO
주요 관리 주체시스템 관리자일반 사용자
데이터 발생 규모소수전체 사용자 수와 건수에 비례

6. 애플리케이션 레벨의 타입 설계

MongoDB는 스키마가 없어 어떤 구조든 유연하게 저장할 수 있지만, 이를 서빙하는 서버는(내 경우는 Java기반임) 정해진 타입 체계가 있다는걸 잊지말자.

// 한 컬렉션에 구조가 다른 데이터를 넣었을 때의 객체 모델
class Report {
    String type;
    SectionA sectionA; // 특정 타입에만 존재
    SectionB sectionB; // 다른 타입에만 존재
    String externalId;
}

구조가 상이한 문서들을 하나의 컬렉션에 몰아넣으면 Java 엔티티 클래스에 null 값이 다수 발생하게 된다. NPE 지옥을 피하기 위한 코드만 해도 무쟈게 늘어날 거다.

DB가 스키마를 강제하지 않더라도 애플리케이션 레벨의 명확한 객체 모델링을 유지하기 위해 데이터 구조가 크게 다르면 별도 컬렉션으로 분리하는 것이 맞다.

7. 컬렉션 명명 규칙

RDB에서는 단일 행을 의미하는 단수형 테이블 이름을 주로 사용했으나, MongoDB에서는 문서들의 집합이라는 의미를 살려 복수형 컬렉션 명칭을 사용하는 생태계의 관례를 따랐다.

알아만 두자!

8. RDB와 MongoDB 설계 방식 비교 요약

기초적인 데이터 모델을 설계하며 정리한 내용을 표로 요약한다.

구분RDB 설계 관점MongoDB 설계 관점
최우선 목표정규화를 통한 중복 제거 우선읽기 패턴과 쿼리 횟수 최적화 우선
데이터 관계외래 키를 통한 엔티티 간 참조자주 함께 조회되는 데이터는 내부에 포함
중복 허용데이터 중복 최소화성능 향상을 위해 전략적으로 중복 허용
데이터 조합조인을 활용해 런타임에 데이터 조합쓰기 시점에 복사하고 단일 쿼리로 조회
이력 관리이력 테이블을 따로 만들어 보존작성 시점의 데이터를 문서 안에 복사
명칭 규칙단수형 테이블 명칭복수형 컬렉션 명칭

© 2022. All rights reserved.

Powered by Hydejack v9.2.1