Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Criar e manter tabelas de consulta separadas para os dados que as aplicações usam frequentemente em consultas quando um repositório de dados não fornece índices secundários adequados. Esta abordagem melhora o desempenho da leitura ao evitar análises completas de dados quando as consultas não utilizam a chave primária ou a chave de partição.
Contexto e problema
Muitos repositórios de dados organizam os dados para uma coleção de entidades usando a chave primária. Uma aplicação pode utilizar esta chave para localizar e obter dados. A figura seguinte mostra um exemplo de um armazenamento de dados que contém informação do cliente organizada pela chave primária, ID do Cliente.
A imagem mostra uma tabela de duas colunas com registos de clientes. A primeira coluna, Chave Primária (ID do Cliente), contém os IDs de Cliente 1 a 9, seguidos de uma elipse, ID 1000 e outra elipse. A segunda coluna, Dados do Cliente, contém dados de clientes consistindo num apelido e cidade seguidos de uma reticente. Exemplos visíveis incluem o ID 1, que corresponde ao apelido Smith e à cidade de Redmond, e o ID 2, que corresponde ao apelido Jones e à cidade de Seattle. Outra fila mostra Smith em Chicago, e outra mostra Smith em Redmond. Mais uma linha mostra Jones em Chicago.
Embora a chave primária seja valiosa para consultas que obtêm dados com base no valor dessa chave, uma aplicação que precisa de recuperar dados com base noutro campo não pode usar a chave primária para essa consulta. No exemplo dos clientes, uma aplicação não pode utilizar a chave primária ID do Cliente para recuperar clientes se consultar os dados baseando-se exclusivamente no valor de outro atributo, tal como a cidade onde o cliente se encontra. Para realizar uma consulta que apenas faça referência à cidade, a aplicação pode ter de buscar e examinar todos os registos de clientes, o que pode ser um processo lento.
Muitos sistemas de gestão de base de dados relacionais suportam índices secundários. Um índice secundário é uma estrutura de dados separada organizada por um ou mais campos-chave não primários (secundários). Um índice secundário indica onde estão armazenados os dados de cada valor indexado. Os itens num índice secundário são normalmente ordenados pelo valor das chaves secundárias para permitir uma pesquisa rápida dos dados. O sistema de gestão de bases de dados normalmente mantém estes índices automaticamente.
As bases de dados relacionais permitem múltiplos índices secundários para suportar vários padrões de consulta. Por exemplo, numa tabela de Clientes numa base de dados relacional onde o ID do Cliente é a chave principal, é benéfico adicionar um índice secundário sobre o campo Cidade se a aplicação procurar frequentemente os clientes pela cidade onde residem.
Embora índices secundários sejam comuns em sistemas relacionais, nem todos os repositórios de dados oferecem uma funcionalidade equivalente. Alguns repositórios de dados não possuem índices secundários, enquanto outros fornecem índices que não satisfaz os requisitos de consulta, partição ou desempenho da carga de trabalho. Nestes casos, as aplicações têm de escolher entre varreduras completas e gestão manual de índices.
Solução
Crie uma tabela de índices que organize os dados por uma chave especificada. A secção seguinte descreve três estratégias comuns para estruturar uma tabela de índices. As duas secções seguintes descrevem as utilizações das tabelas de índice em cenários específicos.
Estratégias de estruturação padrão
As seguintes estratégias são frequentemente usadas para estruturar uma tabela de índices. Escolha uma estratégia com base no número de índices secundários que o seu cenário exige e na natureza das consultas que a sua candidatura realiza.
Desnormalização completa
A desnormalização completa duplica os dados em cada tabela de índice, mas organiza-os por chaves diferentes da chave primária. A figura seguinte mostra tabelas de índice que organizam a mesma informação do cliente por Cidade e Apelido.
Esta estratégia funciona bem para cargas de trabalho intensivas em leitura, nas quais os dados mudam raramente. À medida que a taxa de atualização aumenta, manter cada cópia acrescenta sobrecarga de processamento (ver Complexidade de consistência). Para conjuntos de dados de grande volume, armazenar as cópias pode também requerer espaço significativo.
Índice normalizado
Uma tabela de índice normalizada organiza os dados por chaves diferentes da chave primária. A tabela de índice normalizada faz referência aos dados originais usando a chave primária em vez de duplicar esses dados, como mostrado na figura seguinte. Os dados originais são denominados “tabela de factos”.
Tip
Aqui, tabela de factos significa a tabela de fontes autoritativa referenciada por um índice. Não implica um modelo de dados dimensional.
Esta técnica poupa espaço e reduz os custos da manutenção de dados duplicados. A desvantagem é que uma aplicação tem de realizar duas operações de pesquisa para encontrar dados usando uma chave secundária. A aplicação tem de encontrar a chave primária dos dados na tabela de índice e depois usar a chave primária para consultar os dados na tabela de factos.
Desnormalização parcial
A desnormalização parcial cria tabelas de índice que duplicam campos frequentemente recuperados e são organizadas por chaves diferentes da chave primária. A tabela de factos é referenciada para aceder a campos de acesso menos frequente. A figura seguinte mostra quão frequentemente os dados acedidos são duplicados em cada tabela de índices.
Esta estratégia equilibra as duas primeiras abordagens. Pode rapidamente recuperar dados para consultas comuns usando uma única consulta, enquanto o espaço e a sobrecarga de manutenção não são tão significativos como duplicar todo o conjunto de dados.
Chaves compostas
Algumas aplicações consultam frequentemente dados especificando uma combinação de valores, por exemplo, "Encontre todos os clientes que vivem em Redmond e que tenham o apelido Smith." Nesta situação, crie chaves de índice a partir de múltiplos atributos, como os atributos Town e Apelido, neste caso.
Use uma codificação que preserve os limites dos componentes para que diferentes combinações de valores não possam produzir a mesma chave. Se as consultas dependerem da ordem das chaves, certifique-se também de que a codificação preserva a ordem de ordenação necessária segundo as regras de colocação de chaves do armazenamento de dados.
A figura seguinte mostra uma tabela de índice baseada em chaves compostas. As chaves são ordenadas por Vila e depois por Apelido para registos que têm o mesmo valor para Vila.
O diagrama contém uma tabela de índice à esquerda e uma tabela de factos à direita. A tabela de factos organiza os dados do cliente pela chave principal, Customer ID. Cada linha de Dados do Cliente contém o apelido do cliente, a cidade e uma elipse que indica outros dados. A tabela de índice está organizada por uma chave composta que combina Cidade e Apelido. A sua segunda coluna, Referência do Cliente (ID) e dados frequentemente consultados, contém um ID do cliente e uma reticência. As setas estendem-se das linhas da tabela de índice até à tabela de factos, com cada seta a apontar para a linha do ID do Cliente referenciada por essa entrada do índice. As setas de cruzamento ilustram que as entradas ordenadas pela chave composta podem referenciar linhas de clientes em diferentes posições na tabela de factos.
Tabelas de índice sobre dados fragmentados
As tabelas de índice podem acelerar as operações de consulta sobre dados fragmentados. São especialmente úteis quando é aplicado hash à chave de shard. A figura seguinte mostra um exemplo em que a chave shard é um hash do ID do Cliente. A tabela de índice organiza as entradas por valores sem hash, Town e LastName, e armazena a chave de fragmento com hash correspondente a cada entrada.
Esta configuração suporta pesquisas por intervalo e ordenadas nos valores não sujeitos a hash, fornecendo a informação de encaminhamento necessária para recuperar cada registo do fragmento correto. Por exemplo, uma consulta como "Encontrar todos os clientes que vivem em Redmond" pode localizar os itens correspondentes num bloco contíguo na tabela de índices. A aplicação segue então as referências aos dados do cliente usando as chaves shard armazenadas na tabela de índice.
Problemas e considerações
Considere os seguintes pontos ao decidir como implementar este padrão:
Custos de manutenção. Manter índices secundários pode acrescentar custos adicionais significativos. Analise e compreenda as consultas que a sua aplicação utiliza. Crie tabelas de índice apenas quando for provável que as use regularmente. Não crie tabelas de índice especulativas para suportar consultas que uma aplicação não executa ou só realiza ocasionalmente. À medida que o número de padrões de consulta cresce, o número de tabelas de índice cresce, e cada uma adiciona área de superfície operacional para monitorizar, manter e depurar. Pese o benefício da consulta de cada tabela de índice contra o custo operacional de manter outra estrutura de dados derivada. Revise periodicamente tabelas de índice existentes para identificar e remover aquelas que já não estejam a ser consultadas.
Custos de armazenamento e throughput. Duplicar dados numa tabela de índice aumenta os custos de armazenamento em proporção ao número de tabelas de índice e ao tamanho dos campos copiados. Manter múltiplas cópias dos dados também acrescenta esforço. Cada escrita numa tabela de índice consome capacidade de throughput, como transações contra os limites da conta de armazenamento no Armazenamento de Tabelas do Azure ou unidades de pedido no Azure Cosmos DB. O custo não se limita ao armazenamento, estendendo-se também à taxa de transferência das operações de escrita.
Penalização de pesquisa dupla. A implementação de uma tabela de índice como uma estrutura normalizada que referencia os dados originais precisa de uma aplicação para realizar duas operações de pesquisa para localizar os dados. A primeira operação pesquisa a tabela de índice para obter a chave primária e a segunda utiliza a chave primária para obter os dados.
Consistência, complexidade. Se um sistema incorporar várias tabelas de índice em grandes conjuntos de dados, manter a consistência entre as tabelas de índice e os dados originais pode ser difícil. Se os dados de origem e as entradas do índice não puderem ser atualizados na mesma transação, desenhe a aplicação em torno de um modelo de consistência eventual. Registe cada alteração na origem de modo persistente antes de confirmar a operação de escrita. Por exemplo, consumir um fluxo de alterações da base de dados, usar o padrão Caixa de Saída Transacional para escrever um registo na caixa de saída na mesma transação que a alteração de origem, ou colocar um comando em fila de espera antes de alterar os dados de origem. Na abordagem baseada em comandos, utiliza-se um worker para processar o comando e atualizar os dados de origem e os seus índices. Não atualize os dados de origem e depois publique independentemente uma mensagem de atualização do índice, porque uma falha entre essas operações pode deixar o índice estagnado.
Conceba o consumidor assíncrono para ser idempotente, porque a entrega de mensagens e as novas tentativas podem fazer com que a mesma atualização seja processada mais do que uma vez. Atualizações para o mesmo registo de origem também podem chegar fora de ordem. Inclua uma versão de origem ou número de sequência em cada atualização do índice e aplique uma atualização apenas se for mais recente do que a versão do índice. Para eliminações, mantenha uma lápide versionada ou um high-water mark equivalente para que uma atualização antiga atrasada não possa recriar a entrada do índice eliminada. Durante o intervalo entre a escrita dos dados de origem e a atualização assíncrona do índice, consultas na tabela de índice podem devolver referências obsoletas a registos que foram atualizados ou eliminados nos dados de origem, e podem omitir registos adicionados recentemente.
Particionar tabelas de índices. As tabelas de índice podem ser elas próprias particionadas ou fragmentadas, o que acrescenta complexidade ao encaminhamento das consultas e exige que a estratégia de particionamento se alinhe com os padrões de consulta para os quais o índice foi concebido.
Quando utilizar este padrão
Use este padrão quando uma aplicação frequentemente precisa de recuperar dados usando uma chave diferente da chave primária (ou shard), e o armazenamento de dados não suporta nativamente índices secundários ou os seus índices nativos não satisfaz os requisitos de consulta, partição ou desempenho da carga de trabalho.
Este padrão pode não ser adequado quando:
Os dados são voláteis. Os dados mudam com tanta frequência que a taxa de escrita excede a taxa a que as tabelas de índice podem ser atualizadas assíncronamente. A janela de estagnação cresce até que o índice fique permanentemente desatualizado, tornando-se ineficaz e tornando a sobrecarga de armazenamento e throughput da manutenção da tabela de índices maior do que qualquer poupança de consultas.
Tens chaves sem discriminação. Um campo selecionado como chave secundária para uma tabela de índice não discrimina e só pode ter um pequeno conjunto de valores (por exemplo, um campo booleano que regista se um item está ativo). A tabela de índice suporta custos completos de armazenamento e throughput, mas oferece uma seletividade mínima para as consultas.
Os valores dos dados têm uma distribuição assimétrica. A distribuição dos valores de dados de um campo selecionado como chave secundária numa tabela de índices é muito desigual. Por exemplo, se 90 por cento dos registos contêm o mesmo valor num campo, então criar e manter uma tabela de índices para consultar dados com base neste campo pode gerar mais sobrecarga do que a análise sequencial dos dados. No entanto, se as consultas frequentemente visam valores que se situam nos restantes 10 por cento, este índice pode continuar a ser útil.
Design da carga de trabalho
Um arquiteto deve avaliar como utilizar o padrão Tabela de Índice no design da sua carga de trabalho para abordar os objetivos e princípios abordados nos pilares do Azure Well-Architected Framework. A tabela a seguir fornece orientação sobre como esse padrão suporta as metas de cada pilar.
| Pilar | Como esse padrão suporta os objetivos do pilar |
|---|---|
| A fiabilidade ajuda a sua carga de trabalho a atingir as metas de resiliência e recuperação ao construir redundância e preservar funcionalidades durante falhas. | A manutenção assíncrona do índice pode evitar falhas temporárias de atualização de índice que bloqueiem escritas de dados de origem. O processamento idempotente, o tratamento de mensagens mortas, a monitorização e a reconciliação ajudam a restaurar a consistência do índice após falhas. - RE:07 Autopreservação - Monitorização RE:10 |
| A Eficiência de Desempenho ajuda sua carga de trabalho a atender às demandas de forma eficiente por meio de otimizações em escala, dados e código. | As tabelas de índice ajudam a permitir consultas rápidas em campos-chave não primários sem necessidade de varreduras completas de dados. Para armazenamentos de dados particionados, as tabelas de índice podem organizar entradas por valores não sujeitos a hash, para permitir consultas de intervalo e de ordenação que a chave de fragmentação, por si só, não consegue processar de forma eficiente. - PE:05 Dimensionamento e particionamento - PE:08 Desempenho de Dados |
Como em qualquer decisão de conceção, se este padrão introduzir compromissos dentro de um pilar, avalie-os tendo em conta os objetivos dos outros pilares.
Example
Considere uma aplicação que armazena informações sobre filmes. Suponha que o catálogo é grande e muito lido, que cada filme tem um género principal, que as consultas dos atores são frequentes, que os dados do elenco mudam pouco e que um breve atraso no índice é aceitável. O Armazenamento de Tabelas armazena cada entidade como um conjunto estruturado de propriedades nomeadas. Cada entidade inclui um PartitionKey, um RowKey, e um carimbo temporal, e as entidades na mesma tabela podem ter conjuntos diferentes de propriedades.
O armazenamento de tabelas utiliza uma chave primária composta que consiste em PartitionKey e RowKey. O PartitionKey valor determina a partição onde uma entidade é armazenada. Dentro de uma partição, o RowKey valor identifica unicamente uma entidade. O Armazenamento de Tabelas está otimizado para consultas que especificam ambas as chaves ou obtêm um intervalo contíguo de valores de linha de chave dentro de uma mesma partição.
Tip
O Armazenamento de Tabelas suporta atualizações transacionais de entidades na mesma tabela e partição através de transações de grupo de entidades. Uma transação não pode abranger uma tabela de factos e uma tabela de índice separada. Para atualizar uma entidade de facto e entidades de índice de forma atómica, armazene-as na mesma tabela com o mesmo PartitionKey. As transações do grupo de entidades são limitadas a 100 entidades por lote, com uma carga útil máxima de 4 MiB.
Para este exemplo, crie uma tabela Azure com partições para cada género, usando um identificador de género codificado como chave de partição e um identificador de filme estável e único como chave de linha. Guarde o género e os nomes dos filmes como propriedades. A figura seguinte utiliza nomes legíveis em vez de identificadores para tornar o exemplo mais fácil de seguir.
Esta abordagem será menos eficiente se a aplicação também precisar de pesquisar filmes por ator principal. Nesse caso, crie uma tabela Azure separada que funcione como tabela de índice. Use um identificador de ator codificado e estável como chave de partição e o identificador de filme como chave de linha. Guarde nomes de atores e filmes como propriedades. A figura seguinte utiliza nomes legíveis em vez de identificadores. Se um filme tiver mais do que um ator no elenco, o mesmo filme aparece em múltiplas partições.
A figura seguinte mostra a tabela do índice de atores.
Demonstração guiada do design
A tabela de filmes usa género como chave de partição, o que significa que consultas que filtram por género são executadas eficientemente enquanto a partição analisa intervalos contíguos de chaves de linha. No entanto, o Armazenamento de Tabelas suporta apenas um único índice agrupado em PartitionKey e RowKey. Não tem índices secundários. Uma consulta como "encontrar todos os filmes protagonizados por um ator específico" requer uma análise completa de cada partição de género, o que é dispendioso em grande escala.
A tabela de índices de atores resolve esta limitação invertendo o padrão de acesso. Cada identificador de ator torna-se uma chave de partição e cada identificador de filme torna-se uma chave de linha, pelo que as consultas baseadas em atores resolvem-se como consultas de partição eficientes. Como cada partição contém apenas os filmes de um ator, a consulta devolve um intervalo contíguo de entidades sem escanear dados não relacionados.
Como as entradas de filmes e atores usam tabelas e chaves de partição separadas, estas entradas não podem partilhar uma transação de grupo de entidades. Manter o índice de atores por meio de um mecanismo durável assíncrono de captura de alterações e conceber a aplicação de modo a tolerar um desfasamento temporário nos resultados das consultas.
A tabela de índice aplica desnormalização parcial: cada entrada duplica campos frequentemente acedidos (como os nomes de outros atores), pelo que as consultas mais frequentes podem ser respondidas apenas a partir da tabela de índices com uma única consulta. Para os campos acedidos com menos frequência, a entrada inclui a chave de partição de género da tabela original de filmes, permitindo uma consulta pontual dirigida à partição de género para obter o registo completo. Este design equilibra a velocidade de consulta com o custo de armazenamento e a sobrecarga de manutenção.
Passos seguintes
A orientação para o design de consultas de armazenamento de tabelas descreve padrões de índice secundário que utilizam
PartitionKeyeRowKey, incluindo abordagens para armazenar entradas de índice na mesma partição ou em partições separadas.O Data partitioning Strategies explica como selecionar chaves de partição e linhas com base em padrões de consulta, distribuição de dados, requisitos de transação e objetivos de escalabilidade.
As estratégias de arquitetura para otimizar o desempenho dos dados fornecem orientações para avaliar padrões de consulta, índices, partições e requisitos de monitorização.
Os níveis de consistência no Azure Cosmos DB descrevem os modelos de consistência que são relevantes quando mantém tabelas de índice no Azure Cosmos DB.
Recursos relacionados
Os padrões a seguir também podem ser relevantes quando você implementa esse padrão:
Padrão de fragmentação. O padrão Tabela de Índices é frequentemente utilizado em conjunto com dados particionados através de shards. O padrão Sharding descreve como dividir um armazenamento de dados num conjunto de fragmentos.
Padrão de Vista Materializada. Em vez de indexar dados para suportar consultas que resumem os dados, pode ser mais apropriado criar uma visão materializada dos dados. Este padrão descreve como gerar vistas pré-preenchidas sobre dados para suportar consultas resumidas eficientes.
Padrão Transactional Outbox. Use o padrão Transactional Outbox para ajudar a publicar alterações para a manutenção assíncrona do índice de forma fiável quando os dados de origem e as entradas do índice não podem ser atualizados numa única transação.