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

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