문서 데이터베이스 모델링에서 가장 큰 질문 중 하나는 '비정규화를 어디까지 할 것인가'입니다.

관계형 데이터베이스로 작업할 때는 데이터를 엄격하게 정규화하는 데 익숙합니다. 즉, 각 데이터 항목의 표준적이고 중복되지 않는 인스턴스를 보유합니다. 이를 통해 쿼리 가능성의 범위가 거의 무제한에 가까워지고 일관성을 유지하기가 쉬워집니다.

데이터 모델링에 대한 이전 게시물에서에서 문서 데이터베이스가 어떻게 다른 접근 방식을 취하는지 살펴봤습니다. 문서 데이터베이스는 우리가 함께 액세스하는 데이터를 함께 저장하는 데 최적화되어 있으며, 이를 통해 속도, 배포 가능성, 문서 구조 보존 등과 같은 다양한 이점을 제공합니다.

이 글에서 저는 주문을 여러 테이블로 분할한 다음 매번 SQL 쿼리를 사용하여 재구성하는 것이 아니라 하나의 JSON 문서로 저장하는 스웨그 관리 시스템의 예를 들었습니다.

데이터 집합을 저장하는 이 모델은 자연스럽게 정규화됩니다. 하지만 그렇다고 해서 각 컨텍스트에 대해 모놀리식 문서를 만들어야 한다는 의미는 아닙니다.

대신 여러 곳의 데이터를 하나의 문서에 포함하거나 다른 문서에 대한 참조를 저장할 수 있는 두 가지 옵션을 고려해야 합니다.

키-값 문서 액세스

카우치베이스에서는 키-값 조회와 뷰(맵 축소 쿼리에서 자동으로 생성된 보조 인덱스)라는 두 가지 방법으로 데이터에 액세스할 수 있습니다.

언제 N1QL 가 일반 사용 가능해지면 3개가 됩니다.

이 글에서 살펴보는 모든 내용은 키 값 조회를 기반으로 합니다.

예제 시스템을 간단히 요약하면 다음과 같습니다.

이전 글에서 Couchbase 브랜드 굿즈를 추적하는 재고 관리 시스템의 예를 살펴봤습니다.

표준 경로가 다음과 같다고 가정해 보겠습니다:

  1. 고객이 주문을 합니다.
  2. 재고 피커가 주문을 접수하고 상품을 포장합니다.
  3. 배송 담당자가 배송 서비스를 통해 패키지를 발송합니다.

고객이 주문을 하는 순간, Couchbase에 주문 데이터를 저장하는 방법을 선택할 수 있습니다:

  • 모든 주문 정보를 하나의 문서에 포함시키거나
  • 또는 관련된 각 기록의 기본 사본을 하나씩 보관하고 주문 문서에서 참조하세요.

임베딩

모든 데이터를 하나의 문서에 포함하기로 선택했다면 다음과 같은 결과가 나올 수 있습니다:

  {
"orderID": 200,
"고객":
{
"이름": "스티브 로터리",
"주소": "11-21 폴 스트리트",
"도시": "London"
},
"제품":
[
{
"itemCode": "RedTShirt",
"itemName": "레드 카우치베이스 티셔츠",
"공급업체": "사랑스러운 티셔츠 회사",
"위치": "창고 1, 통로 3, 위치 4",
"수량주문": 3
},
{
"itemCode": "USB",
"공급자": "메모리스틱 포레바",
"itemName": "빨간색 카우치베이스 로고가 있는 검은색 8GB USB 스틱",
"위치": "창고 1, 42번 통로, 위치 12",
"수량 주문": 51
}
],
"상태": "유료"
}

여기에는 주문을 처리하는 데 필요한 모든 것이 하나의 문서에 저장되어 있습니다.

고객 프로필과 품목 세부 정보 문서가 따로 있음에도 불구하고 주문 문서에 고객 데이터의 일부를 복제합니다.

관계의 세계에서 온 사람이라면 이는 낭비이거나 심지어 위험해 보일 수도 있습니다.

하지만 문서 데이터베이스의 경우 이는 매우 정상적인 현상입니다. 앞서 살펴본 것처럼 문서 데이터베이스는 특정 상황에 필요한 모든 것을 하나의 문서에 저장할 수 있다는 개념을 중심으로 작동합니다.

하지만 이와 같은 데이터 임베딩에는 몇 가지 단점이 있습니다.

먼저, 무엇이 잠재적으로 나쁜지 살펴봅시다:

  • 불일치: 주문이 완료된 후 Steve가 주소를 업데이트하려는 경우:
    • 애플리케이션 코드가 데이터베이스에서 그의 주소의 모든 인스턴스를 찾아 업데이트할 수 있을 만큼 견고해야 합니다.
    • 네트워크, 데이터베이스 측 또는 다른 곳에서 업데이트가 완전히 완료되는 것을 방해할 만한 문제가 발생하지 않아야 합니다.
  • 쿼리 가능성: 동일한 데이터의 복사본을 여러 개 만들면 포함된 복사본을 모두 필터링해야 하므로 복제하는 데이터에 대해 쿼리하기가 더 어려워질 수 있습니다.
  • 크기: 중복된 데이터가 많은 대용량 문서가 될 수 있습니다.
  • 더 많은 문서: 큰 문제는 아니지만 캐시된 작업 세트의 크기와 같은 몇 가지 영향을 미칠 수 있습니다.

그렇다면 임베딩을 통해 얻을 수 있는 이점은 무엇일까요? 대부분 이점이 있습니다:

  • 액세스 속도: 하나의 문서에 모든 것을 포함하면 데이터베이스를 한 번만 조회하면 됩니다.
  • 읽기 시 내결함성 향상: 분산 데이터베이스에서는 참조된 문서가 여러 컴퓨터에 분산되어 있으므로 임베딩을 통해 문제가 발생할 가능성이 줄어들고 애플리케이션 측면이 간소화됩니다.

임베드 시기

데이터를 임베드하고 싶을 때가 있습니다:

  1. 읽기가 쓰기보다 훨씬 많습니다.
  2. 여러 복사본에서 데이터가 일관되지 않을 위험이 거의 없습니다.
  3. 액세스 속도를 최적화하고 있습니다.

읽기가 쓰기보다 많은지 묻는 이유는 무엇인가요?

위의 예에서 누군가 주문을 읽을 때마다 주문 상태도 업데이트할 가능성이 높습니다:

  • 창고에 있는 누군가가 주문 문서를 읽고 완료되면 상태를 "픽업됨"으로 업데이트합니다.
  • 발송 팀 중 한 명이 문서를 읽고 소포가 택배사와 함께 있으면 상태를 "발송됨"으로 업데이트합니다.
  • 택배사로부터 자동 배송 알림을 받으면 애플리케이션에서 문서 상태를 "배송됨"으로 업데이트합니다.

따라서 여기에서는 읽기와 쓰기가 상당히 균형을 이룰 가능성이 높습니다.

하지만 스웨그 관리 시스템에 블로그를 추가한 다음 새로운 Couchbase 브랜드 USB 충전기에 대한 게시물을 작성한다고 상상해 보세요. 게시물을 다듬는 동안 문서에 두 번, 어쩌면 세 번 글을 수정할 것입니다. 그러면 그 문서의 수명이 다할 때까지 모두 읽히게 됩니다. 게시물이 인기가 있다면 쓰기 횟수에 비해 읽기 횟수가 수백, 수천 배로 늘어날 수도 있습니다.

임베딩의 장점은 읽기 시점에 나타나고 위험은 주로 쓰기 시점에 나타나므로, 예를 들어 별도의 프로필 문서에서 작성자 세부 정보를 가져오는 것보다 하나의 문서에 블로그 게시물 페이지의 모든 콘텐츠를 임베드하는 것이 합리적입니다.

데이터를 임베드해야 하는 또 다른 강력한 이유가 있습니다:

  • 실제로는 서로 다른 별도의 데이터 복사본을 유지하려고 합니다.

위의 스웨그 주문에서는 고객의 주소를 배송 주소로 사용하고 있습니다. 지금처럼 배송 주소를 임베드하면 주문마다 다른 배송 주소를 선택할 수 있는 옵션을 쉽게 제공할 수 있습니다. 또한 고객이 나중에 프로필에 저장된 주소를 변경하더라도 각 주문이 어디로 배송되었는지에 대한 기록이 남습니다.

속도에 대한 필요성

카우치베이스는 빠릅니다.

모든 쓰기는 관리되는 캐시에 이루어지며, RAM 크기를 적절히 조정하면 해당 캐시에서 작업 세트가 제공됩니다.

이에 따라 트레이드 오프의 가중치가 달라집니다. 주로 속도를 위해 임베딩하는 것은 Couchbase에서 덜 매력적입니다.

Couchbase를 사용하면 평균 읽기/쓰기 작업 시간이 0.5밀리초이므로 쿼리 하나에 서너 개의 작업이 필요한 경우에도 문제가 없습니다.

참조

주문을 표현하는 또 다른 방법은 사용자 프로필 문서와 재고 품목 세부 정보 문서를 참조하되 그 내용을 주문 문서로 가져오지 않는 것입니다.

고객 프로필은 고객의 이메일 주소로 키가 지정되고 재고 품목은 재고 코드로 키가 지정된다고 가정해 보겠습니다. 이를 사용하여 원본 문서를 참조할 수 있습니다:

   {
"orderID": 200,
"customer": "steve@gmail.com",
"제품":
[
{
"itemCode": "RedTShirt",
"수량주문": 3
},
{
"itemCode": "USB",
"수량 주문": 51
}
],
"상태": "유료"
}

Steve의 주문을 볼 때 사용자 프로필(이메일 주소로 키 입력)과 재고 품목 세부 정보(품목 코드로 키 입력) 등 세 가지를 더 읽어 세부 정보를 채울 수 있습니다.

세 번의 추가 읽기가 필요하지만 몇 가지 이점이 있습니다:

  • 일관성: Steve의 프로필 정보와 재고 품목 세부 정보에 대한 표준 사본을 하나만 유지합니다.
  • 쿼리 가능성: 이번에는 데이터 집합을 쿼리할 때 결과가 임베디드 복사본이 아닌 정식 버전의 데이터임을 보다 확실하게 확인할 수 있습니다.
  • 캐시 사용량 개선: 정식 문서에 자주 액세스하므로 액세스 빈도가 낮아 캐시에서 삭제되는 임베딩 복사본이 여러 개 있는 대신 캐시에 계속 남아 있게 됩니다.
  • 더 효율적인 하드웨어 사용: 데이터를 포함하면 동일한 데이터의 복사본이 여러 개 포함된 대용량 문서가 생성되고, 참조를 통해 데이터베이스에 필요한 디스크와 RAM을 줄일 수 있습니다.

단점도 있습니다:

  • 다중 조회: 디스크에서 읽어야 할 때마다 읽기 시간이 증가하므로 주로 캐시 누락에 대한 고려 사항입니다.
  • 문서의 표준 버전을 참조한다는 것은 해당 문서에 대한 업데이트가 해당 문서가 사용되는 모든 상황에 반영된다는 의미입니다.

참조 시기

Couchbase로 모델링할 때는 문서의 정식 인스턴스를 참조하는 것이 좋은 기본값입니다. 특히 다음과 같은 경우에 참조를 사용해야 합니다:

  • 데이터의 일관성이 최우선입니다.
  • 캐시가 효율적으로 사용되기를 원합니다.
  • 임베디드 버전은 다루기 어렵습니다.

마지막 요점은 문서가 무한한 성장 가능성을 가지고 있는 경우에 특히 중요합니다.

시스템의 각 사용자와 관련된 활동 로그를 저장한다고 가정해 보겠습니다. 이러한 로그를 사용자 프로필에 포함하면 상당히 큰 문서가 될 수 있습니다.

개별 문서에 대한 Couchbase의 20MB 상한을 위반할 가능성은 거의 없지만, 프로필의 로그 요소가 증가함에 따라 애플리케이션 측에서 문서를 처리하는 것은 효율성이 떨어질 것입니다. 별도의 문서나 페이지가 매겨진 문서를 참조하여 로그를 보관하는 것이 훨씬 더 효율적일 것입니다.

모든 것을 정상화하세요! (그렇지 않은 경우는 제외)

비정규화는 문서 데이터베이스 작업의 핵심 부분이지만 그렇다고 해서 표준 기록을 유지한 다음 다른 문서에서 참조할 수 없다는 의미는 아닙니다.

실제로 문서를 참조하는 것이 Couchbase를 사용하는 가장 효율적인 방법인 경우가 많으며, 읽기가 많은 워크로드와 같은 특정 상황에서는 임베드해야 합니다.

다음 시간에는 주요 명명 패턴과 소프트 스키마 데이터베이스에서 스키마를 유지하는 방법에 대해 살펴보겠습니다.

작성자

게시자 Matthew Revell, 수석 개발자 옹호자, EMEA 지역, Couchbase

매튜 레벨은 EMEA 카우치베이스의 수석 개발자 옹호자입니다. 그는 제품 개발자들의 마음속에 Couchbase를 각인시키기 위한 글로벌 전략을 개발했습니다.

댓글 하나

  1. 다음이 일반적으로 왜 단점이나 문제인지 이해하지 못했나요?

    문서의 표준 버전을 참조한다는 것은 해당 문서에 대한 업데이트가 해당 문서가 사용되는 모든 상황에 반영된다는 의미입니다.

댓글 남기기