Papers
Reducing the Scope of Impact with Cell-Based Architecture
Robisson Oliveira · AWS Well-Architected whitepaper (publicado em 20 de setembro de 2023), 2023
O problema: um único blast radius e a ideia de bulkhead
O whitepaper parte de uma constatação: organizações precisam ser cada vez mais resilientes, e em ambientes multi-tenant na nuvem os clientes ainda esperam a experiência de um ambiente single-tenant. O guia propõe levar ao nível da arquitetura da carga de trabalho o mesmo isolamento de falhas que a AWS aplica em Zonas de Disponibilidade e Regiões, expandindo a boa prática do Well-Architected de usar arquiteturas bulkhead para limitar o escopo de impacto. A metáfora vem dos navios: divisórias verticais criam compartimentos estanques, de modo que uma brecha no casco fica contida em uma seção. Em sistemas complexos, fronteiras de isolamento de falhas restringem o efeito de uma falha a poucos componentes, e o que está fora da fronteira não é afetado. O documento contrasta isso com uma aplicação típica de três camadas, em que uma falha ou uma mudança atinge 100% dos clientes. A proposta é, em vez de construir serviços como sistemas de imagem única, dividi-los internamente em células e colocar uma camada fina para rotear o tráfego até a célula certa. Segundo o texto, as equipes de serviço da AWS usam arquitetura celular há mais de uma década, e ela pode ser zonal, regional ou global. O guia afirma que esse estilo dá mais isolamento de falhas, previsibilidade e testabilidade, propriedades fundamentais para cargas que exigem níveis extremos de resiliência.
Componentes: roteador de células, célula e plano de controle
A arquitetura celular tem três componentes. A célula é uma instância completa da carga de trabalho, com tudo o que precisa para operar de forma independente; por exemplo, se a aplicação é um Application Load Balancer, algumas instâncias EC2 e um banco Amazon RDS, todos esses elementos pertencem à célula 1, e uma célula 2 exige outro deployment dos três. Idealmente a célula não conhece as outras, não compartilha estado e não faz chamadas entre células nem usa recursos compartilhados como bancos ou buckets S3; o guia chega a incentivar contas AWS separadas. Dependências entre células eliminam rapidamente os benefícios, então devem ser mínimas ou apenas transitórias. O roteador de células é descrito como a camada mais fina possível, com a única responsabilidade de rotear requisições à célula certa, apresentando um único endpoint aos clientes. O plano de controle cuida das tarefas administrativas, como provisionar e remover células e migrar clientes entre elas. O texto lembra que isso não significa necessariamente dobrar ou triplicar a infraestrutura: uma aplicação com 30 hosts pode continuar com 30 hosts, só que agrupados em células atrás de um roteador. O particionamento usa uma chave de partição, como ID do cliente ou do recurso, alinhada ao grão do serviço. Com 10 células atendendo 100 requisições, uma falha em uma célula deixa 90% das requisições sem impacto.
Plano de controle, plano de dados e estabilidade estática
O guia separa o plano de controle do plano de dados, termos emprestados do mundo das redes. O plano de controle oferece as APIs administrativas para provisionar, mover, migrar, atualizar, remover, implantar e monitorar células. O plano de dados executa a função principal do serviço, junto com o roteador de células. Se há cinco células e o número de usuários cresce, o plano de controle provisiona uma nova célula e informa ao roteador para onde enviar o tráfego; depois disso, roteador e célula apenas fazem o trabalho do plano de dados. O texto invoca a estabilidade estática, recomendada no Well-Architected (REL11-BP04, confiar no plano de dados e não no plano de controle durante a recuperação): o sistema continua funcionando mesmo quando uma dependência fica prejudicada. Planos de controle têm, segundo o texto, mais probabilidade estatística de falhar que planos de dados; ainda assim, os recursos já provisionados continuam disponíveis mesmo que criar, alterar ou apagar fique comprometido. O guia também afirma que planos de controle são projetados para falhar em vez de corromper ou dar informação incorreta (CP no teorema CAP), enquanto planos de dados geralmente preferem AP, tentando permanecer disponíveis mesmo com informação desatualizada. Como exemplo, um roteador que carrega as rotas para a memória a partir de um bucket S3 continua direcionando tráfego mesmo se o plano de controle, o S3 ou uma zona estiverem indisponíveis.
Partição, roteamento e tamanho das células
A chave de partição deve acompanhar o grão do serviço e estar facilmente acessível na maioria das chamadas de API. O guia alerta que CustomerID parece boa candidata, mas é preciso pensar em clientes enormes que não cabem numa célula; uma estratégia é incluir uma segunda dimensão na chave. Interações que cruzam células, como scatter-gather, devem ser minoria; uma abordagem é fazê-las voltar pelo roteador em vez de permitir chamadas diretas entre células. Entre os algoritmos listados, sem recomendação específica, estão o mapeamento completo (flexível, mas com dependência crítica da tabela, exigência de consistência read-your-writes e muito estado), o mapeamento por prefixo ou faixa (reduz a cardinalidade, mas favorece células quentes), o módulo ingênuo (distribuição uniforme e estado mínimo, porém exige rebalancear tudo ao mudar o número de células) e o hash consistente (menos movimentação, porém possível desequilíbrio). Para qualquer abordagem, exceto o mapeamento completo (que já oferece isso nativamente), recomenda-se uma tabela de override para forçar chaves a células específicas. O roteador deve ser o mais simples e escalável horizontalmente possível, evitando lógica de negócio complexa, e é o único componente com o estado de todas as células, portanto um ponto único de falha que também deve ser construído de forma celular. Sobre tamanho, três forças se opõem: grande o bastante para comportar as maiores cargas de trabalho, pequena o bastante para testar em escala plena, e grande o bastante para ter economia de escala. Células menores reduzem o impacto, por exemplo 1% por célula com 100 células contra 10% com 10, mas aumentam o número de réplicas a operar.
Trade-offs, implantação em ondas e boas práticas
O guia lista benefícios: escalar horizontalmente por células de tamanho máximo conhecido, menor escopo de impacto, maior testabilidade (é razoável simular a maior carga que cabe em uma célula, que deve corresponder à maior carga de um único cliente), MTBF maior, MTTR menor e maior disponibilidade geral. Uma conclusão natural seria que a disponibilidade fica igual à de um monólito, pois n células têm n vezes mais eventos de falha, cada um com 1/n do impacto; mas o MTBF maior e o MTTR menor significam falhas menos frequentes e mais curtas por célula e, portanto, maior disponibilidade geral. A pergunta proposta é se é melhor 100% dos clientes terem 5% de falha ou 5% dos clientes terem 100% de falha. As desvantagens declaradas são maior complexidade, custo mais alto de infraestrutura, ferramentas e práticas operacionais especializadas e a necessidade de investir numa camada de roteamento; por isso, a arquitetura não serve a todas as cargas. As células também não foram pensadas como domínios de failover nem para mitigar falhas de dependência. Na implantação, o ponto importante é fazer deploy em ondas, célula a célula ou em conjuntos; a primeira célula pode ser um canary, e ao primeiro sinal de falha é possível fazer rollback; é essencial um pipeline de CI/CD automatizado desde o início e observabilidade ciente de células. Entre as boas práticas: tratar a pilha atual como célula zero (colocar o roteador acima dela e distribuir o tráfego aos poucos conforme a estratégia de partição), começar com múltiplas células (o que traz desde cedo a experiência de operar nesse ambiente e reduz surpresas) e com um mecanismo de migração desde o primeiro dia, e fazer uma análise de modos de falha de cada célula.
Veja as ideias em ação
Pratique na escola de System Design os desenhos que usam este paper.