Partilhar via


Modelagem de dados no Azure Cosmos DB

APLICA-SE A: NoSQL

Embora os bancos de dados sem esquema, como o Azure Cosmos DB, tornem super fácil armazenar e consultar dados não estruturados e semiestruturados, você deve gastar algum tempo pensando em seu modelo de dados para obter o máximo do serviço em termos de desempenho e escalabilidade e menor custo.

Como os dados serão armazenados? Como seu aplicativo vai recuperar e consultar dados? Seu aplicativo é pesado em leitura ou gravação?

Depois de ler este artigo, você será capaz de responder às seguintes perguntas:

  • O que é modelagem de dados e por que devo me importar?
  • Qual é a diferença entre os dados de modelagem no Azure Cosmos DB e um banco de dados relacional?
  • Como expresso relações de dados em um banco de dados não relacional?
  • Quando devo incorporar dados e quando devo vincular aos dados?

Números em JSON

O Azure Cosmos DB salva documentos em JSON. O que significa que é necessário determinar cuidadosamente se é necessário converter números em strings antes de armazená-los em json ou não. Idealmente, todos os números devem ser convertidos em um String, se houver alguma chance de que eles estejam fora dos limites de números de precisão dupla de acordo com o binário IEEE 75464. A especificação Json chama a atenção para as razões pelas quais o uso de números fora desse limite em geral é uma má prática no JSON devido a prováveis problemas de interoperabilidade. Essas preocupações são especialmente relevantes para a coluna de chave de partição, porque é imutável e requer migração de dados para alterá-la posteriormente.

Incorporar dados

Ao iniciar a modelagem de dados no Azure Cosmos DB, tente tratar suas entidades como itens autônomos representados como documentos JSON.

Para comparação, vamos primeiro ver como podemos modelar dados em um banco de dados relacional. O exemplo a seguir mostra como uma pessoa pode ser armazenada em um banco de dados relacional.

Modelo de banco de dados relacional

A estratégia, ao trabalhar com bancos de dados relacionais, é normalizar todos os seus dados. Normalizar seus dados normalmente envolve pegar uma entidade, como uma pessoa, e dividi-la em componentes discretos. No exemplo, uma pessoa pode ter vários registros de detalhes de contato e vários registros de endereço. Os detalhes de contato podem ser detalhados extraindo campos comuns como um tipo. O mesmo se aplica ao endereço, cada registro pode ser do tipo Casa ou Empresa.

A premissa orientadora ao normalizar dados é evitar o armazenamento de dados redundantes em cada registro e, em vez disso, referir-se aos dados. Neste exemplo, para ler uma pessoa, com todos os seus detalhes de contato e endereços, você precisa usar JOINS para efetivamente compor de volta (ou desnormalizar) seus dados em tempo de execução.

SELECT p.FirstName, p.LastName, a.City, cd.Detail
FROM Person p
JOIN ContactDetail cd ON cd.PersonId = p.Id
JOIN ContactDetailType cdt ON cdt.Id = cd.TypeId
JOIN Address a ON a.PersonId = p.Id

Operações de gravação em muitas tabelas individuais são necessárias para atualizar os detalhes de contato e endereços de uma única pessoa.

Agora, vamos dar uma olhada em como modelaríamos os mesmos dados como uma entidade independente no Azure Cosmos DB.

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "addresses": [
        {
            "line1": "100 Some Street",
            "line2": "Unit 1",
            "city": "Seattle",
            "state": "WA",
            "zip": 98012
        }
    ],
    "contactDetails": [
        {"email": "thomas@andersen.com"},
        {"phone": "+1 555 555-5555", "extension": 5555}
    ]
}

Usando essa abordagem, desnormalizamos o registro da pessoa, incorporando todas as informações relacionadas a essa pessoa, como seus detalhes de contato e endereços, em um único documento JSON. Além disso, como não estamos confinados a um esquema fixo, temos a flexibilidade de fazer coisas como ter detalhes de contato de diferentes formas inteiramente.

Recuperar um registro de pessoa completo do banco de dados agora é uma única operação de leitura em relação a um único contêiner e para um único item. Atualizar os detalhes de contato e endereços de um registro de pessoa também é uma única operação de gravação em relação a um único item.

Ao desnormalizar os dados, seu aplicativo pode precisar emitir menos consultas e atualizações para concluir operações comuns.

Quando incorporar

Em geral, use modelos de dados incorporados quando:

  • Existem relações contidas entre entidades.
  • Existem relações um-para-poucos entre entidades.
  • Há dados incorporados que mudam com pouca frequência.
  • Há dados incorporados que não crescem sem limites.
  • Há dados incorporados que são consultados frequentemente juntos.

Nota

Normalmente, os modelos de dados desnormalizados fornecem um melhor desempenho de leitura .

Quando não incorporar

Embora a regra geral no Azure Cosmos DB seja desnormalizar tudo e incorporar todos os dados em um único item, isso pode levar a algumas situações que devem ser evitadas.

Pegue este trecho JSON.

{
    "id": "1",
    "name": "What's new in the coolest Cloud",
    "summary": "A blog post by someone real famous",
    "comments": [
        {"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
        {"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
        …
        {"id": 100001, "author": "jane", "comment": "and on we go ..."},
        …
        {"id": 1000000001, "author": "angry", "comment": "blah angry blah angry"},
        …
        {"id": ∞ + 1, "author": "bored", "comment": "oh man, will this ever end?"},
    ]
}

Isso pode ser o que uma entidade de postagem com comentários incorporados pareceria se estivéssemos modelando um blog típico, ou CMS, sistema. O problema com este exemplo é que a matriz de comentários é ilimitada, o que significa que não há limite (prático) para o número de comentários que uma única postagem pode ter. Isso pode se tornar um problema, pois o tamanho do item pode crescer infinitamente grande, então é um design que você deve evitar.

À medida que o tamanho do item cresce, a capacidade de transmitir os dados através do fio e ler e atualizar o item, em escala, será afetada.

Neste caso, seria melhor considerar o seguinte modelo de dados.

Post item:
{
    "id": "1",
    "name": "What's new in the coolest Cloud",
    "summary": "A blog post by someone real famous",
    "recentComments": [
        {"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
        {"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
        {"id": 3, "author": "jane", "comment": "....."}
    ]
}

Comment items:
[
    {"id": 4, "postId": "1", "author": "anon", "comment": "more goodness"},
    {"id": 5, "postId": "1", "author": "bob", "comment": "tails from the field"},
    ...
    {"id": 99, "postId": "1", "author": "angry", "comment": "blah angry blah angry"},
    {"id": 100, "postId": "2", "author": "anon", "comment": "yet more"},
    ...
    {"id": 199, "postId": "2", "author": "bored", "comment": "will this ever end?"}   
]

Este modelo tem um documento para cada comentário com uma propriedade que contém o identificador de postagem. Isso permite que as postagens contenham qualquer número de comentários e possam crescer de forma eficiente. Os usuários que desejam ver mais do que os comentários mais recentes consultariam esse contêiner passando o postId, que deve ser a chave de partição para o contêiner de comentários.

Outro caso em que a incorporação de dados não é uma boa ideia é quando os dados incorporados são usados com frequência entre itens e mudam com frequência.

Pegue este trecho JSON.

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        {
            "numberHeld": 100,
            "stock": { "symbol": "zbzb", "open": 1, "high": 2, "low": 0.5 }
        },
        {
            "numberHeld": 50,
            "stock": { "symbol": "xcxc", "open": 89, "high": 93.24, "low": 88.87 }
        }
    ]
}

Isso pode representar a carteira de ações de uma pessoa. Optamos por incorporar as informações de ações em cada documento de portfólio. Em um ambiente onde os dados relacionados estão mudando com frequência, como um aplicativo de negociação de ações, incorporar dados que mudam com frequência significará que você está constantemente atualizando cada documento de portfólio toda vez que uma ação é negociada.

As ações zbzb podem ser negociadas muitas centenas de vezes em um único dia e milhares de usuários podem ter zbzb em seu portfólio. Com um modelo de dados como o exemplo, teríamos que atualizar muitos milhares de documentos de portfólio muitas vezes todos os dias, levando a um sistema que não será bem dimensionado.

Dados de referência

A incorporação de dados funciona bem para muitos casos, mas há cenários em que a desnormalização dos dados causa mais problemas do que vale a pena. Então, o que fazemos agora?

Os bancos de dados relacionais não são o único lugar onde você pode criar relações entre entidades. Em um banco de dados de documentos, você pode ter informações em um documento relacionadas a dados em outros documentos. Não recomendamos a criação de sistemas que seriam mais adequados para um banco de dados relacional no Azure Cosmos DB ou qualquer outro banco de dados de documentos, mas relacionamentos simples são bons e podem ser úteis.

No JSON, optamos por usar o exemplo de uma carteira de ações anterior, mas desta vez nos referimos ao item de ações na carteira em vez de incorporá-lo. Desta forma, quando o item de estoque muda frequentemente ao longo do dia, o único documento que precisa ser atualizado é o documento de estoque único.

Person document:
{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        { "numberHeld":  100, "stockId": 1},
        { "numberHeld":  50, "stockId": 2}
    ]
}

Stock documents:
{
    "id": "1",
    "symbol": "zbzb",
    "open": 1,
    "high": 2,
    "low": 0.5,
    "vol": 11970000,
    "mkt-cap": 42000000,
    "pe": 5.89
},
{
    "id": "2",
    "symbol": "xcxc",
    "open": 89,
    "high": 93.24,
    "low": 88.87,
    "vol": 2970200,
    "mkt-cap": 1005000,
    "pe": 75.82
}

Uma desvantagem imediata dessa abordagem, porém, é se o seu aplicativo é obrigado a mostrar informações sobre cada ação que é mantida ao exibir a carteira de uma pessoa; Neste caso, você precisaria fazer várias viagens ao banco de dados para carregar as informações de cada documento de estoque. Aqui tomamos a decisão de melhorar a eficiência das operações de escrita, que acontecem frequentemente ao longo do dia, mas por sua vez comprometem as operações de leitura que potencialmente têm menos impacto no desempenho deste sistema em particular.

Nota

Os modelos de dados normalizados podem exigir mais viagens de ida e volta ao servidor.

E as chaves estrangeiras?

Como atualmente não há nenhum conceito de restrição, chave estrangeira ou de outra forma, quaisquer relações entre documentos que você tenha em documentos são efetivamente "links fracos" e não serão verificadas pelo próprio banco de dados. Se quiser garantir que os dados aos quais um documento está se referindo realmente existem, será necessário fazer isso em seu aplicativo ou usando gatilhos do lado do servidor ou procedimentos armazenados no Azure Cosmos DB.

Quando referenciar

Em geral, use modelos de dados normalizados quando:

  • Representando relações um-para-muitos .
  • Representando relações muitos-para-muitos .
  • Os dados relacionados mudam com frequência.
  • Os dados referenciados podem ser ilimitados.

Nota

Normalmente, a normalização proporciona um melhor desempenho de gravação .

Onde coloco a relação?

O crescimento do relacionamento ajuda a determinar em qual documento armazenar a referência.

Se observarmos o JSON que modela editoras e livros.

Publisher document:
{
    "id": "mspress",
    "name": "Microsoft Press",
    "books": [ 1, 2, 3, ..., 100, ..., 1000]
}

Book documents:
{"id": "1", "name": "Azure Cosmos DB 101" }
{"id": "2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "3", "name": "Taking over the world one JSON doc at a time" }
...
{"id": "100", "name": "Learn about Azure Cosmos DB" }
...
{"id": "1000", "name": "Deep Dive into Azure Cosmos DB" }

Se o número de livros por editor for pequeno com crescimento limitado, armazenar a referência do livro dentro do documento do editor pode ser útil. No entanto, se o número de livros por editor for ilimitado, esse modelo de dados levaria a matrizes mutáveis e crescentes, como no documento do editor de exemplo.

Mudar um pouco as coisas resultaria em um modelo que ainda representa os mesmos dados, mas agora evita essas grandes coleções mutáveis.

Publisher document:
{
    "id": "mspress",
    "name": "Microsoft Press"
}

Book documents:
{"id": "1","name": "Azure Cosmos DB 101", "pub-id": "mspress"}
{"id": "2","name": "Azure Cosmos DB for RDBMS Users", "pub-id": "mspress"}
{"id": "3","name": "Taking over the world one JSON doc at a time", "pub-id": "mspress"}
...
{"id": "100","name": "Learn about Azure Cosmos DB", "pub-id": "mspress"}
...
{"id": "1000","name": "Deep Dive into Azure Cosmos DB", "pub-id": "mspress"}

Neste exemplo, descartamos a coleção não limitada no documento do editor. Em vez disso, temos apenas uma referência à editora em cada documento do livro.

Como faço para modelar relacionamentos muitos-para-muitos?

Em um banco de dados relacional, as relações muitos-para-muitos geralmente são modeladas com tabelas de junção, que apenas unem registros de outras tabelas.

Junte-se a mesas

Você pode ficar tentado a replicar a mesma coisa usando documentos e produzir um modelo de dados semelhante ao seguinte.

Author documents:
{"id": "a1", "name": "Thomas Andersen" }
{"id": "a2", "name": "William Wakefield" }

Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101" }
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "b3", "name": "Taking over the world one JSON doc at a time" }
{"id": "b4", "name": "Learn about Azure Cosmos DB" }
{"id": "b5", "name": "Deep Dive into Azure Cosmos DB" }

Joining documents:
{"authorId": "a1", "bookId": "b1" }
{"authorId": "a2", "bookId": "b1" }
{"authorId": "a1", "bookId": "b2" }
{"authorId": "a1", "bookId": "b3" }

Isso funcionaria. No entanto, carregar um autor com os seus livros, ou carregar um livro com o seu autor, exigiria sempre pelo menos duas consultas adicionais na base de dados. Uma consulta ao documento de ingresso e, em seguida, outra consulta para buscar o documento real que está sendo associado.

Se essa junção está apenas colando dois dados, por que não soltá-los completamente? Considere o seguinte exemplo.

Author documents:
{"id": "a1", "name": "Thomas Andersen", "books": ["b1", "b2", "b3"]}
{"id": "a2", "name": "William Wakefield", "books": ["b1", "b4"]}

Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101", "authors": ["a1", "a2"]}
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users", "authors": ["a1"]}
{"id": "b3", "name": "Learn about Azure Cosmos DB", "authors": ["a1"]}
{"id": "b4", "name": "Deep Dive into Azure Cosmos DB", "authors": ["a2"]}

Agora, se eu tivesse um autor, eu imediatamente sei quais livros eles escreveram e, inversamente, se eu tivesse um documento de livro carregado, eu saberia os IDs dos autores. Isso salva essa consulta intermediária na tabela de junção, reduzindo o número de viagens de ida e volta do servidor que seu aplicativo precisa fazer.

Modelos de dados híbridos

Agora analisamos a incorporação (ou desnormalização) e a referência (ou normalização) de dados. Cada abordagem tem vantagens e compromissos.

Nem sempre tem que ser nem um nem outro, não tenha medo de misturar as coisas um pouco.

Com base nos padrões de uso e cargas de trabalho específicos do seu aplicativo, pode haver casos em que misturar dados incorporados e referenciados faça sentido e possa levar a uma lógica de aplicativo mais simples com menos viagens de ida e volta do servidor, mantendo um bom nível de desempenho.

Considere o seguinte JSON.

Author documents:
{
    "id": "a1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "countOfBooks": 3,
    "books": ["b1", "b2", "b3"],
    "images": [
        {"thumbnail": "https://....png"}
        {"profile": "https://....png"}
        {"large": "https://....png"}
    ]
},
{
    "id": "a2",
    "firstName": "William",
    "lastName": "Wakefield",
    "countOfBooks": 1,
    "books": ["b1"],
    "images": [
        {"thumbnail": "https://....png"}
    ]
}

Book documents:
{
    "id": "b1",
    "name": "Azure Cosmos DB 101",
    "authors": [
        {"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
        {"id": "a2", "name": "William Wakefield", "thumbnailUrl": "https://....png"}
    ]
},
{
    "id": "b2",
    "name": "Azure Cosmos DB for RDBMS Users",
    "authors": [
        {"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
    ]
}

Aqui seguimos (principalmente) o modelo incorporado, onde dados de outras entidades são incorporados no documento de nível superior, mas outros dados são referenciados.

Se você olhar para o documento do livro, podemos ver alguns campos interessantes quando olhamos para a variedade de autores. Há um id campo que é o campo que usamos para nos referirmos a um documento do autor, prática padrão em um modelo normalizado, mas também temos name e thumbnailUrl. Poderíamos ter ficado com id e deixado o aplicativo para obter qualquer informação adicional necessária do respetivo documento do autor usando o "link", mas como nosso aplicativo exibe o nome do autor e uma imagem em miniatura com cada livro exibido, podemos salvar uma viagem de ida e volta ao servidor por livro em uma lista, desnormalizando alguns dados do autor.

Claro, se o nome do autor mudasse ou eles quisessem atualizar sua foto, teríamos que atualizar todos os livros que eles já publicaram, mas para o nosso aplicativo, com base na suposição de que os autores não mudam seus nomes com frequência, esta é uma decisão de design aceitável.

No exemplo, há valores agregados pré-calculados para economizar processamento caro em uma operação de leitura. No exemplo, alguns dos dados incorporados no documento do autor são dados calculados em tempo de execução. Sempre que um novo livro é publicado, um documento de livro é criado e o campo countOfBooks é definido como um valor calculado com base no número de documentos de livro que existem para um determinado autor. Essa otimização seria boa em sistemas de leitura pesada, onde podemos nos dar ao luxo de fazer cálculos em gravações para otimizar as leituras.

A capacidade de ter um modelo com campos pré-calculados é possível porque o Azure Cosmos DB dá suporte a transações de vários documentos. Muitos repositórios NoSQL não podem fazer transações entre documentos e, portanto, defendem decisões de design, como "sempre incorporar tudo", devido a essa limitação. Com o Azure Cosmos DB, você pode usar gatilhos do lado do servidor ou procedimentos armazenados que inserem livros e atualizam autores em uma transação ACID. Agora você não precisa incorporar tudo em um documento apenas para ter certeza de que seus dados permanecem consistentes.

Distinguir entre diferentes tipos de documentos

Em alguns cenários, talvez você queira misturar diferentes tipos de documentos na mesma coleção; Este é geralmente o caso quando você deseja que vários documentos relacionados fiquem na mesma partição. Por exemplo, você pode colocar livros e resenhas de livros na mesma coleção e particioná-la por bookId. Em tal situação, você geralmente deseja adicionar aos seus documentos com um campo que identifica seu tipo, a fim de diferenciá-los.

Book documents:
{
    "id": "b1",
    "name": "Azure Cosmos DB 101",
    "bookId": "b1",
    "type": "book"
}

Review documents:
{
    "id": "r1",
    "content": "This book is awesome",
    "bookId": "b1",
    "type": "review"
},
{
    "id": "r2",
    "content": "Best book ever!",
    "bookId": "b1",
    "type": "review"
}

O Azure Synapse Link for Azure Cosmos DB é um recurso de processamento analítico e transacional (HTAP) híbrido nativo da nuvem que permite executar análises quase em tempo real sobre dados operacionais no Azure Cosmos DB. O Azure Synapse Link cria uma integração totalmente integrada entre o Azure Cosmos DB e o Azure Synapse Analytics.

Essa integração acontece por meio do repositório analítico do Azure Cosmos DB, uma representação colunar de seus dados transacionais que permite análises em larga escala sem qualquer impacto em suas cargas de trabalho transacionais. Esse armazenamento analítico é adequado para consultas rápidas e econômicas em grandes conjuntos de dados operacionais, sem copiar dados e afetar o desempenho de suas cargas de trabalho transacionais. Quando você cria um contêiner com o repositório analítico habilitado ou quando habilita o armazenamento analítico em um contêiner existente, todas as inserções, atualizações e exclusões transacionais são sincronizadas com o repositório analítico quase em tempo real, sem necessidade de trabalhos de Feed de Alterações ou ETL.

Com o Azure Synapse Link, agora você pode se conectar diretamente aos contêineres do Azure Cosmos DB a partir do Azure Synapse Analytics e acessar o repositório analítico, sem custos de Unidades de Solicitação (unidades de solicitação). Atualmente, o Azure Synapse Analytics dá suporte ao Azure Synapse Link com Synapse, Apache Spark e pools SQL sem servidor. Se você tiver uma conta do Azure Cosmos DB distribuída globalmente, depois de habilitar o armazenamento analítico para um contêiner, ele estará disponível em todas as regiões para essa conta.

Inferência automática de esquema do repositório analítico

Enquanto o repositório transacional do Azure Cosmos DB é considerado dados semiestruturados orientados a linhas, o repositório analítico tem formato colunar e estruturado. Essa conversão é feita automaticamente para os clientes, usando as regras de inferência de esquema para o repositório analítico. Há limites no processo de conversão: número máximo de níveis aninhados, número máximo de propriedades, tipos de dados sem suporte e muito mais.

Nota

No contexto do armazenamento analítico, consideramos como propriedade as seguintes estruturas:

  • JSON "elementos" ou "pares string-value separados por um : ".
  • Objetos JSON, delimitados por { e }.
  • Matrizes JSON, delimitadas por [ e ].

Você pode minimizar o impacto das conversões de inferência de esquema e maximizar seus recursos analíticos usando as seguintes técnicas.

Normalização

A normalização torna-se sem sentido, uma vez que com o Azure Synapse Link pode juntar-se entre os seus contentores, utilizando T-SQL ou Spark SQL. Os benefícios esperados da normalização são:

  • Menor pegada de dados no armazenamento transacional e analítico.
  • Transações menores.
  • Menos propriedades por documento.
  • Estruturas de dados com menos níveis aninhados.

Esses dois últimos fatores, menos propriedades e menos níveis, ajudam no desempenho de suas consultas analíticas, mas também diminuem as chances de partes de seus dados não serem representadas no repositório analítico. Conforme descrito no artigo sobre regras de inferência automática de esquema, há limites para o número de níveis e propriedades que são representados no repositório analítico.

Outro fator importante para a normalização é que os pools sem servidor do SQL no Azure Synapse suportam conjuntos de resultados com até 1.000 colunas, e a exposição de colunas aninhadas também conta para esse limite. Em outras palavras, tanto o repositório analítico quanto os pools sem servidor Synapse SQL têm um limite de 1.000 propriedades.

Mas o que fazer já que a desnormalização é uma técnica de modelagem de dados importante para o Azure Cosmos DB? A resposta é que você deve encontrar o equilíbrio certo para suas cargas de trabalho transacionais e analíticas.

Chave de Partição

Sua chave de partição (PK) do Azure Cosmos DB não é usada no repositório analítico. E agora você pode usar o particionamento personalizado do repositório analítico para cópias do repositório analítico usando qualquer PK que desejar. Devido a esse isolamento, você pode escolher uma PK para seus dados transacionais com foco na ingestão de dados e leituras de pontos, enquanto consultas entre partições podem ser feitas com o Azure Synapse Link. Vejamos um exemplo:

Em um cenário hipotético de IoT global, device id é uma boa PK, já que todos os dispositivos têm um volume de dados semelhante e com isso você não terá um problema de partição quente. Mas se você quiser analisar os dados de mais de um dispositivo, como "todos os dados de ontem" ou "totais por cidade", você pode ter problemas, pois essas são consultas entre partições. Essas consultas podem prejudicar seu desempenho transacional, pois usam parte da taxa de transferência em unidades de solicitação para serem executadas. Mas com o Azure Synapse Link, você pode executar essas consultas analíticas sem custos unitários de solicitação. O formato colunar do repositório analítico é otimizado para consultas analíticas e o Azure Synapse Link aplica essa característica para permitir um ótimo desempenho com os tempos de execução do Azure Synapse Analytics.

Tipos de dados e nomes de propriedades

O artigo Regras de inferência automática de esquema lista quais são os tipos de dados suportados. Embora o tipo de dados sem suporte bloqueie a representação no repositório analítico, os tipos de dados com suporte podem ser processados de forma diferente pelos tempos de execução do Azure Synapse. Um exemplo é: Ao usar cadeias de caracteres DateTime que seguem o padrão ISO 8601 UTC, os pools Spark no Azure Synapse representarão essas colunas como string e os pools sem servidor SQL no Azure Synapse representarão essas colunas como varchar(8000).

Outro desafio é que nem todos os personagens são aceitos pelo Azure Synapse Spark. Embora espaços em branco sejam aceitos, caracteres como dois pontos, acento grave e vírgula não são. Digamos que seu documento tenha uma propriedade chamada "Nome, Sobrenome". Esta propriedade é representada no repositório analítico e Synapse SQL serverless pool pode lê-lo sem problemas. Mas como ele está no repositório analítico, o Azure Synapse Spark não pode ler nenhum dado do repositório analítico, incluindo todas as outras propriedades. No final do dia, você não pode usar o Azure Synapse Spark quando tiver uma propriedade usando os caracteres sem suporte em seus nomes.

Achatamento de dados

Todas as propriedades no nível raiz dos dados do Azure Cosmos DB serão representadas no repositório analítico como uma coluna e tudo o mais que estiver em níveis mais profundos do seu modelo de dados de documento será representado como JSON, também em estruturas aninhadas. As estruturas aninhadas exigem processamento extra dos tempos de execução do Azure Synapse para nivelar os dados em formato estruturado, o que pode ser um desafio em cenários de big data.

O documento terá apenas duas colunas no repositório analítico, id e contactDetails. Todos os outros dados, email e phone, exigirão processamento extra através de funções SQL para serem lidos individualmente.


{
    "id": "1",
    "contactDetails": [
        {"email": "thomas@andersen.com"},
        {"phone": "+1 555 555-5555"}
    ]
}

O documento terá três colunas no repositório analítico, id, email, e phone. Todos os dados são diretamente acessíveis como colunas.


{
    "id": "1",
    "email": "thomas@andersen.com",
    "phone": "+1 555 555-5555"
}

Hierarquização de dados

O Azure Synapse Link permite reduzir custos das seguintes perspetivas:

  • Menos consultas em execução em seu banco de dados transacional.
  • Uma PK otimizada para ingestão de dados e leituras pontuais, reduzindo o espaço ocupado por dados, cenários de partição ativa e divisões de partições.
  • A hierarquização de dados, uma vez que o tempo de vida analítico (attl) é independente do tempo de vida transacional (tttl). Você pode manter seus dados transacionais no armazenamento transacional por alguns dias, semanas, meses e manter os dados no armazenamento analítico por anos ou para sempre. O formato colunar de armazenamento analítico traz uma compressão natural de dados, de 50% até 90%. E seu custo por GB é de ~10% do preço real da loja transacional. Para obter mais informações sobre as limitações atuais de backup, consulte Visão geral do repositório analítico.
  • Nenhum trabalho de ETL em execução em seu ambiente, o que significa que você não precisa provisionar unidades de solicitação para eles.

Redundância controlada

Esta é uma ótima alternativa para situações em que um modelo de dados já existe e não pode ser alterado. E o modelo de dados existente não se encaixa bem no armazenamento analítico devido a regras automáticas de inferência de esquema, como o limite de níveis aninhados ou o número máximo de propriedades. Se esse for o seu caso, você pode usar o Feed de Alterações do Azure Cosmos DB para replicar seus dados em outro contêiner, aplicando as transformações necessárias para um modelo de dados amigável do Azure Synapse Link. Vejamos um exemplo:

Cenário

O contêiner CustomersOrdersAndItems é usado para armazenar pedidos on-line, incluindo detalhes do cliente e dos itens: endereço de faturamento, endereço de entrega, método de entrega, status de entrega, preço dos itens, etc. Apenas as primeiras 1.000 propriedades são representadas e as informações de chave não são incluídas no repositório analítico, bloqueando o uso do Azure Synapse Link. O contêiner tem PBs de registros, não é possível alterar o aplicativo e remodelar os dados.

Outra perspetiva do problema é o volume de big data. Bilhões de linhas são constantemente usadas pelo Departamento de Análise, o que impede que eles usem tttl para exclusão de dados antigos. A manutenção de todo o histórico de dados no banco de dados transacional devido às necessidades analíticas os força a aumentar constantemente o provisionamento de unidades de solicitação, impactando os custos. Cargas de trabalho transacionais e analíticas competem pelos mesmos recursos ao mesmo tempo.

O que fazer?

Solução com Change Feed

  • A equipe de engenharia decidiu usar o Change Feed para preencher três novos contêineres: Customers, Orders, e Items. Com o Change Feed, eles estão normalizando e nivelando os dados. Informações desnecessárias são removidas do modelo de dados e cada contêiner tem cerca de 100 propriedades, evitando a perda de dados devido a limites automáticos de inferência de esquema.
  • Esses novos contêineres têm o armazenamento analítico habilitado e agora o Departamento de Análise está usando o Synapse Analytics para ler os dados, reduzindo o uso de unidades de solicitação, uma vez que as consultas analíticas estão acontecendo no Synapse Apache Spark e pools SQL sem servidor.
  • O contêiner CustomersOrdersAndItems agora tem tttl definido para manter dados apenas por seis meses, o que permite outra redução de uso de unidades de solicitação, já que há um mínimo de uma unidade de solicitação por GB no Azure Cosmos DB. Menos dados, menos unidades de solicitação.

Conclusões

As maiores conclusões deste artigo são entender que a modelagem de dados em um mundo livre de esquemas é tão importante quanto nunca.

Assim como não há uma maneira única de representar um dado em uma tela, não há uma maneira única de modelar seus dados. Você precisa entender seu aplicativo e como ele produz, consome e processa os dados. Em seguida, aplicando algumas das diretrizes apresentadas aqui, você pode começar a criar um modelo que atenda às necessidades imediatas do seu aplicativo. Quando seus aplicativos precisam ser alterados, você pode usar a flexibilidade de um banco de dados sem esquema para abraçar essa alteração e evoluir seu modelo de dados facilmente.