Saltar al contenido

057 — Consenso y transacciones distribuidas: Raft, 2PC y sagas

🗂️ parte 🎚️ nivel ⏱️ duración 📗 clase

Programa · Parte 10 · ← Anterior · Siguiente →

Parte 10 — Distribución, réplica y consistencia · Avanzado · 4 horas estimadas · motores spanner, cockroachdb, postgresql · laboratorio labs/03-transactions · 4 fuentes.

Conceptos centrales: consenso · elección de líder · commit en dos fases · saga · compensación

En este caso se comparan 8 motores: 7 lo resuelven (5 con el resultado comprobado por máquina) y 1 no, con el motivo escrito.

De qué trata esta clase

Cómo se ponen de acuerdo varios nodos y cómo se confirma algo que abarca varios sistemas. Raft para el consenso y la elección de líder, el commit en dos fases con su fragilidad conocida —el coordinador que cae dejando cerrojos tomados— y la saga con compensaciones como la alternativa que renuncia al aislamiento para no bloquear.

flowchart LR
    C["🗄️ Clase 057"]
    C --> K1["consenso"]
    C --> K2["elección de líder"]
    C --> K3["commit en dos fases"]
    C --> K4["saga"]
    C --> K5["compensación"]
    classDef raiz fill:#0b3d2e,stroke:#3fb950,color:#fff
    class C raiz

Antes de empezar

Esta clase supone que ya trabajaste lo siguiente. Si algo de la última columna no te suena, vuelve a esa clase antes de seguir: aquí se usa sin volver a explicarlo.

# Clase previa Lo que se da por sabido
055 CAP, PACELC y lo que realmente se elige partición de red · disponibilidad · latencia frente a consistencia
047 Concurrencia en la aplicación: idempotencia, reintentos y bloqueo optimista idempotencia · clave de idempotencia · bloqueo optimista · reintento con retroceso

Vocabulario de la clase

Los términos que siguen se usan más adelante con este significado exacto. La definición completa, con sus términos relacionados, está en el glosario del programa.

Término Qué significa Procedencia
consenso Que un conjunto de nodos se ponga de acuerdo en un valor y no cambie de opinión, tolerando caídas de una minoría. Es el cimiento de la elección de líder, de la pertenencia al clúster y del commit atómico; Raft y Paxos son las dos formulaciones de referencia. se introduce aquí
elección de líder Procedimiento por el que la mayoría acuerda quién ordena las escrituras durante un mandato. Que se necesite mayoría es lo que impide dos líderes simultáneos —el escenario de cerebro dividido— cuando la red se parte. se introduce aquí
commit en dos fases Protocolo para confirmar una transacción que abarca varios sistemas: primero se pregunta a todos si pueden, después se les ordena confirmar. Es correcto y es frágil: si el coordinador cae entre las dos fases, los participantes quedan bloqueados con los cerrojos tomados. se introduce aquí
saga Secuencia de transacciones locales, cada una con su compensación, que sustituye a una transacción distribuida. Renuncia al aislamiento —los estados intermedios se ven— a cambio de no bloquear recursos entre servicios. se introduce aquí
compensación Operación de negocio que deshace el efecto de otra ya confirmada: reembolsar en vez de revertir, anular una reserva en vez de borrarla. No es un ROLLBACK, porque el estado intermedio existió y alguien pudo verlo. se introduce aquí

Propósito

Coordinar varios nodos cuando hace falta acuerdo: consenso para elegir líder y ordenar operaciones, y las tres formas de que una operación abarque varios sistemas.

Resultados de aprendizaje

Al terminar podrás:

  1. Explicar qué problema resuelve el consenso y por qué necesita mayoría.
  2. Describir Raft: elección de líder, replicación del registro y confirmación.
  3. Identificar el fallo del commit en dos fases y por qué bloquea.
  4. Diseñar una saga con sus compensaciones.
  5. Elegir entre 2PC, saga y bandeja de salida transaccional.

Fundamentos

El problema del consenso

Varios nodos deben ponerse de acuerdo en un valor, tolerando caídas y mensajes perdidos. Es el problema que subyace a: elegir líder, decidir si una transacción confirma, ordenar un registro replicado.

Por qué mayoría. Con 2f + 1 nodos se toleran f caídas, porque dos mayorías cualesquiera se solapan en al menos un nodo, y ese nodo impide que se tomen dos decisiones contradictorias. Con 5 nodos se toleran 2. Un número par no aporta: 4 nodos toleran 1, igual que 3, y tienen más coordinación.

Lamport resolvió el problema con Paxos (1998); Ongaro y Ousterhout diseñaron Raft (2014) buscando explícitamente que fuese comprensible, y por eso es el que implementan etcd, Consul, CockroachDB y TiKV.

Raft en tres piezas

1. Elección de líder. Cada nodo tiene un temporizador aleatorio. Al agotarse sin recibir señal del líder, se declara candidato del mandato t+1 y pide votos. Con mayoría, es líder. La aleatoriedad de los temporizadores es lo que evita elecciones empatadas indefinidamente.

2. Replicación del registro. El líder recibe las operaciones, las añade a su registro y las envía a los seguidores. Una entrada se confirma cuando la mayoría la ha escrito. Solo entonces se aplica y se responde al cliente.

3. Seguridad. Un candidato solo obtiene votos si su registro está al menos tan actualizado como el del votante. Esa regla garantiza que un líder nuevo contiene todas las entradas confirmadas: nada confirmado se pierde jamás.

sequenceDiagram
    participant C as Cliente
    participant L as Líder
    participant S1 as Seguidor 1
    participant S2 as Seguidor 2
    C->>L: operación
    L->>L: añadir al registro (sin confirmar)
    L->>S1: AppendEntries
    L->>S2: AppendEntries
    S1-->>L: ok
    Note over L: mayoría (2 de 3) → CONFIRMADA
    L->>L: aplicar
    L-->>C: respuesta
    S2-->>L: ok (tardío, no afecta)

Costo: una ida y vuelta a la mayoría por operación. En un centro de datos, ~1 ms; entre regiones, cientos.

Commit en dos fases

Fase 1 (preparar):  el coordinador pregunta a cada participante "¿puedes confirmar?"
                    cada uno responde SÍ o NO, y si dice SÍ queda OBLIGADO
Fase 2 (confirmar): si todos dijeron SÍ, el coordinador ordena confirmar; si no, abortar

El fallo. Si el coordinador cae después de que todos digan SÍ y antes de enviar la decisión, los participantes quedan en duda: no pueden confirmar (quizá alguien dijo NO) ni abortar (quizá todos dijeron SÍ). Mantienen sus bloqueos indefinidamente.

Por eso 2PC se llama protocolo bloqueante. La corrección es un coordinador replicado por consenso —y entonces cada transacción cuesta consenso más dos rondas—. Es lo que hace Spanner, con la ventaja de una red controlada y relojes acotados por TrueTime.

Helland argumenta que a escala esto no se sostiene, y propone el enfoque alternativo.

Sagas

Descomponer la operación en pasos locales atómicos, cada uno con su compensación:

T1 → T2 → T3 → T4
      ↓ falla T3
C2 ← C1                    (compensaciones en orden inverso)

Propiedades que hay que asumir explícitamente:

Bandeja de salida transaccional

El problema más común no es una transacción entre dos bases: es «escribir en la base y publicar un evento» de forma atómica.

BEGIN;
INSERT INTO enrollments (...) VALUES (...);
INSERT INTO outbox (tipo, carga, creado_en)
       VALUES ('inscripcion.creada', '{"student_id":11,...}', now());
COMMIT;
-- Un proceso aparte lee outbox y publica. Si falla, reintenta.

Una sola transacción local garantiza que el evento existe si y solo si la inscripción existe. La publicación es al-menos-una-vez, y el consumidor debe ser idempotente. Resuelve el problema real sin ninguna transacción distribuida, y por eso es la primera opción a considerar.

Ejemplo trabajado

Inscripción que debe: reservar cupo (servicio de cursos), cobrar (servicio de pagos) y emitir credencial (servicio de identidad).

Opción A — 2PC:

coordinador → prepare → cursos, pagos, identidad
todos SÍ → commit

Problemas concretos:

Opción B — saga:

PASOS = [
    ("reservar_cupo",     reservar_cupo,     liberar_cupo),
    ("cobrar",            cobrar,            reembolsar),
    ("emitir_credencial", emitir_credencial, revocar_credencial),
]

def ejecutar_saga(saga_id, ctx):
    hechos = []
    for nombre, accion, compensacion in PASOS:
        try:
            # La clave de idempotencia deriva de la saga y del paso:
            # un reintento repite exactamente la misma clave.
            accion(ctx, clave_idem=f"{saga_id}:{nombre}")
            hechos.append((nombre, compensacion))
        except ErrorPermanente:
            for nombre_h, comp in reversed(hechos):
                comp(ctx, clave_idem=f"{saga_id}:comp:{nombre_h}")
            raise SagaAbortada(nombre)
    return "ok"

Traza de un fallo en el cobro:

t0  reservar_cupo    → ok (cupo 39/40)
t1  cobrar           → tarjeta rechazada (ErrorPermanente)
t2  liberar_cupo     → ok (cupo 40/40)
resultado: saga abortada, estado coherente

La falta de aislamiento, hecha visible:

t0    reservar_cupo → cupo 39/40
t0+1s otro usuario consulta → ve 39 plazas libres, no 40
t1    cobro falla
t2    liberar_cupo → cupo 40/40

Durante ~1 segundo el sistema mostró un cupo que no estaba realmente comprometido. Es inherente a la saga y hay que decidir si es aceptable. Aquí sí lo es: como mucho, alguien vio una plaza menos de las disponibles, lo que es conservador. Si el error fuese al revés —mostrar plazas que no existen— no sería aceptable.

Opción C — bandeja de salida + coreografía, la que suele resultar mejor:

inscripciones: BEGIN; INSERT enrollment; INSERT outbox('inscripcion.creada'); COMMIT
pagos:         consume 'inscripcion.creada' → cobra → publica 'pago.ok' o 'pago.fallido'
inscripciones: consume 'pago.fallido'      → marca la inscripción como anulada
identidad:     consume 'pago.ok'           → emite credencial

Ningún coordinador, ninguna transacción distribuida, cada paso atómico en su propia base. A cambio: la coreografía es más difícil de seguir que la orquestación, y hace falta trazabilidad para saber dónde se detuvo una inscripción.

Comparación

Mecanismo Atomicidad Aislamiento Bloqueante Cuándo
Transacción local Total Total No Una sola base: siempre que se pueda
Bandeja de salida Total en el origen Del origen No Base + evento
2PC Total Total Pocos participantes, todos con prepare, red controlada
2PC sobre consenso Total Total No Spanner, CockroachDB
Saga (orquestada) Eventual Ninguno No Servicios heterogéneos, pasos largos
Saga (coreografiada) Eventual Ninguno No Muchos servicios, bajo acoplamiento

Errores frecuentes

  1. 2PC entre microservicios por red pública. Bloqueo garantizado ante fallo del coordinador.
  2. Sagas sin compensación para algún paso. Un fallo posterior deja el sistema inconsistente sin remedio.
  3. Compensaciones no idempotentes. Un reintento reembolsa dos veces.
  4. Ignorar la falta de aislamiento de las sagas. Los estados intermedios se ven y a veces se actúa sobre ellos.
  5. Publicar el evento antes del COMMIT. Si la transacción se revierte, el evento ya salió.
  6. Número par de nodos en consenso. Más coordinación, misma tolerancia.

De la clase a la operación

La mayoría de las «transacciones distribuidas» que se plantean en un diseño desaparecen al reunir los datos que deben cambiar juntos en una misma base (clase 024). Antes de coordinar, conviene comprobar si la frontera del agregado está mal trazada.

Reto de transferencia

  1. Identifica una operación de tu sistema que abarque dos almacenes.
  2. Diséñala como saga con compensaciones idempotentes.
  3. Implementa la bandeja de salida y demuestra la atomicidad entre estado y evento.
  4. Documenta qué estado intermedio queda visible y por cuánto tiempo.

Preguntas de evaluación

  1. ¿Por qué el consenso necesita mayoría estricta y no basta la mitad?
  2. Traza el fallo del coordinador en 2PC y explica por qué los participantes no pueden decidir.
  3. Da un paso de tu sistema cuya compensación no sea una simple inversión.
  4. ¿Qué garantiza la bandeja de salida y qué obligación traslada al consumidor?

🌐 El mismo problema en cada motor

Caso: Cuando no hay ROLLBACK que valga: compensar en vez de deshacer

Dos servicios, dos bases de datos, una operación: reservar vuelo y hotel. El vuelo se confirma; el hotel falla. No hay transacción que abarque a los dos, así que no hay nada que revertir: la reserva del vuelo se confirmó de verdad, existió como reserva válida y alguien pudo verla.

La respuesta es una saga: ejecutar la acción inversa. Y la acción inversa no es un ROLLBACK, es una operación de negocio —cancelar— con sus propias reglas, su penalización posible y su propia probabilidad de fallar.

El caso deja las dos cosas por escrito: el hotel fallido y el vuelo compensado. Ver compensado en vez de que la fila del vuelo haya desaparecido es exactamente la diferencia entre una saga y una transacción.

Salida esperada, idéntica en todos los motores que lo resuelven:

paso estado
hotel fallido
vuelo compensado

El contrato vive en motores.yaml y lo comprueba python scripts/verificar_equivalencia.py --clase 057: 5 de las 7 implementaciones se ejecutan de verdad y su resultado se compara con esa tabla; el resto se declara como material revisado, no ejecutado.

Motor ¿Resuelve el caso? Nivel de prueba Código Fuente
SQLite núcleo código doc oficial
DuckDB núcleo código doc oficial
PostgreSQL servicio código doc oficial
MongoDB servicio código doc oficial
Redis servicio código doc oficial
Google Cloud Spanner declarado código doc oficial
CockroachDB declarado código doc oficial
Apache Kafka no doc oficial

Los que resuelven el caso

SQLite · implementaciones/sqlite/consulta.sql

verificado — se ejecuta en CI sin servicios

-- motor: sqlite
-- doc: https://sqlite.org/lang_update.html
-- nota: la fila del vuelo NO desaparece: queda como 'compensado'. Esa
--       diferencia con un ROLLBACK es la clase entera. Y hay una segunda
--       leccion escondida: si el proceso muere entre el fallo del hotel y la
--       compensacion, nadie sabra que habia que compensar. El registro de la
--       saga tiene que ser duradero ANTES de dar el primer paso.

-- === preparacion ===
CREATE TABLE reservas (
    paso   TEXT PRIMARY KEY,
    estado TEXT NOT NULL
);

-- Paso 1: el servicio de vuelos confirma. En SU base de datos, esto ya esta
-- hecho y CONFIRMADO: nadie de fuera puede deshacerlo.
INSERT INTO reservas (paso, estado) VALUES ('vuelo', 'confirmado');

-- Paso 2: el servicio de hoteles no tiene habitaciones. Falla.
INSERT INTO reservas (paso, estado) VALUES ('hotel', 'fallido');

-- Compensacion. Y aqui esta toda la clase: esto NO es un ROLLBACK. El vuelo se
-- confirmo de verdad, existio como reserva valida durante un tiempo, y alguien
-- pudo verlo. Deshacerlo exige ejecutar la ACCION INVERSA —cancelar—, que es
-- una operacion de negocio con sus propias reglas: puede tener penalizacion,
-- puede requerir autorizacion, y puede fallar tambien.
UPDATE reservas SET estado = 'compensado' WHERE paso = 'vuelo';

-- === consulta ===
SELECT paso, estado FROM reservas ORDER BY paso;

DuckDB · implementaciones/duckdb/consulta.sql

verificado — se ejecuta en CI sin servicios

-- motor: duckdb
-- doc: https://duckdb.org/docs/stable/sql/query_syntax/select.html
-- nota: la consulta que de verdad importa aqui es la de auditoria, la que se
--       ejecuta sobre el registro de todas las sagas:
--         SELECT saga_id FROM pasos WHERE estado = 'fallido'
--         AND saga_id NOT IN (SELECT saga_id FROM pasos WHERE estado = 'compensado');
--       Es decir: que sagas quedaron a medias. Ahi esta el dinero perdido.

-- === preparacion ===
CREATE TABLE reservas (
    paso   VARCHAR PRIMARY KEY,
    estado VARCHAR NOT NULL
);

-- Paso 1: el servicio de vuelos confirma. En SU base de datos, esto ya esta
-- hecho y CONFIRMADO: nadie de fuera puede deshacerlo.
INSERT INTO reservas (paso, estado) VALUES ('vuelo', 'confirmado');

-- Paso 2: el servicio de hoteles no tiene habitaciones. Falla.
INSERT INTO reservas (paso, estado) VALUES ('hotel', 'fallido');

-- Compensacion. Y aqui esta toda la clase: esto NO es un ROLLBACK. El vuelo se
-- confirmo de verdad, existio como reserva valida durante un tiempo, y alguien
-- pudo verlo. Deshacerlo exige ejecutar la ACCION INVERSA —cancelar—, que es
-- una operacion de negocio con sus propias reglas: puede tener penalizacion,
-- puede requerir autorizacion, y puede fallar tambien.
UPDATE reservas SET estado = 'compensado' WHERE paso = 'vuelo';

-- === consulta ===
SELECT paso, estado FROM reservas ORDER BY paso;

PostgreSQL · implementaciones/postgresql/consulta.sql

verificado — se ejecuta contra el motor real levantado con docker compose

-- motor: postgresql
-- doc: https://www.postgresql.org/docs/current/sql-prepare-transaction.html
-- nota: PostgreSQL SI implementa la confirmacion en dos fases:
--         BEGIN; ...; PREPARE TRANSACTION 'reserva-42';
--         COMMIT PREPARED 'reserva-42';   -- o ROLLBACK PREPARED
--       Y conviene saber por que casi nadie la usa: una transaccion preparada
--       retiene sus bloqueos INDEFINIDAMENTE si el coordinador desaparece, y
--       basta una olvidada para impedir el vacio de toda la base. Por eso
--       max_prepared_transactions vale 0 por omision: hay que activarlo a
--       proposito. La saga existe porque esa alternativa sale peor.

-- === preparacion ===
DROP TABLE IF EXISTS reservas;

CREATE TABLE reservas (
    paso   text PRIMARY KEY,
    estado text NOT NULL
);

-- Paso 1: el servicio de vuelos confirma. En SU base de datos, esto ya esta
-- hecho y CONFIRMADO: nadie de fuera puede deshacerlo.
INSERT INTO reservas (paso, estado) VALUES ('vuelo', 'confirmado');

-- Paso 2: el servicio de hoteles no tiene habitaciones. Falla.
INSERT INTO reservas (paso, estado) VALUES ('hotel', 'fallido');

-- Compensacion. Y aqui esta toda la clase: esto NO es un ROLLBACK. El vuelo se
-- confirmo de verdad, existio como reserva valida durante un tiempo, y alguien
-- pudo verlo. Deshacerlo exige ejecutar la ACCION INVERSA —cancelar—, que es
-- una operacion de negocio con sus propias reglas: puede tener penalizacion,
-- puede requerir autorizacion, y puede fallar tambien.
UPDATE reservas SET estado = 'compensado' WHERE paso = 'vuelo';

-- === consulta ===
SELECT paso, estado FROM reservas ORDER BY paso;

MongoDB · implementaciones/mongodb/consulta.js

verificado — se ejecuta contra el motor real levantado con docker compose

// motor: mongodb
// doc: https://www.mongodb.com/docs/manual/core/write-operations-atomicity/
// nota: el registro de la saga cabe en un documento por operacion, con los
//       pasos dentro. Escribir un paso es una escritura atomica: ni transaccion
//       ni conjunto de replicas. Lo que NO garantiza nada es que el estado
//       escrito aqui coincida con el mundo real: si el servicio de vuelos no
//       recibio la cancelacion, el documento dira 'compensado' igualmente.

// === preparacion ===
db.sagas.drop();
db.sagas.insertOne({ _id: "reserva-42", pasos: [] });

// Paso 1: el vuelo se confirma de verdad, en otro sistema.
db.sagas.updateOne(
  { _id: "reserva-42" },
  { $push: { pasos: { paso: "vuelo", estado: "confirmado" } } },
);

// Paso 2: el hotel falla.
db.sagas.updateOne(
  { _id: "reserva-42" },
  { $push: { pasos: { paso: "hotel", estado: "fallido" } } },
);

// Compensacion: accion inversa sobre el paso ya confirmado.
db.sagas.updateOne(
  { _id: "reserva-42", "pasos.paso": "vuelo" },
  { $set: { "pasos.$.estado": "compensado" } },
);

// === consulta ===
db.sagas
  .aggregate([
    { $unwind: "$pasos" },
    { $project: { _id: 0, paso: "$pasos.paso", estado: "$pasos.estado" } },
    { $sort: { paso: 1 } },
  ])
  .forEach((d) => print(d.paso + "|" + d.estado));

Redis · implementaciones/redis/consulta.txt

verificado — se ejecuta contra el motor real levantado con docker compose

# motor: redis
# doc: https://redis.io/docs/latest/commands/hset/
# nota: Redis es un buen sitio para el ESTADO EN VUELO de la saga —que pasos van
#       hechos, cuales faltan— con caducidad para detectar las que se quedaron a
#       medias:
#         EXPIRE saga:reserva-42 3600
#       Lo que NO debe ser es el unico sitio donde vive ese estado: con
#       appendfsync everysec se puede perder hasta un segundo, y una saga
#       huerfana es dinero que nadie devuelve.

# === preparacion ===
FLUSHDB
HSET saga:reserva-42 vuelo confirmado
HSET saga:reserva-42 hotel fallido
HSET saga:reserva-42 vuelo compensado

# === consulta ===
EVAL "local r={} for _,p in ipairs({'hotel','vuelo'}) do r[#r+1]=p..'|'..redis.call('HGET',KEYS[1],p) end return r" 1 saga:reserva-42

Google Cloud Spanner · implementaciones/spanner/consulta.sql

declarado — se revisa a mano contra la documentación citada; la máquina no lo ejecuta

-- motor: spanner
-- doc: https://cloud.google.com/spanner/docs/transactions
-- nota: implementacion declarada, y deliberadamente distinta: aqui NO hay saga.
--       Si los dos datos caben en el mismo sistema, Spanner ofrece la
--       alternativa que la saga sustituye: una transaccion distribuida de
--       verdad, serializable, con confirmacion en dos fases sobre Paxos. El
--       coordinador tambien esta replicado por consenso, asi que no puede
--       quedarse colgado como el coordinador XA del que huye la saga.
--
--       Y el limite, que es el que importa: esto solo vale si los dos servicios
--       COMPARTEN base de datos. Una arquitectura de servicios independientes
--       evita eso a proposito, y por eso la saga sigue existiendo.

-- === preparacion ===
CREATE TABLE reservas (
    paso   STRING(MAX) NOT NULL,
    estado STRING(MAX) NOT NULL,
) PRIMARY KEY (paso);

-- === consulta ===
-- Una sola transaccion para las dos reservas. Si la segunda falla, la primera
-- NO ocurrio: no hay estado intermedio observable y no hay nada que compensar.
--
--   BEGIN;
--     INSERT INTO reservas (paso, estado) VALUES ('vuelo', 'confirmado');
--     INSERT INTO reservas (paso, estado) VALUES ('hotel', 'confirmado');
--   COMMIT;   -- o ROLLBACK entero
--
SELECT paso, estado FROM reservas ORDER BY paso;

CockroachDB · implementaciones/cockroachdb/consulta.sql

declarado — se revisa a mano contra la documentación citada; la máquina no lo ejecuta

-- motor: cockroachdb
-- doc: https://www.cockroachlabs.com/docs/stable/transactions
-- nota: implementacion declarada. Misma alternativa que Spanner —transaccion
--       distribuida serializable sobre Raft— con protocolo de PostgreSQL, asi
--       que se puede probar sin reescribir la aplicacion.
--       Lo que SI hay que escribir es el ciclo de reintento: las transacciones
--       que abarcan varios rangos pueden abortarse por conflicto con el codigo
--       de error 40001, y reintentar no es opcional. Parte de la complejidad
--       que la saga hacia explicita vuelve por esta puerta.

-- === preparacion ===
DROP TABLE IF EXISTS reservas;

CREATE TABLE reservas (
    paso   STRING PRIMARY KEY,
    estado STRING NOT NULL
);

-- === consulta ===
--   BEGIN;
--     SAVEPOINT cockroach_restart;      -- punto de reintento
--     INSERT INTO reservas VALUES ('vuelo', 'confirmado');
--     INSERT INTO reservas VALUES ('hotel', 'confirmado');
--     RELEASE SAVEPOINT cockroach_restart;
--   COMMIT;
--
SELECT paso, estado FROM reservas ORDER BY paso;

Los que no resuelven este caso — y qué se hace en su lugar

Descartar un motor con un argumento es tan formativo como usarlo. Ninguna de estas filas dice que el motor sea peor: dice que este problema no es el suyo.

Motor Por qué no Qué se hace en su lugar Fuente
Apache Kafka No es un almacén de estado ni ejecuta pasos: es el registro por el que viajan los eventos entre servicios. La saga se coordina con él, pero no se implementa en él. El patrón de bandeja de salida: cada servicio escribe el cambio y el evento en la misma transacción local, y un proceso de captura de cambios publica el evento en Kafka. Así nunca hay un cambio sin evento ni un evento sin cambio, que es el problema que hunde a las sagas escritas a mano. doc

Laboratorio

python scripts/validate_repository.py
python labs/03-transactions/run_transactions_lab.py

Guarda como evidencia la salida completa, la versión del motor y la semilla o los parámetros usados. Una captura sin comando no es evidencia: no se puede repetir.

Evaluación

Criterio Peso Qué se comprueba
Comprensión conceptual 25 % Explica el mecanismo, no solo el resultado
Ejecución reproducible 25 % Otra persona obtiene lo mismo con las instrucciones dadas
Interpretación basada en evidencia 25 % Cada conclusión se apoya en una salida o una medición
Límites y riesgos declarados 25 % Dice qué no demuestra el ejercicio y qué faltaría en producción

La clase se da por superada cuando la respuesta explica el mecanismo, muestra la salida que la respalda y declara al menos un límite del ejercicio.

Fuentes de esta clase

Todo lo afirmado arriba procede de estas obras. Los identificadores viven en catalog/sources.json y el estado de los enlaces se comprueba con python scripts/check_external_links.py.


Programa · Parte 10 · ← Anterior · Siguiente →