Voltar ao blog

Porque deves migrar de PHP para Go

O PHP ocupa um worker inteiro por cada pedido, enquanto um único processo Go consegue manter milhares de ligações ao mesmo tempo. Essa diferença na forma como os dois lidam com a concorrência é a verdadeira razão pela qual as equipas migram, e podes mudar um serviço de cada vez em vez de reescrever tudo.

Porque deves migrar de PHP para Go

Quando o PHP tem dificuldades em escalar, a velocidade bruta da linguagem raramente é a causa. O problema está na forma como lida com pedidos concorrentes. Com PHP-FPM, cada pedido ocupa um processo worker completo durante toda a sua duração. Qualquer pedido que passe o tempo à espera, seja de uma query lenta, de uma chamada a um serviço externo ou de uma ligação que fica aberta, prende um worker que podia estar a servir outra pessoa. Funcionalidades em tempo real como dashboards ao vivo, respostas em streaming e APIs com muito fan-out batem depressa nesse limite. Acrescentar workers ou máquinas empurra o teto para cima, mas ele continua lá.

O modelo de concorrência do Go não tem esse teto, e essa é a principal razão pela qual as equipas passam de PHP para Go. Este artigo aborda porque é que o modelo importa, o que ele faz pelo deploy e pelos custos, o que as equipas relataram depois da mudança e como migrar um serviço de cada vez em vez de apostar o produto numa reescrita.

Resumo rápido

  • Migra pelo modelo de concorrência. O design de um processo por pedido do PHP prende um worker inteiro durante toda a vida de cada pedido. As goroutines do Go permitem que um único processo lide com milhares de ligações concorrentes. A diferença aumenta à medida que acrescentas funcionalidades em tempo real e com muitas ligações.
  • A diferença de memória é grande. Um worker PHP-FPM ocupa aproximadamente 30 a 60 MB depois de o teu framework carregar. Uma goroutine começa com cerca de 8 KB. Cinquenta streams concorrentes podem saturar um pool FPM, enquanto um único processo Go mal dá por eles.
  • As equipas que mudaram relatam grandes ganhos. Engenheiros da Cloudflare e da fintech Curve descrevem um serviço que passou de cerca de 10 pedidos por segundo para milhares depois da mudança para Go, com engenheiros novos a ficarem produtivos ao fim de uma ou duas semanas (Go Time #316).
  • O deploy é mais simples. O Go compila para um único binário estático. Não instalas um runtime, não corres o Composer nem afinas o FPM, o que encaixa bem em microsserviços e Kubernetes.
  • Não tens de reescrever tudo. Usa o padrão strangler. Migra o serviço que mais dói, corre-o ao lado do teu PHP e expande quando ele tiver dado provas.

Índice

A verdadeira razão para migrar é a concorrência

O PHP clássico funciona em modo share-nothing, com um processo por pedido. Chega um pedido, o PHP-FPM entrega-o a um worker, e o worker corre o teu código a partir de um estado limpo até à resposta e depois reinicia-se. O design é simples e bem isolado. O que ele te custa é memória.

Um único worker PHP-FPM ocupa tipicamente 30 a 60 MB de memória residente depois de o framework estar carregado. Servir 50 pedidos em simultâneo significa 50 workers, cerca de 1,5 a 3 GB de RAM gastos sobretudo a esperar.

Agora imagina que esses 50 pedidos são streams de Server-Sent Events ou ligações WebSocket que ficam abertas durante minutos. Cinquenta ligações de longa duração podem saturar um pool FPM inteiro, e o 51.º utilizador fica à espera na fila. Resolver isto com mais hardware fica caro depressa, porque cada ligação concorrente custa um processo inteiro.

O Go funciona ao contrário. Uma goroutine, a unidade de concorrência do Go, começa com uma stack de cerca de 8 KB, e o runtime multiplexa milhares delas sobre um pequeno conjunto de threads do sistema operativo. Quando uma goroutine bloqueia em I/O, o scheduler suspende-a e corre outra na mesma thread. Um processo Go consegue manter dezenas de milhares de ligações abertas com uma fração da memória de que uma frota FPM equivalente precisaria. Um handler de ligação é apenas uma função normal:

example.gogo
func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) {
	// Each connection is one cheap goroutine. Blocking here parks
	// this goroutine and frees the thread for others, no pool to exhaust.
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "streaming unsupported", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/event-stream")

	for event := range s.events(r.Context()) {
		fmt.Fprintf(w, "data: %s\n\n", event)
		flusher.Flush()
	}
}

O servidor net/http corre cada pedido na sua própria goroutine automaticamente, por isso este código mantém uma ligação de streaming ativa sem reservar um worker pesado. Se o teu produto está a avançar para funcionalidades em tempo real, API gateways com muito fan-out ou serviços que mantêm muitas ligações concorrentes, esta diferença pesa muito. O modelo do PHP trabalha contra essas cargas, e o do Go lida com elas sem esforço especial. Se as goroutines e os channels são novidade para ti, o curso Go Concurrency Fundamentals mostra o modelo na prática.

O que as equipas ganham depois da migração

O melhor relato em primeira mão de uma migração real de PHP para Go é o episódio 316 do podcast Go Time. Engenheiros da Cloudflare e da fintech Curve falaram sobre como passaram sistemas PHP de produção para Go (Go Time #316, Changelog). São profissionais a descrever os seus próprios sistemas, o que torna o relato mais útil do que um estudo de caso de um fornecedor ou um artigo de blog a adivinhar números de fora.

Segundo contaram, um serviço passou de cerca de 10 pedidos por segundo com a configuração antiga para milhares depois da mudança. Resumiram o runtime com «até o Go mal escrito tem um desempenho incrível», o que significa que tens muito throughput antes de precisares sequer de otimizar. Os engenheiros novos ficaram produtivos ao fim de uma ou duas semanas em vez de meses, o que se deve à superfície pequena do Go.

A linhagem Go da Curve remonta ao Monzo, outra fintech que apostou fortemente em Go, e isso explica em parte como o padrão se espalhou nesse setor.

Há também outros dados sobre Go em produção. A HelloFresh construiu em Go o seu API gateway, Janus, e publicou-o como open source (Janus no GitHub). Um gateway é uma camada de routing com muito fan-out, exatamente o tipo de carga em que as goroutines mais ajudam. Também vais encontrar muitos engenheiros a relatar que um único job batch reescrito de PHP para Go corre muito mais depressa com uma fração dos recursos.

Go vs PHP: as vantagens técnicas

Eis como os dois se comparam nos aspetos que importam para um serviço de backend.

DimensãoPHPGo
ConcorrênciaUm processo por pedido, com Fibers que acrescentam concorrência mas não paralelismoGoroutines num scheduler M:N, paralelismo real entre núcleos
TipagemDinâmica, com type hints graduaisEstática, verificada em tempo de compilação
DeployCódigo + dependências do Composer + FPM + servidor webUm único binário estático, sem runtime para instalar
ExecuçãoInterpretado, com ajuda de JITCompilado para código máquina nativo

A concorrência é o que mais importa. O PHP 8.1 acrescentou Fibers, que são uma melhoria real, mas vale a pena ser preciso sobre o que fazem. As Fibers dão-te concorrência cooperativa para estruturar I/O assíncrono. Permitem que um worker intercale tarefas em espera. Não permitem que um processo PHP sature 16 núcleos com trabalho de CPU como faz o scheduler do Go.

O Go distribui muitas goroutines por muitas threads do sistema operativo, corre-as em paralelo e suspende-as a baixo custo quando bloqueiam. A mesma primitiva dá-te concorrência barata e paralelismo real. Um worker pool é um bom exemplo. Recorres a um constantemente em Go, e o PHP clássico não tem um equivalente limpo num único processo:

example.gogo
func process(jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
	defer wg.Done()
	for job := range jobs {
		results <- job.Run() // each worker pulls the next job when free
	}
}

func main() {
	jobs := make(chan Job, 100)
	results := make(chan Result, 100)
	var wg sync.WaitGroup

	// Fan out to 8 workers sharing one queue, all in one process.
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go process(jobs, results, &wg)
	}

	go func() {
		for _, j := range loadJobs() { // pull the batch from your work source
			jobs <- j
		}
		close(jobs)
	}()

	go func() { wg.Wait(); close(results) }()
	for r := range results {
		record(r)
	}
}

O deploy recebe menos atenção do que merece. Um build de Go produz um único binário estático, e pô-lo em produção pode ser tão simples como copiar esse ficheiro para um servidor ou para um contentor scratch. Não há runtime PHP para instalar, nem passo de Composer, nem afinação do FPM, nem configuração de servidor web à frente. Se corres microsserviços em Kubernetes, tens esse artefacto pequeno e autónomo em cada deploy.

A tipagem estática também ajuda. O compilador apanha em tempo de compilação uma classe de erros que o PHP só revela em tempo de execução, e isso importa mais à medida que um serviço cresce e mais pessoas trabalham nele.

Depois há a objeção «o PHP moderno já é rápido». O PHP fechou de facto grande parte da diferença de velocidade bruta, com o JIT do PHP 8 e runtimes de workers como FrankenPHP e RoadRunner, que mantêm a tua aplicação carregada em memória entre pedidos. Mas o FrankenPHP e o RoadRunner são ambos escritos em Go. Quando o ecossistema PHP precisou de servidores de aplicações de alta concorrência, construiu-os sobre o runtime do Go. Se o teu problema é a concorrência, podes pôr um runtime baseado em Go à frente do PHP, ou podes escrever diretamente em Go o serviço com muitas ligações e saltar a camada intermédia.

A verdade sobre os benchmarks (e o verdadeiro retorno)

Vais ver números chamativos a afirmar que o Go é 10 a 30 vezes mais rápido do que o PHP. Vêm de suites sintéticas como os benchmarks da TechEmpower, que testam os runtimes em condições idealizadas, limitadas pela CPU e pelo framework (TechEmpower Framework Benchmarks). Os números são reais, mas não preveem a tua latência em produção, por isso não migres à espera desse multiplicador.

Em produção, a maior parte do tempo de um pedido vai para a base de dados e para a rede, e aí o Go e o PHP esperam exatamente da mesma forma. O multiplicador encolhe depressa. As equipas que medem com honestidade depois de uma migração relatam muitas vezes ganhos de poucos por cento, ou pouco mais de 10 %, na latência média de ponta a ponta.

A média esconde onde o Go ganha, que é na estabilidade da latência de cauda e no custo por pedido. As latências p99 e p99.9 do Go mantêm-se planas sob carga porque nenhum pedido tem de esperar por um worker FPM livre. Também serves o mesmo tráfego com muito menos máquinas, já que um processo absorve concorrência que um pool não consegue. São esses os ganhos que notas em produção e na fatura, e duram muito depois de alguém se importar com um benchmark sintético.

Contratar mais depressa, entregar mais depressa

A contratação e o onboarding também favorecem o Go. A sua superfície é pequena, com 25 palavras-chave e um único formatador obrigatório, por isso um engenheiro competente vindo de PHP ou de qualquer outra linguagem fica produtivo ao fim de uma ou duas semanas. Isso bate certo com o tempo de onboarding que os engenheiros da Cloudflare e da Curve descreveram.

Assim, não precisas de contratar pessoas que já saibam Go. Aprendem-no depressa, e a formatação obrigatória e a tipagem estática mantêm o código delas consistente desde o primeiro dia.

O Go também costuma estar associado a um prémio salarial nos inquéritos de mercado, o que sugere que a procura supera a oferta. Os números exatos citados de fontes como a Glassdoor variam por região e função, por isso trata-os como um vento a favor na contratação, não como uma linha num business case. O ecossistema do Go é mais forte em redes, infraestrutura, CLIs e serviços de alto throughput, que é precisamente o trabalho que irias migrar. As goroutines foram criadas para esse trabalho, e as bibliotecas e as ferramentas à volta delas são maduras.

Como migrar sem pôr a empresa em jogo

Não reescrevas tudo de uma vez. As reescritas completas são uma das formas mais fiáveis de paralisar um produto, porque o código que funciona guarda anos de correções de bugs e casos-limite que irias deitar fora (Joel on Software). As equipas da Cloudflare e da Curve também não fizeram uma reescrita big-bang. Usaram o padrão strangler, e vale a pena copiá-lo.

Começa pelo serviço que está realmente a causar problemas: o stream SSE que satura o teu pool, a API com fan-out que prende os teus workers ou o gateway que não aguenta ligações suficientes. Reconstrói só esse serviço em Go, põe-no atrás da mesma camada de routing e corre-o ao lado do teu PHP.

Encaminha uma parte do tráfego para ele e observa a memória e a latência de cauda antes de encaminhares mais. O teu PHP continua a servir tudo o resto durante todo o processo, por isso não há um dia D e nada depende de uma única passagem. Quando esse primeiro serviço dá provas, o que normalmente acontece, tens números reais e um modelo para o seguinte.

Isso faz da migração uma decisão por serviço. Não tens de te tornar «uma empresa de Go». Mantém o PHP onde faz sentido, como o CMS, as páginas de conteúdo ou o painel de administração em Laravel com que a tua equipa entrega rapidamente, e passa para Go os serviços com muitas ligações e alto throughput.

Perguntas frequentes

O Go é mais rápido do que o PHP?

Em benchmarks sintéticos limitados pela CPU, sim, muitas vezes por uma margem grande (TechEmpower). Em produção, a diferença na latência média costuma ser menor, porque a maior parte do tempo de um pedido vai para a base de dados e para a rede, e aí as duas linguagens esperam exatamente da mesma forma. O Go mantém a vantagem na concorrência e na latência de cauda sob carga elevada. Mantém milhares de ligações num único processo e mantém a latência p99 plana onde um pool FPM ficaria sem workers, por isso precisas de menos máquinas para o mesmo tráfego.

Devo reescrever toda a minha aplicação PHP em Go?

Não. Migra de forma incremental. As reescritas completas são uma das formas mais fiáveis de paralisar um produto (Joel on Software). Usa o padrão strangler: passa para Go o serviço problemático de alta concorrência, corre-o ao lado do teu PHP, passa o tráfego para lá de forma gradual e expande quando ele der provas. Ficas com os benefícios sem um dia D arriscado.

Que serviços devo migrar primeiro?

Começa pelos que têm muitas ligações e muito fan-out: endpoints WebSocket e SSE, funcionalidades em tempo real, API gateways, respostas em streaming e qualquer serviço em que muitos pedidos ficam abertos à espera de I/O. O modelo de um processo por pedido do PHP fica sem margem primeiro nestes, e as goroutines ajudam logo, por isso dão-te a prova mais rápida de que a migração está a compensar.

O PHP consegue lidar com WebSockets e funcionalidades em tempo real?

Sim, com ajuda. O PHP-FPM clássico prende um worker por ligação, por isso muitas ligações de longa duração esgotam o pool. Runtimes como Swoole e FrankenPHP mantêm os workers vivos e acrescentam concorrência orientada a eventos para que isso funcione. O Go lida com a mesma carga de forma nativa, com uma goroutine barata por ligação. Os serviços com muitas ligações são a razão mais comum para as equipas migrarem.

O Go é mais difícil de aprender do que o PHP?

A sintaxe não é. O Go tem apenas 25 palavras-chave e um único estilo de formatação obrigatório, e a maioria dos engenheiros fica produtiva ao fim de uma ou duas semanas. O que é novo para um programador PHP é o modelo de concorrência (goroutines e channels) e o tratamento de erros como valores devolvidos em vez de exceções. Isso exige alguma prática, e são também as competências que mais vais usar nos serviços que migrares.

Pronto para escrever Go de produção?

A melhor forma de avaliar o Go é escrever algum código. No LevelUpGo, cada lição é um exercício prático que resolves no navegador. O teu código corre numa toolchain Go real e é verificado com os testes do exercício, por isso recebes feedback imediato sobre Go que funciona, em vez de apenas leres sobre ele.

Um percurso para quem vem de PHP:

  • Go Fundamentals cobre tipos, structs e tratamento de erros, as partes que parecem diferentes para quem vem de PHP. Começar é grátis e não precisas de cartão de crédito.
  • Composite Types cobre slices, maps e structs, os blocos básicos do dia a dia para os dados que fazes passar por um serviço.
  • Go Concurrency Fundamentals ensina goroutines, channels e select, as funcionalidades que permitem a um processo Go fazer o que um pool PHP-FPM inteiro não consegue.
  • O roteiro traça o caminho desde o teu primeiro programa até um projeto final concorrente.

Para mais comparações, lê Mudar de Python para Go, Go vs Rust em 2026 e Go vs Node.js e a questão da cadeia de fornecimento.

Fontes

Fontes citadas neste artigo, com os relatos em primeira mão de profissionais listados primeiro:

Escreve Go como um engenheiro sénior

Lições interativas no navegador. As primeiras são grátis.

Experimenta uma lição grátisOu cria uma conta gratuita