¿Más núcleos, mejor? Go, Java, Rust y Python de 1 a 8 vCPU
Cuánto escala realmente una API al pasar de 1 a 8 núcleos, y cuánto cuesta.
Resumen
- De 1 a 8 vCPU, la capacidad creció entre 6.6 y 7.2 veces, cerca del ideal de 8, aun con la aplicación, Nginx y PostgreSQL en el mismo servidor.
- Con 8 vCPU: Go 22,000 usuarios, Java 21,000, Rust 19,000 y Python 10,000. Go, Java y Rust quedan en el mismo rango.
- Go lo logra con ~175 MB de RAM. Java necesitó ~3.6 GB.
- En los tres más rápidos, PostgreSQL consume más de la mitad de la CPU y la aplicación menos de un tercio.
- Los saltos no fueron uniformes: los núcleos de la máquina de 4 vCPU eran un 20 % más lentos.
- Más núcleos dan más capacidad total, pero cada usuario cuesta entre 10 % y 22 % más.
- Con código optimizado, en un reto abierto con 8 vCPU dedicadas, una implementación en Rust llegó a 100,000 usuarios.
El experimento
- Servidores: DigitalOcean Premium AMD con 1 vCPU y 2 GB ($14/mes), 2 vCPU y 4 GB ($28), 4 vCPU y 8 GB ($56), y 8 vCPU y 16 GB ($112). Una vCPU es un hilo lógico, no un núcleo físico reservado.
- Todo en la misma máquina: Nginx, la aplicación y PostgreSQL comparten la CPU. La configuración de PostgreSQL escala con el tamaño del servidor.
- Aplicación: un clon de Medium con 19 endpoints: registro, inicio de sesión, artículos, seguir usuarios, feed, favoritos y comentarios.
- Datos: 100,000 usuarios, 500,000 artículos, 5 millones de favoritos, 2.5 millones de comentarios y 2 millones de relaciones de seguimiento (~2.3 GB).
- Condiciones iguales: mismos endpoints, respuestas, SQL y datos. Sin ORM ni caché, con índices normales y solo los 100 comentarios más recientes por artículo. Todas las versiones pasaron las mismas pruebas de conformidad.
- Stacks: Python con FastAPI + Uvicorn, Java con Spring Boot y virtual threads, Go con net/http + pgx, y Rust con Axum + Tokio + SQLx.
- Usuarios simulados: unos 7 segundos entre páginas, sesiones de unos 15 minutos y un 5 % de escrituras. Con 1,000 usuarios se generan unas 250 a 270 req/s.
- Criterio para aprobar: p95 < 500 ms, p99 < 1 s y menos de 1 % de errores, sostenido durante 5 minutos.
Resultados
- Go: 3,200 → 4,900 → 9,800 → 22,000 usuarios (~5,600 req/s con 8 vCPU). Escaló 6.9 veces.
- Java: 2,900 → 5,000 → 9,300 → 21,000 usuarios (~5,400 req/s). Escaló 7.2 veces.
- Rust: 2,900 → 4,900 → 10,000 → 19,000 usuarios (casi 5,000 req/s). Escaló 6.6 veces y tuvo el mejor resultado con 4 vCPU.
- Python: 1,400 → 2,500 → 4,100 → 10,000 usuarios (~2,600 req/s). Escaló 7.1 veces.
Cada configuración se midió con una sola búsqueda de capacidad, así que diferencias de 5 % a 10 % no definen un ranking.
Cómo usa cada lenguaje los núcleos
- Python: el GIL impide ejecutar código Python en paralelo dentro de un intérprete. La solución son varios procesos worker, cada uno con su intérprete, su event loop y su pool de conexiones. Un worker por núcleo rindió más que 2N+1: 10,000 frente a 9,300 usuarios con 8 vCPU, usando 900 MB de RAM en lugar de 1.4 GB.
- Java: con virtual threads, cada petición tiene un hilo ligero que libera el hilo del sistema operativo mientras espera a la base de datos.
- Go: las goroutines se reparten entre hilos del sistema operativo, así que un solo proceso aprovecha todos los núcleos.
- Rust: el runtime multihilo de Tokio crea un hilo de trabajo por núcleo. El hash de contraseñas (bcrypt), que es costoso, va a un pool aparte para no bloquear las tareas asíncronas.
Get Fernando Dilland’s stories in your inbox
Join Medium for free to get updates from this writer.
Por qué los saltos no son uniformes
De 1 a 2 vCPU, Go solo ganó un 53 %. De 4 a 8 vCPU, más que duplicó. La causa es el hardware:
Las cuatro máquinas se venden con la misma CPU y precio lineal, pero un benchmark de CPU mostró núcleos un 7 % más lentos en la de 2 vCPU y un 20 % más lentos en la de 4. El tiempo de CPU robado por otros inquilinos fue casi nulo, así que la diferencia viene del hardware asignado.
La base de datos se queda con la mayor parte de la CPU
Con Go, Java y Rust, PostgreSQL usó algo más de la mitad de la CPU, la aplicación entre 27 % y 30 % y Nginx entre 11 % y 12 %. Cambiar de lenguaje no cambia el trabajo de las consultas. Python es la excepción: su aplicación usó ~55 % de la CPU y PostgreSQL solo 31 %.
Escalar verticalmente sale más caro por usuario
Con Go, el servidor de $14 atendía ~229 usuarios por dólar y el de $112, ~196. El servidor grande da más capacidad total y simplicidad operativa, pero no mejor precio por usuario.
Con código optimizado, el techo es otro
Las mismas reglas se abrieron como reto a la comunidad, en un servidor de 8 vCPU dedicadas y 16 GB, sin Nginx y con código optimizado.
- Rust con Tokio: 100,000 usuarios, ~26,000 req/s y p95 de 119 ms.
- C con io_uring, COBOL y C# con Kestrel: 84,000 usuarios cada uno.
- Python con uvloop: 46,000 usuarios, más del doble del mejor resultado con código estándar.
- Incluso así, PostgreSQL consumió entre 48 % y 80 % de la CPU.
El hardware no es idéntico, así que no es una comparación directa. Aun así, la diferencia muestra cuánto pesa la implementación frente al lenguaje.
Conclusiones
- Escalar verticalmente funciona: con 8 veces más vCPU se obtuvo entre 6.6 y 7.2 veces más capacidad en una carga limitada por CPU.
- El stack debe saber usar los núcleos: procesos en Python, virtual threads en Java, goroutines en Go y un runtime asíncrono multihilo en Rust.
- En Python, un worker por núcleo. Más workers no crean más CPU, solo consumen memoria.
- Mide la máquina, no solo la etiqueta: núcleos de la misma familia pueden rendir hasta 20 % menos.
- Calcula el costo por usuario: el servidor pequeño rinde más por dólar, y el grande se justifica por capacidad total.
- La base de datos manda: con más de la mitad de la CPU en PostgreSQL, el siguiente paso es separarla u optimizar las consultas.
- Limita las respuestas: un artículo con más de 2,000 comentarios devueltos completos encarece cada lectura conforme crece.
Limitaciones
- Una sola búsqueda de capacidad por configuración.
- vCPU compartidas, con velocidad por núcleo distinta entre tamaños de máquina.
- Frameworks estándar, sin optimizar.
- Los resultados optimizados de la comunidad se midieron en hardware distinto.
Créditos: los datos de este artículo provienen del experimento publicado en https://www.youtube.com/watch?v=pG3xXuNdL34 y de su reto abierto en github.com/arjaythedev/multicore-server-challenge. Gracias por compartir la metodología y los resultados.









