
No ecossistema PHP, atualmente existem dois conectores para o servidor Tarantool: a extensão oficial PECL
tarantool / tarantool-php escrita em C e
tarantool-php / client escrito em PHP. Eu sou o autor deste último.
Neste artigo, gostaria de compartilhar os resultados dos testes de desempenho dessas duas bibliotecas e mostrar como você pode obter uma melhoria de desempenho 3x-5x (
em testes sintéticos! ) Com alterações mínimas no código.
O que vamos testar?
Testaremos os conectores síncronos mencionados acima, lançados de forma assíncrona, em paralelo e assíncrona em paralelo. Além disso, não queremos alterações no código fonte dos conectores. No momento, existem várias extensões disponíveis que podem fazer o trabalho:
- Swoole , uma estrutura assíncrona de alto desempenho para PHP. Usado por gigantes da Internet como Alibaba e Baidu. Desde a versão 4.1.0, o incrível gancho de tempo de execução Swoole \ Runtime :: enableCoroutine () apareceu, permitindo "transformar bibliotecas de rede PHP síncronas em bibliotecas co-rotineiras usando uma única linha de código".
- Async, uma extensão muito promissora para trabalho assíncrono em PHP até recentemente. Por que até recentemente? Infelizmente, por razões que não conheço, o autor excluiu o repositório e o futuro do projeto é questionável. Vou usar um dos garfos. Como o Swoole, essa extensão facilita a ativação do modo assíncrono, substituindo as implementações de fluxo padrão do PHP por suas contrapartes assíncronas. Isso é feito através da opção " async.tcp = 1 ".
- Paralelamente , uma extensão bastante nova do conhecido Joe Watkins, autor de bibliotecas como phpdbg, apcu, pthreads, pcov, uopz. A extensão fornece uma API multi-threading para PHP e é posicionada como um substituto para pthreads. Uma limitação significativa da biblioteca é que ela funciona apenas com a versão ZTS (Zend Thread Safe) do PHP.
Como vamos testar?
Executaremos uma instância do Tarantool com o registro write-ahead desativado (
wal_mode = none ) e um buffer de rede estendido (
readahead = 1 * 1024 * 1024 ). A primeira opção impedirá operações de E / S na unidade de disco, a segunda permitirá a leitura de mais solicitações do buffer do sistema operacional e, assim, minimizará o número de chamadas do sistema.
Para benchmarks que funcionam com dados (inserção, exclusão, leitura etc.), um espaço memtx será (re) criado antes do início do benchmark, e os valores iniciais de índice para esse espaço serão criados pelo gerador de sequência.
DDL do espaço é o seguinte:
space = box.schema.space.create(config.space_name, { id = config.space_id, temporary = true }) space:create_index('primary', { type = 'tree', parts = {1, 'unsigned'}, sequence = true }) space:format({ {name = 'id', type = 'unsigned'}, {name = 'name', type = 'string', is_nullable = false} })
Se necessário, antes de iniciar o benchmark, o espaço é preenchido com 10.000 tuplas do seguinte formulário:
{id, 'tuple_' .. id}
As tuplas são acessadas usando o valor da chave aleatória.
O benchmark é uma solicitação única para o servidor que é executada 10.000 vezes (revoluções), que por sua vez são executadas em iterações. As iterações são repetidas até que todos os desvios de tempo entre 5 iterações estejam dentro da margem de erro de 3% *. Depois disso, o resultado médio é obtido. Entre as iterações, há uma pausa de 1 segundo para impedir que a CPU estrangule. O coletor de lixo Lua é desativado antes de cada iteração e é forçado a iniciar após o término da iteração. O processo PHP é iniciado apenas com extensões necessárias para o benchmark, com o buffer de saída ativado e o coletor de lixo desativado.
* O número de rotações, iterações e limite de erro pode ser alterado nas configurações de benchmark.Ambiente de teste
Os resultados publicados abaixo foram feitos no MacBookPro (meados de 2015) com o Fedora 30 (versão 5.3.8-200.fc30.x86_64 do kernel). O Tarantool foi lançado no docker com a
configuração "
--network host ".
Versões do pacote:Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19/03/3, compilação a872fc2f86
PHP: 7.3.11 (cli) (construído: 22 de outubro de 2019 08:11:04)
tarantool / cliente: 0.6.0
rybakit / msgpack: 0.6.1
ext-tarantool: 0.3.2 (corrigido) *
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
paralelo ext: 1.1.3
* Infelizmente, o conector oficial não funciona com PHP> 7.2. Para compilar e executar a extensão no PHP 7.3, tive que usar um patch .Resultados
Sincronizar (padrão)
O protocolo Tarantool usa o formato binário
MessagePack para serializar mensagens. No conector PECL, a serialização está oculta dentro da biblioteca, portanto
, parece impossível afetar o processo de codificação do código da terra do usuário. Por outro lado, o conector PHP puro fornece a capacidade de personalizar o processo de codificação, estendendo um dos codificadores padrão ou usando sua própria implementação. Dois codificadores estão disponíveis
imediatamente : um é baseado no
msgpack / msgpack-php (a extensão oficial PECL da MessagePack) e o outro é baseado no
rybakit / msgpack (PHP puro).
Antes de continuarmos a comparar os conectores, vamos medir o desempenho dos codificadores MessagePack para o conector PHP, para que possamos usar o melhor desempenho posteriormente em nossos testes:
Embora a versão PHP (Pure) não seja tão rápida quanto a extensão PECL, eu ainda recomendaria o uso do
rybakit / msgpack em projetos reais, porque a extensão oficial PECL implementa a especificação MessagePack apenas parcialmente (por exemplo, não há suporte para tipos de dados personalizados, e sem ele você não pode usar Decimal - um novo tipo de dados introduzido no Tarantool 2.3) e possui vários outros
problemas (incluindo problemas de compatibilidade com o PHP 7.4). E o projeto parece abandonado em geral.
Então, vamos medir o desempenho dos conectores no modo síncrono:
Como você pode ver no gráfico, o conector PECL (Tarantool) tem um desempenho melhor que o conector PHP (Client). Isso não é surpreendente, considerando que o último, além de ser implementado em uma linguagem mais lenta, na verdade faz mais trabalho: um novo objeto de
Solicitação e
Resposta é criado com cada solicitação (no caso de Select, também há
Critérios , e no No caso de Update / Upsert, existem
Operações ),
Connection ,
Packer e
Handler também adicionam alguma sobrecarga. É desnecessário dizer que uma maior flexibilidade traz um custo. No entanto, o interpretador PHP mostra bom desempenho em geral. Embora exista uma diferença, é insignificante e pode ficar ainda menos com o uso de pré-carregamento no PHP 7.4, sem mencionar o JIT no PHP 8.
Seguindo em frente agora. O Tarantool 2.0 introduziu o suporte SQL. Vamos tentar executar as operações Selecionar, Inserir, Atualizar e Excluir usando o protocolo SQL e comparar os resultados com os equivalentes noSQL (binários):
Os resultados do SQL não são tão impressionantes (deixe-me lembrá-lo de que ainda estamos testando o modo síncrono). No entanto, não ficaria chateado com isso antes: o suporte ao SQL ainda está em desenvolvimento ativo (por exemplo, o suporte a
instruções preparadas foi adicionado há pouco tempo) e, de acordo com a lista de
problemas , o mecanismo do SQL obtenha várias otimizações no futuro.
Assíncrono
Bem, vamos ver agora como a extensão Async pode nos ajudar a melhorar os resultados acima. Para programação assíncrona, a extensão fornece uma API baseada em corotinas, que vamos usar aqui. Primeiro, como descobrimos por meio de testes, o número ideal de corotinas para nosso ambiente é 25:
Em seguida, distribuímos 10.000 operações em 25 corotinas e verificamos o resultado:
O número de operações por segundo aumentou mais de 3 vezes para o conector PHP! Infelizmente, o conector PECL falhou ao iniciar com ext-async.
E o SQL?
Como você pode ver, no modo assíncrono, a diferença entre o protocolo binário e o SQL está dentro da margem de erro.
Swoole
Novamente, vamos determinar o número ideal de corotinas, desta vez para Swoole:
Vamos pegar 25. Agora, repetindo o mesmo truque da extensão Async: distribua 10.000 operações entre 25 corotinas. Além disso, vamos adicionar mais um teste, onde dividimos tudo em dois processos (ou seja, cada processo executará 5.000 operações em 25 corotinas). Os processos serão criados com a ajuda de
Swoole \ Process .
Resultados:
Swoole mostra um desempenho ligeiramente inferior ao Async ao executar em um processo, mas em 2 processos a imagem muda drasticamente (2 não é escolhido por acidente, na minha máquina esse número exato de processos apresentou o melhor resultado).
A propósito, também há uma API para trabalhar com processos na extensão Async, mas não notei diferença entre o lançamento de benchmarks em um único processo ou em vários processos (é possível que eu tenha cometido alguns erros).
SQL versus protocolo binário:
Como no Async, a diferença entre operações binárias e SQL é nivelada no modo assíncrono.
Paralela
Como a extensão Parallel é sobre threads, não coroutines, devemos medir o número ideal de threads paralelos:
São 16 na minha máquina. Agora vamos comparar os conectores em 16 threads paralelos:
Como você pode ver, o resultado é ainda melhor do que com extensões assíncronas (exceto Swoole lançado com 2 processos). Observe que, para o conector PECL, as operações Update e Upsert não possuem barra. Isso ocorre porque essas operações falharam com um erro, e não tenho certeza do que é o culpado: ext-parallel, ext-tarantool ou ambos.
Agora vamos adicionar o desempenho do SQL à comparação:
Você notou semelhanças com o gráfico para os conectores iniciados de forma síncrona?
Tudo em um
Por fim, vamos combinar todos os resultados em um gráfico para ver a imagem inteira das extensões em teste. Vamos adicionar apenas um novo teste ao gráfico, o que ainda não fizemos: inicie as rotinas assíncronas em paralelo usando Parallel *. A idéia de integrar as extensões mencionadas acima já foi
discutida pelos autores, mas não foi alcançado um consenso; portanto, teremos que fazer isso sozinhos.
* Falha ao iniciar as rotinas da Swoole com Parallel; parece que essas extensões são incompatíveis.Agora, os resultados finais:
Conclusão
Na minha opinião, os resultados são bastante decentes, mas há algo que me faz acreditar que ainda não chegamos! Se você tiver alguma idéia de como melhorar os benchmarks, teremos prazer em revisar sua solicitação de recebimento. Todo o código com instruções de lançamento e resultados é publicado em um
repositório dedicado.
Deixando você decidir se precisaria disso em um projeto real, eu diria apenas que foi um experimento empolgante que me permitiu estimar quanto se poderia obter de um conector TCP síncrono com o mínimo de esforço.