Particiones de Red

Estrategias para manejar split-brain y nodos desconectados. ⏱️ 2.5 horas

El problema de las particiones

En sistemas distribuidos, las particiones de red son inevitables. Cuando los nodos no pueden comunicarse entre sí, surgen problemas:

⚠️ Teorema CAP

En presencia de particiones, debes elegir entre Consistencia y Disponibilidad. Erlang/Elixir tiende hacia AP (disponibilidad), pero Mnesia puede configurarse para CP en ciertos casos.

Detectando particiones

defmodule ClusterMonitor do
  use GenServer
  require Logger

  def start_link(_) do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  @impl true
  def init(_) do
    :net_kernel.monitor_nodes(true, [nodedown_reason: true])
    {:ok, %{known_nodes: MapSet.new(Node.list())}}
  end

  @impl true
  def handle_info({:nodeup, node, _info}, state) do
    Logger.info("Node joined: #{node}")
    maybe_heal_partition(node)
    {:noreply, %{state | known_nodes: MapSet.put(state.known_nodes, node)}}
  end

  @impl true
  def handle_info({:nodedown, node, info}, state) do
    reason = Keyword.get(info, :nodedown_reason, :unknown)
    Logger.warn("Node down: #{node}, reason: #{inspect(reason)}")

    case reason do
      :connection_closed -> handle_graceful_disconnect(node)
      :net_tick_timeout -> handle_potential_partition(node)
      _ -> handle_unexpected_disconnect(node)
    end

    {:noreply, %{state | known_nodes: MapSet.delete(state.known_nodes, node)}}
  end

  defp handle_potential_partition(node) do
    Logger.error("Possible network partition! Lost: #{node}")
    :telemetry.execute([:cluster, :partition], %{count: 1}, %{node: node})
  end

  defp maybe_heal_partition(_node) do
    # Reconectar pg, :global, etc.
  end

  defp handle_graceful_disconnect(_node), do: :ok
  defp handle_unexpected_disconnect(_node), do: :ok
end

Estrategias de resolución

1. Majority quorum

defmodule PartitionStrategy.Majority do
  def should_continue?(expected_nodes) do
    current = length(Node.list()) + 1
    majority = div(expected_nodes, 2) + 1
    current >= majority
  end

  def handle_minority do
    # Opciones: shutdown, degraded mode, read-only
    :application.stop(:mi_app)
  end
end

2. Static master

defmodule PartitionStrategy.StaticMaster do
  @master_node :primary@dc1

  def am_i_master? do
    node() == @master_node
  end

  def handle_partition do
    if am_i_master?() do
      # Continuar, soy el master
      :continue
    else
      if Node.ping(@master_node) == :pong do
        :continue  # Master accesible
      else
        :degraded  # Sin master, modo degradado
      end
    end
  end
end

Mnesia y particiones

# Detectar inconsistencias de Mnesia
:mnesia.subscribe(:system)

# En handle_info
def handle_info({:mnesia_system_event, {:inconsistent_database, context, node}}, state) do
  Logger.error("Mnesia inconsistency with #{node}: #{inspect(context)}")
  handle_mnesia_split(node)
  {:noreply, state}
end

defp handle_mnesia_split(node) do
  # Opción 1: Forzar sincronización desde este nodo
  :mnesia.set_master_nodes([node()])

  # Opción 2: Esperar y reintentar
  Process.send_after(self(), {:retry_sync, node}, 5000)
end

Libscluster y auto-healing

# config/config.exs
config :libcluster,
  topologies: [
    k8s: [
      strategy: Cluster.Strategy.Kubernetes,
      config: [
        kubernetes_node_basename: "myapp",
        kubernetes_selector: "app=myapp"
      ]
    ]
  ]

# Libcluster reconecta automáticamente después de particiones
Ejercicio 16.1 Simulador de particiones Intermedio

Crea un módulo para simular particiones y probar tu sistema:

  • Función para "desconectar" nodos específicos
  • Función para "reconectar" después de N segundos
  • Verificar comportamiento de tu aplicación durante partición
Ejercicio 16.2 Graceful degradation Avanzado

Implementa un sistema que degrada gracefully durante particiones:

  • Modo normal: writes distribuidos, consistencia eventual
  • Modo partición: solo reads locales, queue writes
  • Reconciliación al sanar: procesar queue, resolver conflictos

Conexión con el proyecto final

Nuestro sistema de distribución financiera debe: