// 08 · aquí es donde Go humilla a todos
Build, deploy y rendimiento
El deploy de Go es copiar un archivo. Sin JVM que instalar, sin node_modules, sin virtualenv — y con profiler de producción incluido.
// un binario para dominarlos a todos
Builds y cross-compiling
# Binario optimizado (sin símbolos de debug: ~30% más pequeño)
$ go build -ldflags="-s -w" -o api ./cmd/api
# Desde tu Windows, compilar para el servidor Linux del trabajo:
$ $env:GOOS="linux"; $env:GOARCH="amd64"; go build -o api ./cmd/api
# ¿Una Raspberry? ¿Mac con Apple Silicon? Cambia dos variables:
# GOOS: linux | windows | darwin GOARCH: amd64 | arm64
# Inyectar la versión en compile-time (adiós MANIFEST.MF):
$ go build -ldflags="-X main.version=1.4.2" ./cmd/apiPara releases multiplataformaLa herramienta goreleaser automatiza todo: compila para cada SO/arquitectura, genera changelogs, sube binarios a GitHub Releases y publica imágenes Docker — con un solo YAML. Es el estándar para distribuir CLIs.
// imágenes de 15 MB
Docker multi-stage
# Etapa 1: compilar
FROM golang:1.24-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download # capa cacheada: solo se repite si cambian deps
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/api
# Etapa 2: imagen final SIN sistema operativo (solo tu binario + certs)
FROM gcr.io/distroless/static-debian12
COPY --from=build /app /app
EXPOSE 8080
ENTRYPOINT ["/app"]
# Resultado: ~15 MB. Tu imagen de Spring Boot: 200-400 MB.Lo que esto significa en la prácticaDeploys en segundos, autoscaling instantáneo (sin warm-up), costos de RAM divididos entre 10, y CI que compila + testea + construye la imagen en menos de dos minutos. Es la razón #1 por la que las startups de infraestructura eligen Go.
Go dentro de contenedores: GOMAXPROCS y GOMEMLIMIT
GOMAXPROCS— cuántos hilos de CPU usa el runtime. En Kubernetes con límite de CPU (ej. 2 cores en un nodo de 64), ajústalo al límite real: variable de entornoGOMAXPROCS=2o la libuber-go/automaxprocsque lo detecta sola.GOMEMLIMIT(Go 1.19+) — techo de memoria "suave" para el GC:GOMEMLIMIT=400MiBhace que el GC trabaje más agresivo cerca del límite, evitando el OOMKill del contenedor. Es tu-Xmx, pero como sugerencia inteligente y no como muro.
// logs, métricas y trazas
Observabilidad
Los tres pilares, versión Go: log/slog para logs estructurados (explicado en la página de stdlib), Prometheus para métricas y OpenTelemetry para trazas distribuidas. El ecosistema Prometheus/Grafana está escrito en Go — el soporte es de primera clase.
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var pagosProcesados = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "pagos_procesados_total",
Help: "Pagos procesados por resultado.",
}, []string{"resultado"})
func procesarPago(p Pago) {
if err := cobrar(p); err != nil {
pagosProcesados.WithLabelValues("error").Inc()
return
}
pagosProcesados.WithLabelValues("ok").Inc()
}
func main() {
http.Handle("GET /metrics", promhttp.Handler()) // scrapeable por Prometheus
// ... el resto de tu servidor
}// nuevo · medir antes de optimizar
Rendimiento: benchmarks y pprof
Go trae la cadena completa para no optimizar a ciegas: benchmarks reproducibles en el paquete testing y , un profiler de CPU/memoria que puedes dejar encendido en producción.
func BenchmarkBuscar(b *testing.B) {
datos := generarDatos(10_000)
b.ResetTimer() // no midas la preparación
for b.Loop() { // Go 1.24; antes: for i := 0; i < b.N; i++
Buscar(datos, "clave-742")
}
}
// $ go test -bench=Buscar -benchmem
// BenchmarkBuscar-8 84523 14102 ns/op 4096 B/op 12 allocs/op
// ↑ bytes/op ↑ asignaciones/op
// Los allocs/op son EL número a vigilar: menos asignaciones,
// menos trabajo para el GC, menos latencia.
// Comparar antes/después de un cambio con estadística seria:
// $ go test -bench=Buscar -count=10 > antes.txt
// (cambias el código)
// $ go test -bench=Buscar -count=10 > despues.txt
// $ benchstat antes.txt despues.txtimport _ "net/http/pprof" // el import registra los endpoints /debug/pprof
func main() {
// Puerto interno, NUNCA expuesto a Internet:
go http.ListenAndServe("localhost:6060", nil)
// ... tu servidor normal en :8080
}
// Desde tu máquina, con el servicio corriendo:
// $ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/profile?seconds=10
// → se abre un flame graph interactivo de CPU en el navegador
// $ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap
// → ídem para memoria
// $ curl localhost:6060/debug/pprof/goroutine?debug=1
// → lista de TODAS las goroutines vivas (encuentra leaks al instante)Comparado con la JVMEsto es JProfiler + async-profiler + jstack, integrado en la stdlib, sin agentes, sin licencias y con overhead tan bajo que se usa en producción. La cultura Go de rendimiento gira alrededor de estas dos herramientas: benchmark local, pprof en vivo.
Tres optimizaciones que sí mueven la aguja (y una que no)
- Pre-dimensiona slices y maps:
make([]T, 0, n)cuando conoces el tamaño evita re-alocaciones en cadena deappend. strings.Builderen bucles: concatenar con+=en un bucle es O(n²) en asignaciones.- Evita punteros innecesarios en structs pequeños: los valores viven en el stack (baratos); los punteros suelen escapar al heap (trabajo para el GC). El compilador decide con escape analysis —
go build -gcflags="-m"te muestra qué escapa. - La que no: microoptimizar antes de medir. El proverbio aplica doble en Go: el compilador y el GC son mejores que tu intuición. Benchmark primero, siempre.