Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel

No ecossistema PHP existem atualmente dois conectores para trabalhar com o servidor Tarantool - esta é a extensão PECL oficial tarantool/tarantool-php, escrito em C, e tarantool-php/cliente, escrito em PHP. Eu sou o autor deste último.

Neste artigo, gostaria de compartilhar os resultados dos testes de desempenho de ambas as bibliotecas e mostrar como, com alterações mínimas no código, você pode obter um aumento de desempenho de 3 a 5 (em testes sintéticos!).

O que vamos testar?

Vamos testar os mencionados acima síncrona conectores executados de forma assíncrona, em paralelo e em paralelo assíncrono. 🙂 Também não queremos mexer no código dos próprios conectores. Atualmente existem várias extensões disponíveis para conseguir o que você deseja:

  • 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 apareceu um método mágico SwooleRuntime::enableCoroutine(), que permite “converter bibliotecas de rede PHP síncronas em assíncronas com uma linha de código”.
  • Async era até recentemente uma extensão muito promissora para trabalho assíncrono em PHP. Por que até recentemente? Infelizmente, por um motivo que desconheço, o autor excluiu o repositório e o destino futuro do projeto não está claro. vou ter que usar um de garfos. Assim como o Swoole, esta extensão permite que você vista as calças facilmente com um movimento do pulso para permitir a assincronia, substituindo a implementação padrão de fluxos TCP e TLS por suas versões assíncronas. Isso é feito através da opção “assíncrono.tcp = 1".
  • Paralelo ― uma extensão relativamente nova do conhecido Joe Watkins, autor de bibliotecas como phpdbg, apcu, pthreads, pcov, uopz. A extensão fornece uma API para multithreading em PHP e está posicionada como um substituto para pthreads. Uma limitação significativa da biblioteca é que ela só funciona com a versão ZTS (Zend Thread Safe) do PHP.

Como iremos testar?

Vamos iniciar uma instância do Tarantool com o registro write-ahead desabilitado (wal_mode = nenhum) e aumento do buffer de rede (leitura antecipada = 1 * 1024 * 1024). A primeira opção eliminará o trabalho com o disco, a segunda permitirá ler mais solicitações do buffer do sistema operacional e assim minimizar o número de chamadas do sistema.

Para benchmarks que trabalham com dados (inserção, exclusão, leitura, etc.), antes de iniciar o benchmark, será (re)criado um espaço memtx, no qual os valores do índice primário são criados por um gerador de valores inteiros ordenados ​(sequência).
O espaço DDL se parece com isto:

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 executar o benchmark, o espaço é preenchido com 10,000 tuplas do formulário

{id, "tuplе_<id>"}

As tuplas são acessadas usando um valor de chave aleatório.

O benchmark em si é uma única solicitação ao 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 de um erro aceitável de 3%*. Depois disso, o resultado médio é obtido. Há uma pausa de 1 segundo entre as iterações para evitar que o processador acelere. O coletor de lixo de Lua é desabilitado antes de cada iteração e é forçado a iniciar após sua conclusão. O processo PHP é iniciado apenas com as extensões necessárias para o benchmark, com buffer de saída habilitado e coletor de lixo desabilitado.

* O número de revoluções, iterações e limite de erro podem ser alterados nas configurações de benchmark.

Ambiente de teste

Os resultados publicados abaixo foram feitos em um MacBookPro (2015), sistema operacional – Fedora 30 (kernel versão 5.3.8-200.fc30.x86_64). Tarantool foi lançado no docker com o parâmetro "--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 (+ patch para 7.3)*
ext-msgpack: 2.0.3
ext-assíncrono: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-paralelo: 1.1.3

* Infelizmente, o conector oficial não funciona com a versão PHP > 7.2. Para compilar e executar a extensão no PHP 7.3, tive que usar correção.

Descobertas

Modo síncrono

O protocolo Tarantool usa um formato binário Pacote de mensagens para serializar mensagens. No conector PECL, a serialização está oculta nas profundezas da biblioteca e afeta o processo de codificação do código do usuário não é possível. Um conector PHP puro, por outro lado, oferece a capacidade de personalizar o processo de codificação estendendo o codificador padrão ou usando sua própria implementação. Existem dois codificadores disponíveis imediatamente, um é baseado em msgpack/msgpack-php (extensão oficial MessagePack PECL), o outro está ligado rybakit/msgpack (em PHP puro).

Antes de comparar os conectores, mediremos o desempenho dos codificadores MessagePack para o conector PHP e em testes posteriores utilizaremos aquele que apresentar o melhor resultado:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Embora a versão PHP (Pure) seja inferior à extensão PECL em velocidade, em projetos reais eu ainda recomendaria usá-la rybakit/msgpack, porque na extensão oficial do MessagePack a especificação do formato é implementada apenas parcialmente (por exemplo, não há suporte para tipos de dados personalizados, sem os quais você não poderá usar Decimal - um novo tipo de dados introduzido no Tarantool 2.3) e tem um número de outros проблем (incluindo problemas de compatibilidade com PHP 7.4). Bem, em geral o projeto parece abandonado.

Então, vamos medir o desempenho dos conectores no modo síncrono:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Como pode ser visto no gráfico, o conector PECL (Tarantool) apresenta melhor desempenho em comparação ao conector PHP (Cliente). Mas isso não é surpreendente, visto que este último, além de ser implementado em uma linguagem mais lenta, na verdade funciona mais: um novo objeto é criado a cada chamada SOLICITAÇÃO и Resposta (no caso de Select - também Critérios, e no caso de Update/Upsert ― Operações), entidades separadas Conexão, Empacotador и Treinador eles também adicionam sobrecarga. Obviamente, a flexibilidade tem um preço. Porém, em geral, o interpretador PHP apresenta bom desempenho, embora haja uma diferença, é insignificante e, talvez, será ainda menor ao usar o pré-carregamento no PHP 7.4, sem falar no JIT no PHP 8.

Vamos continuar. Tarantool 2.0 introduziu suporte SQL. Vamos tentar realizar operações de seleção, inserção, atualização e exclusão usando o protocolo SQL e comparar os resultados com os equivalentes noSQL (binários):

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Os resultados do SQL não são muito impressionantes (lembro que ainda estamos testando o modo síncrono). No entanto, eu não ficaria chateado com isso antecipadamente; o suporte SQL ainda está em desenvolvimento ativo (relativamente recentemente, por exemplo, o suporte foi adicionado declarações preparadas) e, a julgar pela lista questões, o mecanismo SQL passará por diversas otimizações no futuro.

Assíncrono

Bem, agora vamos ver como a extensão Async pode nos ajudar a melhorar os resultados acima. Para escrever programas assíncronos, a extensão fornece uma API baseada em corrotinas, que usaremos. Descobrimos empiricamente que o número ideal de corrotinas para nosso ambiente é 25:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
“Espalhe” 10,000 operações em 25 corrotinas e veja o que acontece:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
O número de operações por segundo aumentou mais de 3 vezes para tarantool-php/cliente!

Infelizmente, o conector PECL não começou com ext-async.

E quanto ao SQL?

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Como você pode ver, no modo assíncrono a diferença entre o protocolo binário e o SQL ficou dentro da margem de erro.

Swoole

Novamente descobrimos o número ideal de corrotinas, desta vez para Swoole:
Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Vamos parar em 25. Vamos repetir o mesmo truque da extensão Async - distribuir 10,000 operações entre 25 corrotinas. Além disso, adicionaremos outro teste no qual dividiremos todo o trabalho em 2 dois processos (ou seja, cada processo realizará 5,000 operações em 25 corrotinas). Os processos serão criados usando SwooleProcesso.

Resultados:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Swole mostra um resultado um pouco inferior em comparação ao Async quando executado em um processo, mas com 2 processos o quadro muda drasticamente (o número 2 não foi escolhido por acaso; na minha máquina foram 2 processos que apresentaram o melhor resultado).

Aliás, a extensão Async também possui uma API para trabalhar com processos, mas aí não notei nenhuma diferença em executar benchmarks em um ou mais processos (é possível que eu tenha errado em algum lugar).

SQL vs protocolo binário:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Tal como acontece com o Async, a diferença entre operações binárias e SQL é eliminada no modo assíncrono.

Paralelo

Como a extensão Parallel não trata de corrotinas, mas de threads, vamos medir o número ideal de threads paralelos:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
É igual a 16 na minha máquina. Vamos executar benchmarks de conectores em 16 threads paralelos:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Como você pode ver, o resultado é ainda melhor do que com extensões assíncronas (sem contar o Swoole rodando em 2 processos). Observe que para o conector PECL, as operações Update e Upsert estão vazias. Isso se deve ao fato de que essas operações falharam com um erro - não sei se foi culpa do ext-parallel, do ext-tarantool ou de ambos.

Agora vamos comparar o desempenho do SQL:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel
Observe a semelhança com o gráfico dos conectores executados de forma síncrona?

Junto

E, finalmente, vamos resumir todos os resultados em um gráfico para ter uma visão geral das extensões testadas. Vamos adicionar apenas um novo teste ao gráfico, o que ainda não fizemos - vamos executar corrotinas Async em paralelo usando Parallel*. A ideia de integrar as extensões acima já está discutido autores, mas nenhum consenso foi alcançado, você terá que fazer isso sozinho.

* Não foi possível iniciar corrotinas Swoole com Parallel; parece que essas extensões são incompatíveis.

Então, os resultados finais:

Acelerando conectores PHP para Tarantool usando Async, Swoole e Parallel

Em vez de uma conclusão

Na minha opinião, os resultados revelaram-se bastante válidos e, por alguma razão, tenho a certeza que este não é o limite! Se você precisa decidir isso em um projeto real exclusivamente para você, direi apenas que para mim foi um experimento interessante que permite avaliar o quanto você pode “espremer” de um conector TCP síncrono com o mínimo esforço. Se você tiver ideias para melhorar os benchmarks, ficarei feliz em considerar sua solicitação de pull. Todo o código com instruções de lançamento e resultados é publicado em separado repositórios.

Fonte: habr.com

Adicionar um comentário