Consigna
Un servicio en Go se ejecuta en un Pod de Kubernetes sobre un nodo con 64 CPUs lógicas, mientras que el Pod tiene un límite de CPU de 2.5. Tras actualizar a Go 1.25, explica el valor predeterminado de GOMAXPROCS, su relación con los CPU requests, si se siguen los cambios en cgroups y cómo validarías la latencia de cola (tail latency), el throughput y el comportamiento del GC. Indica qué deshabilitan las configuraciones manuales de GOMAXPROCS u GODEBUG.
Qué está evaluando el entrevistador
Esto evalúa la concurrencia en tiempo de ejecución (runtime), la semántica de recursos en contenedores y el diagnóstico de rendimiento. Distingue entre CPUs lógicas, límites de CPU, CPU requests, afinidad de procesos y GOMAXPROCS. Go 1.25 lee el límite de throughput promedio de CPU de cgroups en Linux y actualiza periódicamente el valor predeterminado, pero las anulaciones manuales deshabilitan ese comportamiento. Decir simplemente «establecerlo en 2» sin considerar cuotas fraccionarias, latencia ante ráfagas (burst latency) y rollback es incompleto.
Preguntas para clarificar
- ¿Qué versión de Go, versión de cgroups de Linux y runtime de contenedores se utilizan?
- ¿El Pod define un límite de CPU, un request o ambos, y puede cambiar el límite?
- ¿El objetivo es throughput, latencia P99, pausas del GC o costos, y se esperan ráfagas?
- ¿
GOMAXPROCSya está configurado mediante una variable de entorno, flag de inicio o código?
Un marco de trabajo de 30 segundos
El valor predeterminado se deriva del mínimo entre las CPUs lógicas, la afinidad de CPU y el límite de throughput promedio de CPU de cgroups; los límites fraccionarios se redondean hacia arriba y el runtime generalmente no elegirá un valor inferior a 2, a menos que la máquina o la afinidad tengan menos de 2 CPUs. Los requests no se utilizan. Luego, aborda la precedencia de anulación, las actualizaciones, la observabilidad y el rollback.
Diseño paso a paso
1. Comprender el valor predeterminado
Sin un valor en la variable de entorno GOMAXPROCS o una llamada a runtime.GOMAXPROCS, Go 1.25 en Linux lee la cuota/periodo de CPU de cgroups como un límite de throughput promedio. También considera las CPUs lógicas y la afinidad del proceso, eligiendo típicamente el mínimo. Un límite de 2.5 CPUs se redondea hacia arriba a 3. Un request es una garantía de programación (scheduling), no un límite estricto de throughput, por lo que no se utiliza para este valor predeterminado.
2. Actualizaciones y precedencia de anulación
El runtime comprueba periódicamente el recuento de CPUs lógicas, la afinidad y los cambios en las cuotas de cgroups, normalmente no más de una vez por segundo. Establecer la variable de entorno GOMAXPROCS o llamar a runtime.GOMAXPROCS deshabilita las actualizaciones automáticas; runtime.SetDefaultGOMAXPROCS() restablece el valor predeterminado del runtime. GODEBUG=containermaxprocs=0 ignora los límites de cgroups, mientras que updatemaxprocs=0 deshabilita las actualizaciones.
3. Relacionarlo con la programación y el throttling
GOMAXPROCS limita la ejecución simultánea del código de usuario de Go; no es un tope de CPU para el contenedor y no limita los hilos bloqueados en llamadas al sistema (system calls). Un valor muy por encima del límite de CPU puede provocar throttling a nivel de kernel y picos en la latencia de cola. Un valor demasiado bajo puede reducir el throughput y el paralelismo del GC. Analízalo teniendo en cuenta los límites, requests y la sobreasignación (overcommit) de nodos en Kubernetes.
4. Instrumentar y verificar
Registra runtime.GOMAXPROCS(0), runtime.NumCPU(), afinidad, cuota/periodo de cgroups y GODEBUG al inicio. Correlaciona /sched/gomaxprocs:threads, throttling de CPU, cola de ejecución (run queue), P99, fracción de CPU del GC, throughput y errores. Tras modificar un límite, verifica que GOMAXPROCS lo siga en lugar de confiar en un único registro al inicio.
5. Ejecutar experimentos con cargas de trabajo
Con la misma versión de Go y los mismos datos, compara los valores predeterminados, un valor explícito y la versión anterior de Go bajo cargas intensivas de CPU, de espera de I/O y con ráfagas de solicitudes cortas. Mide el estado estable y las ráfagas. La documentación de Go señala que un GOMAXPROCS más bajo puede reducir el throttling, mientras que las cargas de trabajo con picos pueden experimentar una mayor latencia cuando el paralelismo está restringido. Decide evaluando tanto P95/P99 como los costos.
6. Despliegue y rollback
Realiza un despliegue canary de Go 1.25 con un límite fijo y compuertas de validación (gates) explícitas. Si el throttling, el P99 o el GC sufren regresiones, revierte la imagen o utiliza un valor explícito justificado experimentalmente, documentando que esto deshabilita las actualizaciones automáticas. Eliminar la anulación y llamar a SetDefaultGOMAXPROCS restaura el cálculo predeterminado; verifica nuevamente tras un cambio de cuota.
7. Declarar modos de falla y límites
No trates un request como un límite, runtime.NumCPU() como paralelismo disponible, ni a cgroups como algo universal. Los sistemas que no son Linux, la ausencia de cuotas, las configuraciones manuales y las versiones anteriores de Go presentan diferencias. Registra la versión de Go, GODEBUG, las especificaciones de recursos y el responsable del rollback en la lista de verificación de lanzamiento (release checklist).
Ejemplo de una respuesta sólida
«Sin una anulación explícita, Go 1.25 en Linux lee la cuota/periodo de cgroups y toma el mínimo entre ese límite de throughput, las CPUs lógicas y la afinidad; 2.5 CPUs se redondea a 3, y los requests no se utilizan. El runtime comprueba los cambios de cuota periódicamente, pero un valor de entorno, runtime.GOMAXPROCS o GODEBUG=containermaxprocs=0/updatemaxprocs=0 modifican este comportamiento. Yo registraría GOMAXPROCS, datos de cgroups, /sched/gomaxprocs, throttling, P99, throughput y GC, para luego comparar los valores predeterminados, valores explícitos y la versión anterior bajo cargas intensivas de CPU y con ráfagas. Un canary que cruce un umbral de control se revierte, y el runbook documenta la pérdida de actualizaciones automáticas al aplicar anulaciones».
Modos de falla comunes
- Afirmar que Go utiliza directamente los CPU requests.
- Redondear un límite sin explicar las cuotas fraccionarias y la regla del mínimo.
- Olvidar que las variables de entorno, las llamadas de código y GODEBUG deshabilitan las actualizaciones automáticas.
- Medir el throughput ignorando el throttling, P99, GC y la latencia ante ráfagas.
- Tratar a GOMAXPROCS como el tope de CPU del contenedor o como un límite de hilos para llamadas al sistema.
Preguntas de seguimiento
¿Por qué 2.5 CPUs pueden convertirse en 3?
GOMAXPROCS es un entero positivo, por lo que el runtime redondea un límite de throughput fraccionario hacia arriba para aprovechar la cuota completa.
¿Por qué se excluye el CPU request?
Un request es una garantía flexible de programación que puede superarse cuando la capacidad está inactiva; no constituye un límite estricto y estable de throughput.
¿Cómo se verifican las actualizaciones automáticas?
Modifica la cuota de cgroups y observa los logs junto con /sched/gomaxprocs:threads, mientras verificas la variable de entorno y los ajustes de GODEBUG.
¿Cuándo lo configurarías manualmente?
Configúralo cuando los experimentos demuestren que la carga de trabajo, la plataforma o el objetivo de latencia requieren un paralelismo fijo, y mantén una configuración de rollback explícita.
¿Qué ocurre con Go 1.24?
Utiliza un valor explícito o un enfoque de compatibilidad compatible con cgroups como puente, y luego vuelve a probar los límites, la afinidad y el throttling tras actualizar a Go 1.25.
Referencias
Go 1.25 «Release Notes», The Go Blog «Container-aware GOMAXPROCS» y pkg.go.dev «runtime».