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: