Tuning de la BEAM
Configura la VM para máximo rendimiento en sistemas de baja latencia.⏱️ 2.5 horas
Flags de la VM
# Iniciar con flags de rendimiento
ERL_FLAGS="+P 1000000 +Q 1000000" iex -S mix
# O en vm.args / rel/env.sh
+P 1000000 # Max procesos (default 262144)
+Q 1000000 # Max puertos (sockets, files)
+K true # Kernel poll (epoll/kqueue)
+A 128 # Async threads para I/O
+SDcpu 16:16 # Schedulers online:available
+stbt db # Scheduler bind type
+sbwt very_long # Scheduler busy wait
+swt very_low # Scheduler wakeup threshold
+sub true # Scheduler utilization balancing
Schedulers
# Ver configuración actual
:erlang.system_info(:schedulers)
:erlang.system_info(:schedulers_online)
:erlang.system_info(:dirty_cpu_schedulers)
:erlang.system_info(:dirty_io_schedulers)
# Ajustar en runtime
:erlang.system_flag(:schedulers_online, 8)
Scheduler binding
# Opciones de +stbt:
# db - default binding (recomendado)
# u - unbound
# ns - no spread
# ts - thread spread
# ps - processor spread
# s - spread
# nnts - no node thread spread
# nnps - no node processor spread
# Para baja latencia en servidor dedicado:
+stbt db +sbwt very_long
Garbage Collection
# GC por proceso - defaults
:erlang.system_info(:garbage_collection)
# Configurar GC de un proceso específico
Process.flag(:min_heap_size, 10_000) # Palabras
Process.flag(:min_bin_vheap_size, 100_000) # Para binarios
Process.flag(:fullsweep_after, 0) # GC completo frecuente
# Forzar GC
:erlang.garbage_collect(pid)
:erlang.garbage_collect(pid, [type: :major])
Hibernate para procesos idle
# En GenServer, retornar :hibernate
{:noreply, state, :hibernate}
# Esto hace GC completo y reduce memoria
# Útil para procesos que esperan mucho entre mensajes
Memory allocators
# Ver uso de memoria
:erlang.memory()
# => [total: 123456789, processes: ..., binary: ..., ets: ...]
# Flags de allocators
+MBas aobf # Address order best fit
+MBacul 0 # Carrier utilization limit
# Para debugging de memoria
:recon_alloc.memory(:allocated)
:recon_alloc.fragmentation(:current)
Process flags para latencia
defmodule LowLatencyWorker do
use GenServer
@impl true
def init(state) do
# Prioridad alta (max, high, normal, low)
Process.flag(:priority, :high)
# Heap inicial grande para evitar GC frecuente
Process.flag(:min_heap_size, 100_000)
# Mensaje queue off-heap (reduce pausas GC)
Process.flag(:message_queue_data, :off_heap)
{:ok, state}
end
end
Network tuning
# Opciones de socket para baja latencia
socket_opts = [
:binary,
active: :once,
nodelay: true, # Disable Nagle
delay_send: false, # No agrupar sends
sndbuf: 65536, # Send buffer
recbuf: 65536, # Receive buffer
buffer: 65536, # User-level buffer
high_watermark: 131072, # Backpressure threshold
low_watermark: 65536
]
# Flags de red de la VM
+zdbbl 32768 # Distribution buffer busy limit (KB)
⚠️ Medir antes de optimizar
No apliques todos estos flags a ciegas. Mide el rendimiento base, aplica cambios incrementalmente, y mide de nuevo. Muchos defaults son buenos para casos generales.
Ejercicio 18.1
Perfil de latencia
Intermedio
Crea un perfil de VM optimizado para tu sistema:
- Experimenta con diferentes valores de schedulers
- Prueba scheduler binding options
- Documenta el impacto en latencia p99
Ejercicio 18.2
GC tuning
Avanzado
Optimiza el GC de tus workers críticos:
- Medir pausas de GC actuales
- Ajustar heap sizes
- Implementar hibernate donde tenga sentido
Conexión con el proyecto final
Para el sistema de distribución configuraremos:
- Schedulers: bound a CPUs para predecibilidad
- Workers de feed: prioridad alta, heaps grandes
- Sockets: nodelay, buffers optimizados
- Message queues: off-heap para handlers de alto throughput