Desenvolvimento

29 jul, 2016

Aplicativo de armazenamento de dados usando SSDs

Publicidade

Como vocês já devem ter percebido, eu adoro a Netflix. E digo isso não apenas pelo conteúdo: séries, filmes e animes. A tecnologia que eles empregam, a metodologia de desenvolvimento e os inúmeros projetos open source que eles disponibilizam para a comunidade a fim de melhorarem seus serviços fazem com que eu navegue constantemente pelo seu techblog e divulgue aqui no iMasters.

Neste artigo, trago uma explicação de como eles conseguem manter a personalização dos dados dos seus clientes em qualquer parte do mundo. Basicamente é o modo como eles mantêm a persistência e a replicação dos dados personalizados de 81 milhões de usuários, o que, embora não seja algo comum, é um estudo de caso interessante para explicar como eles trabalham o custo-benefício, o balanceamento do armazenamento, a importância dos testes A/B, as aplicações em produção e o aumento da performance causado pelo impacto do projeto. E tudo isso de uma maneira open source que você pode ajudar a construir.

Vamos falar do projeto Moneta, o EVCache de próxima geração para uma melhor otimização de custos.

Com a expansão global da Netflix no início deste ano, veio a expansão global dos dados. Depois do projeto Active-Active e agora com a arquitetura N+1, os dados personalizados mais recentes precisam estar em todos os lugares em todos os momentos para servir qualquer membro de qualquer região. O caching desempenha um papel crítico na história de persistência para personalização aos membros conforme detalhado neste artigo do blog.

Há dois componentes principais para a arquitetura Netflix. O primeiro é o plano de controle que é executado na nuvem AWS para ser uma computação genérica, escalável para a experiência de inscrição do membro, de navegação e reprodução. O segundo é o plano de dados, chamado Open Connect, que é a rede de distribuição de vídeo global que eles utilizam. Este artigo fala sobre como eles trouxeram o poder e a economia do SSDs para o EVCache – o sistema de cache primário em uso na Netflix para aplicativos executados no plano de controle na AWS.

Um dos principais casos de uso de EVCache é agir como armazenamento globalmente replicado para os dados personalizados para cada um dos mais de 81 milhões de membros da Netflix. EVCache desempenha uma variedade de papéis dentro da Netflix, além de reter esses dados, inclusive atuando como um cache de conjunto de trabalho padrão para coisas como as informações do assinante. Mas o seu maior papel é para a personalização. Servir qualquer pessoa de qualquer lugar significa que eles têm que manter todos os dados personalizados para cada membro em cada uma das três regiões em que operam. Isso permite uma experiência consistente em todas as regiões da AWS e permite aos devs da Netflix mudar facilmente de tráfego durante as interrupções regionais ou durante tráfego regular, moldando exercícios para balancear a carga. Eles falaram longamente sobre o sistema de replicação usado para fazer isso acontecer em um texto anterior.

Durante o estado estacionário, as regiões tendem a ver os mesmos membros várias vezes. Entretanto, alternar entre regiões não é um fenômeno muito comum para os seus usuários. Mesmo que seus dados estejam na memória RAM em todas as três regiões, apenas uma região está sendo usada regularmente pelo membro. Extrapolando a partir disso, é possível ver que cada região tem um conjunto de trabalho diferente para esses tipos de caches. Um pequeno subconjunto são dados quentes, e o resto é frio.

Além da separação de dados quente/frio, o custo de manter todos esses dados na memória está crescendo junto com a base de membros. Assim, os diferentes testes A/B e outras mudanças internas podem adicionar ainda mais dados. Para o conjunto de trabalho de membros, eles já possuem milhares de milhões de chaves, e esse número só tende a crescer. Existe o desafio de continuar a apoiar os casos de uso do Netflix, equilibrando custo. Para enfrentar esse desafio, está sendo introduzindo um esquema de cache multi-nível usando RAM e SSD.

O projeto EVCache, para tirar proveito dessa distribuição entre pedido e custo de otimização global, é chamado de  Moneta, em homenagem à deusa latina da memória, e Juno Moneta, a protetora de fundos para Juno.

Arquitetura atual

Agora, vou abordar a arquitetura atual dos servidores EVCache e, em seguida, falar sobre como isso está evoluindo para ativar o suporte SSD.

A figura abaixo mostra uma implementação típica para EVCache e a relação entre uma instância de cliente única e os servidores. Um cliente da EVCache vai conectar vários clusters de servidores EVCache. Em uma região, há várias cópias de todo o conjunto de dados, separados por Zona de disponibilidade AWS. As caixas tracejadas delineiam as réplicas em região, com cada uma tendo uma cópia completa dos dados e agindo como uma unidade. Eles administram essas cópias como grupos separados de autoescalabilidade AWS. Alguns caches têm duas cópias por região, e alguns têm muitas. Essa arquitetura de alto nível ainda é válida para a Netflix para o futuro previsível, e não está mudando. Cada cliente se conecta a todos os servidores em todas as zonas em sua própria região. As gravações são enviadas para todas as cópias e leituras, preferencialmente em servidores topologicamente perto das solicitações de leitura. Para ver mais detalhes sobre a arquitetura EVCache, veja este texto.

netflix-1

O servidor, à medida que vem evoluindo ao longo dos últimos anos, é uma coleção de alguns processos, com dois principais: stock Memcached, um armazenamento popular e muito testado de key-value in-memory, e Prana, o processo paralelo da Netflix. Prana é o gancho do servidor para o resto do ecossistema da Netflix, que ainda é essencialmente baseado em Java. Os clientes se conectam diretamente no processo Memcached rodando em cada servidor. Os servidores são independentes e não se comunicam uns com os outros.

netflix-2

Otimização

Como um dos maiores subsistemas da nuvem Netflix, eles estão em uma posição única para aplicar otimizações através de uma percentagem significativa de nossa nuvem. O custo de manter todos os dados armazenados na memória está crescendo junto com a base de membros. A saída de um único estágio do processo de lote de personalização de um único dia pode carregar mais de 5 terabytes de dados em seu conjunto EVCache dedicado. O custo de armazenar esses dados é multiplicado pelo número de cópias globais de dados que armazenam. Como mencionado anteriormente, os diferentes testes A/B e outras mudanças internas podem adicionar ainda mais dados. Para apenas o conjunto de trabalho de membros da Netflix, hoje eles têm muitos milhares de milhões de chaves.

Para aproveitar os diferentes padrões de acesso a dados que eles observaram em diferentes regiões, eles construíram um sistema para armazenar os dados quentes na RAM e os dados frios no disco. Esse é a clássica arquitetura de cache um de dois níveis (onde L1 é RAM, e L2 é disco), mas os engenheiros da Netflix têm confiado no desempenho consistente e na baixa latência do EVCache. Os requisitos deles se resumem a ter a maior baixa latência possível, usar uma quantidade mais equilibrada do (caro) RAM, e tirar proveito do armazenamento SSD de baixo custo, enquanto entregam a baixa latência que seus usuários esperam.

Clusters In-memory EVCache são executados na família de tipo de instância AWS R3, que são otimizados para grande uso de memória. Movendo para a família i2 de instâncias, eles conseguem um ganho de acesso 10 vezes a quantidade de armazenamento mais rápido SSD que possuíam na família R3 (80 → 800GB de r3.xlarge para i2.xlarge) com o equivalente de RAM e CPU. Também foi possível rebaixar os tamanhos da instância para uma menor quantidade de memória. Combinando esses dois, eles conseguiram um potencial de otimização substancial de custos em seus muitos milhares de servidores.

Arquitetura Moneta

O projeto Moneta introduz dois novos processos no servidor EVCache: Rend e Mnemonic. Rend é um proxy de alta performance escrito em Go com o caso de uso da Netflix como o principal motor para o desenvolvimento. Mnemonic é um armazenamento de key-value baseado em disco e no RocksDB. Mnemonic reutiliza os componentes do servidor Rend que lidam com protocolo de análise (para falar com os protocolos Memcached), gerenciamento de conexão e de bloqueio paralelo (para correção). Todos os três servidores, na verdade, falam o protocolo de texto e binários do Memcached, de modo que as interações com o cliente entre qualquer um dos três têm a mesma semântica. Eles utilizam isso a seu favor quando depuram ou fazem a verificação de consistência.

netflix-3

Onde os clientes previamente se conectavam ao Memcached diretamente, eles agora se conectam ao Rend. A partir daí, Rend cuida das interações L1/L2 entre Memcached e Mnemonic. Mesmo em servidores que não usam Mnemonic, Rend ainda fornece métricas valiosas do lado do servidor que não podiam ser obtidas antes a partir do Memcached, como latências de solicitação do servidor. A latência introduzida por Rend, em conjunto com apenas Memcached, em média é apenas algumas dezenas de microssegundos.

Como parte dessa reformulação, seria possível ter integrado os três processos em conjunto. Eles escolheram ter três processos independentes em execução em cada servidor para manter a separação de interesses. Essa configuração proporciona uma melhor durabilidade de dados no servidor. Se o Rend falhar, os dados ainda estarão intactos no Memcached e no Mnemonic. O servidor é capaz de atender às solicitações dos clientes, uma vez que se reconecta a um processo Rend ressuscitado. Se Memcached falhar, eles perdem o conjunto de trabalho, mas os dados no L2 (Mnemonic) ainda estarão disponíveis. Uma vez que os dados são solicitados novamente, ele estará de volta no conjunto quente e servido como era anteriormente. Se Mnemonic cair, ele não iria perder todos os dados, mas apenas eventualmente um pequeno conjunto que foi escrito muito recentemente. Mesmo que tenha perdido os dados, pelo menos eles têm os dados quentes ainda na RAM e disponíveis para os usuários que utilizam o serviço. Essa resiliência a falhas está no topo das medidas de resiliência no cliente EVCache.

Rend

Rend, como mencionado acima, funciona como um proxy na frente dos dois outros processos no servidor que realmente armazenam os dados. É um servidor de alto desempenho que fala os protocolos Memcached binários e de texto. Ele é escrito em Go e depende fortemente de goroutines e outras primitivas de linguagem para lidar com a concorrência. O projeto é totalmente de código aberto e está disponível no GitHub. A decisão de usar Go foi deliberada, porque eles precisavam de algo que tivesse latência mais baixa do que Java (onde pausas de garbage collection são um problema), fosse mais produtivo para os desenvolvedores do que C, e ao mesmo tempo, lidasse com dezenas de milhares de conexões do cliente. Go se encaixa bem nesse espaço.

Rend tem a responsabilidade de gerir a relação entre os caches L1 e L2 na caixa. Ele tem algumas políticas internas diferentes que se aplicam a diferentes casos de uso. Ele também possui um recurso para cortar dados em pedaços de tamanho fixo enquanto os dados estão sendo inseridos no Memcached para evitar comportamento patológico do esquema de alocação de memória dentro Memcached. Esse chunking do lado do servidor está substituindo a atual versão do lado do cliente, e já está se mostrando promissor. Até agora, é duas vezes mais rápido para leituras e até 30 vezes mais rápido para as gravações. Felizmente, Memcached, a partir do 1.4.25, tornou-se muito mais resistente ao comportamento do cliente ruim que causou problemas anteriormente. Assim é permissível retirar o recurso chunking no futuro, enquanto eles podem depender de L2 para ter os dados, se forem despejados de L1.

Design

O design do Rend é modular para permitir funcionalidades configuráveis. Internamente, existem algumas camadas: gerenciamento de conexão, um loop de servidor, o código específico do protocolo, o pedido de orquestração e manipuladores de backend. De um lado, é um pacote de métricas personalizadas que permite Prana, um processo paralelo, pegar as métricas de informações não sendo demasiado intrusivo. Rend também vem com uma biblioteca de cliente de teste que tem uma base de código separada. Isso os têm ajudado a encontrar bugs de protocolo ou outros erros, tais como: desalinhamento, buffers unflushed e respostas inacabadas.

netflix-4

O design do Rend permite que diferentes backends possam ser ligados com o cumprimento de uma interface e uma função do construtor. Para provar esse projeto, um engenheiro familiarizado com a base de código levou menos de um dia para aprender LMDB e integrar como um backend de armazenamento. O código para essa experiência pode ser encontrado em https://github.com/Netflix/rend-lmdb.

Uso em produção

Para os caches que Moneta serve melhor, há algumas diferentes classes de clientes que servem um único servidor. Uma classe é o tráfego online no caminho quente, solicitando dados de personalização para um membro visitante. O outro é o tráfego dos sistemas offline e nearline que produzem dados de personalização. Estes normalmente são executados em grandes lotes durante a noite e continuamente escrevem por horas a fio.

A modularidade permite que a implementação padrão otimize a computação em lote noturno através da inserção de dados em L2 diretamente e substituindo de forma inteligente dados quentes no L1, em vez de deixar essas gravações consumirem o cache L1 durante o precompute noturno. Os dados replicados vindos de outras regiões também podem ser inseridos diretamente em L2, já que é improvável que os dados replicados e outra região sejam “quentes” em sua região de destino. O diagrama abaixo mostra as várias portas abertas em um processo de Rend em que ambas se ligam nas backing stores. Com a modularidade do Rend, foi fácil introduzir outro servidor em uma porta diferente para o lote e o tráfego de replicação com apenas mais um par de linhas de código.

netflix-5

Performance

Rend por si só, é de muito alto rendimento. Ao testar Rend separadamente, eles sempre batem o limite da largura de banda ou de processamento de pacotes antes de estourar o limite da CPU. Um único servidor, para os pedidos que não precisam pegar nada do backing store, tem atingido 2.86 milhões de pedidos por segundo. Essa é uma estimativa, mas um número irrealista. Com Memcached como o único backing storage, Rend pode sustentar 225k inserções por segundo e 200 mil leituras por segundo simultaneamente no maior exemplo que eles testaram. Uma instância i2.xlarge configurada para usar ambos L1 e L2 (memória e disco) e dados chunking, que é utilizado como um exemplo padrão para os nossos grupos de produção, pode realizar 22k inserções por segundo (com apenas sets), 21K leituras por segundo (apenas com gets), e cerca de 10 mil sets e 10k gets por segundo se ambos forem feitos simultaneamente. Estes são os mais baixos limites para o tráfego de produção deles, porque a carga de teste consistiu em chaves aleatórias não proporcionando benefícios de localidade de dados durante o acesso. Tráfego real vai bater o cache L1 com muito mais frequência do que chaves aleatórias poderiam fazer.

Como uma aplicação do lado do servidor, Rend desbloqueia todos os tipos de possibilidades futuras para a inteligência no servidor EVCache. Além disso, o armazenamento subjacente é completamente desconectado do protocolo usado para se comunicar. Dependendo das necessidades do Netflix, eles podem mover o armazenamento L2 off-box, substituindo o Memcached L1 por outro armazenamento, ou alterar a lógica do servidor para adicionar bloqueio global ou consistência. Estes não são projetos previstos, mas eles são possíveis agora que possuem o código personalizado em execução no servidor.

Mnemonic

Mnemonic é a solução L2 baseada no RocksDB. Ele armazena dados no disco. A análise de protocolo, gerenciamento de conexão e controle de concorrência do Mnemonic são gerenciados pelas mesmas bibliotecas que abastecem o Rend. Mnemonic é outro backend que está conectado a um servidor Rend. As bibliotecas nativas no projeto Mnemonic expõem uma API personalizada C que é consumida por um manipulador Rend.

netflix-6

As partes interessantes do Mnemonic estão na camada do núcleo C++ que envolve o RocksDB. Mnemonic processa os pedidos de estilo Memcached, implementando cada uma das operações necessárias para estar em conformidade com o comportamento Memcached, incluindo suporte TTL. Ele inclui uma característica mais importante: quebra as solicitações entre vários bancos de dados RocksDB em um sistema local para reduzir o trabalho de cada instância individual do RocksDB. As razões por que faz isso serão exploradas na próxima seção.

RocksDB

Depois de olhar para algumas opções de forma eficiente para acessar SSDs, o Netflix escolheu o RocksDB, um armazenador de key-value embutido que usa um design de estrutura de dados de árvore de mesclagem log estruturado (Log Strutured Merge Tree). As operações de gravação são primeiramente inseridas em uma estrutura de dados na memória (memtable) que é descarregada para o disco quando ela estiver cheia. Quando liberada para o disco, o memtable torna-se um arquivo de SST imutável. Isso faz com que a maioria seja escrita sequencialmente para o SSD, o que reduz a quantidade de garbage collection interno que o SSD deve executar e, assim, melhorar a latência em instâncias longas de funcionamento, ao mesmo tempo reduzindo o desgaste.

Um tipo de trabalho que é feito em segundo plano por cada instância separada do RocksDB inclui compactação. Eles inicialmente utilizaram o nível de configuração de compactação por estilo (style compaction), que foi a principal razão para fragmentar os pedidos em vários bancos de dados. No entanto, enquanto estavam avaliando essa configuração de compactação com dados de produção e tráfego de produção, descobriram que a compactação estava causando uma grande quantidade de tráfego extra de leitura/gravação para o SSD, aumentando as latências que no passado pensavam ser aceitáveis. O tráfego de leitura SSD ultrapassou algumas vezes 200MB/seg. Esse tráfego de avaliação incluiu um período prolongado em que o número de operações de gravação foi alta, simulando processos diários de computação em lotes. Durante esse período, RocksDB estava constantemente movendo novos registros L0 nos níveis mais elevados, provocando uma amplificação muito elevada de escrita.

Para evitar essa sobrecarga, eles transferiram para a compactação estilo FIFO. Nessa configuração, nenhuma operação de compactação verdadeira é feita. Arquivos SST antigos são excluídos com base no tamanho máximo do banco de dados. Registros ficam no disco no nível 0, de modo que os registros só são ordenados por tempo entre os múltiplos arquivos de SST. A desvantagem dessa configuração é que uma operação de leitura deve verificar cada arquivo de SST em ordem cronológica inversa antes de uma tecla ser determinada como faltando. Essa verificação geralmente não requer uma leitura de disco, já que os filtros bloom do RocksDB evitam que altas porcentagens das consultas exijam um acesso em disco a cada SST. No entanto, o número de arquivos de SST faz com que a eficácia global do conjunto de filtros de bloom seja menor do que o estilo normal de compactação. A fragmentação inicial das entradas de leitura e escrita pedidas através das várias instâncias RocksDB ajudam a diminuir o impacto negativo na digitalização de tantos arquivos.

Performance

Reexecutando o teste de avaliação com a configuração final de compactação, eles foram capazes de alcançar uma latência de 99% de ~9ms para consultas de leitura durante a carga precompute. Após a carga precompute ser concluída, o percentual de 99 de latência na leitura foi reduzida para ~600μs no mesmo nível de tráfego de leitura. Todos esses testes foram executados sem Memcached e sem o RocksDB block caching.

Para permitir que essa solução trabalhe com mais utilizações variadas, eles precisarão reduzir o número de arquivos de SST que precisa ser verificado por consulta. Eles estão explorando opções como a compactação no estilo universal do RocksDB ou a sua própria compactação personalizada, onde poderiam controlar melhor a taxa de compactação, diminuindo assim a quantidade de dados transferidos de e para o SSD.

Conclusão

Com isso, a Netflix está lançando a sua solução em fases para produção. Rend está atualmente em produção servindo alguns dos seus mais importantes conjuntos de dados de personalização. Números iniciais mostram as operações mais rápidas com maior confiabilidade, pois são menos propensas a problemas temporários de rede. Além disso, estão no processo de implantação do backend Mnemonic (L2) para seus usuários de adoção rápida. Enquanto ainda estão no processo de ajustar o sistema, os resultados parecem promissores com potencial para economia substancial de custos, enquanto continuam permitindo a facilidade de uso e velocidade que o EVCache sempre proporcionou aos seus usuários.

Tem sido uma viagem e tanto para o deploy em produção, e ainda há muito a fazer: fazer o deploy amplamente monitorar, otimizar, enxaguar e repetir. A nova arquitetura para EVCache Server é o que permite a eles continuar a inovar de forma objetiva. Se você quiser ajudar a resolver este ou outros grandes problemas semelhantes em arquitetura de nuvem, junte-se ao time da Netflix.

***

Fonte: http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html