Modelagem de dados no Azure Cosmos DB

APLICA-SE A: NoSQL

Embora os bancos de dados sem esquema, como o Azure Cosmos DB, facilitem o armazenamento e a consulta de dados não estruturados e semiestruturados, você deve reservar um tempo para pensar em seu modelo de dados, a fim de obter o máximo do serviço em termos de desempenho e escalabilidade e custo mais baixo.

Como eles serão armazenados? Como seu aplicativo vai recuperá-los e consultá-los? O aplicativo realizará grandes volumes de leitura e gravação?

Após ler este artigo, você poderá responder as perguntas a seguir:

  • O que é modelagem de dados e como ele me afeta?
  • Como a modelagem de dados no Azure Cosmos DB difere de um banco de dados relacional?
  • Como posso expressar relações de dados em um banco de dados não relacional?
  • Quando eu insiro dados e quando vinculo a eles?

Números em JSON

O Azure Cosmos DB salva documentos em JSON. Significa que é necessário determinar cuidadosamente se é necessário converter números em cadeias de caracteres 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 estejam fora dos limites dos números de precisão dupla de acordo com IEEE 754 binary64. A especificação JSON destaca os motivos pelos quais o uso de números fora desse limite em geral é uma prática ruim no JSON devido a prováveis problemas de interoperabilidade. Essas questões são especialmente relevantes para a coluna de chave de partição, pois ela é imutável e requer a migração de dados para ser alterada posteriormente.

Inserir dados

Quando você começar a modelar dados no Azure Cosmos DB, tente tratar suas entidades como itens autossuficientes representados como documentos JSON.

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

Modelo de banco de dados relacional

A estratégia, quando se trabalha com bancos de dados relacionais, é normalizar todos os dados. Isso geralmente envolve pegar uma entidade, como uma pessoa, e dividi-la em componentes discretos. No exemplo acima, uma pessoa pode ter diversos registros de detalhes de contato, bem como vários registros de endereço. Os detalhes de contato podem ser divididos ainda mais com a extração de campos comuns, como um tipo. O mesmo se aplica ao endereço: cada registro pode ser do tipo Doméstico ou Comercial.

A premissa que orienta a normalização de dados é evitar armazenar dados redundantes em cada registro e, em vez disso, referir-se a eles. Neste exemplo, para ler uma pessoa, com todos os detalhes de contato e endereços, você precisará usar JOINS para recompor (ou desnormalizar) os dados de modo eficaz 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

As operações de gravação em várias tabelas individuais são exigidas para atualizar os detalhes e endereços de contato de uma única pessoa.

Agora, vejamos como modelaríamos os mesmos dados como uma entidade autossuficiente 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 a abordagem acima, desnormalizamos o registro da pessoa integrando todas as informações relacionadas a ela, como seus detalhes de contato e endereços, em um documento único do JSON. Além disso, como não estamos restritos a um esquema fixo, nós temos a flexibilidade de, por exemplo, ter detalhes de contato com formas totalmente diferentes.

Agora, a recuperação do registro completo de uma pessoa do banco de dados é uma única operação de leitura em relação a um só contêiner e para um único item. A atualização dos detalhes de contato e endereços do registro de uma pessoa também é uma única operação de gravação de um só item.

Com a desnormalização dos dados, seu aplicativo possivelmente precisará emitir menos consultas e atualizações para concluir operações comuns.

Quando inserir

De modo geral, use modelos de dados inseridos quando:

  • Houver relações contidas entre entidades.
  • Houver relações de um para poucos entre entidades.
  • Houver dados inseridos que são alterados com pouca frequência.
  • Houver dados inseridos que não serão aumentados sem limite.
  • Houver dados inseridos que são consultados frequentemente em conjunto.

Observação

De modo geral, modelos de dados desnormalizados oferecem melhor desempenho de leitura .

Quando não inserir

Embora o princípio básico do Azure Cosmos DB seja desnormalizar tudo e inserir todos os dados em um único item, isso pode levar a algumas situações que devem ser evitadas.

Veja este snippet de 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?"},
    ]
}

Uma entidade de postagem com comentários inseridos seria assim se estivéssemos modelando um sistema de blog comum, ou CMS. 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 qualquer postagem pode ter. Isso pode se tornar um problema, pois o tamanho do item pode ficar infinitamente maior, portanto, este é um design que você deve evitar.

Conforme o tamanho dele aumentar, a capacidade de transmitir dados eletronicamente e de ler e atualizar o item, em escala, será afetada.

Nesse caso, seria melhor considerar o modelo de dados a seguir.

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 possam conter qualquer quantidade de comentários e crescer de forma eficiente. Os usuários que desejam ver mais do que os comentários mais recentes consultariam esse contêiner com a transmissão do postID, que deve ser a chave de partição do contêiner de comentários.

Inserir dados também não é recomendado nos casos em que os dados inseridos são usados em diferentes itens e são alterados com frequência.

Veja este snippet de 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 }
        }
    ]
}

Isto pode representar o portfólio de ações de alguém. Optamos por inserir as informações das ações em cada documento do portfólio. Em um ambiente onde dados relacionados mudam com frequência, como um aplicativo de corretagem de ações, inserir dados que mudam frequentemente significa que você atualiza constantemente cada documento do portfólio, sempre que uma ação for negociada.

A ação zbzb pode ser negociada centenas de vezes em apenas um dia, e milhares de usuários podem ter a ação zbzb em seus portfólios. Com um modelo de dados como o acima, teríamos que atualizar vários milhares de documentos de portfólio muitas vezes por dia, o que levaria a um sistema a mal dimensionado.

Dados de referência

A inserção de dados funciona bem em muitos casos, mas há situações em que desnormalizar os dados trará mais problemas do que soluções. E o que podemos fazer?

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 que se relacionam a dados de outros documentos. Não recomendamos a criação de sistemas que seriam mais adequados a um banco de dados relacional no Azure Cosmos DB ou a qualquer outro banco de dados de documentos; as relações simples são ótimas e podem ser úteis.

No JSON abaixo, optamos por usar o exemplo do portfólio de ações, mas, dessa vez, fazemos referência ao item de estoque no portfólio em vez de inseri-lo. Dessa forma, quando o item de estoque mudar frequentemente ao longo do dia, o único documento que precisará ser atualizado será o documento de estoque.

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 é se o aplicativo precisar mostrar informações sobre cada ação contida no portfólio de uma pessoa ao mostrar o portfólio. Nesse caso, você precisaria ir várias vezes ao banco de dados para carregar as informações de cada documento de ação. Aqui, nós decidimos aumentar a eficiência das operações de gravação, que acontecem com frequência ao longo do dia. Por outro lado, comprometemos as operações de leitura, que têm menor potencial de afetar o desempenho deste sistema em particular.

Observação

Modelos de dados normalizados podem demandar mais viagens de ida e volta ao servidor .

E as chaves estrangeiras?

Como no momento não há o conceito de restrição e chave estrangeira, entre outros, qualquer relação interna que houver nos documentos será um "elo fraco" e não será verificada pelo banco de dados. Se você desejar garantir que os dados aos quais um documento está se referindo realmente existem, precisará fazer isso no aplicativo pelo uso de gatilhos do lado do servidor ou de procedimentos armazenados no Azure Cosmos DB.

Quando fazer referência

De modo geral, use modelos de dados normalizados quando:

  • Representar relações de um para muitos .
  • Representar relações de muitos para muitos .
  • Dados relacionados mudarem com frequência.
  • Dados referenciados podem ser ilimitados.

Observação

De moro geral, normalizar traz melhor desempenho de gravação .

Onde eu coloco a relação?

O crescimento da relação ajuda a determinar em qual documento armazenar a referência.

Observemos o JSON abaixo 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 editora for pequeno, com crescimento limitado, armazenar a referência do livro no documento da editora pode ser útil. No entanto, se o número de livros por editora for ilimitado, esse modelo de dados levaria a matrizes crescentes e mutáveis, como no exemplo de documento de editora abaixo.

Mudar um pouco as coisas resultaria em um modelo que ainda representa os mesmos dados, mas que 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"}

No exemplo acima, tiramos a coleção ilimitada do documento da editora. Em vez disso, temos apenas uma referência à editora em cada documento de livro.

Como fazer para modelar relações de muitos para muitos?

Em um banco de dados relacional, relações muitos para muitos frequentemente são modeladas com tabelas de junção, que simplesmente reúnem os registros de outras tabelas.

Associar tabelas

Você pode ficar tentado a fazer 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 seus livros ou carregar um livro com o autor iria sempre demandar pelo menos duas consultas adicionais no banco de dados. Uma consulta do documento de junção e outra consulta para obter o documento real que sofreu a junção.

Se esta junção é apenas está fazendo é unir dois dados, por que não simplesmente removê-la? Considere o exemplo a seguir.

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

Se eu tivesse um autor, saberia imediatamente quais livros ele escreveu e, por outro lado, se eu carregasse o documento de um livro, saberia quem é o autor. Isso anula a consulta intermediária, da tabela de junção, reduzindo o número de viagens de ida e volta ao servidor que o aplicativo precisa fazer.

Modelos de dados híbridos

Acabamos de dar uma olhada na inserção (ou desnormalização) e na referência (ou normalização) de dados. Cada abordagem tem vantagens e comprometimentos.

Nem sempre é necessário escolher uma das duas. Não tenha medo de combinar um pouco as duas.

Com base nas cargas de trabalho e nos padrões de uso específicos do aplicativo, pode haver casos em que misturar dados inseridos e referenciados faz sentido, levando a uma lógica mais simples do aplicativo, com menos viagens de ia e volta ao servidor e mantendo um bom nível de desempenho.

Considere o JSON a seguir.

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 inserido, em que dados de outras entidades são inseridos no documento de nível superior, mas outros dados são referenciados.

Olhando o documento do livro, vemos alguns campos interessantes na matriz de autores. Há um campo id que é o que usamos para fazer referência a um documento de autor, prática padrão em um modelo normalizado, mas também temos name e thumbnailUrl. Nós poderíamos ter ficado com a id e deixado o aplicativo obter informações adicionais do documento do autor usando o "vínculo". Porém, como nosso aplicativo mostra o nome do autor e uma imagem em miniatura com cada livro mostrado, podemos eliminar uma viagem de ida e volta ao servidor por livro da lista desnormalizando alguns dados do autor.

Claro, se o nome do autor mudasse ou se ele quisesse alterar sua foto, teríamos de atualizar cada livro publicado. Mas para nosso aplicativo, com base no fato de que autores não mudam de nome com frequência, essa é uma decisão de design aceitável.

No exemplo, há valores agregados pré-calculados para reduzir o processamento extensivo das operações de leitura. No exemplo, alguns dos dados inseridos no documento do autor são calculados em tempo de execução. Sempre que um novo livro é publicado, um documento de livro é criado e o campo countOfBooks, relativo à contagem de livros, é definido como um valor calculado com base no número de documentos de livros que existem para um dado autor. Essa otimização seria útil em sistemas com grandes volumes de leitura nos quais podemos computar as gravações para otimizar as leituras.

A capacidade de ter um modelo com campos pré-calculados é possibilitada 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 por isso defendem decisões de design, como "sempre inserir tudo", devido a essa limitação. Com o Azure Cosmos DB, você pode usar gatilhos do lado do servidor ou procedimentos armazenados, que inserem manuais e atualizam autores, tudo isso em uma transação ACID. Você não precisa inserir tudo em um documento para garantir a consistência de seus dados.

Diferenciar entre tipos de documentos

Em alguns cenários, talvez você queira misturar tipos de documentos diferentes na mesma coleção; normalmente, esse é o caso quando você deseja que vários documentos relacionados sejam colocados na mesma partição. Por exemplo, você pode colocar livros e resenhas de livros na mesma coleção e particioná-los por bookId. Nessa situação, geralmente convém adicionar aos seus documentos um campo que identifica o tipo deles, para 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 Link do Azure Synapse para Azure Cosmos DB é uma funcionalidade de HTAP (processamento transacional híbrido e analítico) nativa de nuvem que permite executar análises quase em tempo real sobre dados operacionais. O Link do Azure Synapse cria uma integração perfeita entre o Azure Cosmos DB e o Azure Synapse Analytics.

Essa integração acontece por meio do armazenamento analítico do Azure Cosmos DB, uma representação colunar dos seus dados transacionais que habilita a análise em grande escala sem qualquer impacto em suas cargas de trabalho transacionais. Esse repositório analítico é adequado para consultas rápidas e econômicas em grandes conjuntos de dados operacionais, sem copiar dados e impactar o desempenho de suas cargas de trabalho transacionais. Quando você cria um contêiner com o armazenamento analítico habilitado ou quando você 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 armazenamento analítico em tempo quase real, nenhum trabalho de Feed de Alterações ou ETL é necessário.

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

Inferência automática de esquema do armazenamento analítico

Embora o armazenamento transacional do Azure Cosmos DB seja considerado dados semiestruturados orientados a linhas, o armazenamento analítico tem formato colunar e estruturado. Essa conversão é feita automaticamente para os clientes por meio das regras de inferência de esquema do 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.

Observação

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

  • Os "elements" do JSON ou o "string-value pairs separated by a : ".
  • Objetos JSON, delimitados por { e }.
  • Matrizes JSON, delimitadas por [ e ].

E possível minimizar o impacto das conversões de inferência de esquema e maximizar seus recursos analíticos usando técnicas a seguir.

Normalização

A normalização se torna insignificante, pois com o Link do Azure Synapse, você pode ingressar entre seus contêineres, usando T-SQL ou Spark SQL. Os benefícios esperados da normalização são:

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

Observe que 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 armazenamento analítico. Conforme descrito no artigo sobre regras automáticas de inferência de esquema, há limites para o número de níveis e propriedades representados no armazenamento analítico.

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

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

Chave de Partição

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

Em um cenário de IoT global hipotético, device id é um bom PK, pois todos os dispositivos têm um volume de dados semelhante e, com isso, você não terá um problema de partição hot. Mas se você quiser analisar os dados de mais de um dispositivo, como "todos os dados de ontem" ou "totais por cidade", poderá ter problemas, pois são consultas entre partições. Essas consultas podem prejudicar seu desempenho transacional, pois usam parte da sua taxa de transferência em unidades de solicitação para execução. No entanto, com o Link do Azure Synapse, você pode executar essas consultas analíticas sem custos de unidades de solicitação. O formato de coluna do armazenamento analítico é otimizado para consultas analíticas e o Link do Azure Synapse 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 de esquema automático lista quais são os tipos de dados com suporte. Embora o tipo de dados sem suporte bloqueia a representação no armazenamento 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 UTC ISO 8601, os Pool do Spark no Azure Synapse representarão essas colunas como cadeia de caracteres e pools sem servidor do SQL Azure Synapse representarão essas colunas como varchar(8000).

Outro desafio é que nem todos os caracteres são aceitos pelo Azure Synapse Spark. Embora espaços em branco sejam aceitos, caracteres como dois-pontos, ênfase grave e vírgula não são. Digamos que seu documento tenha uma propriedade chamada "Nome, Sobrenome". Essa propriedade será representada no armazenamento analítico e o pool sem servidor do SQL do Synapse pode lê-lo sem problemas. Porém, como ele está no armazenamento analítico, o Spark do Azure Synapse não pode ler dados do armazenamento analítico, incluindo todas as outras propriedades. No final do dia, você não pode usar o Spark do Azure Synapse quando você tem uma propriedade usando os caracteres sem suporte em seus nomes.

Nivelamento de dados

Todas as propriedades no nível raiz de seus dados do Azure Cosmos DB serão representadas no armazenamento analítico como uma coluna, e todo o resto que está 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 achatar os dados no formato estruturado, o que pode ser um desafio em cenários de big data.

O documento abaixo terá apenas duas colunas no armazenamento analítico, ide contactDetails. Todos os outros dados email e phone, exigirão processamento extra por meio funções do SQL para serem lidas individualmente.


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

O documento abaixo terá três colunas no armazenamento 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"
}

Camada de dados

O Link do Azure Synapse permite reduzir os custos das seguintes perspectivas:

  • Menos consultas em execução no banco de dados transacional.
  • Um PK otimizado para ingestão de dados e leituras de pontos, reduzindo o espaço de dados, cenários de partição quente e divisões de partições.
  • A camada de dados desde a ATTL (vida útil analítica) é independente da TTTL (vida útil transacional). É possível 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 do armazenamento analítico traz uma compactação de dados natural, de 50% a 90%. E seu custo por GB é de ~10% do preço real do armazenamento transacional. Para obter mais informações sobre as limitações de backup atuais, confira Visão geral do repositório analítico.
  • Sem trabalhos 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

Essa é 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 obrigatórias para um modelo de dados amigável do Link do Azure Synapse. Vejamos um exemplo:

Cenário

O contêiner CustomersOrdersAndItems é usado para armazenar pedidos online, incluindo detalhes de clientes e itens: endereço de cobrança, endereço de entrega, método de entrega, status de entrega, preço de itens, etc. Somente as primeiras 1000 propriedades são representadas e as informações importantes não são incluídas no armazenamento analítico, bloqueando o uso do Link do Azure Synapse. O contêiner tem PBs de registros, não é possível alterar o aplicativo e remodelar os dados.

Outra perspectiva do problema é o volume de Big Data. Bilhões de linhas são constantemente usadas pelo Departamento de Análise, o que os impede de usar tttl para exclusão de dados antigos. Manter 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, afetando os custos. Cargas de trabalho transacionais e analíticas competem pelos mesmos recursos ao mesmo tempo.

O que fazer?

Solução com Feed de Alterações

  • A equipe de engenharia decidiu usar o Feed de Alterações para preencher três novos contêineres: Customers, Orderse Items. Com o Feed de Alterações, eles estão normalizando e nivelando os dados. As 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 aos 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, já que as consultas analíticas estão ocorrendo no Spark do Synapse Apache e em pools sem servidor de pool de SQL.
  • 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 10 unidades de solicitação por GB no Azure Cosmos DB. Menos dados, menos unidades de solicitação.

Observações

A maior lição deste artigo é entender que a modelagem de dados em um mundo sem esquemas tornou-se mais importante do que nunca.

Assim como não há apenas uma forma de representar um dado em uma tela, não há apenas uma forma de modelar seus dados. Você precisa entender eu aplicativo e como ele vai produzir, consumir e processar dados. E então, aplicando algumas das diretrizes apresentadas aqui, você pode começar a criar um modelo que trata das necessidades imediatas do seu aplicativo. Quando seus aplicativos precisarem mudar, você poderá usar a flexibilidade de um banco de dados sem esquemas para adotar as mudanças e desenvolver seu modelo de dados facilmente.

Próximas etapas