Cassandra. Como não morrer se você só conhece Oracle

Olá, Habr.

Meu nome é Misha Butrimov, gostaria de contar um pouco sobre Cassandra. Minha história será útil para aqueles que nunca encontraram bancos de dados NoSQL - ela possui muitos recursos de implementação e armadilhas que você precisa conhecer. E se você não viu nada além do Oracle ou qualquer outro banco de dados relacional, essas coisas salvarão sua vida.

O que há de tão bom em Cassandra? É um banco de dados NoSQL projetado sem um único ponto de falha e com boa escalabilidade. Se você precisar adicionar alguns terabytes para algum banco de dados, basta adicionar nós ao anel. Expandi-lo para outro data center? Adicione nós ao cluster. Aumentar o RPS processado? Adicione nós ao cluster. Também funciona na direção oposta.

Cassandra. Como não morrer se você só conhece Oracle

No que mais ela é boa? Trata-se de lidar com muitas solicitações. Mas quanto é muito? 10, 20, 30, 40 mil solicitações por segundo não é muito. 100 mil solicitações de gravação por segundo - também. Há empresas que disseram que guardam 2 milhões de solicitações por segundo. Eles provavelmente terão que acreditar.

E, em princípio, Cassandra tem uma grande diferença em relação aos dados relacionais - não é nada semelhante a eles. E isso é muito importante lembrar.

Nem tudo que parece igual funciona da mesma forma

Certa vez, um colega veio até mim e perguntou: “Aqui está uma linguagem de consulta CQL Cassandra, e ela tem uma instrução select, tem onde, tem e. Escrevo cartas e não funciona. Por que?". Tratar Cassandra como um banco de dados relacional é a maneira perfeita de cometer suicídio violento. E não estou promovendo isso, é proibido na Rússia. Você apenas projetará algo errado.

Por exemplo, um cliente chega até nós e diz: “Vamos construir um banco de dados para séries de TV ou um banco de dados para um diretório de receitas. Teremos pratos de comida lá ou uma lista de séries de TV e atores.” Dizemos com alegria: “Vamos!” Basta enviar dois bytes, alguns sinais e pronto, tudo funcionará de forma muito rápida e confiável. E está tudo bem até que os clientes chegam e dizem que as donas de casa também estão resolvendo o problema oposto: têm uma lista de produtos e querem saber que prato querem preparar. Você está morto.

Isso ocorre porque Cassandra é um banco de dados híbrido: ele fornece simultaneamente um valor-chave e armazena dados em colunas largas. Em Java ou Kotlin, poderia ser descrito assim:

Map<RowKey, SortedMap<ColumnKey, ColumnValue>>

Ou seja, um mapa que também contém um mapa ordenado. A primeira chave para este mapa é a chave de linha ou chave de partição - a chave de particionamento. A segunda chave, que é a chave para um mapa já classificado, é a chave Clustering.

Para ilustrar a distribuição do banco de dados, vamos desenhar três nós. Agora você precisa entender como decompor os dados em nós. Porque se amontoarmos tudo em um (aliás, pode haver mil, dois mil, cinco - quantos você quiser), não se trata realmente de distribuição. Portanto, precisamos de uma função matemática que retorne um número. Apenas um número, um inteiro longo que cairá em algum intervalo. E teremos um nó responsável por um intervalo, o segundo pelo segundo, o enésimo pelo enésimo.

Cassandra. Como não morrer se você só conhece Oracle

Esse número é obtido usando uma função hash, que é aplicada ao que chamamos de chave de partição. Esta é a coluna especificada na diretiva Chave primária, e esta é a coluna que será a primeira e mais básica chave do mapa. Ele determina qual nó receberá quais dados. Uma tabela é criada no Cassandra com quase a mesma sintaxe do SQL:

CREATE TABLE users (
	user_id uu id,
	name text,
	year int,
	salary float,
	PRIMARY KEY(user_id)

)

A chave primária, neste caso, consiste em uma coluna e também é a chave de particionamento.

Qual será o desempenho de nossos usuários? Alguns irão para um nó, alguns para outro e alguns para um terceiro. O resultado é uma tabela hash comum, também conhecida como mapa, também conhecida como dicionário em Python, ou uma estrutura de valor-chave simples a partir da qual podemos ler todos os valores, ler e escrever por chave.

Cassandra. Como não morrer se você só conhece Oracle

Selecione: quando a filtragem permitida se transforma em verificação completa ou o que não fazer

Vamos escrever alguma instrução select: select * from users where, userid = . Acontece como no Oracle: escrevemos select, especificamos as condições e tudo funciona, os usuários entendem. Mas se você selecionar, por exemplo, um usuário com determinado ano de nascimento, Cassandra reclama que não consegue atender a solicitação. Porque ela não sabe absolutamente nada sobre como distribuímos os dados sobre o ano de nascimento - ela tem apenas uma coluna indicada como chave. Então ela diz: “Tudo bem, ainda posso atender a esse pedido. Adicione permissão de filtragem." Adicionamos a diretiva, tudo funciona. E neste momento algo terrível acontece.

Quando executamos os dados de teste, está tudo bem. E quando você executa uma consulta em produção, onde temos, por exemplo, 4 milhões de registros, então nem tudo fica muito bom para nós. Porque permitir filtragem é uma diretiva que permite ao Cassandra coletar todos os dados desta tabela de todos os nós, todos os data centers (se houver muitos deles neste cluster), e só então filtrá-los. Este é um análogo do Full Scan e quase ninguém fica satisfeito com ele.

Se precisássemos apenas de usuários por ID, estaríamos bem com isso. Mas às vezes precisamos escrever outras consultas e impor outras restrições à seleção. Portanto, lembramos: tudo isso é um mapa que possui uma chave de particionamento, mas dentro dele está um mapa ordenado.

E ela também tem uma chave, que chamamos de Clustering Key. Chave esta, que, por sua vez, consiste nas colunas que selecionamos, com a ajuda das quais Cassandra entende como seus dados são ordenados fisicamente e estarão localizados em cada nó. Ou seja, para alguma chave de partição, a chave de cluster lhe dirá exatamente como enviar os dados para esta árvore, que lugar eles ocuparão lá.

Esta é realmente uma árvore, ali é simplesmente chamado um comparador, para o qual passamos um determinado conjunto de colunas na forma de um objeto, e também é especificado como uma lista de colunas.

CREATE TABLE users_by_year_salary_id (
	user_id uuid,
	name text,
	year int,
	salary float,
	PRIMARY KEY((year), salary, user_id)

Preste atenção à diretiva Chave primária; seu primeiro argumento (no nosso caso, o ano) é sempre Chave de partição. Pode consistir em uma ou mais colunas, não importa. Se houver várias colunas, ela precisa ser removida novamente entre colchetes para que o pré-processador da linguagem entenda que esta é a chave primária, e atrás dela todas as outras colunas são a chave de cluster. Neste caso, serão transmitidos no comparador na ordem em que aparecem. Ou seja, a primeira coluna é mais significativa, a segunda é menos significativa e assim por diante. Como escrevemos, por exemplo, campos iguais para classes de dados: listamos os campos, e para eles escrevemos quais são maiores e quais são menores. No Cassandra, estes são, relativamente falando, os campos da classe de dados, aos quais serão aplicados os iguais escritos para ela.

Definimos classificação e impomos restrições

Você precisa lembrar que a ordem de classificação (decrescente, crescente, qualquer que seja) é definida no mesmo momento em que a chave é criada e não pode ser alterada posteriormente. Ele determina fisicamente como os dados serão classificados e como serão armazenados. Se precisar alterar a chave de cluster ou a ordem de classificação, você terá que criar uma nova tabela e transferir dados para ela. Isso não funcionará com um existente.

Cassandra. Como não morrer se você só conhece Oracle

Preenchemos nossa tabela com usuários e vimos que eles entravam em um anel, primeiro por ano de nascimento, e depois dentro de cada nó por salário e ID de usuário. Agora podemos selecionar impondo restrições.

Nosso trabalho aparece novamente where, and, e conseguimos usuários, e está tudo bem novamente. Mas se tentarmos usar apenas uma parte da chave de Clustering, e uma menos significativa, então Cassandra reclamará imediatamente que não consegue encontrar o local em nosso mapa onde está este objeto, que possui esses campos para o comparador nulo, e este aqui isso foi definido, - onde ele está. Terei que extrair todos os dados deste nó novamente e filtrá-los. E isso é análogo ao Full Scan dentro de um nó, isso é ruim.

Em qualquer situação pouco clara, crie uma nova tabela

Se quisermos atingir os usuários por ID, ou por idade, ou por salário, o que devemos fazer? Nada. Basta usar duas tabelas. Se você precisar alcançar os usuários de três maneiras diferentes, serão três tabelas. Já se foi o tempo em que economizávamos espaço no parafuso. Este é o recurso mais barato. Custa muito menos que o tempo de resposta, o que pode ser prejudicial ao usuário. É muito mais agradável para o usuário receber algo em um segundo do que em 10 minutos.

Trocamos espaço desnecessário e dados desnormalizados pela capacidade de escalar bem e operar de forma confiável. Afinal, de fato, um cluster composto por três data centers, cada um com cinco nós, com um nível aceitável de preservação de dados (quando nada é perdido), é capaz de sobreviver completamente à morte de um data center. E mais dois nós em cada um dos dois restantes. E só depois disso começam os problemas. Esta é uma redundância muito boa, vale a pena alguns drives SSD e processadores extras. Portanto, para usar o Cassandra, que nunca é SQL, no qual não há relacionamentos, chaves estrangeiras, você precisa conhecer regras simples.

Projetamos tudo de acordo com seu pedido. O principal não são os dados, mas como o aplicativo vai funcionar com eles. Se for necessário receber dados diferentes de maneiras diferentes ou os mesmos dados de maneiras diferentes, devemos colocá-los de uma forma que seja conveniente para a aplicação. Caso contrário, falharemos no Full Scan e Cassandra não nos dará nenhuma vantagem.

Desnormalizar dados é a norma. Esquecemos os formulários normais, não temos mais bancos de dados relacionais. Se colocarmos algo no chão 100 vezes, ele cairá 100 vezes. Ainda é mais barato do que parar.

Selecionamos as chaves para particionamento para que sejam distribuídas normalmente. Não queremos que o hash de nossas chaves caia em um intervalo estreito. Ou seja, o ano de nascimento do exemplo acima é um mau exemplo. Mais precisamente, é bom que nossos usuários estejam normalmente distribuídos por ano de nascimento, e é ruim se estivermos falando de alunos do 5º ano - o particionamento aí não será muito bom.

A classificação é selecionada uma vez no estágio de criação da chave de cluster. Se precisar ser alterado, teremos que atualizar nossa tabela com uma chave diferente.

E o mais importante: se precisarmos recuperar os mesmos dados de 100 maneiras diferentes, teremos 100 tabelas diferentes.

Fonte: habr.com

Adicionar um comentário