October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk10 min

Pipelines tolerantes a fallos en Python: cómo reanudar desde checkpoints en SQLite

Diseña pipelines en Python que continúan tras una caída: estado durable en SQLite, transacciones explícitas, límites con efectos externos y diferencia entre checkpoint de aplicación y checkpoint WAL.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Para reanudar un pipeline de Python desde SQLite, guarda en una tabla una fila por unidad de trabajo (elemento, página o lote pequeño). Escribe su resultado y su estado «completado» en la misma transacción. Al arrancar, procesa solo las filas que no estén completadas. Eso da un checkpoint de aplicación fiable para el trabajo local. No da «exactly once» para efectos externos como una llamada HTTP o un correo, y no tiene nada que ver con el «checkpoint WAL» de SQLite, que es otra cosa. Este artículo explica las dos ideas, el diseño de la tabla, el código y los límites.

Dos «checkpoints» que no deben confundirse

El término aparece con dos significados distintos en este tema:

As an Amazon Associate I earn from qualifying purchases.

Checkpoint de aplicación Checkpoint WAL de SQLite
Qué es Filas que registran qué pasos terminaron y qué resultados quedaron guardados Copia de páginas desde el archivo WAL al archivo principal de la base de datos
Quién lo define Tu código y tu esquema SQLite (automático o con PRAGMA wal_checkpoint)
Para qué sirve Saber desde dónde continuar tras una caída Mantener el WAL acotado y el archivo principal al día
Afecta a la reanudación Sí, es la reanudación No: los datos confirmados son legibles esté o no hecho el checkpoint WAL

Para la durabilidad, la documentación de SQLite («SQLite Is Transactional») afirma: «SQLite implements serializable transactions that are atomic, consistent, isolated, and durable, even if the transaction is interrupted by a program crash, an operating system crash, or a power failure to the computer.» Esa garantía cubre las transacciones de la base de datos. No se extiende a lo que tu programa haga fuera de ella.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Paso 1: define la unidad reanudable

Antes de escribir código, decide qué es lo mínimo que puedes volver a hacer sin dolor. Hay tres opciones habituales:

  • Un elemento: máxima granularidad. Tras una caída repites como mucho un elemento, pero haces más commits.
  • Una página de entrada: equilibrio natural si la fuente ya pagina (API, CSV por bloques).
  • Un lote pequeño: menos commits, pero una caída obliga a repetir todo el lote en curso.

La regla práctica: elige el tamaño según cuánto trabajo (y cuántos efectos externos) puedes permitirte repetir. Si repetir es caro o peligroso, baja la granularidad.

Paso 2: diseña la tabla de estado

Guarda una identidad estable de la ejecución, la clave del elemento, el estado, el resultado que necesites reutilizar, un contador de intentos con diagnóstico y una marca de actualización. Una restricción UNIQUE impide que la misma unidad aparezca dos veces.

CREATE TABLE IF NOT EXISTS runs (
    run_id           TEXT PRIMARY KEY,
    pipeline_version INTEGER NOT NULL,
    created_at       TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS items (
    run_id     TEXT NOT NULL REFERENCES runs(run_id),
    item_key   TEXT NOT NULL,
    status     TEXT NOT NULL DEFAULT 'pending'
               CHECK (status IN ('pending','running','done','failed')),
    result     TEXT,
    attempts   INTEGER NOT NULL DEFAULT 0,
    last_error TEXT,
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    UNIQUE (run_id, item_key)
);

Si el trabajo cambia entre versiones del código, guarda pipeline_version (o la versión del esquema) y decide por escrito si un estado antiguo puede reanudarse o debe invalidarse. Reanudar con resultados calculados por una lógica distinta es una fuente silenciosa de datos incoherentes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2

Paso 3: transacciones explícitas en Python

La documentación actual de Python recomienda el atributo autocommit de sqlite3.connect(), disponible desde Python 3.12. Con autocommit=False se sigue el comportamiento de PEP 249: siempre hay una transacción abierta y el programa confirma o revierte. Con isolation_level se conserva el comportamiento anterior (LEGACY_TRANSACTION_CONTROL), y su valor por omisión depende del modo y de la versión.

Los ejemplos de aquí usan Python 3.12 o superior con autocommit=True y transacciones BEGIN IMMEDIATE escritas a mano. Es una elección propia, por dos motivos: PRAGMA journal_mode=WAL no puede cambiarse dentro de una transacción (con autocommit=False ya habría una abierta), y BEGIN IMMEDIATE toma el bloqueo de escritura desde el principio, de modo que el tiempo de espera se aplica de forma predecible. En versiones anteriores, isolation_level=None da un comportamiento equivalente.

import json
import sqlite3
from contextlib import contextmanager

def connect(path: str) -> sqlite3.Connection:
    conn = sqlite3.connect(path, autocommit=True, timeout=10.0)
    conn.execute("PRAGMA journal_mode=WAL")   # persistente en el archivo
    conn.execute("PRAGMA busy_timeout=10000")
    conn.execute("PRAGMA foreign_keys=ON")
    return conn

@contextmanager
def tx(conn):
    conn.execute("BEGIN IMMEDIATE")
    try:
        yield conn
    except BaseException:
        conn.execute("ROLLBACK")
        raise
    else:
        conn.execute("COMMIT")

Paso 4: el bucle que se puede reanudar

Al arrancar, el proceso (único trabajador en este ejemplo) devuelve a pending las filas que quedaron en running por una caída anterior, siembra los elementos que falten y procesa lo pendiente.

PIPELINE_VERSION = 3

def start_or_resume(conn, run_id, keys):
    with tx(conn):
        row = conn.execute(
            "SELECT pipeline_version FROM runs WHERE run_id=?", (run_id,)
        ).fetchone()
        if row is None:
            conn.execute(
                "INSERT INTO runs(run_id, pipeline_version) VALUES (?,?)",
                (run_id, PIPELINE_VERSION),
            )
        elif row[0] != PIPELINE_VERSION:
            raise RuntimeError(
                f"Estado de la versión {row[0]}; el código es la {PIPELINE_VERSION}. "
                "Invalida o migra el estado antes de reanudar."
            )
        conn.executemany(
            "INSERT OR IGNORE INTO items(run_id, item_key) VALUES (?,?)",
            [(run_id, k) for k in keys],
        )
        conn.execute(
            "UPDATE items SET status='pending' "
            "WHERE run_id=? AND status='running'", (run_id,)
        )

def run(conn, run_id, transform):
    while True:
        with tx(conn):
            row = conn.execute(
                "SELECT item_key FROM items "
                "WHERE run_id=? AND status='pending' ORDER BY item_key LIMIT 1",
                (run_id,),
            ).fetchone()
            if row is None:
                return
            key = row[0]
            conn.execute(
                "UPDATE items SET status='running', attempts=attempts+1, "
                "updated_at=datetime('now') WHERE run_id=? AND item_key=?",
                (run_id, key),
            )
        try:
            result = transform(key)          # trabajo fuera de la transacción
        except Exception as exc:
            with tx(conn):
                conn.execute(
                    "UPDATE items SET status='failed', last_error=?, "
                    "updated_at=datetime('now') WHERE run_id=? AND item_key=?",
                    (repr(exc), run_id, key),
                )
            continue
        with tx(conn):                        # resultado + marca, juntos
            conn.execute(
                "UPDATE items SET status='done', result=?, last_error=NULL, "
                "updated_at=datetime('now') WHERE run_id=? AND item_key=?",
                (json.dumps(result), run_id, key),
            )

Puntos que importan:

  • El resultado y done se escriben en una sola transacción. Tras una caída verás ambos o ninguno; nunca un «hecho» sin resultado.
  • El trabajo pesado queda fuera de la transacción. Mantener transacciones de escritura breves evita bloquear a otros escritores.
  • Los pasos siguientes leen de la base, no de memoria. Así un reinicio y una ejecución normal siguen el mismo camino.
  • Las filas failed no se reintentan solas. Decide tu política (reintentar con límite de attempts, o revisión manual) y haz que sea explícita.

Si hay varios procesos trabajadores sobre el mismo archivo, la reclamación de filas necesita más cuidado (por ejemplo, identificar al dueño de cada running y caducar reclamos antiguos). El reinicio «todo running vuelve a pending» de arriba solo es correcto con un único trabajador activo.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

El límite que SQLite no resuelve: efectos externos

Una transacción de SQLite no incluye una llamada HTTP, un correo ni una escritura en otro sistema. Considera esta secuencia: envías el pago o el correo, el proceso muere y aún no has guardado «hecho». Al reanudar, el elemento sigue pendiente y el efecto se repite. Guardar un cursor no convierte esto en «exactly once»; solo reduce el trabajo repetido.

Opciones, de más a menos robusta:

  1. Idempotency key remota. Si el servicio la admite, deriva la clave de la identidad estable del trabajo (por ejemplo f"{run_id}:{item_key}"), envíala en cada intento y guarda la respuesta junto al estado. Un reintento devolverá el resultado original en lugar de duplicar el efecto, según cómo implemente el servicio la idempotencia.
  2. Deduplicación en el destino. Escribe con una clave natural (upsert) para que repetir sea inocuo.
  3. Protocolo coordinado. Si ambos lados lo permiten, registra la intención antes del efecto y confirma después, con un paso de reconciliación para las intenciones sin confirmar.
  4. Sin idempotencia posible. Asume que puede repetirse, o exige reconciliación manual de los elementos ambiguos, y comunícalo a quien use el pipeline.

Estos son patrones de diseño de aplicación, no garantías automáticas de SQLite.

Un elemento, una página o un lote: qué se repite tras una caída

Granularidad Trabajo que puede repetirse Coste de commits Cuándo encaja
Cada elemento Un elemento Más commits y más escrituras serializadas Efectos externos caros o con riesgo
Página de entrada Una página Intermedio Fuentes paginadas
Lote pequeño Todo el lote en curso Menos commits Transformaciones locales baratas y repetibles

Rollback journal frente a WAL

SQLite usa por omisión un rollback journal. El modo WAL (PRAGMA journal_mode=WAL) cambia la forma en que se registran los cambios y, en muchos casos, permite que lectores y un escritor avancen a la vez. Para un pipeline resulta útil cuando otro proceso o un panel de monitorización consulta el progreso mientras el pipeline escribe.

Rollback journal WAL
Lectura y escritura simultáneas Más limitadas Lectores y escritor pueden progresar a la vez en muchos casos
Escritores Uno a la vez Uno a la vez: SQLite sigue serializando escritores
Entorno Más permisivo Todos los procesos en el mismo host; no sirve entre máquinas por sistema de archivos de red
SQLITE_BUSY Posible Sigue siendo posible en ciertos escenarios

Por eso hay que configurar una espera (timeout o busy_timeout), limitar los reintentos y registrar si un fallo parece un bloqueo temporal o un error permanente. Si necesitas coordinar varios hosts, WAL de SQLite no ofrece esa coordinación: necesitas un servidor de base de datos o una cola diseñada para ello.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Checkpoints WAL: qué hacen y cuándo vigilarlos

Un checkpoint WAL copia cambios del WAL al archivo principal. Según la documentación de WAL de SQLite, por defecto se inicia un checkpoint automático cuando un COMMIT hace que el WAL alcance 1000 páginas. Es un valor predeterminado documentado, no una recomendación universal ni el resultado de un estudio.

El problema real es que los lectores de larga duración pueden impedir que el checkpoint termine y que el WAL se reinicie, con lo que el archivo crece. Controla el tamaño del archivo -wal y la duración de las transacciones de lectura (cierra cursores y no dejes lecturas abiertas durante procesos largos).

Modo Comportamiento resumido Úsalo si…
PASSIVE Copia lo que puede sin esperar a lectores ni escritores No toleras bloqueos; aceptas que quede trabajo pendiente
FULL Espera a que no haya escritores y lectores que lo impidan, y completa el checkpoint Puedes tolerar una breve espera
RESTART Como FULL, y además espera a que los lectores usen ya el archivo principal para que el siguiente escritor reinicie el WAL Quieres que el WAL se reutilice desde el principio
TRUNCATE Como RESTART, y además trunca el archivo WAL a cero bytes Necesitas recuperar espacio en disco, p. ej. al terminar la ejecución
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()
# devuelve (busy, páginas_en_wal, páginas_checkpointed); busy=1 indica que no terminó

Un patrón razonable: deja el checkpoint automático por defecto durante la ejecución y lanza un TRUNCATE al terminar, comprobando el valor busy. Ninguno de estos modos cambia qué pasos están «hechos»: eso lo dicen solo tus filas.

Respaldos de una base activa

No copies a ciegas el archivo de una base en uso: con WAL, parte de los datos confirmados puede estar aún en el archivo -wal. Dos alternativas:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Online Backup API, expuesta en Python como Connection.backup().
  • VACUUM INTO 'copia.db', que genera una copia compacta.
import sqlite3
src = sqlite3.connect("pipeline.db")
dst = sqlite3.connect("pipeline-backup.db")
with dst:
    src.backup(dst)
dst.close(); src.close()

Si prefieres copiar archivos, incluye el WAL y sigue un procedimiento consistente. En cualquier caso, prueba la restauración: abre la copia, ejecuta PRAGMA integrity_check y reanuda el pipeline con ella.

Durabilidad y velocidad

Cada commit durable implica sincronizar a disco, y eso es el coste dominante de guardar elemento a elemento. El ajuste PRAGMA synchronous cambia ese equilibrio; antes de relajarlo, consulta en la documentación de SQLite qué garantías pierdes ante un corte de energía frente a una caída del proceso. Para un checkpoint de aplicación, perder el último commit equivale a repetir esa unidad, así que decide con eso en mente.

Lista de comprobación antes de ponerlo en producción

  • La unidad reanudable está definida y tiene clave estable con restricción UNIQUE.
  • Resultado y estado «completado» se escriben en la misma transacción.
  • Los efectos externos usan idempotency key, deduplicación o reconciliación; los límites están documentados.
  • La versión del pipeline se guarda y existe una política para estados antiguos.
  • Hay espera y reintentos limitados para SQLITE_BUSY, con registros que distinguen bloqueo temporal de fallo permanente.
  • Los lectores no dejan transacciones largas abiertas; se vigila el tamaño del -wal.
  • Todos los procesos están en el mismo host.
  • El respaldo usa Backup API o VACUUM INTO, y la restauración se ha probado.
  • Probaste la reanudación matando el proceso (kill -9) en mitad de un elemento y comprobaste que el resultado final es el esperado.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.