// 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

terminal — builds y cross-compile
# 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/api
i

Para 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

Dockerfile — 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 entorno GOMAXPROCS=2 o la lib uber-go/automaxprocs que lo detecta sola.
  • GOMEMLIMIT (Go 1.19+) — techo de memoria "suave" para el GC: GOMEMLIMIT=400MiB hace 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.

metrics.go — Prometheus en 15 líneas
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.

bench_test.go
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.txt
pprof.go — profiling en producción
import _ "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 de append.
  • strings.Builder en 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.