Puesta en Producción
Despliega tu sistema de distribución financiera en producción.⏱️ 3 horas
Releases con Mix
# mix.exs
def project do
[
app: :market_feed,
version: "1.0.0",
releases: [
market_feed: [
include_executables_for: [:unix],
applications: [runtime_tools: :permanent],
steps: [:assemble, :tar]
]
]
]
end
# Construir release
MIX_ENV=prod mix release
Configuración de producción
# config/runtime.exs
import Config
if config_env() == :prod do
config :market_feed,
feed_host: System.get_env("FEED_HOST") |> String.to_charlist(),
feed_port: System.get_env("FEED_PORT") |> String.to_integer(),
subscriber_port: System.get_env("SUBSCRIBER_PORT", "9000") |> String.to_integer()
config :logger, level: :info
end
vm.args para rendimiento
# rel/vm.args.eex
-name <%= @release.name %>@<%= System.get_env("HOSTNAME", "localhost") %>
-setcookie <%= System.get_env("RELEASE_COOKIE", "default_cookie") %>
## Performance tuning
+P 1000000
+Q 65536
+K true
+A 128
+SDcpu <%= System.schedulers_online() %>:<%= System.schedulers_online() %>
+stbt db
+sbwt very_long
+swt very_low
+sub true
## Memory
+MBas aobf
+MBacul 0
## Distribution
+zdbbl 32768
Docker
# Dockerfile
FROM elixir:1.15-alpine AS builder
RUN apk add --no-cache build-base git
WORKDIR /app
ENV MIX_ENV=prod
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod
RUN mix deps.compile
COPY lib lib
COPY config config
COPY rel rel
RUN mix release
# Runtime image
FROM alpine:3.18
RUN apk add --no-cache libstdc++ openssl ncurses-libs
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/market_feed ./
ENV HOME=/app
EXPOSE 9000 4369 9100-9200
CMD ["bin/market_feed", "start"]
Kubernetes
# k8s/deployment.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: market-feed
spec:
serviceName: market-feed
replicas: 3
selector:
matchLabels:
app: market-feed
template:
metadata:
labels:
app: market-feed
spec:
containers:
- name: market-feed
image: market-feed:latest
ports:
- containerPort: 9000
name: subscriber
- containerPort: 4369
name: epmd
env:
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: FEED_HOST
value: "feed.internal"
- name: FEED_PORT
value: "5000"
resources:
requests:
memory: "2Gi"
cpu: "2"
limits:
memory: "4Gi"
cpu: "4"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8080
Observabilidad
Health checks
defmodule MarketFeed.HealthHandler do
def init(req, state) do
status = check_health()
code = if status.healthy, do: 200, else: 503
req = :cowboy_req.reply(code,
%{"content-type" => "application/json"},
Jason.encode!(status),
req
)
{:ok, req, state}
end
defp check_health do
%{
healthy: true,
feed_connected: MarketFeed.FeedConnector.connected?(),
subscribers: :ranch_server.count_connections(:subscriber_listener),
uptime_seconds: :erlang.statistics(:wall_clock) |> elem(0) |> div(1000)
}
end
end
Métricas con Telemetry
defmodule MarketFeed.Telemetry do
use Supervisor
import Telemetry.Metrics
def start_link(opts) do
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(_opts) do
children = [
{TelemetryMetricsPrometheus, [metrics: metrics()]}
]
Supervisor.init(children, strategy: :one_for_one)
end
def metrics do
[
counter("market_feed.ticks.total"),
last_value("market_feed.subscribers.count"),
distribution("market_feed.tick.latency",
unit: {:native, :microsecond},
buckets: [10, 50, 100, 500, 1000, 5000]
),
summary("vm.memory.total", unit: :byte),
summary("vm.total_run_queue_lengths.total")
]
end
end
Runbook de operaciones
Comandos útiles via remote console
# Conectar a nodo en producción
./bin/market_feed remote
# Ver procesos con más mensajes
:recon.proc_count(:message_queue_len, 10)
# Ver uso de memoria
:recon.bin_leak(5)
# Info de conexiones
:ranch.info(:subscriber_listener)
# Forzar GC global
:erlang.memory()
:recon.bin_leak(100)
# Ver estado de pg groups
:pg.which_groups(MarketFeed.PG)
Checklist de producción
- Releases configurados con vm.args optimizado
- Health checks implementados y probados
- Métricas exportadas a Prometheus/Grafana
- Logs estructurados (JSON) enviados a agregador
- Alertas configuradas para errores críticos
- Runbook documentado para operaciones comunes
- Backups de configuración/estado si aplica
- Plan de rollback probado
- Load testing completado
- Seguridad revisada (TLS, autenticación, firewalls)
Felicitaciones
Has completado el libro. Ahora tienes las herramientas para construir sistemas de distribución de datos financieros de baja latencia en Elixir. El siguiente paso es construir, medir, y mejorar iterativamente.
Ejercicio Final
Sistema completo
Proyecto
Despliega tu sistema completo:
- Publisher + 2 subscribers en nodos separados
- Monitoreo con Prometheus + Grafana
- Prueba de carga con 1000+ conexiones simuladas
- Simula fallo de nodo y verifica recuperación
- Documenta latencia p99 bajo carga