Martin Fowler, autor de diversos livros essenciais sobre arquitetura e desenvolvimento de sistemas corporativos, publicou recentemente em seu blog uma descrição detalhada da arquitetura da plataforma financeira LMAX. Também apresentou como foi desenvolvida a plataforma de modo a atingir uma impressionante taxa de 6 milhões de transações por segundo, usando apenas uma thread, usando hardware comum:
A medição de 6 milhões de transações por segundo foi realizada em um servidor Dell Nehalem quad core dual socket de 3Ghz com 32 GB RAM.
A plataforma financeira LMAX foi apresentada em 2010, nos QCon de Londres e de São Francisco, este último com gravação do InfoQ. As apresentações tiveram grande repercussão em sites e blogs sobre arquitetura de sistemas.
As plataformas financeiras em geral têm requisitos complexos, mas esta tinha um novo complicador. Ao invés de ser vendida para poucos clientes corporativos, seria oferecida a clientes no varejo, com uma expectativa de altíssimo volume de transações por segundo.
Tendo lidado com diversos problemas de desempenho no desenvolvimento de um sistema de apostas esportivas, a equipe do LMAX decidiu explorar arquiteturas alternativas às soluções tradicionais envolvendo coordenação de sessões com bancos de dados relacionais.
Paralelismo e filas
O primeiro protótipo da arquitetura, com foco em paralelismo, foi construído usando o modelo de atores. Neste modelo ao invés de se controlar a concorrência com as primitivas tradicionais (Mutex, Semaforo etc.), são utilizados objetos de alto nível como atores e mensagens.
Apesar de ter sua origem na década de 70, o modelo de atores tem ganhado popularidade apenas recentemente, devido a dificuldades apresentadas no controle de concorrência entre múltiplos núcleos de processamento, como os encontrados nas CPUs modernas. No entanto, o modelo já é cidadão de primeira classe em linguagens como Scala e Erlang.
A equipe do LMAX foi até a raiz do problema na avaliação do desempenho do protótipo e observou que o gerenciamento de filas pelos atores é ineficiente do ponto de vista de uso do cache interno do processador, cujo acesso é muito mais rápido que o acesso à RAM. Fowler comenta:
Para colocar alguma informação em uma fila, é preciso (obviamente) escrever naquela fila. De modo similar, para retirar dados da fila, deve-se escrever na fila para realizar a remoção. Há portanto uma contenção de escrita: mais que um cliente pode precisar escrever na mesma estrutura de dados. Para lidar com esse tipo de contenção em filas, normalmente utilizam-se travas (locks). Mas se uma trava é usada, isso pode causar uma troca de contexto com o kernel e quando isso ocorre, o processador provavelmente perderá os dados em seus caches.
A conclusão foi que, para otimização do cache, é necessário que apenas um núcleo do processador escreva em uma dada região de memória. Devido a esta restrição, surgiram duas decisões importantes. A primeira foi criar uma nova estrutura para controle de concorrência sem travas, batizada de "Disruptor". A segunda foi investigar o quão rápido pode ser o processamento da lógica de negócio em uma única thread, se não for usado o controle de concorrência.
Disruptor
A estrutura de controle Disruptor funciona como uma fila circular para múltiplos clientes. Nessa fila, tudo o que é adicionado pelos produtores é disponibilizado paralelamente aos consumidores. São utilizados apenas contadores simples, ao invés de travas, para garantir que o limites de leitura não sejam ultrapassados.
No caso do LMAX, todo evento produzido é processado por três consumidores antes de chegar à lógica de negócio (veja a figura): o journaler, o replicator e o unmarshaller:
- O papel do journaler é persistir todos os eventos. Para isso, não é usado um banco de dados, e sim apenas arquivos sequenciais, cujo acesso no disco é muito eficiente.
- O replicator retransmite os eventos para nós redundantes, que são usados para alta disponibilidade.
- O unmarshaller desserializa os eventos para objetos Java, que serão consumidos pela lógica de negócio.
O artigo de Fowler descreve em mais detalhes os componentes e suas possíveis variações. A empresa LMAX (que deu nome à arquitetura) disponibilizou o Disruptor como um framework sob licença Apache no Google Code, mostrando inovação considerável em um mercado que é geralmente "segredista".
Dave Farley, co-autor do livro Continuous Delivery e membro da empresa LMAX, apresenta em seu blog mais detalhes sobre a lógica de negócio, que é implementada em interfaces e objetos comuns, possibilitando o uso de modelos orientados a objetos ricos e flexíveis. Farley conclui:
Orgulho-me da nossa infraestrutura tecnológica, mas para mim o ganho real não é o grau de sofisticação da arquitetura, mas sim o quanto ela libertou a equipe para se concentrar nos problemas de negócio que somos pagos pra resolver.
Thread única
Com a arquitetura descrita, o processamento da lógica de negócio pode ser executado em apenas uma thread, pois os Disruptors gerenciam I/O e concorrência. Os Disruptors e o cache "aquecido" – que é acessado o máximo possível antes de se se buscar na memória principal – foram fatores essenciais para se atingir o altíssimo desempenho medido.
Fowler ressalta ainda o quanto os testes de desempenho foram importantes para se chegar a tais resultados, mas alerta:
Criar código de testes de alto desempenho pode ser tão difícil quanto escrever código para produção. É comum obter resultados falsos, devido aos testes não serem tão rápidos quanto o componente sob medição.
Aplicações
Na sua conclusão, Martin Fowler apresenta considerações importantes sobre como podemos aproveitar a arquitetura LMAX em nossos sistemas. Apesar de o cenário da LMAX ser um tanto particular e extremo, as decisões e componentes utilizados são comuns em arquiteturas modernas.
Manter os dados em memória RAM e usar o disco apenas para restauração e backup, por exemplo, é uma estratégia comum em produtos de grid e noSQL, como Infinispan, Coherence, Prevayler, entre outros. A maneira como os eventos são utilizados é semelhante ao sistema CQRS, apesar de este não ser usado explicitamente pelo LMAX. (Para uma introdução ao CQRS, veja o artigo de Fowler sobre o assunto e essa palestra de Pedro Teixeira, realizada no ultimo QCon São Paulo.)
Mesmo que a sua arquitetura seja menos estrita e use muito menos recursos, os dilemas e decisões enfrentados pela LMAX são certamente instrutivos e reaproveitáveis em diversas situações.