NovoSQL = NoSQL + ACID

NovoSQL = NoSQL + ACID
Até recentemente, Odnoklassniki armazenava cerca de 50 TB de dados processados ​​em tempo real no SQL Server. Para tal volume, é quase impossível fornecer acesso rápido e confiável e até mesmo tolerante a falhas ao data center usando um SGBD SQL. Normalmente, nesses casos, um dos armazenamentos NoSQL é usado, mas nem tudo pode ser transferido para NoSQL: algumas entidades exigem garantias de transação ACID.

Isso nos levou ao uso do armazenamento NewSQL, ou seja, um SGBD que fornece tolerância a falhas, escalabilidade e desempenho de sistemas NoSQL, mas ao mesmo tempo mantém as garantias ACID familiares aos sistemas clássicos. Existem poucos sistemas industriais desta nova classe em funcionamento, por isso nós mesmos implementamos esse sistema e o colocamos em operação comercial.

Como funciona e o que aconteceu - leia abaixo.

Hoje, a audiência mensal do Odnoklassniki é de mais de 70 milhões de visitantes únicos. Nós Estamos entre os cinco primeiros maiores redes sociais do mundo e entre os vinte sites em que os usuários passam mais tempo. A infraestrutura OK lida com cargas muito altas: mais de um milhão de solicitações HTTP/s por front. Partes de uma frota de servidores de mais de 8000 unidades estão localizadas próximas umas das outras - em quatro data centers em Moscou, o que permite uma latência de rede de menos de 1 ms entre eles.

Usamos Cassandra desde 2010, começando com a versão 0.6. Hoje existem várias dezenas de clusters em operação. O cluster mais rápido processa mais de 4 milhões de operações por segundo e o maior armazena 260 TB.

No entanto, todos esses são clusters NoSQL comuns usados ​​para armazenamento fracamente coordenado dados. Queríamos substituir o principal armazenamento consistente, o Microsoft SQL Server, que tem sido usado desde a fundação do Odnoklassniki. O armazenamento consistia em mais de 300 máquinas SQL Server Standard Edition, que continham 50 TB de dados – entidades empresariais. Esses dados são modificados como parte de transações ACID e requerem alta consistência.

Para distribuir dados entre nós do SQL Server, usamos verticais e horizontais particionamento (fragmentação). Historicamente, usamos um esquema simples de fragmentação de dados: cada entidade era associada a um token – uma função do ID da entidade. Entidades com o mesmo token foram colocadas no mesmo servidor SQL. O relacionamento mestre-detalhe foi implementado para que os tokens dos registros principal e filho sempre correspondessem e estivessem localizados no mesmo servidor. Em uma rede social, quase todos os registros são gerados em nome do usuário – o que significa que todos os dados do usuário dentro de um subsistema funcional são armazenados em um servidor. Ou seja, uma transação comercial quase sempre envolvia tabelas de um servidor SQL, o que possibilitou garantir a consistência dos dados por meio de transações ACID locais, sem a necessidade de utilização lento e não confiável transações ACID distribuídas.

Graças à fragmentação e à aceleração do SQL:

  • Não utilizamos restrições de chave estrangeira, pois ao fragmentar o ID da entidade pode estar localizado em outro servidor.
  • Não usamos procedimentos armazenados e gatilhos devido à carga adicional na CPU do DBMS.
  • Não usamos JOINs por causa de todos os itens acima e de muitas leituras aleatórias do disco.
  • Fora de uma transação, usamos o nível de isolamento Read Uncommited para reduzir conflitos.
  • Realizamos apenas transações curtas (em média menores que 100 ms).
  • Não usamos UPDATE e DELETE de várias linhas devido ao grande número de deadlocks - atualizamos apenas um registro por vez.
  • Sempre realizamos consultas apenas em índices - uma consulta com um plano de varredura de tabela completo para nós significa sobrecarregar o banco de dados e causar sua falha.

Essas etapas nos permitiram extrair quase o máximo desempenho dos servidores SQL. No entanto, os problemas tornaram-se cada vez mais numerosos. Vamos dar uma olhada neles.

Problemas com SQL

  • Como usamos sharding auto-escrito, a adição de novos shards foi feita manualmente pelos administradores. Durante todo esse tempo, as réplicas de dados escalonáveis ​​não atendiam às solicitações.
  • À medida que o número de registros na tabela aumenta, a velocidade de inserção e modificação diminui; ao adicionar índices a uma tabela existente, a velocidade cai por um fator; a criação e recriação de índices ocorre com tempo de inatividade.
  • Ter uma pequena quantidade de Windows para SQL Server em produção dificulta o gerenciamento da infraestrutura

Mas o principal problema é

tolerância ao erro

O servidor SQL clássico tem baixa tolerância a falhas. Digamos que você tenha apenas um servidor de banco de dados e ele falhe uma vez a cada três anos. Durante esse período, o site fica fora do ar por 20 minutos, o que é aceitável. Se você tiver 64 servidores, o site ficará inativo uma vez a cada três semanas. E se você tiver 200 servidores, o site não funciona todas as semanas. Isso é um problema.

O que pode ser feito para melhorar a tolerância a falhas de um servidor SQL? A Wikipedia nos convida a construir cluster altamente disponível: onde em caso de falha de algum dos componentes existe um backup.

Isso requer uma frota de equipamentos caros: inúmeras duplicações, fibra óptica, armazenamento compartilhado e a inclusão de uma reserva não funciona de forma confiável: cerca de 10% das comutações terminam com a falha do nó de backup como um trem atrás do nó principal.

Mas a principal desvantagem de um cluster tão altamente disponível é a disponibilidade zero se o data center no qual ele está localizado falhar. Odnoklassniki possui quatro data centers e precisamos garantir o funcionamento em caso de falha total em um deles.

Para isso poderíamos usar Multimestre replicação incorporada no SQL Server. Esta solução é muito mais cara devido ao custo do software e sofre de problemas bem conhecidos de replicação - atrasos imprevisíveis nas transações com replicação síncrona e atrasos na aplicação de replicações (e, como resultado, modificações perdidas) com replicação assíncrona. O implícito resolução manual de conflitos torna esta opção completamente inaplicável para nós.

Todos esses problemas exigiam uma solução radical e começamos a analisá-los detalhadamente. Aqui precisamos nos familiarizar com o que o SQL Server faz principalmente - transações.

Transação simples

Consideremos a transação mais simples, do ponto de vista de um programador SQL aplicado: adicionar uma foto a um álbum. Álbuns e fotografias são armazenados em placas diferentes. O álbum possui um contador de fotos públicas. Então tal transação é dividida nas seguintes etapas:

  1. Trancamos o álbum com chave.
  2. Crie uma entrada na tabela de fotos.
  3. Se a foto tiver status público, adicione um contador de fotos públicas ao álbum, atualize o registro e confirme a transação.

Ou em pseudocódigo:

TX.start("Albums", id);
Album album = albums.lock(id);
Photo photo = photos.create(…);

if (photo.status == PUBLIC ) {
    album.incPublicPhotosCount();
}
album.update();

TX.commit();

Vemos que o cenário mais comum para uma transação comercial é ler os dados do banco de dados na memória do servidor de aplicativos, alterar algo e salvar os novos valores de volta no banco de dados. Normalmente em tal transação atualizamos diversas entidades, diversas tabelas.

Ao executar uma transação, pode ocorrer modificação simultânea dos mesmos dados de outro sistema. Por exemplo, o Antispam pode decidir que o usuário é de alguma forma suspeito e, portanto, todas as fotos do usuário não devem mais ser públicas, elas precisam ser enviadas para moderação, o que significa alterar photo.status para algum outro valor e desligar os contadores correspondentes. Obviamente, se esta operação ocorrer sem garantias de atomicidade de aplicação e isolamento de modificações concorrentes, como em ACID, o resultado não será o necessário - ou o contador de fotos mostrará o valor errado ou nem todas as fotos serão enviadas para moderação.

Muitos códigos semelhantes, manipulando várias entidades comerciais dentro de uma transação, foram escritos ao longo de toda a existência do Odnoklassniki. Com base na experiência de migrações para NoSQL de Consistência Eventual Sabemos que o maior desafio (e investimento de tempo) vem do desenvolvimento de código para manter a consistência dos dados. Portanto, consideramos que o principal requisito para o novo armazenamento seja o fornecimento de transações ACID reais para a lógica da aplicação.

Outros requisitos, não menos importantes, foram:

  • Se o data center falhar, tanto a leitura quanto a gravação no novo armazenamento deverão estar disponíveis.
  • Manter a velocidade de desenvolvimento atual. Ou seja, ao trabalhar com um novo repositório, a quantidade de código deve ser aproximadamente a mesma, não deve haver necessidade de adicionar nada ao repositório, desenvolver algoritmos para resolução de conflitos, manutenção de índices secundários, etc.
  • A velocidade do novo armazenamento tinha que ser bastante elevada, tanto na leitura de dados como no processamento de transações, o que efetivamente significava que soluções academicamente rigorosas, universais, mas lentas, como, por exemplo, não eram aplicáveis commits de duas fases.
  • Dimensionamento automático em tempo real.
  • Usando servidores regulares baratos, sem a necessidade de adquirir hardware exótico.
  • Possibilidade de desenvolvimento de armazenamento por desenvolvedores da empresa. Ou seja, foi dada prioridade a soluções proprietárias ou de código aberto, preferencialmente em Java.

Decisões decisões

Analisando possíveis soluções, chegamos a duas opções de arquitetura possíveis:

A primeira é pegar qualquer servidor SQL e implementar a tolerância a falhas necessária, mecanismo de escalonamento, cluster de failover, resolução de conflitos e transações ACID distribuídas, confiáveis ​​e rápidas. Classificamos esta opção como muito pouco trivial e trabalhosa.

A segunda opção é usar um armazenamento NoSQL pronto com escalonamento implementado, um cluster de failover, resolução de conflitos e implementar transações e SQL você mesmo. À primeira vista, até mesmo a tarefa de implementar SQL, sem falar nas transações ACID, parece uma tarefa que levará anos. Mas então percebemos que o conjunto de recursos SQL que usamos na prática está tão longe do ANSI SQL quanto Cassandra CQL longe do ANSI SQL. Olhando ainda mais de perto o CQL, percebemos que ele estava bem próximo do que precisávamos.

Cassandra e CQL

Então, o que há de interessante em Cassandra, quais capacidades ela possui?

Primeiramente, aqui você pode criar tabelas que suportam vários tipos de dados; você pode fazer SELECT ou UPDATE na chave primária.

CREATE TABLE photos (id bigint KEY, owner bigint,…);
SELECT * FROM photos WHERE id=?;
UPDATE photos SET … WHERE id=?;

Para garantir a consistência dos dados da réplica, Cassandra usa abordagem de quórum. No caso mais simples, isso significa que quando três réplicas da mesma linha são colocadas em diferentes nós do cluster, a gravação é considerada bem-sucedida se a maioria dos nós (ou seja, dois em cada três) confirmar o sucesso desta operação de gravação. . Os dados da linha são considerados consistentes se, durante a leitura, a maioria dos nós foi pesquisada e confirmada. Assim, com três réplicas, a consistência completa e instantânea dos dados é garantida caso um nó falhe. Esta abordagem permitiu-nos implementar um esquema ainda mais fiável: enviar sempre pedidos para as três réplicas, aguardando uma resposta das duas mais rápidas. A resposta tardia da terceira réplica é descartada neste caso. Um nó que demora a responder pode ter sérios problemas - freios, coleta de lixo na JVM, recuperação direta de memória no kernel Linux, falha de hardware, desconexão da rede. No entanto, isso não afeta de forma alguma as operações ou dados do cliente.

A abordagem quando contatamos três nós e recebemos uma resposta de dois é chamada especulação: uma solicitação de réplicas extras é enviada antes mesmo de “cair”.

Outro benefício do Cassandra é o Batchlog, um mecanismo que garante que um lote de alterações feitas seja totalmente aplicado ou não seja aplicado. Isso nos permite resolver A em ACID - atomicidade imediatamente.

A coisa mais próxima das transações em Cassandra são as chamadas “transações leves". Mas estão longe de ser transações ACID “reais”: na verdade, esta é uma oportunidade para fazer CAS em dados de apenas um registro, usando consenso usando o protocolo pesado Paxos. Portanto, a velocidade dessas transações é baixa.

O que estávamos perdendo em Cassandra

Então, tivemos que implementar transações ACID reais no Cassandra. Com o qual poderíamos implementar facilmente dois outros recursos convenientes do SGBD clássico: índices rápidos consistentes, que nos permitiriam realizar seleções de dados não apenas pela chave primária, e um gerador regular de IDs monotônicos de incremento automático.

Cone

Assim nasceu um novo SGBD Cone, consistindo em três tipos de nós de servidor:

  • Armazenamento – servidores Cassandra (quase) padrão responsáveis ​​por armazenar dados em discos locais. À medida que a carga e o volume de dados aumentam, sua quantidade pode ser facilmente dimensionada para dezenas e centenas.
  • Coordenadores de transações - garantem a execução das transações.
  • Os clientes são servidores de aplicativos que implementam operações de negócios e iniciam transações. Pode haver milhares desses clientes.

NovoSQL = NoSQL + ACID

Servidores de todos os tipos fazem parte de um cluster comum, usam o protocolo interno de mensagens Cassandra para se comunicarem entre si e fofoca para troca de informações do cluster. Com o Heartbeat, os servidores aprendem sobre falhas mútuas, mantêm um único esquema de dados - tabelas, sua estrutura e replicação; esquema de particionamento, topologia de cluster, etc.

Clientes

NovoSQL = NoSQL + ACID

Em vez de drivers padrão, o modo Fat Client é usado. Tal nó não armazena dados, mas pode atuar como coordenador de execução de solicitações, ou seja, o próprio Cliente atua como coordenador de suas solicitações: consulta réplicas de armazenamento e resolve conflitos. Isso não só é mais confiável e rápido que o driver padrão, que requer comunicação com um coordenador remoto, mas também permite controlar a transmissão de solicitações. Fora de uma transação aberta no cliente, as solicitações são enviadas aos repositórios. Se o cliente abriu uma transação, todas as solicitações dentro da transação serão enviadas ao coordenador da transação.
NovoSQL = NoSQL + ACID

Coordenador de Transações C*One

O coordenador é algo que implementamos para o C*One do zero. É responsável por gerenciar transações, bloqueios e a ordem em que as transações são aplicadas.

Para cada transação atendida, o coordenador gera um carimbo de data/hora: cada transação subsequente é maior que a transação anterior. Como o sistema de resolução de conflitos do Cassandra é baseado em carimbos de data/hora (de dois registros conflitantes, aquele com o carimbo de data/hora mais recente é considerado atual), o conflito sempre será resolvido em favor da transação subsequente. Assim implementamos Relógio Lamport - uma forma barata de resolver conflitos em um sistema distribuído.

Fechaduras

Para garantir o isolamento, decidimos usar o método mais simples - bloqueios pessimistas baseados na chave primária do registro. Em outras palavras, em uma transação, um registro deve primeiro ser bloqueado, só depois lido, modificado e salvo. Somente após um commit bem-sucedido um registro pode ser desbloqueado para que transações concorrentes possam utilizá-lo.

A implementação desse bloqueio é simples em um ambiente não distribuído. Em um sistema distribuído, existem duas opções principais: implementar o bloqueio distribuído no cluster ou distribuir as transações de forma que as transações que envolvem o mesmo registro sejam sempre atendidas pelo mesmo coordenador.

Como no nosso caso os dados já estão distribuídos entre grupos de transações locais em SQL, optou-se por atribuir grupos de transações locais aos coordenadores: um coordenador realiza todas as transações com tokens de 0 a 9, o segundo - com tokens de 10 a 19, e assim por diante. Como resultado, cada uma das instâncias coordenadoras torna-se a mestre do grupo de transações.

Então os bloqueios podem ser implementados na forma de um HashMap banal na memória do coordenador.

Falhas do coordenador

Como um coordenador atende exclusivamente um grupo de transações, é muito importante determinar rapidamente o fato de sua falha para que a segunda tentativa de execução da transação expire. Para tornar isso rápido e confiável, usamos um protocolo de heartbeat de quórum totalmente conectado:

Cada data center hospeda pelo menos dois nós coordenadores. Periodicamente, cada coordenador envia uma mensagem de heartbeat aos demais coordenadores e os informa sobre seu funcionamento, bem como quais mensagens de heartbeat recebeu de quais coordenadores do cluster na última vez.

NovoSQL = NoSQL + ACID

Recebendo informações semelhantes de outros como parte de suas mensagens de pulsação, cada coordenador decide por si mesmo quais nós do cluster estão funcionando e quais não estão, guiado pelo princípio do quórum: se o nó X recebeu informações da maioria dos nós do cluster sobre o normal recebimento de mensagens do nó Y, então, Y funciona. E vice-versa, assim que a maioria relata mensagens perdidas do nó Y, então Y recusou. É curioso que se o quorum informar ao nó X que não está mais recebendo mensagens dele, então o próprio nó X considerará que falhou.

As mensagens de pulsação são enviadas com alta frequência, cerca de 20 vezes por segundo, com período de 50 ms. Em Java, é difícil garantir a resposta do aplicativo dentro de 50 ms devido à duração comparável das pausas causadas pelo coletor de lixo. Conseguimos atingir esse tempo de resposta usando o coletor de lixo G1, que nos permite especificar uma meta para a duração das pausas do GC. Porém, às vezes, muito raramente, as pausas do coletor ultrapassam 50 ms, o que pode levar a uma falsa detecção de falta. Para evitar que isso aconteça, o coordenador não reporta uma falha de um nó remoto quando a primeira mensagem de heartbeat dele desaparece, apenas se vários tiverem desaparecido seguidos. Foi assim que conseguimos detectar uma falha do nó coordenador em 200 EM.

Mas não basta entender rapidamente qual nó parou de funcionar. Precisamos fazer algo sobre isso.

Reserva

O esquema clássico envolve, no caso de falha do mestre, iniciar uma nova eleição utilizando um dos na moda universal algoritmos. No entanto, tais algoritmos têm problemas bem conhecidos com a convergência temporal e a duração do próprio processo eleitoral. Conseguimos evitar esses atrasos adicionais usando um esquema de substituição de coordenador em uma rede totalmente conectada:

NovoSQL = NoSQL + ACID

Digamos que queremos executar uma transação no grupo 50. Vamos determinar antecipadamente o esquema de substituição, ou seja, quais nós executarão as transações do grupo 50 em caso de falha do coordenador principal. Nosso objetivo é manter a funcionalidade do sistema no caso de falha do data center. Vamos determinar que a primeira reserva será um nó de outro data center e a segunda reserva será um nó de um terceiro. Este esquema é selecionado uma vez e não muda até que a topologia do cluster mude, ou seja, até que novos nós entrem nele (o que acontece muito raramente). O procedimento para seleção de um novo mestre ativo caso o antigo falhe será sempre o seguinte: a primeira reserva passará a ser o mestre ativo, e caso tenha parado de funcionar, a segunda reserva passará a ser o mestre ativo.

Este esquema é mais confiável que o algoritmo universal, pois para ativar um novo mestre basta determinar a falha do antigo.

Mas como os clientes entenderão qual mestre está trabalhando agora? É impossível enviar informações a milhares de clientes em 50 ms. Uma situação é possível quando um cliente envia uma solicitação para abrir uma transação, ainda sem saber que este master não está mais funcionando, e a solicitação irá expirar. Para evitar que isso aconteça, os clientes enviam especulativamente uma solicitação de abertura de transação ao master do grupo e a ambas as suas reservas ao mesmo tempo, mas somente aquele que é o master ativo no momento responderá a essa solicitação. O cliente fará todas as comunicações subsequentes dentro da transação somente com o master ativo.

Os mestres de backup colocam as solicitações recebidas de transações que não são suas na fila de transações não nascidas, onde são armazenadas por algum tempo. Se o mestre ativo morrer, o novo mestre processará solicitações para abrir transações de sua fila e responderá ao cliente. Se o cliente já abriu uma transação com o antigo mestre, a segunda resposta será ignorada (e, obviamente, tal transação não será concluída e será repetida pelo cliente).

Como funciona a transação

Digamos que um cliente enviou uma solicitação ao coordenador para abrir uma transação para tal ou tal entidade com tal ou qual chave primária. O coordenador bloqueia esta entidade e a coloca na tabela de bloqueios na memória. Se necessário, o coordenador lê esta entidade do armazenamento e armazena os dados resultantes em estado de transação na memória do coordenador.

NovoSQL = NoSQL + ACID

Quando um cliente deseja alterar dados em uma transação, ele envia uma solicitação ao coordenador para modificar a entidade, e o coordenador coloca os novos dados na tabela de status da transação na memória. Isto completa a gravação - nenhuma gravação é feita no armazenamento.

NovoSQL = NoSQL + ACID

Quando um cliente solicita seus próprios dados alterados como parte de uma transação ativa, o coordenador atua da seguinte forma:

  • se o ID já estiver na transação, os dados serão retirados da memória;
  • se não houver ID na memória, os dados faltantes são lidos dos nós de armazenamento, combinados com os já na memória, e o resultado é entregue ao cliente.

Assim, o cliente pode ler suas próprias alterações, mas outros clientes não veem essas alterações, pois elas estão armazenadas apenas na memória do coordenador; ainda não estão nos nós Cassandra.

NovoSQL = NoSQL + ACID

Quando o cliente envia o commit, o estado que estava na memória do serviço é salvo pelo coordenador em um lote registrado e enviado como um lote registrado para o armazenamento do Cassandra. As lojas fazem todo o necessário para garantir que esse pacote seja aplicado atomicamente (completamente), e retornam uma resposta ao coordenador, que libera os bloqueios e confirma ao cliente o sucesso da transação.

NovoSQL = NoSQL + ACID

E para reverter, o coordenador só precisa liberar a memória ocupada pelo estado da transação.

Como resultado das melhorias acima, implementamos os princípios ACID:

  • Atomicidade. Esta é uma garantia de que nenhuma transação será parcialmente registrada no sistema; ou todas as suas suboperações serão concluídas ou nenhuma será concluída. Aderimos a este princípio por meio de lote logado no Cassandra.
  • Consistência. Cada transação bem-sucedida, por definição, registra apenas resultados válidos. Se, após abrir uma transação e realizar parte das operações, for descoberto que o resultado é inválido, é realizado um rollback.
  • Isolamento. Quando uma transação é executada, as transações simultâneas não devem afetar o seu resultado. As transações concorrentes são isoladas usando bloqueios pessimistas no coordenador. Para leituras fora de uma transação, o princípio de isolamento é observado no nível Read Committed.
  • Estabilidade. Independentemente dos problemas nos níveis mais baixos – apagão do sistema, falha de hardware – as alterações feitas por uma transação concluída com êxito devem permanecer preservadas quando as operações forem retomadas.

Leitura por índices

Vamos pegar uma tabela simples:

CREATE TABLE photos (
id bigint primary key,
owner bigint,
modified timestamp,
…)

Possui ID (chave primária), proprietário e data de modificação. Você precisa fazer uma solicitação muito simples - selecione os dados do proprietário com a data de alteração “do último dia”.

SELECT *
WHERE owner=?
AND modified>?

Para que tal consulta seja processada rapidamente, em um SGBD SQL clássico, você precisa construir um índice por colunas (proprietário, modificado). Podemos fazer isso facilmente, pois agora temos garantias ACID!

Índices em C*One

Existe uma tabela de origem com fotografias em que o ID do registro é a chave primária.

NovoSQL = NoSQL + ACID

Para um índice, C*One cria uma nova tabela que é uma cópia da original. A chave é igual à expressão do índice e também inclui a chave primária do registro da tabela de origem:

NovoSQL = NoSQL + ACID

Agora a consulta “proprietário do último dia” pode ser reescrita como uma seleção de outra tabela:

SELECT * FROM i1_test
WHERE owner=?
AND modified>?

A consistência dos dados nas fotos da tabela de origem e na tabela de índice i1 é mantida automaticamente pelo coordenador. Com base apenas no esquema de dados, quando uma alteração é recebida, o coordenador gera e armazena a alteração não apenas na tabela principal, mas também em cópias. Nenhuma ação adicional é executada na tabela de índice, os logs não são lidos e nenhum bloqueio é usado. Ou seja, adicionar índices quase não consome recursos e praticamente não afeta a velocidade de aplicação de modificações.

Usando ACID, conseguimos implementar índices semelhantes a SQL. Eles são consistentes, escaláveis, rápidos, combináveis ​​e integrados à linguagem de consulta CQL. Nenhuma alteração no código do aplicativo é necessária para oferecer suporte a índices. Tudo é tão simples quanto no SQL. E o mais importante, os índices não afetam a velocidade de execução das modificações na tabela de transações original.

O que aconteceu

Desenvolvemos o C*One há três anos e o lançamos em operação comercial.

O que conseguimos no final? Vamos avaliar isso usando o exemplo do subsistema de processamento e armazenamento de fotos, um dos tipos de dados mais importantes em uma rede social. Não estamos falando dos corpos das fotografias em si, mas de todo tipo de metainformação. Agora Odnoklassniki tem cerca de 20 bilhões desses registros, o sistema processa 80 mil solicitações de leitura por segundo, até 8 mil transações ACID por segundo associadas à modificação de dados.

Quando usamos SQL com fator de replicação = 1 (mas em RAID 10), a metainformação da foto foi armazenada em um cluster altamente disponível de 32 máquinas rodando Microsoft SQL Server (mais 11 backups). Também foram alocados 10 servidores para armazenamento de backups. Um total de 50 carros caros. Ao mesmo tempo, o sistema operou com carga nominal, sem reserva.

Após a migração para o novo sistema, recebemos fator de replicação = 3 – uma cópia em cada data center. O sistema consiste em 63 nós de armazenamento Cassandra e 6 máquinas coordenadoras, totalizando 69 servidores. Mas essas máquinas são muito mais baratas, seu custo total é cerca de 30% do custo de um sistema SQL. Ao mesmo tempo, a carga é mantida em 30%.

Com a introdução do C*One, a latência também diminuiu: no SQL, uma operação de gravação demorava cerca de 4,5 ms. Em C*One - cerca de 1,6 ms. A duração da transação é em média inferior a 40 ms, o commit é concluído em 2 ms, a duração de leitura e gravação é em média 2 ms. 99º percentil - apenas 3-3,1 ms, o número de tempos limite diminuiu 100 vezes - tudo devido ao uso generalizado de especulação.

Até agora, a maioria dos nós do SQL Server foram desativados; novos produtos estão sendo desenvolvidos apenas usando C*One. Adaptamos o C*One para funcionar em nossa nuvem uma nuvem, o que permitiu agilizar a implantação de novos clusters, simplificar a configuração e automatizar a operação. Sem o código-fonte, fazer isso seria muito mais difícil e complicado.

Agora estamos trabalhando na transferência de nossas outras instalações de armazenamento para a nuvem – mas isso é uma história completamente diferente.

Fonte: habr.com

Adicionar um comentário