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:
- Split-brain: dos partes del cluster creen ser el "master"
- Datos inconsistentes: operaciones conflictivas en particiones separadas
- Procesos huérfanos: :global y pg pueden perder sincronización
⚠️ 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:
- Detectar particiones rápidamente (segundos, no minutos)
- Continuar sirviendo datos locales durante partición
- Evitar datos duplicados al sanar la partición
- Alertar operadores para intervención si es necesario