Compartilhar via


Como modelar e particionar dados usando um exemplo do mundo real

Este artigo baseia-se em vários conceitos do Azure Cosmos DB, como modelagem de dados, particionamento e taxa de transferência provisionada para demonstrar como lidar com um exercício de design de dados do mundo real.

Se você geralmente trabalha com bancos de dados relacionais, provavelmente desenvolveu hábitos para criar modelos de dados. Devido às restrições específicas, mas também aos pontos fortes exclusivos do Azure Cosmos DB, a maioria dessas práticas recomendadas não se traduz bem e pode arrastá-lo para soluções abaixo do ideal. O objetivo deste artigo é guiá-lo pelo processo completo de modelagem de um caso de uso do mundo real no Azure Cosmos DB, da modelagem de item à colocação de entidade e particionamento de contêiner.

Para obter um exemplo que ilustra os conceitos neste artigo, baixe ou exiba esse código-fonte gerado pela comunidade.

Importante

Um colaborador da comunidade contribuiu com este exemplo de código. A equipe do Azure Cosmos DB não dá suporte à sua manutenção.

O cenário

Para este exercício, vamos considerar o domínio de uma plataforma de blog em que os usuários podem criar postagens. Os usuários também podem gostar e adicionar comentários a essas postagens.

Dica

Algumas palavras são realçadas em itálico para identificar o tipo de "coisas" que nosso modelo manipula.

Adicionando mais requisitos à nossa especificação:

  • Uma primeira página exibe um feed de postagens criadas recentemente.
  • Podemos buscar todas as postagens para um usuário, todos os comentários de uma postagem e todos os likes para uma postagem.
  • As postagens são retornadas com o nome de usuário de seus autores e uma contagem de quantos comentários e curtidas eles têm.
  • Comentários e curtidas também são retornados com o nome de usuário dos usuários que os criaram.
  • Quando exibidas como listas, as postagens só precisam apresentar um resumo truncado de seu conteúdo.

Identificar os principais padrões de acesso

Para começar, damos alguma estrutura à nossa especificação inicial identificando os padrões de acesso da nossa solução. Ao criar um modelo de dados para o Azure Cosmos DB, é importante entender quais solicitações nosso modelo precisa servir para garantir que o modelo atenda a essas solicitações com eficiência.

Para tornar o processo geral mais fácil de seguir, categorizamos essas solicitações diferentes como comandos ou consultas, emprestando algum vocabulário da segregação de responsabilidade de consulta de comando (CQRS). No padrão CQRS, os comandos são requisições de escrita (ou seja, intenções de modificar o sistema) e as consultas são requisições de somente leitura.

Aqui está a lista de solicitações que nossa plataforma expõe:

  • [C1] Criar ou editar um usuário
  • [Q1] Recuperar um usuário
  • [C2] Criar ou editar uma postagem
  • [Q2] Recuperar uma postagem
  • [Q3] Listar as postagens de um usuário em formato curto
  • [C3] Criar um comentário
  • [Q4] Listar os comentários de uma postagem
  • [C4] Curtir uma postagem
  • [Q5] Listar as curtidas de uma postagem
  • [Q6] Listar as x postagens mais recentes criadas em forma curta (feed)

Neste estágio, não pensamos nos detalhes do que cada entidade (usuário, postagem etc.) contém. Essa etapa geralmente está entre as primeiras a serem abordadas ao projetar em um relational store. Começamos com essa etapa primeiro porque temos que descobrir como essas entidades se traduzem em termos de tabelas, colunas, chaves estrangeiras e assim por diante. Isso é muito menos preocupante em um banco de dados de documentos que não impõe nenhum esquema na gravação.

É importante identificar nossos padrões de acesso desde o início porque essa lista de solicitações será nosso conjunto de testes. Sempre que iteramos em nosso modelo de dados, passamos por cada uma das solicitações e verificamos seu desempenho e escalabilidade. Calculamos as RU (unidades de solicitação) consumidas em cada modelo e as otimizamos. Todos esses modelos usam a política de indexação padrão e você pode substituí-la indexando propriedades específicas, o que pode melhorar ainda mais o consumo de RU e a latência.

V1: Uma primeira versão

Começamos com dois contêineres: users e posts.

Contêiner de usuários

Esse contêiner armazena apenas itens de usuário:

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

Particionamos esse contêiner, ido que significa que cada partição lógica dentro desse contêiner contém apenas um item.

Contêiner de postagens

Esse contêiner hospeda entidades como postagens, comentários e curtidas:

{
    "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>"
}

Particionamos esse contêiner por postId, o que significa que cada partição lógica dentro desse contêiner contém uma postagem, todos os comentários dessa postagem e todos os likes para essa postagem.

Introduzimos uma type propriedade nos itens armazenados neste contêiner para distinguir entre os três tipos de entidades que esse contêiner hospeda.

Além disso, optamos por fazer referência aos dados relacionados em vez de inseri-los porque:

  • Não há limite superior para quantas postagens um usuário pode criar.
  • As postagens podem ser arbitrariamente longas.
  • Não há limite superior para quantos comentários e curtidas uma postagem pode ter.
  • Queremos poder adicionar um comentário ou um like a uma postagem sem precisar atualizar a postagem em si.

Para saber mais sobre esses conceitos, consulte Modelagem de dados no Azure Cosmos DB.

Qual é o desempenho do nosso modelo?

Agora é hora de avaliar o desempenho e a escalabilidade da nossa primeira versão. Para cada uma das solicitações identificadas anteriormente, medimos sua latência e quantas unidades de solicitação ela consome. Essa medida é feita em um conjunto de dados fictício contendo 100.000 usuários com 5 a 50 postagens por usuário e até 25 comentários e 100 curtidas por postagem.

[C1] Criar ou editar um usuário

Essa solicitação é simples de implementar, pois apenas criamos ou atualizamos um item no users contêiner. As solicitações são distribuídas eficientemente em todas as partições graças à id chave de partição.

Diagrama de gravação de um único item no contêiner de usuários.

Latency Unidades de Solicitação Desempenho
7 ms 5.71 RU

[Q1] Recuperar um usuário

A recuperação de um usuário é feita lendo o item correspondente do users contêiner.

Diagrama de recuperação de um único item do contêiner de usuários.

Latency Unidades de Solicitação Desempenho
2 ms 1 RU

[C2] Criar ou editar uma postagem

Da mesma forma que fazemos com [C1], só precisamos escrever no posts contêiner.

Diagrama de gravação de um único item de postagem no contêiner de postagens.

Latency Unidades de Solicitação Desempenho
9 ms 8.76 RU

[Q2] Recuperar uma postagem

Começamos recuperando o documento correspondente do posts contêiner. Mas isso não é suficiente, de acordo com nossa especificação, também temos que agregar o nome de usuário do autor da postagem, contagens de comentários e contagens de curtidas para a postagem. As agregações listadas exigem que mais três consultas SQL sejam emitidas.

Diagrama de recuperação de uma postagem e agregação de dados adicionais.

Cada uma das consultas aplica um filtro na chave de partição de seu respectivo contêiner, a fim de maximizar o desempenho e a escalabilidade. Mas eventualmente temos que executar quatro operações para retornar um único post, então vamos melhorar isso em uma próxima iteração.

Latency Unidades de Solicitação Desempenho
9 ms 19.54 RU

[Q3] Listar as postagens de um usuário em formato curto

Primeiro, precisamos recuperar as postagens desejadas com uma consulta SQL que busca as postagens correspondentes a esse usuário específico. Mas também precisamos emitir mais consultas para agregar o nome de usuário do autor e as contagens de comentários e curtidas.

Diagrama de recuperação de todas as postagens para um usuário e agregação de seus dados adicionais.

Essa implementação apresenta muitas desvantagens:

  • As consultas que agregam as contagens de curtidas e comentários devem ser realizadas para cada publicação retornada pela primeira consulta.
  • A consulta principal não filtra pela chave de partição do contêiner posts, o que leva a uma ramificação e a uma varredura de partição em todo o contêiner.
Latency Unidades de Solicitação Desempenho
130 ms 619.41 RU

[C3] Criar um comentário

Um comentário é criado escrevendo o item correspondente no posts contêiner.

Diagrama da escrita de um único item de comentário no contêiner de postagens.

Latency Unidades de Solicitação Desempenho
7 ms 8.57 RU

[Q4] Listar os comentários de uma postagem

Começamos com uma consulta que busca todos os comentários dessa postagem e, mais uma vez, também precisamos agregar nomes de usuário separadamente para cada comentário.

Diagrama de recuperação de todos os comentários para uma postagem e agregação de seus dados adicionais.

Embora a consulta principal filtre na chave de partição do contêiner, a agregação separada dos nomes de usuário penaliza o desempenho geral. Melhoramos isso mais tarde.

Latency Unidades de Solicitação Desempenho
23 ms 27.72 RU

[C4] Curtir uma postagem

Assim como [C3], criamos o item correspondente no posts contêiner.

Diagrama da gravação de um item individual (curtida) no contêiner de postagens.

Latency Unidades de Solicitação Desempenho
6 ms 7.05 RU

[Q5] Listar as curtidas de uma postagem

Assim como [Q4], consultamos as curtidas dessa postagem e, em seguida, agregamos seus nomes de usuário.

Diagrama de recuperação de todos os likes para uma postagem e agregação de seus dados adicionais.

Latency Unidades de Solicitação Desempenho
59 ms 58.92 RU

[Q6] Listar as x postagens mais recentes criadas em formato curto (feed)

Buscamos as postagens mais recentes consultando o posts contêiner classificado pela data de criação decrescente e agregamos nomes de usuário e contagens de comentários e curtidas para cada uma das postagens.

Diagrama de recuperação de postagens mais recentes e agregação de seus dados adicionais.

Mais uma vez, nossa consulta inicial não filtra a chave de partição do contêiner posts, o que dispara um fan-out dispendioso. Isso é ainda pior, pois visamos um conjunto de resultados maior e classificamos os resultados com uma cláusula ORDER BY, o que o torna mais caro em termos de unidades de solicitação.

Latency Unidades de Solicitação Desempenho
306 ms 2063.54 RU

Refletir sobre o desempenho da V1

Analisando os problemas de desempenho que enfrentamos na seção anterior, podemos identificar duas classes principais de problemas:

  • Algumas solicitações exigem que várias consultas sejam emitidas para coletar todos os dados que precisamos retornar.
  • Algumas consultas não filtram pela chave de partição dos contêineres que visam, o que leva a uma dispersão que impede nossa escalabilidade.

Vamos resolver cada um desses problemas, começando com o primeiro.

V2: Introduzir a desnormalização para otimizar consultas de leitura

O motivo pelo qual precisamos emitir mais solicitações em alguns casos é porque os resultados da solicitação inicial não contêm todos os dados que precisamos retornar. A desnormalização de dados resolve esse tipo de problema em nosso conjunto de dados ao trabalhar com um armazenamento de dados não relacional, como o Azure Cosmos DB.

Em nosso exemplo, modificamos itens de postagem para adicionar o nome de usuário do autor da postagem, a contagem de comentários e a contagem de curtidas:

{
    "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>"
}

Também modificamos comentários e itens semelhantes para adicionar o nome de usuário do usuário que os criou:

{
    "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>"
}

Desnormalizar comentários e contagens de curtidas

O que queremos alcançar é que sempre que adicionarmos um comentário ou um like, também incrementamos o commentCount ou o likeCount no post correspondente. Como nosso contêiner postId é particionado por posts, o novo item (comentário ou curtida) e seu post correspondente ficam na mesma partição lógica. Como resultado, podemos usar um procedimento armazenado para executar essa operação.

Quando você cria um comentário ([C3]), em vez de apenas adicionar um novo item no posts contêiner, chamamos o seguinte procedimento armazenado nesse contêiner:

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
          );
        }
      );
    })
}

Esse procedimento armazenado usa a ID da postagem e o corpo do novo comentário como parâmetros, em seguida:

  • recupera a postagem.
  • incrementa a commentCount.
  • substitui a postagem.
  • adiciona o novo comentário.

Como os procedimentos armazenados são executados como transações atômicas, o valor de commentCount e o número real de comentários sempre permanecem em sincronia.

Obviamente, chamamos um procedimento armazenado semelhante ao adicionar novas curtidas para incrementar a likeCount.

Desnormalizar nomes de usuário

Os nomes de usuário exigem uma abordagem diferente, pois os usuários não apenas se sentam em partições diferentes, mas em um contêiner diferente. Quando tivermos que desnormalizar dados entre partições e contêineres, podemos usar o feed de alterações do contêiner de origem.

Em nosso exemplo, usamos o feed de alterações do users contêiner para reagir sempre que os usuários atualizam seus nomes de usuário. Quando isso acontece, propagamos a alteração chamando outro procedimento armazenado no posts contêiner:

Diagrama de desnormalizar nomes de usuário no contêiner de postagens.

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);
      }
    });
}

Esse procedimento armazenado usa a ID do usuário e o novo nome de usuário do usuário como parâmetros, em seguida:

  • busca todos os itens correspondentes a userId (que podem ser postagens, comentários ou curtidas).
  • para cada um desses itens:
    • substitui o userUsername.
    • substitui o item.

Importante

Essa operação é dispendiosa porque requer que esse procedimento armazenado seja executado em cada partição do posts contêiner. Presumimos que a maioria dos usuários escolhe um nome de usuário adequado durante a inscrição e nunca o altera, portanto, essa atualização é executada muito raramente.

Quais são os ganhos de desempenho da V2?

Vamos falar sobre alguns dos ganhos de desempenho da V2.

[Q2] Recuperar uma postagem

Agora que a desnormalização está em vigor, precisamos apenas buscar um item individual para processar essa solicitação.

Diagrama de recuperação de um único item do contêiner de postagens desnormalizadas.

Latency Unidades de Solicitação Desempenho
2 ms 1 RU

[Q4] Listar os comentários de uma postagem

Aqui novamente, podemos poupar as solicitações extras que buscaram os nomes de usuário, resultando em uma única consulta que filtra pela chave de partição.

Diagrama de recuperação de todos os comentários de uma postagem desnormalizada.

Latency Unidades de Solicitação Desempenho
4 ms 7.72 RU

[Q5] Listar as curtidas de uma postagem

Exatamente a mesma situação ao listar as curtidas.

Diagrama de recuperação de todos os likes para uma postagem desnormalizada.

Latency Unidades de Solicitação Desempenho
4 ms 8.92 RU

V3: Verifique se todas as solicitações são escalonáveis

Ainda há duas solicitações que não otimizamos totalmente ao examinar nossas melhorias gerais de desempenho. Essas solicitações são [Q3] e [Q6]. Elas são as solicitações que envolvem consultas que não filtram a chave de partição dos contêineres aos quais se destinam.

[Q3] Listar as postagens de um usuário em formato curto

Esta solicitação já se beneficia dos aperfeiçoamentos introduzidos na V2, o que poupa consultas adicionais.

Diagrama que mostra a consulta para listar as postagens desnormalizadas de um usuário em forma curta.

Porém, a consulta restante ainda não filtra a chave de partição do contêiner posts.

A maneira de pensar sobre essa situação é simples:

  • Essa solicitação tem que filtrar no userId porque queremos obter todas as postagens para um usuário específico.
  • Ela não tem um bom desempenho porque é executada no contêiner posts, que não é particionanda por userId.
  • Indicando o óbvio, resolveríamos nosso problema de desempenho executando essa solicitação em um contêiner particionado com userId.
  • Acontece que já temos um contêiner desse tipo: o users contêiner!

Portanto, introduzimos um segundo nível de desnormalização duplicando postagens inteiras no users contêiner. Fazendo isso, obtemos efetivamente uma cópia das postagens, apenas particionadas em uma dimensão diferente, tornando a recuperação delas mais eficiente pelas respectivas userId.

O users contêiner agora contém dois tipos de itens:

{
    "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>"
}

Neste exemplo:

  • Introduzimos um type campo no item de usuário para distinguir os usuários das postagens.
  • Também adicionamos um userId campo no item de usuário, que é redundante com o id campo, mas é necessário, pois o users contêiner agora é particionado com userId (e não id como anteriormente).

Para obter essa desnormalização, recorremos novamente ao fluxo de alterações. Desta vez, reagimos ao feed de alterações do contêiner posts para despachar qualquer postagem nova ou atualizada para o contêiner users. E já que a listagem de posts não exige que o conteúdo completo deles seja retornado, podemos truncá-los no processo.

Diagrama de desnormalização de postagens no contêiner dos usuários.

Agora podemos rotear nossa consulta para o users contêiner, filtrando na chave de partição do contêiner.

Diagrama de recuperação de todas as postagens para um usuário desnormalizado.

Latency Unidades de Solicitação Desempenho
4 ms 6.46 RU

[Q6] Listar as x postagens mais recentes criadas em formato curto (feed)

Temos de lidar com uma situação semelhante aqui: mesmo depois de poupar as consultas adicionais tornadas desnecessárias pela desnormalização introduzida na V2, a consulta restante não filtra na chave de partição do contêiner:

Diagrama que mostra a consulta para listar as x postagens mais recentes criadas em forma curta.

Seguindo a mesma abordagem, maximizar o desempenho e a escalabilidade dessa solicitação requer que ela atinja apenas uma partição. Isso é concebível porque precisamos apenas atingir um número limitado de itens. Para preencher a página inicial da nossa plataforma de blogs, precisamos apenas obter as 100 postagens mais recentes, sem a necessidade de paginar por todo o conjunto de dados.

Portanto, para otimizar essa última solicitação, apresentamos um terceiro contêiner ao nosso design, totalmente dedicado a atender a essa solicitação. Desnormalizamos nossas postagens nesse novo feed contêiner:

{
    "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>"
}

O type campo particiona esse contêiner, que é sempre post em nossos itens. Fazer isso garante que todos os itens neste contêiner fiquem na mesma partição.

Para obter a desnormalização, precisamos apenas nos conectar ao pipeline do feed de alterações que introduzimos anteriormente para expedir as postagens para esse novo contêiner. Uma coisa importante a ter em mente é que precisamos garantir que armazenemos apenas as 100 postagens mais recentes; caso contrário, o conteúdo do contêiner pode crescer além do tamanho máximo de uma partição. Essa limitação pode ser implementada chamando um post-trigger sempre que um documento é adicionado no container:

Diagrama de desnormalização de postagens no contêiner de feed.

Aqui está o corpo do pós-gatilho que trunca a coleção:

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);
        });
    }
  }
}

A etapa final é redirecionar nossa consulta para nosso novo feed contêiner:

Diagrama de recuperação das postagens mais recentes.

Latency Unidades de Solicitação Desempenho
9 ms 16.97 RU

Conclusion

Vamos dar uma olhada nas melhorias gerais de desempenho e escalabilidade que introduzimos nas diferentes versões do nosso design.

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

Otimizamos um cenário com alta demanda de leitura

Você deve ter notado que concentramos nossos esforços em melhorar o desempenho das solicitações de leitura (consultas) em detrimento das solicitações de gravação (comandos). Em muitos casos, as operações de gravação já disparam uma desnormalização seguinte por meio de feeds de alterações, o que as torna mais caras em termos de computação e faz com que levem mais tempo para se materializar.

Justificamos esse foco no desempenho de leitura pelo fato de que uma plataforma de blog, assim como a maioria dos aplicativos sociais, é mais utilizada para leitura. Uma carga de trabalho de leitura pesada indica que a quantidade de solicitações de leitura que ela precisa atender geralmente é uma ordem de magnitude maior que o número de solicitações de gravação. Portanto, faz sentido tornar as solicitações de gravação mais caras para serem executadas para permitir que as solicitações de leitura sejam mais baratas e de melhor desempenho.

Se examinarmos a otimização mais extrema que fizemos, [Q6] passou de mais de 2000 RUs para apenas 17 RUs; conseguimos isso desnormalizando postagens a um custo de cerca de 10 RUs por item. Como atenderíamos muito mais solicitações de feed do que a criação ou atualizações de postagens, o custo dessa desnormalização é insignificante considerando a economia geral.

A desnormalização pode ser aplicada incrementalmente

As melhorias de escalabilidade que exploramos neste artigo envolvem desnormalização e duplicação de dados em todo o conjunto de dados. Observe que essas otimizações não precisam ser implementadas no primeiro dia. As consultas que filtram as chaves de partição têm um desempenho melhor em escala, mas as consultas de partição cruzada podem ser aceitáveis se forem chamadas raramente ou em um conjunto de dados limitado. Se você estiver apenas criando um protótipo ou iniciando um produto com uma base de usuários pequena e controlada, provavelmente poderá poupar essas melhorias para mais tarde. O importante, então, é monitorar o desempenho do modelo para que você possa decidir se e quando é hora de trazê-los.

O feed de alterações que usamos para distribuir atualizações para outros contêineres armazena todas essas atualizações persistentemente. Essa persistência possibilita solicitar todas as atualizações desde a criação do contêiner e inicializar exibições desnormalizadas como uma operação de recuperação única, mesmo que seu sistema já tenha muitos dados.

Próximas etapas

Após esta introdução à modelagem prática de dados e ao particionamento, convém verificar os seguintes artigos para examinar os conceitos: