Papers
Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases
Alexandre Verbitski, Anurag Gupta, Debanjan Saha, Murali Brahmadesam, Kamal Gupta, Raman Mittal, Sailesh Krishnamurthy, Sandor Maurice, Tengiz Kharatishvili, Xiaofeng Bao · ACM SIGMOD International Conference on Management of Data (SIGMOD) 2017, 2017
O problema: na nuvem, o gargalo do banco passou a ser a rede
O paper parte de uma observação: em um banco relacional tradicional, o limite de desempenho vinha de CPU e disco, mas na nuvem a computação é desacoplada do armazenamento e os dados são espalhados por muitos nós e discos de uma frota multi-tenant. Nesse cenário, discos individuais deixam de ser o ponto quente e o gargalo se desloca para a rede entre a camada do banco, que emite as operações de I/O, e a camada de armazenamento, que as executa. Além de pacotes por segundo e largura de banda, há amplificação de tráfego, pois um banco de alto desempenho envia suas escritas a muitos nós em paralelo, e o nó, disco ou caminho de rede mais lento (o outlier) pode dominar o tempo de resposta. Os autores também apontam fontes de travamento no motor: um cache miss que obriga a esperar a leitura em disco, a expulsão de páginas sujas, checkpoints e escritas em background que causam stalls, e protocolos de commit de várias fases como o 2PC, que toleram mal as falhas e a latência de sistemas distribuídos em escala. A resposta proposta é o Aurora, um serviço de banco relacional para cargas OLTP que empurra o processamento do redo log para um serviço de armazenamento scale-out, multi-tenant e construído sob medida, reduzindo o tráfego de rede e permitindo recuperação rápida e armazenamento tolerante a falhas e auto-reparável.
Durabilidade em escala: quórum 4/6 em três zonas e segmentos de 10 GB
Para tolerar falhas, sistemas replicados usam quóruns: com V cópias, uma leitura exige Vr votos e uma escrita exige Vw votos, e duas regras garantem consistência: Vr + Vw > V (toda leitura enxerga a escrita mais recente) e Vw > V/2 (evita escritas conflitantes). A abordagem comum, três réplicas com quóruns 2/3, é considerada insuficiente pelos autores. O motivo é que, num momento qualquer, alguns discos ou nós já estão falhos por ruído de fundo, e a perda de uma Zona de Disponibilidade (AZ) inteira é uma falha correlacionada: se, além dela, um nó de outra AZ estiver com problema, o quórum se quebra. O Aurora usa então 6 cópias de cada dado em 3 AZs, duas por AZ, com quórum de escrita de 4/6 e de leitura de 3/6. Isso permite perder uma AZ inteira mais um nó (AZ+1) sem perder dados, e perder uma AZ inteira sem perder a capacidade de escrita. Para reduzir a janela de vulnerabilidade a uma segunda falha, o volume é particionado em segmentos de 10 GB, cada um replicado seis vezes em um Protection Group (PG). Segmentos são a unidade de falha e reparo: um segmento de 10 GB pode ser reparado em cerca de 10 segundos numa rede de 10 Gbps, então perder o quórum exigiria duas falhas independentes na mesma janela somadas a uma falha de AZ. O volume pode crescer até 64 TB.
O log é o banco de dados: reduzindo a amplificação de escritas
Replicar cada segmento seis vezes com quórum de escrita 4/6 seria inviável com um motor tradicional como o MySQL, que gera muitos tipos de I/O por escrita da aplicação: redo log, binlog, páginas de dados, uma segunda escrita da página (double-write, contra páginas parciais) e arquivos de metadados, muitos deles em passos sequenciais e síncronos. No Aurora, a única coisa que cruza a rede a partir da camada do banco são registros de redo log: nenhuma página é escrita, nem em background, nem em checkpoint, nem na expulsão do cache. A camada de armazenamento aplica o log para materializar páginas, continuamente em background ou sob demanda, e do ponto de vista da correção o motor trata as páginas materializadas como um simples cache: o log é o banco de dados. Os registros são agrupados em lotes por PG e enviados às seis réplicas, e o banco espera confirmação de 4. A maior parte do trabalho do nó de armazenamento é assíncrona; só receber o registro e persisti-lo em disco ficam no caminho síncrono. No experimento do paper (SysBench write-only, 100 GB, 30 minutos), o Aurora processou 27.378.000 transações contra 780.000 do MySQL espelhado, 35 vezes mais, com 0,95 I/O por transação contra 7,4, ou seja, 7,7 vezes menos I/Os por transação no nó de banco, mesmo amplificando as escritas seis vezes.
Consistência sem 2PC: LSNs, VDL, commit assíncrono e leituras sem quórum
Em vez de um protocolo de consenso caro e verboso como o 2PC, o Aurora aproveita que o log avança em ordem: cada registro recebe um LSN (Log Sequence Number) crescente, gerado pelo banco. Cada transação é quebrada em mini-transações atômicas, cujo último registro é um ponto de consistência (CPL). O armazenamento calcula o VCL, o maior LSN para o qual todos os registros anteriores estão disponíveis, e o VDL (Volume Durable LSN), o maior CPL menor ou igual ao VCL, que é até onde o volume é considerado durável. Nós de armazenamento que perderam registros os recuperam conversando entre si (gossip). Para que o banco não corra muito à frente do armazenamento, ele não aloca LSN maior que o VDL atual somado a um limite (LAL, hoje 10 milhões), o que cria contrapressão. Os commits são assíncronos: a thread que recebe o COMMIT registra o commit LSN numa lista de espera e segue para outro trabalho; a transação só é confirmada ao cliente quando o VDL alcança ou ultrapassa esse LSN, e uma thread dedicada envia as confirmações. As leituras, em operação normal, não precisam de quórum: o banco define um read-point (o VDL no momento do pedido) e, como acompanha qual segmento já está completo até esse ponto, lê direto de um segmento adequado. Quórum de leitura só é usado na recuperação, quando o estado de runtime se perdeu.
Réplicas, recuperação rápida e resultados medidos
Um único writer e até 15 réplicas de leitura montam o mesmo volume de armazenamento compartilhado, então réplicas não adicionam custo de armazenamento nem escritas em disco. Para reduzir o atraso, o fluxo de log do writer também é enviado às réplicas, que aplicam apenas registros com LSN menor ou igual ao VDL e aplicam as mini-transações de forma atômica; o paper diz que a réplica tipicamente fica atrás do writer por 20 ms ou menos. Na recuperação, bancos tradicionais (estilo ARIES) reaplicam o log desde o último checkpoint, algo caro. No Aurora, a aplicação do redo ocorre continuamente no armazenamento, então, ao reiniciar, o banco só consulta um quórum de leitura de cada PG para recalcular o VDL e truncar registros acima dele; o paper afirma que a recuperação costuma levar menos de 10 segundos, mesmo após uma queda com mais de 100.000 escritas por segundo. Nos resultados (Aurora baseado no MySQL 5.6, serviço disponível desde julho de 2015), a vazão escala linearmente com o tamanho da instância, chegando a 5 vezes a do MySQL 5.7 na maior (121.000 escritas/s e 600.000 leituras/s). Em escrita com 100 GB, até 67 vezes mais rápido; em 1 TB fora do cache, 34 vezes. A 10.000 escritas/s, a defasagem da réplica foi de 5,38 ms no Aurora contra 300 segundos no MySQL.
Veja as ideias em ação
Pratique na escola de System Design os desenhos que usam este paper.