Partilhar 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 modelação de dados, particionamento e throughput provisionado, para demonstrar como abordar um exercício real de concepção de dados.

Se normalmente trabalha com bases de dados relacionais, provavelmente desenvolveu hábitos para desenhar modelos de dados. Devido às restrições específicas, mas também às forças únicas do Azure Cosmos DB, a maioria destas boas práticas não se traduz bem e pode arrastar-te para soluções subótimas. O objetivo deste artigo é guiá-lo através de todo o processo de modelação de um caso de uso real no Azure Cosmos DB, desde a modelação de itens até à colocation de entidades e particionamento de contentores.

Para um exemplo que ilustre os conceitos deste artigo, descarregue ou veja este código-fonte gerado pela comunidade.

Importante

Um colaborador da comunidade contribuiu com este exemplo de código. A equipa do Azure Cosmos DB não suporta a sua manutenção.

O cenário

Para este exercício, vamos considerar o domínio de uma plataforma de blogue onde os utilizadores podem criar publicações. Os utilizadores também podem gostar e adicionar comentários a essas publicações.

Sugestão

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

Adicionando mais requisitos à nossa especificação:

  • Uma página inicial mostra um feed de publicações recentemente criadas.
  • Podemos obter todas as publicações de um utilizador, todos os comentários de uma publicação e todos os gostos de uma publicação.
  • As publicações são devolvidas com o nome de utilizador dos seus autores e uma contagem de quantos comentários e gostos têm.
  • Os comentários e gostos também são devolvidos com o nome de utilizador dos utilizadores que os criaram.
  • Quando exibidos como listas, os posts só têm de apresentar um resumo truncado do 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 desenhar um modelo de dados para o Azure Cosmos DB, é importante perceber quais os pedidos que o nosso modelo tem de servir para garantir que o modelo serve esses pedidos de forma eficiente.

Para tornar o processo global mais fácil de seguir, categorizamos esses diferentes pedidos como comandos ou consultas, tomando algum vocabulário da segregação de responsabilidades por consultas de comandos (CQRS). No CQRS, comandos são requisições de escrita (isto é, intenções de atualizar o sistema) e consultas são requisições apenas de leitura.

Aqui está a lista de pedidos que a nossa plataforma expõe:

  • [C1] Criar ou editar um utilizador
  • [Q1] Recuperar um utilizador
  • [C2] Crie ou edite uma publicação
  • [Q2] Recuperar uma publicação
  • [P3] Liste as publicações de um utilizador em formato curto
  • [C3] Crie um comentário
  • [Q4] Liste os comentários de uma publicação
  • [C4] Gostar de uma publicação
  • [Q5] Liste os likes de uma publicação
  • [Q6] Lista os x posts mais recentes criados em formato curto (feed)

Nesta fase, ainda não pensámos nos detalhes do que cada entidade (utilizador, publicação, etc.) contém. Este passo é geralmente um dos primeiros a ser abordado ao desenhar contra uma loja relacional. Começamos por este passo porque temos de perceber como essas entidades se traduzem em termos de tabelas, colunas, chaves estrangeiras, e assim por diante. É muito menos preocupante com uma base de dados de documentos que não impõe qualquer esquema na escrita.

É importante identificar os nossos padrões de acesso desde o início porque esta lista de pedidos vai ser o nosso conjunto de testes. Sempre que iteramos sobre o nosso modelo de dados, analisamos cada um dos pedidos e verificamos o seu desempenho e escalabilidade. Calculamos as unidades de pedido (RU) consumidas em cada modelo e otimizámo-las. Todos estes modelos usam a política de indexação por defeito e pode sobrepô-la indexando propriedades específicas, o que pode melhorar ainda mais o consumo e a latência das unidades de solicitação.

V1: Uma primeira versão

Começamos com dois recipientes: users e posts.

Contêiner de utilizadores

Este contentor armazena apenas os itens do utilizador:

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

Particionamos este contentor por id, o que significa que cada partição lógica dentro desse contentor contém apenas um item.

Contêiner de publicações

Este contêiner aloja entidades como publicações, 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 este contentor por postId, o que significa que cada partição lógica dentro desse contentor contém um post, todos os comentários desse post e todos os gostos daquele post.

Introduzimos uma type propriedade nos itens armazenados neste contentor para distinguir entre os três tipos de entidades que este contentor alberga.

Além disso, optámos por referenciar dados relacionados em vez de os incorporar porque:

  • Não há limite máximo para o número de publicações que um utilizador pode criar.
  • As publicações podem ser arbitrariamente longas.
  • Não há limite máximo para o número de comentários e gostos que uma publicação pode ter.
  • Queremos poder adicionar um comentário ou um gosto a uma publicação sem ter de atualizar a publicação em si.

Para saber mais sobre estes conceitos, consulte Modelação de dados no Azure Cosmos DB.

Quão bem se comporta o nosso modelo?

Agora é altura de avaliar o desempenho e a escalabilidade da nossa primeira versão. Para cada um dos pedidos previamente identificados, medimos a sua latência e quantas unidades de pedido consome. Esta medição é feita contra um conjunto de dados fictício contendo 100.000 utilizadores, com 5 a 50 publicações por utilizador, e até 25 comentários e 100 gostos por publicação.

[C1] Criar ou editar um utilizador

Este pedido é simples de implementar, pois simplesmente criamos ou atualizamos um item no users contentor. Os pedidos distribuem-se uniformemente por todas as partições graças à id chave de partição.

Diagrama de escrever um único item para o contentor do utilizador.

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

[Q1] Recuperar um utilizador

A recuperação de um utilizador é feita lendo o item correspondente do users contentor.

Diagrama de recuperação de um único item do contentor do utilizador.

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

[C2] Crie ou edite uma publicação

De forma semelhante ao [C1], só temos de escrever no posts container.

Diagrama de escrever um único item de publicação para o contentor de publicações.

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

[Q2] Recuperar uma publicação

Começamos por recuperar o documento correspondente do posts contentor. Mas isso não chega, conforme a nossa especificação, também temos de agregar o nome de utilizador do autor da publicação, as contagens de comentários e as contagens de gostos da publicação. As agregações listadas requerem que sejam emitidas mais três consultas SQL.

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

Cada uma das consultas filtra a chave de partição do seu respetivo contentor, que é exatamente o que queremos para maximizar o desempenho e a escalabilidade. Mas acabamos por ter de realizar quatro operações para devolver um único post, por isso vamos melhorar isso numa próxima iteração.

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

[P3] Liste as publicações de um utilizador em formato curto

Primeiro, temos de recuperar as publicações desejadas com uma consulta SQL que recupere as publicações correspondentes a esse utilizador em particular. Mas também temos de realizar mais consultas para agregar o nome de utilizador do autor e a contagem de comentários e gostos.

Diagrama de recuperar todas as publicações de um utilizador e agregar os seus dados adicionais.

Esta implementação apresenta muitas desvantagens:

  • As consultas que agregam as contagens de comentários e curtidas devem ser realizadas para cada publicação devolvida pela primeira consulta.
  • A consulta principal não filtra na chave de partição do posts contentor, causando um fan-out e uma varredura de partição através do contentor.
Latency Unidades de Solicitação Desempenho
130 ms 619.41 RU

[C3] Crie um comentário

Um comentário é criado escrevendo o item correspondente no posts contentor.

Diagrama de escrever um único item de comentário no contentor de publicações.

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

[Q4] Liste os comentários de uma publicação

Começamos com uma consulta que recolhe todos os comentários dessa publicação e, mais uma vez, precisamos também de agregar os nomes de utilizador separadamente para cada comentário.

Diagrama de recuperar todos os comentários de uma publicação e agregar os seus dados adicionais.

Embora a consulta principal filtre na chave de partição do contentor, agregar os nomes de utilizador separadamente penaliza o desempenho global. Melhoramos isso mais tarde.

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

[C4] Gostar de um post

Tal como [C3], criamos o item correspondente no posts contentor.

Diagrama de escrever um único item (igual) no contentor das publicações.

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

[Q5] Liste likes de uma publicação

Tal como [Q4], consultamos as reacções dessa publicação e depois agregamos os nomes de utilizador.

Diagrama para obter todas as curtidas de uma publicação e agregar os seus dados adicionais.

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

[Q6] Lista os x posts mais recentes criados em formato curto (feed)

Recolhemos as publicações mais recentes consultando o posts contentor ordenado por data de criação decrescente, depois agregamos nomes de utilizador e contagens de comentários e gostos de cada publicação.

Diagrama de recuperação das publicações mais recentes e agregação dos seus dados adicionais.

Mais uma vez, a nossa consulta inicial não filtra na chave de partição do contentor posts, o que desencadeia um dispendioso espalhamento. Esta é ainda pior porque pretendemos alcançar um conjunto de resultados maior e ordenamos os resultados utilizando uma cláusula ORDER BY, o que torna-o mais dispendioso em termos de unidades de pedido.

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

Reflita sobre o desempenho da V1

Analisando os problemas de desempenho que enfrentámos na secção anterior, podemos identificar duas classes principais de problemas:

  • Alguns pedidos exigem múltiplas consultas para recolher todos os dados que precisamos de devolver.
  • Algumas consultas não filtram na chave de partição dos contentores que visam, levando a uma dispersão que prejudica a nossa escalabilidade.

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

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

A razão pela qual temos de emitir mais pedidos em alguns casos é porque os resultados do pedido inicial não contêm todos os dados que precisamos de devolver. Desnormalizar dados resolve este tipo de problema em todo o nosso conjunto de dados quando se trabalha com um armazenamento de dados não relacional como o Azure Cosmos DB.

No nosso exemplo, modificamos os itens da publicação para adicionar o nome de utilizador do autor da publicação, a contagem de comentários e a contagem de gostos:

{
    "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 os itens de comentários e gostos para adicionar o nome de utilizador do utilizador 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 a contagem de comentários e gostos

O que queremos conseguir é que, sempre que adicionamos um comentário ou um gosto, também incrementemos o commentCount ou o likeCount no post correspondente. À medida que o nosso postId contentor é particionado, o novo item (comentário ou like) e a sua publicação correspondente ficam na mesma partição lógica. Como resultado, podemos usar um procedimento armazenado para realizar essa operação.

Quando cria um comentário ([C3]), em vez de simplesmente adicionar um novo item ao posts contentor, chamamos o seguinte procedimento armazenado nesse contentor:

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

Este procedimento armazenado toma o ID da publicação e o corpo do novo comentário como parâmetros, depois:

  • recupera a publicação.
  • incrementa o commentCount.
  • substitui o poste.
  • acrescenta 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 mantêm-se sempre sincronizados.

Obviamente, chamamos um procedimento armazenado semelhante quando se adicionam novos likes para aumentar o likeCount.

Desnormalizar nomes de utilizador

Os nomes de utilizador exigem uma abordagem diferente, pois os utilizadores não só ficam em partições diferentes, mas também num contentor diferente. Quando temos de desnormalizar dados entre partições e contentores, podemos usar o feed de alterações do contentor de origem.

No nosso exemplo, usamos o feed de alterações do users contentor para reagir sempre que os utilizadores atualizam os seus nomes de utilizador. Quando isso acontece, propagamos a alteração chamando outro procedimento armazenado no posts contentor:

Diagrama de desnormalização dos nomes de utilizador no contentor de publicações.

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

Este procedimento armazenado recebe o ID do utilizador e o novo nome de utilizador como parâmetros, em seguida:

  • recupera todos os itens que correspondem ao userId (que podem ser publicações, comentários ou gostos).
  • Para cada um desses itens:
    • substitui o userUsername.
    • substitui o item.

Importante

Esta operação é dispendiosa porque requer que este procedimento armazenado seja executado em todas as partições do posts contentor. Assumimos que a maioria dos utilizadores escolhe um nome de utilizador adequado durante o registo e nunca o altera, por isso esta atualização é muito raramente.

Quais são as melhorias de desempenho da V2?

Vamos falar sobre alguns dos ganhos de desempenho do V2.

[Q2] Recuperar uma publicação

Agora que a nossa desnormalização está implementada, apenas precisamos de buscar um único item para tratar dessa requisição.

Diagrama de recuperação de um único item do contentor de publicações desnormalizadas.

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

[Q4] Liste os comentários de uma publicação

Mais uma vez, podemos poupar os pedidos adicionais que recuperaram os nomes de utilizador, resultando numa única consulta que filtra a chave de partição.

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

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

[Q5] Liste likes de uma publicação

Exatamente a mesma situação ao listar os likes.

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

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

V3: Garantir que todos os pedidos são escaláveis

Ainda há dois pedidos que não otimizámos totalmente quando analisamos as melhorias globais de desempenho. Estes pedidos são [Q3] e [Q6]. São as requisições que envolvem consultas que não filtrem pela chave de partição dos contentores aos quais têm como alvo.

[P3] Liste as publicações de um utilizador em formato curto

Este pedido já beneficia das melhorias introduzidas na V2, que economiza mais consultas de dados.

Diagrama que mostra a consulta para listar as publicações desnormalizadas de um utilizador em formato curto.

Mas a consulta restante continua sem filtrar na chave de partição do posts contentor.

A forma de pensar nesta situação é simples:

  • Este pedido tem de filtrar em userId porque queremos obter todas as publicações de um utilizador em particular.
  • Não tem um desempenho adequado porque é executado contra o posts contentor, que não está particionado com o userId.
  • Afirmando o óbvio, resolveríamos o nosso problema de desempenho executando este pedido contra um contentor particionado com userId.
  • Acontece que já temos um contentor assim: o users contentor!

Por isso, introduzimos um segundo nível de desnormalização ao duplicar publicações inteiras no users contentor. Ao fazer isso, obtemos efetivamente uma cópia das nossas publicações, só que particionadas numa dimensão diferente, tornando-as muito mais eficientes de recuperar pelo seu userId.

O users recipiente contém agora 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 campo type no item de utilizador para distinguir utilizadores de publicações.
  • Também adicionámos um userId campo no item do utilizador, que é redundante com o id campo mas é obrigatório porque o users contentor agora está particionado com userId (e não id como antes).

Para alcançar esta desnormalização, voltamos a usar o feed de alteração. Desta vez, reagimos ao feed de alterações do posts contentor para enviar qualquer publicação nova ou atualizada para o users contentor. Uma vez que listar publicações não exige devolver o conteúdo completo, podemos truncá-las durante o processo.

Diagrama de desnormalizar publicações no repositório dos utilizadores.

Agora podemos encaminhar a nossa consulta para o users contentor, filtrando pela chave de partição do contentor.

Diagrama de recuperação de todas as publicações para um utilizador desnormalizado.

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

[Q6] Lista os x posts mais recentes criados em formato curto (feed)

Temos de lidar com uma situação semelhante aqui: mesmo após minimizar as consultas que foram deixadas desnecessárias pela desnormalização introduzida no V2, a consulta restante não filtra na chave de partição do contentor:

Diagrama que mostra a consulta para listar os x posts mais recentes criados em formato curto.

Seguindo a mesma abordagem, maximizar o desempenho e a escalabilidade deste pedido requer que ele atinja apenas uma partição. Só é possível acessar uma única partição porque só temos de retornar um número limitado de itens. Para preencher a página inicial da nossa plataforma de blogues, basta obter as 100 publicações mais recentes, sem necessidade de paginar todo o conjunto de dados.

Para otimizar este último pedido, introduzimos um terceiro contentor no nosso design, inteiramente dedicado a servir este pedido. Nós desnormalizamos os nossos posts para esse novo feed container:

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

Nos nossos elementos, o type campo particiona este contentor, que está sempre post. Fazer isso garante que todos os itens deste contentor ficam na mesma partição.

Para conseguir a desnormalização, só temos de nos ligar ao pipeline de alimentação de alterações que introduzimos anteriormente para despachar os posts para esse novo contentor. Uma coisa importante a ter em conta é que temos de garantir que armazenamos apenas as 100 publicações mais recentes; caso contrário, o conteúdo do contentor pode crescer para além do tamanho máximo de uma partição. Esta limitação pode ser implementada chamando um post-trigger sempre que um documento é adicionado ao contentor:

Diagrama de desnormalização das publicações no contentor de feed.

Aqui está o script do pós-trigger para truncar 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);
        });
    }
  }
}

O passo final é redirecionar a nossa consulta para o nosso novo feed contentor:

Diagrama da recuperação das publicações mais recentes.

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

Conclusion

Vamos analisar as melhorias globais de desempenho e escalabilidade que introduzimos em relação às 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
[P3] 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

Otimizámos um cenário com muita leitura

Pode notar que concentrámos os nossos esforços em melhorar o desempenho dos pedidos de leitura (consultas) em detrimento dos pedidos de escrita (comandos). Em muitos casos, as operações de escrita agora desencadeiam desnormalizações subsequentes através de fluxos de alterações, o que as torna mais dispendiosas computacionalmente e mais demoradas a concretizar-se.

Justificamos este foco no desempenho de leitura pelo facto de uma plataforma de blogues, como a maioria das aplicações sociais, ser muito rica em leituras. Uma carga de trabalho pesada em leitura indica que o número de pedidos de leitura que precisa atender é geralmente ordens de magnitude maiores que o número de pedidos de escrita. Por isso, faz sentido tornar os pedidos de escrita mais caros de executar para que os pedidos de leitura sejam mais baratos e com melhor desempenho.

Se olharmos para a otimização mais extrema que fizemos, [Q6] passou de 2000+ RUs para apenas 17 RUs; conseguimos isso ao desnormalizar publicações a um custo de cerca de 10 RUs por item. Como atenderíamos muito mais pedidos de feed do que criação ou atualizações de publicações, o custo desta desnormalização é negligenciável tendo em conta a poupança global.

A desnormalização pode ser aplicada de forma incremental

As melhorias de escalabilidade que explorámos neste artigo envolvem a desnormalização e duplicação de dados ao longo do conjunto de dados. Deve notar-se que estas otimizações não precisam de ser implementadas logo no primeiro dia. Consultas que filtram em chaves de partição têm melhor desempenho em grande escala, mas consultas entre partições podem ser aceitáveis se forem chamadas raramente ou sobre um conjunto de dados limitado. Se estiveres apenas a construir um protótipo, ou a lançar um produto com uma base de utilizadores pequena e controlada, provavelmente podes deixar essas melhorias para mais tarde. O importante então é monitorizar o desempenho do seu modelo para poder decidir se e quando é altura de os trazer.

O feed de alterações que usamos para distribuir atualizações para outros contentores armazena todas essas atualizações de forma persistente. Esta persistência torna possível solicitar todas as atualizações desde a criação do contentor e desnormalizar vistas de bootstrap como uma operação de recuperação pontual, mesmo que o seu sistema já tenha muitos dados.

Próximos passos

Após esta introdução à modelação prática de dados e particionamento, poderá querer consultar os seguintes artigos para rever os conceitos: