Volver al blog

Por qué deberías migrar de PHP a Go

PHP ocupa un worker entero por cada petición, mientras que un solo proceso de Go puede mantener miles de conexiones a la vez. Esa diferencia en cómo gestionan la concurrencia es la verdadera razón por la que los equipos migran, y puedes mover un servicio cada vez en lugar de reescribirlo todo.

Por qué deberías migrar de PHP a Go

Cuando PHP sufre al escalar, la velocidad bruta del lenguaje rara vez es la causa. El problema es cómo gestiona las peticiones concurrentes. Con PHP-FPM, cada petición ocupa un proceso worker completo durante toda su duración. Cualquier petición que pasa el tiempo esperando, ya sea por una consulta lenta, una llamada a un servicio externo o una conexión que sigue abierta, bloquea un worker que podría estar atendiendo a otra persona. Las funciones en tiempo real como los dashboards en vivo, las respuestas en streaming y las APIs con mucho fan-out chocan con ese límite enseguida. Añadir workers o máquinas aleja el techo, pero sigue ahí.

El modelo de concurrencia de Go no tiene ese techo, y esa es la razón principal por la que los equipos se pasan de PHP a Go. Este artículo cubre por qué importa el modelo, qué aporta al despliegue y al coste, lo que contaron los equipos después de migrar y cómo migrar un servicio cada vez en lugar de jugarte el producto en una reescritura.

Resumen rápido

  • Migra por el modelo de concurrencia. El diseño de PHP de un proceso por petición ocupa un worker entero durante toda la vida de cada petición. Las goroutines de Go permiten que un solo proceso gestione miles de conexiones concurrentes. La diferencia crece a medida que añades funciones en tiempo real y con muchas conexiones.
  • La diferencia de memoria es grande. Un worker de PHP-FPM ocupa aproximadamente de 30 a 60 MB una vez que carga tu framework. Una goroutine empieza con unos 8 KB. Cincuenta streams concurrentes pueden saturar un pool de FPM, mientras que un solo proceso de Go apenas los nota.
  • Los equipos que migraron cuentan grandes mejoras. Ingenieros de Cloudflare y de la fintech Curve describen cómo un servicio pasó de unas 10 peticiones por segundo a miles tras pasarse a Go, y cómo los ingenieros nuevos eran productivos en una o dos semanas (Go Time #316).
  • El despliegue es más sencillo. Go compila a un único binario estático. No instalas un runtime, no ejecutas Composer ni ajustas FPM, lo que encaja bien con microservicios y Kubernetes.
  • No tienes que reescribirlo todo. Usa el patrón strangler. Migra el servicio que más duele, ejecútalo junto a tu PHP y amplía cuando haya demostrado que funciona.

Índice

La verdadera razón para migrar es la concurrencia

El PHP clásico es share-nothing, con un proceso por petición. Llega una petición, PHP-FPM se la entrega a un worker, y el worker ejecuta tu código desde cero hasta la respuesta y luego se reinicia. El diseño es sencillo y está bien aislado. Lo que te cuesta es memoria.

Un solo worker de PHP-FPM suele ocupar de 30 a 60 MB de memoria residente una vez cargado el framework. Atender 50 peticiones a la vez significa 50 workers, aproximadamente de 1,5 a 3 GB de RAM que se gastan sobre todo en esperar.

Ahora imagina que esas 50 peticiones son streams de Server-Sent Events o conexiones WebSocket que se quedan abiertas durante minutos. Cincuenta conexiones de larga duración pueden saturar un pool de FPM entero, y el usuario número 51 se queda esperando en la cola. Resolverlo con más hardware sale caro enseguida, porque cada conexión concurrente cuesta un proceso entero.

Go funciona al revés. Una goroutine, la unidad de concurrencia de Go, empieza con una pila de unos 8 KB, y el runtime multiplexa miles de ellas sobre un pequeño pool de hilos del sistema operativo. Cuando una goroutine se bloquea en E/S, el planificador la aparca y ejecuta otra en el mismo hilo. Un proceso de Go puede mantener decenas de miles de conexiones abiertas usando una fracción de la memoria que necesitaría una flota de FPM equivalente. Un handler de conexión es simplemente una función 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()
	}
}

El servidor de net/http ejecuta cada petición en su propia goroutine de forma automática, así que este código mantiene una conexión de streaming activa sin reservar un worker pesado. Si tu producto avanza hacia funciones en tiempo real, API gateways con mucho fan-out o servicios que mantienen muchas conexiones concurrentes, esta diferencia pesa mucho. El modelo de PHP va en contra de esas cargas de trabajo, y el de Go las gestiona sin esfuerzo especial. Si las goroutines y los channels son nuevos para ti, el curso Go Concurrency Fundamentals recorre el modelo de forma práctica.

Qué ganan los equipos tras la migración

El mejor relato de primera mano sobre una migración real de PHP a Go es el episodio 316 del podcast Go Time. Ingenieros de Cloudflare y de la fintech Curve hablaron de cómo pasaron sistemas PHP de producción a Go (Go Time #316, Changelog). Son profesionales describiendo sus propios sistemas, lo que lo hace más útil que un caso de estudio de un proveedor o un artículo que intenta adivinar cifras desde fuera.

Según contaron, un servicio pasó de atender unas 10 peticiones por segundo con la configuración anterior a miles después de la migración. Resumieron el runtime con «incluso el Go mal escrito tiene un rendimiento increíble», es decir, obtienes mucho throughput antes de necesitar optimizar nada. Los ingenieros nuevos eran productivos en una o dos semanas en lugar de meses, algo que se debe a lo pequeña que es la superficie de Go.

El linaje Go de Curve se remonta a Monzo, otra fintech que construyó mucho sobre Go, y eso explica en parte cómo se extendió el patrón en ese sector.

También hay otros datos de Go en producción. HelloFresh construyó en Go su API gateway, Janus, y lo publicó como código abierto (Janus en GitHub). Un gateway es una capa de enrutamiento con mucho fan-out, justo el tipo de carga en la que más ayudan las goroutines. También encontrarás muchos ingenieros que cuentan que un único proceso batch reescrito de PHP a Go se ejecuta mucho más rápido con una fracción de los recursos.

Go vs PHP: las ventajas técnicas

Así se comparan los dos en lo que importa para un servicio de backend.

DimensiónPHPGo
ConcurrenciaUn proceso por petición, con Fibers que añaden concurrencia pero no paralelismoGoroutines en un planificador M:N, paralelismo real entre núcleos
TipadoDinámico, con type hints gradualesEstático, comprobado en tiempo de compilación
DespliegueCódigo + dependencias de Composer + FPM + servidor webUn único binario estático, sin runtime que instalar
EjecuciónInterpretado, con apoyo de JITCompilado a código máquina nativo

La concurrencia es lo que más importa. PHP 8.1 añadió Fibers, que son una mejora real, pero conviene ser preciso sobre lo que hacen. Las Fibers te dan concurrencia cooperativa para estructurar E/S asíncrona. Permiten que un worker intercale tareas en espera. No permiten que un proceso de PHP sature 16 núcleos con trabajo de CPU como lo hace el planificador de Go.

Go reparte muchas goroutines entre muchos hilos del sistema operativo, las ejecuta en paralelo y las aparca con muy poco coste cuando se bloquean. La misma primitiva te da concurrencia barata y paralelismo real. Un worker pool es un buen ejemplo. En Go recurres a uno constantemente, y el PHP clásico no tiene un equivalente limpio dentro de un solo proceso:

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)
	}
}

El despliegue recibe menos atención de la que merece. Una compilación de Go produce un único binario estático, y publicarlo puede ser tan simple como copiar ese archivo a un servidor o a un contenedor scratch. No hay runtime de PHP que instalar, ni paso de Composer, ni ajuste de FPM, ni configuración de un servidor web delante. Si ejecutas microservicios en Kubernetes, tienes ese artefacto pequeño y autocontenido en cada despliegue.

El tipado estático también ayuda. El compilador detecta al compilar un tipo de errores que PHP solo muestra en tiempo de ejecución, y eso importa más a medida que un servicio crece y más personas trabajan en él.

Luego está la objeción de «el PHP moderno ya es rápido». PHP sí recortó gran parte de la diferencia de velocidad bruta, con el JIT de PHP 8 y runtimes de workers como FrankenPHP y RoadRunner, que mantienen tu aplicación arrancada en memoria entre peticiones. Pero FrankenPHP y RoadRunner están escritos en Go. Cuando el ecosistema PHP necesitó servidores de aplicaciones de alta concurrencia, los construyó sobre el runtime de Go. Si tu problema es la concurrencia, puedes poner un runtime basado en Go delante de PHP, o puedes escribir directamente en Go el servicio con muchas conexiones y saltarte la capa intermedia.

La verdad sobre los benchmarks (y la ganancia real)

Verás cifras llamativas que afirman que Go es entre 10 y 30 veces más rápido que PHP. Salen de suites sintéticas como los benchmarks de TechEmpower, que prueban los runtimes en condiciones idealizadas, limitadas por la CPU y el framework (TechEmpower Framework Benchmarks). Las cifras son reales, pero no predicen tu latencia en producción, así que no migres esperando ese multiplicador.

En producción, la mayor parte del tiempo de una petición se va en la base de datos y la red, y Go y PHP esperan eso exactamente igual. El multiplicador se reduce rápido. Los equipos que miden con honestidad después de una migración suelen hablar de mejoras en la latencia media de extremo a extremo que van de un solo dígito porcentual a poco más de una decena.

La media oculta dónde gana Go, que es la estabilidad de la latencia de cola y el coste por petición. Las latencias p99 y p99.9 de Go se mantienen planas bajo carga porque ninguna petición tiene que esperar a que quede libre un worker de FPM. Además atiendes el mismo tráfico con muchas menos máquinas, ya que un proceso absorbe una concurrencia que un pool no puede absorber. Esas son las ganancias que notas en producción y en la factura, y duran mucho después de que a nadie le importe un benchmark sintético.

Contratar más rápido, entregar más rápido

La contratación y la incorporación también favorecen a Go. Su superficie es pequeña, con 25 palabras clave y un único formateador obligatorio, así que un ingeniero competente que venga de PHP o de cualquier otro lenguaje es productivo en una o dos semanas. Eso coincide con el tiempo de incorporación que describieron los ingenieros de Cloudflare y Curve.

Así que no necesitas contratar a gente que ya sepa Go. Lo aprenden rápido, y el formato obligatorio y el tipado estático mantienen su código coherente desde el primer día.

Go también suele ir asociado a un salario más alto en las encuestas de mercado, lo que sugiere que la demanda supera a la oferta. Las cifras exactas que se citan de fuentes como Glassdoor varían según la región y el puesto, así que tómalas como un viento a favor para contratar, no como una línea de un caso de negocio. El ecosistema de Go es más fuerte en redes, infraestructura, CLIs y servicios de alto throughput, que es justo el trabajo que migrarías. Las goroutines se crearon para ese trabajo, y las bibliotecas y herramientas que lo rodean son maduras.

Cómo migrar sin jugarte la empresa

No lo reescribas todo de golpe. Las reescrituras completas son una de las maneras más fiables de frenar un producto, porque el código que funciona recoge años de correcciones de bugs y casos límite que tirarías a la basura (Joel on Software). Los equipos de Cloudflare y Curve tampoco hicieron una reescritura de golpe. Usaron el patrón strangler, y merece la pena copiarlo.

Empieza por el servicio que de verdad está dando problemas: el stream SSE que satura tu pool, la API con fan-out que bloquea tus workers o el gateway que no aguanta suficientes conexiones. Reconstruye solo ese servicio en Go, ponlo detrás de la misma capa de enrutamiento y ejecútalo junto a tu PHP.

Envía una parte del tráfico hacia él y vigila la memoria y la latencia de cola antes de enviar más. Tu PHP sigue atendiendo todo lo demás durante todo el proceso, así que no hay un día D y nada depende de un único cambio. Cuando ese primer servicio demuestra que funciona, algo que normalmente ocurre, tienes cifras reales y una plantilla para el siguiente.

Eso convierte la migración en una decisión por servicio. No tienes que convertirte en «una empresa de Go». Mantén PHP donde encaja, como el CMS, las páginas de contenido o el panel de administración en Laravel con el que tu equipo entrega rápido, y pasa a Go los servicios con muchas conexiones y alto throughput.

Preguntas frecuentes

¿Go es más rápido que PHP?

En benchmarks sintéticos limitados por la CPU, sí, a menudo por un factor grande (TechEmpower). En producción, la diferencia de latencia media suele ser menor, porque la mayor parte del tiempo de una petición se va en la base de datos y la red, y ambos lenguajes esperan eso exactamente igual. Go mantiene su ventaja en concurrencia y latencia de cola bajo mucha carga. Mantiene miles de conexiones en un solo proceso y conserva plana la latencia p99 donde un pool de FPM se quedaría sin workers, así que necesitas menos máquinas para el mismo tráfico.

¿Debería reescribir toda mi aplicación PHP en Go?

No. Migra de forma incremental. Las reescrituras completas son una de las maneras más fiables de frenar un producto (Joel on Software). Usa el patrón strangler: pasa a Go el servicio problemático de alta concurrencia, ejecútalo junto a tu PHP, desvía tráfico hacia él poco a poco y amplía cuando demuestre que funciona. Obtienes los beneficios sin un día D arriesgado.

¿Qué servicios debería migrar primero?

Empieza por los que tienen muchas conexiones y mucho fan-out: endpoints WebSocket y SSE, funciones en tiempo real, API gateways, respuestas en streaming y cualquier servicio en el que muchas peticiones se quedan abiertas esperando E/S. El modelo de un proceso por petición de PHP se queda sin margen primero en estos, y las goroutines ayudan desde el primer momento, así que te dan la prueba más rápida de que la migración está dando resultado.

¿Puede PHP manejar WebSockets y funciones en tiempo real?

Sí, con ayuda. El PHP-FPM clásico ocupa un worker por conexión, así que muchas conexiones de larga duración agotan el pool. Runtimes como Swoole y FrankenPHP mantienen los workers vivos y añaden concurrencia orientada a eventos para que funcione. Go gestiona la misma carga de forma nativa, con una goroutine barata por conexión. Los servicios con muchas conexiones son el motivo más habitual por el que los equipos migran.

¿Go es más difícil de aprender que PHP?

La sintaxis no. Go tiene solo 25 palabras clave y un único estilo de formato obligatorio, y la mayoría de los ingenieros son productivos en una o dos semanas. Lo nuevo para un desarrollador PHP es el modelo de concurrencia (goroutines y channels) y tratar los errores como valores devueltos en lugar de excepciones. Eso requiere algo de práctica, y también son las habilidades que más usarás en los servicios que migres.

¿Listo para escribir Go de producción?

La mejor forma de evaluar Go es escribir algo de código. En LevelUpGo, cada lección es un ejercicio práctico que resuelves en el navegador. Tu código se ejecuta en una toolchain real de Go y se comprueba con los tests del ejercicio, así que recibes feedback inmediato sobre Go que funciona en lugar de solo leer sobre él.

Un recorrido recomendado si vienes de PHP:

  • Go Fundamentals cubre tipos, structs y manejo de errores, las partes que resultan distintas cuando vienes de PHP. Empezar es gratis, sin tarjeta de crédito.
  • Composite Types cubre slices, maps y structs, las piezas básicas del día a día para los datos que mueves por un servicio.
  • Go Concurrency Fundamentals enseña goroutines, channels y select, las herramientas que permiten que un proceso de Go haga lo que un pool entero de PHP-FPM no puede.
  • La hoja de ruta traza el camino desde tu primer programa hasta un proyecto final concurrente.

Para más comparativas, lee Pasar de Python a Go, Go vs Rust en 2026 y Go vs Node.js y la seguridad de la cadena de suministro.

Fuentes

Fuentes citadas en este artículo, con los relatos de primera mano de profesionales en primer lugar:

Escribe Go como un ingeniero sénior

Lecciones interactivas en tu navegador. Las primeras son gratis.

Prueba una lección gratisO crea una cuenta gratuita