실제 예제를 사용하여 Azure Cosmos DB에서 데이터를 모델링하고 분할하는 방법

적용 대상: NoSQL

이 문서는 데이터 모델링, 분할 및 프로비전된 처리량같은 여러 Azure Cosmos DB 개념을 기반으로 하여 실제 데이터 디자인 연습을 해결하는 방법을 보여 줍니다.

일반적으로 관계형 데이터베이스를 사용하는 경우 데이터 모델을 디자인하는 방법에 대한 습관과 직관을 작성했을 것입니다. 특정 제약 조건뿐만 아니라 Azure Cosmos DB의 고유한 강점으로 인해 이러한 모범 사례의 대부분은 잘 변환되지 않으며 최적이 아닌 솔루션으로 끌어들일 수 있습니다. 이 문서의 목표는 항목 모델링에서 엔터티 공동 배치 및 컨테이너 분할에 이르기까지 Azure Cosmos DB의 실제 사용 사례를 모델링하는 전체 프로세스를 안내하는 것입니다.

이 문서의 개념을 설명하는 커뮤니티 생성 소스 코드를 다운로드하거나 봅니다.

Important

커뮤니티 기여자 이 코드 샘플을 제공했으며 Azure Cosmos DB 팀은 기본 테넌트를 지원하지 않습니다.

시나리오

이 연습에서는 사용자가 게시물을 만들 수 있는 블로깅 플랫폼의 기본 살펴보겠습니다. 사용자는 해당 게시물에 댓글을 좋아하고 추가할 수도 있습니다.

우리는 기울임을 강조했습니다. 이러한 단어는 모델이 조작해야 할 "사물"의 종류를 식별합니다.

사양에 추가해야 하는 요구 사항은 다음과 같습니다.

  • 첫 페이지에는 최근에 만든 게시물의 피드가 표시됩니다.
  • 사용자의 모든 게시물, 게시물에 대한 모든 댓글 및 게시물에 대한 모든 좋아요를 가져올 수 있습니다.
  • 게시물은 작성자의 사용자 이름과 댓글 및 좋아요 수와 함께 반환됩니다.
  • 댓글 및 좋아요도 만든 사용자의 사용자 이름과 함께 반환됩니다.
  • 목록으로 표시되는 경우 게시물은 해당 콘텐츠의 잘린 요약만 표시하면됩니다.

기본 액세스 패턴 식별

먼저 솔루션의 액세스 패턴을 식별하여 초기 사양에 몇 가지 구조를 제공합니다. Azure Cosmos DB용 데이터 모델을 디자인할 때 모델이 해당 요청을 효율적으로 제공하는지 확인하기 위해 모델이 제공해야 하는 요청을 이해하는 것이 중요합니다.

전체 프로세스를 더 쉽게 따를 수 있도록 다양한 요청을 명령 또는 쿼리로 분류하여 CQRS에서 일부 어휘를 대여합니다. CQRS에서 명령은 쓰기 요청(즉, 시스템을 업데이트하려는 의도)이며 쿼리는 읽기 전용 요청입니다.

플랫폼이 노출하는 요청 목록은 다음과 같습니다.

  • [C1] 사용자 만들기/편집
  • [Q1] 사용자 검색
  • [C2] 게시물 만들기/편집
  • [Q2] 게시물 검색
  • [Q3] 짧은 형식으로 사용자의 게시물 나열
  • [C3] 댓글 만들기
  • [Q4] 게시물의 댓글 나열
  • [C4] 게시물에 대한 좋아요 표시
  • [Q5] 게시물의 좋아요 나열
  • [Q6] 짧은 형식으로 만든 x 최근 게시물 나열(피드)

이 단계에서는 각 엔터티(사용자, 게시물 등)에 포함된 세부 정보에 대해 생각하지 않았습니다. 이 단계는 일반적으로 관계형 저장소에 대해 디자인할 때 해결해야 할 첫 번째 단계 중 하나입니다. 이러한 엔터티가 테이블, 열, 외세 키 등의 측면에서 어떻게 변환되는지 파악해야 하기 때문에 먼저 이 단계로 시작합니다. 쓰기 시 스키마를 적용하지 않는 문서 데이터베이스에 대한 우려는 훨씬 적습니다.

처음부터 액세스 패턴을 식별하는 것이 중요한 기본 이유는 이 요청 목록이 테스트 도구 모음이 되기 때문입니다. 데이터 모델을 반복할 때마다 각 요청을 살펴보고 성능 및 확장성을 검사. 각 모델에서 사용되는 요청 단위를 계산하고 최적화합니다. 이러한 모든 모델은 기본 인덱싱 정책을 사용하며 특정 속성을 인덱싱하여 재정의할 수 있으므로 RU 사용량 및 대기 시간을 더욱 향상시킬 수 있습니다.

V1: 첫 번째 버전

두 개의 컨테이너로 시작합니다. usersposts

사용자 컨테이너

이 컨테이너는 사용자 항목만 저장합니다.

{
    "id": "<user-id>",
    "username": "<username>"
}

이 컨테이너를 분할합니다 id. 즉, 해당 컨테이너 내의 각 논리 파티션에는 하나의 항목만 포함됩니다.

게시물 컨테이너

이 컨테이너는 게시물, 메모 및 좋아요와 같은 엔터티를 호스트합니다.

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "title": "<post-title>",
    "content": "<post-content>",
    "creationDate": "<post-creation-date>"
}

{
    "id": "<comment-id>",
    "type": "comment",
    "postId": "<post-id>",
    "userId": "<comment-author-id>",
    "content": "<comment-content>",
    "creationDate": "<comment-creation-date>"
}

{
    "id": "<like-id>",
    "type": "like",
    "postId": "<post-id>",
    "userId": "<liker-id>",
    "creationDate": "<like-creation-date>"
}

이 컨테이너를 분할합니다 postId. 즉, 해당 컨테이너 내의 각 논리 파티션에는 하나의 게시물, 해당 게시물에 대한 모든 메모 및 해당 게시물에 대한 모든 좋아요가 포함됩니다.

이 컨테이너에서 호스트하는 type 세 가지 유형의 엔터티를 구분하기 위해 이 컨테이너에 저장된 항목에 속성을 도입했습니다.

또한 다음과 같은 이유로 관련 데이터를 포함하는 대신 참조하도록 선택했습니다(이러한 개념에 대한 자세한 내용은 이 섹션 검사).

  • 사용자가 만들 수 있는 게시물의 수에 대한 상한이 없습니다.
  • 게시물은 임의로 길 수 있습니다.
  • 게시물이 가질 수 있는 댓글 및 좋아요 수에 대한 상한은 없습니다.
  • 게시물 자체를 업데이트하지 않고도 게시물에 댓글 또는 좋아요를 추가할 수 있기를 원합니다.

모델의 성능은 어떻나요?

이제 첫 번째 버전의 성능과 확장성을 평가할 차례입니다. 이전에 식별된 각 요청에 대해 대기 시간 및 사용하는 요청 단위 수를 측정합니다. 이 측정은 사용자당 5~50개의 게시물이 있는 100,000명의 사용자와 게시물당 최대 25개의 댓글과 100개의 좋아요를 포함하는 더미 데이터 집합에 대해 수행됩니다.

[C1] 사용자 만들기/편집

이 요청은 컨테이너에서 항목을 만들거나 업데이트하기만 하면 간단하게 구현할 users 수 있습니다. 요청은 파티션 키 덕분에 id 모든 파티션에 잘 분산됩니다.

Diagram of writing a single item to the users' container.

대기 시간 RU 요금 성능
7 ms(ms) 5.71 Ru

[Q1] 사용자 검색

사용자 검색은 컨테이너에서 해당 항목을 읽어 수행됩니다 users .

Diagram of retrieving a single item from the users' container.

대기 시간 RU 요금 성능
2 ms(ms) 1 Ru

[C2] 게시물 만들기/편집

[C1]과 마찬가지로 posts 컨테이너에 쓰기만 하면 됩니다.

Diagram of writing a single post item to the posts container.

대기 시간 RU 요금 성능
9 ms(ms) 8.76 Ru

[Q2] 게시물 검색

먼저 컨테이너에서 해당 문서를 검색합니다 posts . 그러나 사양에 따라 게시물 작성자의 사용자 이름, 댓글 수 및 게시물에 대한 좋아요 수를 집계해야 합니다. 나열된 집계를 실행하려면 3개의 SQL 쿼리가 추가로 필요합니다.

Diagram of retrieving a post and aggregating additional data.

각 쿼리는 각 컨테이너의 파티션 키를 필터링하며, 이는 성능과 확장성을 최대화하려는 것입니다. 하지만 결국에는 4개의 작업을 수행하여 하나의 게시물을 반환해야 하므로 이 부분은 다음 반복에서 개선됩니다.

대기 시간 RU 요금 성능
9 ms(ms) 19.54 Ru

[Q3] 짧은 형식으로 사용자의 게시물 나열

먼저 특정 사용자에 해당하는 게시물을 가져오는 SQL 쿼리를 사용하여 원하는 게시물을 검색해야 합니다. 그러나 작성자의 사용자 이름과 댓글 및 좋아요 수를 집계하기 위해 더 많은 쿼리를 실행해야 합니다.

Diagram of retrieving all posts for a user and aggregating their additional data.

이 구현은 다음과 같은 많은 단점을 제공합니다.

  • 댓글 및 좋아요 수를 집계하는 쿼리는 첫 번째 쿼리에서 반환된 각 게시물에 대해 발급되어야 합니다.
  • 기본 쿼리는 컨테이너의 posts 파티션 키를 필터링하지 않으므로 컨테이너 전체에서 팬아웃 및 파티션 검색이 수행됩니다.
대기 시간 RU 요금 성능
130 ms(ms) 619.41 Ru

[C3] 댓글 만들기

컨테이너에 해당 항목을 작성하여 주석을 posts 만듭니다.

Diagram of writing a single comment item to the posts container.

대기 시간 RU 요금 성능
7 ms(ms) 8.57 Ru

[Q4] 게시물의 댓글 나열

해당 게시물에 대한 모든 댓글을 가져오는 쿼리로 시작하고, 각 메모에 대해 사용자 이름을 별도로 집계해야 합니다.

Diagram of retrieving all comments for a post and aggregating their additional data.

주 쿼리는 컨테이너의 파티션 키를 필터링하지만, 사용자 이름을 개별적으로 집계하면 전체 성능이 저하됩니다. 나중에 개선합니다.

대기 시간 RU 요금 성능
23 ms(ms) 27.72 Ru

[C4] 게시물에 대한 좋아요 표시

[C3]와 마찬가지로 컨테이너에 posts 해당 항목을 만듭니다.

Diagram of writing a single (like) item to the posts container.

대기 시간 RU 요금 성능
6 ms(ms) 7.05 Ru

[Q5] 게시물의 좋아요 나열

[Q4]와 마찬가지로 해당 게시물에 대한 좋아요를 쿼리한 다음, 해당 사용자 이름을 집계합니다.

Diagram of retrieving all likes for a post and aggregating their additional data.

대기 시간 RU 요금 성능
59 ms(ms) 58.92 Ru

[Q6] 짧은 형식으로 만든 x 최근 게시물 나열(피드)

posts 컨테이너를 쿼리하여 만든 날짜를 내림차순으로 정렬한 최근 게시물을 가져온 다음, 각 게시물에 대한 사용자 이름과 댓글 및 좋아요의 수를 집계합니다.

Diagram of retrieving most recent posts and aggregating their additional data.

다시 한번, 초기 쿼리는 컨테이너의 posts 파티션 키를 필터링하지 않으므로 비용이 많이 드는 팬아웃을 트리거합니다. 이는 더 큰 결과 집합을 대상으로 지정하고 절을 사용하여 ORDER BY 결과를 정렬하므로 요청 단위 측면에서 비용이 더 많이 들기 때문에 더욱 악화됩니다.

대기 시간 RU 요금 성능
306 ms(ms) 2063.54 Ru

V1의 성능 반영

이전 섹션에서 직면한 성능 문제를 살펴보면 두 가지 기본 문제 클래스를 식별할 수 있습니다.

  • 일부 요청은 반환해야 하는 모든 데이터를 수집하기 위해 여러 쿼리를 실행해야 합니다.
  • 일부 쿼리는 대상 컨테이너의 파티션 키를 필터링하지 않으므로 확장성을 저해하는 팬아웃이 생성됩니다.

첫 번째 문제부터 시작하여 이러한 각 문제를 해결해 보겠습니다.

V2: 읽기 쿼리를 최적화하기 위한 비정규화 소개

경우에 따라 더 많은 요청을 발급해야 하는 이유는 초기 요청의 결과에 반환해야 하는 모든 데이터가 포함되지 않기 때문입니다. 데이터를 비정규화하면 Azure Cosmos DB와 같은 비관계형 데이터 저장소로 작업할 때 데이터 집합에서 이러한 종류의 문제가 해결됩니다.

이 예제에서는 게시물 항목을 수정하여 게시물 작성자의 사용자 이름, 댓글 수 및 좋아요 수를 추가합니다.

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "userUsername": "<post-author-username>",
    "title": "<post-title>",
    "content": "<post-content>",
    "commentCount": <count-of-comments>,
    "likeCount": <count-of-likes>,
    "creationDate": "<post-creation-date>"
}

또한 주석 및 좋아요 항목을 수정하여 해당 항목을 만든 사용자의 사용자 이름을 추가합니다.

{
    "id": "<comment-id>",
    "type": "comment",
    "postId": "<post-id>",
    "userId": "<comment-author-id>",
    "userUsername": "<comment-author-username>",
    "content": "<comment-content>",
    "creationDate": "<comment-creation-date>"
}

{
    "id": "<like-id>",
    "type": "like",
    "postId": "<post-id>",
    "userId": "<liker-id>",
    "userUsername": "<liker-username>",
    "creationDate": "<like-creation-date>"
}

주석 및 좋아요 수 비정규화

우리가 달성하고자하는 것은 우리가 코멘트 등을 추가 할 때마다, 우리는 또한 그 또는 해당 게시물에 증가 commentCountlikeCount 한다는 것입니다. 컨테이너를 분할할 postspostId 새 항목(주석 등)과 해당 게시물이 동일한 논리 파티션에 배치됩니다. 따라서 저장 프로시저사용하여 해당 작업을 수행할 수 있습니다.

주석([C3])을 만들 때 컨테이너에 새 항목을 추가하는 대신 해당 컨테이너에서 posts 다음 저장 프로시저를 호출합니다.

function createComment(postId, comment) {
  var collection = getContext().getCollection();

  collection.readDocument(
    `${collection.getAltLink()}/docs/${postId}`,
    function (err, post) {
      if (err) throw err;

      post.commentCount++;
      collection.replaceDocument(
        post._self,
        post,
        function (err) {
          if (err) throw err;

          comment.postId = postId;
          collection.createDocument(
            collection.getSelfLink(),
            comment
          );
        }
      );
    })
}

이 저장 프로시저는 게시물의 ID와 새 댓글의 본문을 매개 변수로 사용하여 다음을 수행합니다.

  • 게시물을 검색합니다.
  • 를 증분합니다. commentCount
  • 게시물을 바꿉니다.
  • 새 주석을 추가합니다.

저장 프로시저가 원자성 트랜잭션으로 실행될 때 값 commentCount 과 실제 주석 수는 항상 동기화 상태로 유지됩니다.

새 좋아요를 추가하여 증 likeCount분할 때 유사한 저장 프로시저를 호출합니다.

사용자 이름 비정규화

사용자 이름은 사용자가 다른 파티션뿐만 아니라 다른 컨테이너에 있기 때문에 다른 접근 방식이 필요합니다. 파티션과 컨테이너 간에 데이터를 비정규화해야 하는 경우 원본 컨테이너의 변경 피드를 사용할 수 있습니다.

이 예제에서는 컨테이너의 변경 피드를 users 사용하여 사용자가 사용자 이름을 업데이트할 때마다 반응합니다. 이 경우 컨테이너에서 다른 저장 프로시저 posts 를 호출하여 변경 사항을 전파합니다.

Diagram of denormalizing usernames into the posts container.

function updateUsernames(userId, username) {
  var collection = getContext().getCollection();
  
  collection.queryDocuments(
    collection.getSelfLink(),
    `SELECT * FROM p WHERE p.userId = '${userId}'`,
    function (err, results) {
      if (err) throw err;

      for (var i in results) {
        var doc = results[i];
        doc.userUsername = username;

        collection.upsertDocument(
          collection.getSelfLink(),
          doc);
      }
    });
}

이 저장 프로시저는 사용자의 ID와 사용자의 새 사용자 이름을 매개 변수로 사용합니다.

  • 는 게시물, 댓글 또는 좋아요와 일치하는 userId 모든 항목을 가져옵니다.
  • 각 항목에 대해
    • userUsername
    • 항목을 바꿉니다.

Important

이 작업은 컨테이너의 posts 모든 파티션에서 이 저장 프로시저를 실행해야 하기 때문에 비용이 많이 듭니다. 대부분의 사용자는 등록 중에 적절한 사용자 이름을 선택하고 변경하지 않으므로 이 업데이트는 거의 실행되지 않습니다.

V2의 성능 향상은 무엇인가요?

V2의 성능 향상에 대해 알아보겠습니다.

[Q2] 게시물 검색

이제 비정규화가 완료되었으므로 해당 요청을 처리하기 위해 하나의 항목만 가져오면 됩니다.

Diagram of retrieving a single item from the denormalized posts container.

대기 시간 RU 요금 성능
2 ms(ms) 1 Ru

[Q4] 게시물의 댓글 나열

여기서는 사용자 이름을 가져온 추가 요청을 절약하고 파티션 키를 필터링하는 단일 쿼리로 끝날 수 있습니다.

Diagram of retrieving all comments for a denormalized post.

대기 시간 RU 요금 성능
4 ms(ms) 7.72 Ru

[Q5] 게시물의 좋아요 나열

좋아요를 나열할 때와 똑같은 상황입니다.

Diagram of retrieving all likes for a denormalized post.

대기 시간 RU 요금 성능
4 ms(ms) 8.92 Ru

V3: 모든 요청이 확장 가능한지 확인

전반적인 성능 향상을 살펴볼 때 완전히 최적화되지 않은 두 가지 요청이 여전히 있습니다. 이러한 요청은 [Q3][Q6]입니다. 대상 컨테이너의 파티션 키를 필터링하지 않는 쿼리와 관련된 요청입니다.

[Q3] 짧은 형식으로 사용자의 게시물 나열

이 요청은 이미 V2에 도입된 향상된 기능으로 인해 더 많은 쿼리를 절약할 수 있습니다.

Diagram that shows the query to list a user's denormalized posts in short form.

그러나 다시 기본 쿼리는 컨테이너의 posts 파티션 키를 필터링하지 않습니다.

이 상황에 대해 생각하는 방법은 간단합니다.

  1. 이 요청 특정 사용자에 대한 userId 모든 게시물을 가져오려고 하기 때문에 필터링해야 합니다.
  2. 분할이 없는 컨테이너에 대해 posts 실행되므로 성능이 userId 좋지 않습니다.
  3. 분명히 말하면, 로 분할된 userId컨테이너에 대해 이 요청을 실행하여 성능 문제를 해결할 것입니다.
  4. 컨테이너와 같은 컨테이너가 이미 있는 것으로 밝혀졌습니다 users .

따라서 전체 게시물을 users 컨테이너에 복제하여 두 번째 수준의 비정규화를 도입합니다. 이렇게 하면 게시물의 복사본을 효과적으로 얻을 수 있으며, 다른 차원을 따라 분할되어 해당 게시물에 의해 userId검색하는 것이 더 효율적입니다.

이제 컨테이너에는 users 다음 두 종류의 항목이 포함됩니다.

{
    "id": "<user-id>",
    "type": "user",
    "userId": "<user-id>",
    "username": "<username>"
}

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "userUsername": "<post-author-username>",
    "title": "<post-title>",
    "content": "<post-content>",
    "commentCount": <count-of-comments>,
    "likeCount": <count-of-likes>,
    "creationDate": "<post-creation-date>"
}

이 예에서는 다음이 적용됩니다.

  • 사용자 항목에 게시물과 사용자를 구분하는 필드가 도입되었습니다 type .
  • 또한 사용자 항목에 필드를 추가 userId 했습니다. 이 필드는 필드와 id 중복되지만 컨테이너가 이제 이전과 같이 분할되기 userId 때문에 users 필요합니다.id

이러한 비정규화를 달성하기 위해 변경 피드를 다시 한 번 사용합니다. 이번에는 컨테이너의 posts 변경 피드에 반응하여 새 게시물이나 업데이트된 게시물을 컨테이너에 users 디스패치합니다. 게시물을 나열해도 전체 콘텐츠를 반환할 필요가 없으므로 프로세스에서 삭제할 수 있습니다.

Diagram of denormalizing posts into the users' container.

이제 컨테이너의 파티션 키를 필터링하여 users 쿼리를 컨테이너로 라우팅할 수 있습니다.

Diagram of retrieving all posts for a denormalized user.

대기 시간 RU 요금 성능
4 ms(ms) 6.46 Ru

[Q6] 짧은 형식으로 만든 x 최근 게시물 나열(피드)

여기서도 비슷한 상황을 처리해야 합니다. V2에 도입된 비정규화로 인해 더 많은 쿼리가 불필요하게 남아 있는 경우에도 다시 기본 쿼리는 컨테이너의 파티션 키를 필터링하지 않습니다.

Diagram that shows the query to list the x most recent posts created in short form.

동일한 접근 방식에 따라 이 요청의 성능과 확장성을 최대화하려면 하나의 파티션만 적중해야 합니다. 제한된 수의 항목만 반환해야 하므로 단일 파티션을 적중하는 것만 생각할 수 있습니다. 블로깅 플랫폼의 홈페이지를 채우려면 전체 데이터 집합을 페이지를 매길 필요 없이 가장 최근 게시물 100개만 가져와야 합니다.

따라서 이 마지막 요청을 최적화하기 위해 이 요청을 제공하는 데 전적으로 전념하는 세 번째 컨테이너를 디자인에 도입했습니다. 게시물을 새 feed 컨테이너에 비정규화합니다.

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "userUsername": "<post-author-username>",
    "title": "<post-title>",
    "content": "<post-content>",
    "commentCount": <count-of-comments>,
    "likeCount": <count-of-likes>,
    "creationDate": "<post-creation-date>"
}

type 필드는 항상 post 항목에 있는 이 컨테이너를 분할합니다. 이렇게 하면 이 컨테이너의 모든 항목이 동일한 파티션에 배치됩니다.

비정규화를 달성하려면 이전에 도입한 변경 피드 파이프라인을 후크하여 해당 새 컨테이너에 게시물을 디스패치하기만 하면 합니다. 명심해야 할 중요한 한 가지는 최근 게시물 100개만 저장해야 한다는 것입니다. 그렇지 않으면 컨테이너의 콘텐츠가 파티션의 최대 크기를 초과하여 증가할 수 있습니다. 이 제한은 컨테이너에 문서가 추가될 때마다 사후 트리거를 호출하여 구현할 수 있습니다.

Diagram of denormalizing posts into the feed container.

컬렉션을 자르는 사후 트리거의 본문은 다음과 같습니다.

function truncateFeed() {
  const maxDocs = 100;
  var context = getContext();
  var collection = context.getCollection();

  collection.queryDocuments(
    collection.getSelfLink(),
    "SELECT VALUE COUNT(1) FROM f",
    function (err, results) {
      if (err) throw err;

      processCountResults(results);
    });

  function processCountResults(results) {
    // + 1 because the query didn't count the newly inserted doc
    if ((results[0] + 1) > maxDocs) {
      var docsToRemove = results[0] + 1 - maxDocs;
      collection.queryDocuments(
        collection.getSelfLink(),
        `SELECT TOP ${docsToRemove} * FROM f ORDER BY f.creationDate`,
        function (err, results) {
          if (err) throw err;

          processDocsToRemove(results, 0);
        });
    }
  }

  function processDocsToRemove(results, index) {
    var doc = results[index];
    if (doc) {
      collection.deleteDocument(
        doc._self,
        function (err) {
          if (err) throw err;

          processDocsToRemove(results, index + 1);
        });
    }
  }
}

마지막 단계는 쿼리를 새 feed 컨테이너로 다시 라우팅하는 것입니다.

Diagram of retrieving the most recent posts.

대기 시간 RU 요금 성능
9 ms(ms) 16.97 Ru

결론

다양한 버전의 디자인에 대해 도입한 전반적인 성능 및 확장성 향상을 살펴보겠습니다.

V1 V2 V3
[C1] 7 ms / 5.71 RU 7 ms / 5.71 RU 7 ms / 5.71 RU
[Q1] 2 ms / 1 RU 2 ms / 1 RU 2 ms / 1 RU
[C2] 9 ms / 8.76 RU 9 ms / 8.76 RU 9 ms / 8.76 RU
[Q2] 9 ms / 19.54 RU 2 ms / 1 RU 2 ms / 1 RU
[Q3] 130 ms / 619.41 RU 28 ms / 201.54 RU 4 ms / 6.46 RU
[C3] 7 ms / 8.57 RU 7 ms / 15.27 RU 7 ms / 15.27 RU
[Q4] 23 ms / 27.72 RU 4 ms / 7.72 RU 4 ms / 7.72 RU
[C4] 6 ms / 7.05 RU 7 ms / 14.67 RU 7 ms / 14.67 RU
[Q5] 59 ms / 58.92 RU 4 ms / 8.92 RU 4 ms / 8.92 RU
[Q6] 306 ms / 2063.54 RU 83 ms / 532.33 RU 9 ms / 16.97 RU

읽기가 많은 시나리오를 최적화했습니다.

쓰기 요청(명령)을 희생하면서 읽기 요청(쿼리)의 성능을 개선하기 위한 노력을 집중했을 수 있습니다. 대부분의 경우 쓰기 작업은 변경 피드를 통해 후속 비정규화를 트리거하므로 계산 비용이 더 많이 들고 구체화하는 데 더 오래 걸립니다.

대부분의 소셜 앱과 같은 블로깅 플랫폼이 읽기가 무거우므로 읽기 성능에 중점을 두는 것이 좋습니다. 읽기 작업이 많은 워크로드는 처리해야 하는 읽기 요청의 양이 일반적으로 쓰기 요청 수보다 큰 순서임을 나타냅니다. 따라서 읽기 요청이 더 저렴하고 더 나은 성능을 발휘할 수 있도록 쓰기 요청을 실행하는 데 비용이 더 많이 드는 것이 좋습니다.

가장 극단적인 최적화 를 살펴보면 [Q6] 은 2000개 이상의 RU에서 단 17RU로 진행되었으며, 항목당 약 10RU의 비용으로 게시물을 비정규화하여 이를 달성했습니다. 게시물을 만들거나 업데이트하는 것보다 훨씬 더 많은 피드 요청을 제공하므로 전체 절감액을 고려할 때 이 비정규화 비용은 무시할 수 있습니다.

비정규화는 증분 방식으로 적용할 수 있습니다.

이 문서에서 살펴본 향상된 확장성 기능은 데이터 세트 전체의 데이터 비정규화 및 복제와 관련이 있습니다. 이러한 최적화는 1일차에 적용할 필요가 없다는 점에 유의해야 합니다. 파티션 키를 필터링하는 쿼리는 대규모로 더 잘 수행되지만 파티션 간 쿼리는 드물게 호출되거나 제한된 데이터 집합에 대해 호출되는 경우 허용될 수 있습니다. 프로토타입을 빌드하거나 작고 제어된 사용자 기반을 사용하여 제품을 출시하는 경우 나중에 이러한 개선 사항을 절약할 수 있습니다. 중요한 것은 모델의 성능을 모니터링하여 모델을 가져올 시기와 시기를 결정할 수 있도록 하는 것입니다.

다른 컨테이너에 업데이트를 배포하는 데 사용하는 변경 피드는 모든 업데이트를 영구적으로 저장합니다. 이러한 지속성을 통해 시스템에 이미 많은 데이터가 있는 경우에도 컨테이너 및 부트스트랩 비정규화된 뷰를 일회성 캐치업 작업으로 만들기 때문에 모든 업데이트를 요청할 수 있습니다.

다음 단계

실제 데이터 모델링 및 분할에 대한 소개 후 다음 문서를 검사 설명한 개념을 검토할 수 있습니다.