FrankenPHP, Swoole ou RoadRunner: o PHP que você zoava não existe mais
Comparação real dos runtimes PHP em 2026: worker mode, benchmarks de FrankenPHP, Swoole e RoadRunner, riscos de memory leak e qual escolher para produção.
FrankenPHP, Swoole ou RoadRunner: o PHP que você zoava não existe mais
Minha stack declarada é Python e Go, e durante anos PHP foi minha piada de retrospectiva. Aí o backend do Quick Tools acabou rodando em FrankenPHP — decisão de outro contexto, herança de arquitetura — e eu tive que parar de rir e começar a medir. O resultado: uma API que boota o framework uma vez, responde em milissegundos e faz deploy como um binário estático. Igualzinho ao que eu fazia em Go, só que com 20 anos de ecossistema atrás.
Esse artigo é sobre os três runtimes que mudaram esse jogo — FrankenPHP, Swoole e RoadRunner —, o que cada um faz de diferente, os números reais de benchmark (e por que você não deve confiar cegamente neles) e o preço que o worker mode cobra em troca do throughput.
O problema nunca foi o PHP — era o modelo de execução
A piada do "PHP é lento" sempre mirou no alvo errado. O interpretador do PHP 8 com JIT é rápido. O que era lento é o modelo shared-nothing: a cada request HTTP, o PHP-FPM entrega a requisição a um processo que carrega o autoloader, lê a configuração, monta o container de injeção de dependência, registra os service providers do framework, processa a request — e joga tudo fora. Na request seguinte, repete do zero.
Para um script de 200 linhas em 2005, esse modelo era uma feature: impossível vazar estado, impossível vazar memória, crash de um processo não derruba nada. Para um Laravel ou Symfony de 2026, é um imposto fixo de dezenas de milissegundos de bootstrap pago em toda request, antes de qualquer linha do seu código rodar. OPcache elimina o custo de parsing, mas não o de inicialização do framework.
Node.js, Go e Python (com Gunicorn/Uvicorn) nunca pagaram esse imposto: a aplicação sobe uma vez e fica residente em memória atendendo requests. A novidade dos últimos anos é que o PHP agora também faz isso — e de três jeitos diferentes.
Worker mode: a aplicação que não morre a cada request
A ideia comum aos três runtimes é o worker mode: o framework boota uma vez e entra num loop que recebe requests já com tudo quente — container montado, rotas compiladas, conexões abertas. No FrankenPHP, o esqueleto de um worker é literalmente isso:
<?php
// public/worker.php
ignore_user_abort(true);
$app = bootApplication(); // roda UMA vez, não a cada request
$handler = static function () use ($app) {
$response = $app->handle(Request::fromGlobals());
$response->send();
};
while (frankenphp_handle_request($handler)) {
// limpeza entre requests, se necessário
}
Na prática você raramente escreve esse loop na mão: o Laravel Octane e o runtime do Symfony abstraem isso para os três runtimes. Mas entender que existe um while ali embaixo importa — porque tudo que vaza dentro desse loop, vaza para a próxima request. Volto nisso adiante, porque é onde mora o perigo.
O ganho, dependendo do peso do seu bootstrap, vai de 2× a 10× de throughput. Não porque o PHP ficou mais rápido — porque ele parou de refazer o mesmo trabalho centenas de vezes por segundo.
FrankenPHP: o servidor e o PHP viram uma coisa só
O FrankenPHP embute o interpretador PHP dentro do Caddy, o servidor web escrito em Go. Não tem nginx na frente, não tem FPM atrás: o servidor HTTP e o runtime PHP são o mesmo processo. Isso traz de graça o que o Caddy já faz bem — HTTPS automático, HTTP/2 e HTTP/3 nativos, early hints (HTTP 103) — e elimina uma camada inteira da sua infraestrutura.
O argumento mais forte na prática é operacional: deploy de um binário estático ou de uma imagem Docker mínima. Sem orquestrar nginx + FPM + supervisor, sem sincronizar timeout de proxy com timeout de worker. Para container em Kubernetes ou qualquer ambiente cloud-native, é o caminho de menor atrito. E roda como um único processo multi-thread, o que dá o menor footprint de memória dos três — relevante quando seu pod tem limite de 256MB.
O ponto fraco é a idade: é o runtime mais novo, com a menor base de conhecimento acumulado da comunidade. Quando algo quebra às 23h, tem menos issue antiga no GitHub com a sua cara. Isso está mudando rápido — o projeto foi adotado pela organização oficial do PHP — mas em 2026 ainda é um fator.
Swoole: corrotinas de verdade, com letras miúdas
O Swoole é diferente dos outros dois por natureza: não é um servidor na frente do PHP, é uma extensão em C que transforma o PHP num runtime assíncrono orientado a eventos, com corrotinas. O detalhe elegante é que os coroutine hooks interceptam as funções de I/O bloqueantes do PHP — file_get_contents, PDO, Redis — e as tornam cooperativas sem você reescrever nada com async/await:
use function Swoole\Coroutine\run;
use function Swoole\Coroutine\go;
run(function () {
// as duas chamadas executam concorrentemente:
// cada corrotina cede o controle enquanto espera I/O
go(fn () => file_get_contents('https://api-pagamentos.example.com/status'));
go(fn () => file_get_contents('https://api-frete.example.com/cotacao'));
});
Quando a sua carga é dominada por espera de I/O — agregação de APIs externas lentas, WebSockets com milhares de conexões simultâneas, banco com latência alta — o Swoole faz coisas que os outros dois não fazem, porque um worker atende outras requests enquanto espera.
As letras miúdas: é uma extensão C que precisa estar instalada (esqueça hosting genérico), o tooling de debug tem incompatibilidades históricas, e o seu código precisa ser seguro para corrotinas — uma lib que guarda estado em variável estática vira condição de corrida. E tem o ponto que quase ninguém fala: se o seu I/O é rápido, o scheduling de corrotinas vira overhead puro. Os números abaixo mostram isso de forma constrangedora.
RoadRunner: o veterano que ganha benchmark
O RoadRunner é um servidor de aplicação em Go que mantém um pool de workers PHP vivos e conversa com eles por pipes. É a opção mais madura dos três no Octane e a que tem mais histórico de produção acumulado.
A diferença filosófica: RoadRunner não quer ser só um servidor HTTP. Ele traz um sistema de plugins — filas (jobs), key-value store, servidor gRPC, integração com Temporal para workflows — que o transforma numa plataforma de aplicação. Se o seu monolito precisa de fila e gRPC além de HTTP, o RoadRunner resolve no mesmo binário o que os outros exigiriam de infraestrutura adicional.
O modelo de worker é o mais conservador dos três: processos PHP comuns, residentes em memória, sem corrotinas. E é exatamente essa simplicidade que ganha benchmark.
Os números — e por que você não deve confiar cegamente neles
O benchmark mais recente que encontrei (PHP 8.5, Laravel 11, 100 conexões concorrentes, workload de CPU + uma query rápida no MySQL):
| Runtime | Req/s | P99 | vs PHP-FPM |
|---|---|---|---|
| PHP-FPM (baseline) | 374,6 | 2753ms | — |
| RoadRunner | 792,4 | 1618ms | +111,6% |
| FrankenPHP | 375,9 | 3127ms | +0,3% |
| Swoole | 371,1 | 3084ms | −0,9% |
Lendo só a tabela, a conclusão é "RoadRunner ganha, Swoole perde até pro FPM". Lendo a metodologia, a história muda:
- O Swoole perdeu porque a query do teste levava menos de 1ms. Sem espera de I/O, corrotina não tem o que sobrepor — só paga o custo do scheduling. Troque o
SELECT 1por uma chamada de 200ms a uma API externa e a tabela inverte dramaticamente. - O FrankenPHP empatou com o FPM em parte porque o ApacheBench só fala HTTP/1.0 — o multiplexing de HTTP/2/3, que é metade do argumento dele, ficou fora do teste. E o footprint de memória dele foi o menor de todos, o que a tabela de RPS não mostra.
- O RoadRunner ganhou porque o teste mede exatamente o que ele otimiza: eliminar o custo de bootstrap em requests curtas. É um resultado real, mas é um perfil de carga.
A lição não é "benchmarks mentem". É que benchmark responde à pergunta que o autor fez, não à sua. Antes de migrar, rode 30 minutos de load test com a sua rota mais quente. É a diferença entre escolher runtime e escolher gráfico.
O preço do worker mode: estado que vaza entre requests
Aqui está a parte que os posts de hype omitem. O modelo shared-nothing do FPM era um colchão de segurança: vinte anos de código PHP — incluindo as libs que você usa — foram escritos assumindo que tudo morre no fim da request. No worker mode, essa premissa quebra:
- Propriedade estática ou singleton que acumula dados vira memory leak. Em FPM ninguém percebia; num worker que atende 100 mil requests, o processo incha até o OOM killer agir.
- Estado de um usuário vazando para outro. O FrankenPHP teve um advisory de segurança exatamente sobre isso:
$_SESSIONnão era resetada entre requests no worker mode. O clássico equivalente em Symfony é o EntityManager do Doctrine carregando entidades da request anterior. - Conexões e caches com ciclo de vida errado — o que era "por request" virou "para sempre" sem ninguém decidir isso.
As mitigações são conhecidas: use Octane ou o runtime do Symfony (que resetam o container entre requests em vez de confiar na sua disciplina), configure reciclagem de workers após N requests (MAX_REQUESTS no FrankenPHP, max_jobs no RoadRunner) como rede de proteção contra leaks, e monitore a memória por worker — não a do host. E audite as libs menos famosas da sua árvore de dependências: framework moderno é worker-safe; aquela lib de 2017 que você usa para gerar boleto, provavelmente não.
Worker mode te dá throughput e cobra em disciplina. O trade é bom — mas é um trade, não mágica.
Escolha pelo seu I/O, não pelo benchmark
Depois de meses com FrankenPHP em produção e dos números acima, minha matriz de decisão ficou assim: API HTTP stateless em container, projeto novo — FrankenPHP, pelo deploy de binário único e o menor footprint. Monolito web + API que também precisa de filas ou gRPC, time com CI maduro — RoadRunner, pela maturidade e pelos plugins. WebSockets em escala ou carga dominada por I/O lento de terceiros — Swoole, que é o único dos três com concorrência real dentro do worker. E se o seu site atende 50 requests por minuto sem reclamar, PHP-FPM continua sendo uma resposta correta — migrar de runtime por hype é trocar um problema que você não tem por vários que você não conhece.
O ponto maior: a crítica de 2015 ao PHP morreu de velhice. O modelo de execução que a justificava não é mais obrigatório — virou uma escolha, com três alternativas sólidas. Se faz tempo que você não olha para esse ecossistema, os números acima são um bom motivo para olhar de novo.
Nota: o conteúdo editorial acabou aqui. O que vem abaixo é uma indicação de ferramenta relacionada ao tema do post.
Ferramenta relacionada
Se você vai fazer load test ou debugar respostas de uma API PHP nesses runtimes, o JSON Formatter ajuda a inspecionar e validar os payloads de resposta — formata, destaca erros de sintaxe e roda 100% no navegador, sem enviar seus dados para servidor nenhum.
- 01 O que é DevOps além das ferramentas DevOps não é um pipeline nem um cargo. É responsabilidade compartilhada entre quem escreve código e quem coloca em produção — e por que a maioria dos times erra nisso.
- 02 VPS, VPC e servidor dedicado: diferenças VPS, VPC e servidor dedicado aparecem juntos em toda comparação de hospedagem — mas significam coisas bem diferentes. Entenda onde o dinheiro vai e como decidir.