TigerBeetle: la base de datos financiera en tiempo real escrita en Zig

TigerBeetle no es una base de datos de propósito general. Es una base de datos financiera diseñada para un caso de uso muy concreto: registrar transferencias de dinero con contabilidad de doble entrada en tiempo real, con las garantías de consistencia que el sector financiero exige. Está escrita íntegramente en Zig y lleva en producción desde 2022. Es probablemente el proyecto en Zig más significativo que existe hoy en producción real.

Qué es la contabilidad de doble entrada

La contabilidad de doble entrada es el sistema que usan todos los bancos y empresas financieras desde el siglo XV. Cada transacción tiene dos partes: un débito y un crédito. La cuenta que pierde dinero se debita y la que gana se acredita. La suma de todos los débitos siempre debe igualar la suma de todos los créditos.

Implementar esto correctamente en una base de datos convencional como PostgreSQL es posible, pero requiere gestionar la concurrencia, los bloqueos y la consistencia a mano. TigerBeetle convierte esto en una primitiva nativa: las operaciones de la base de datos son directamente transferencias entre cuentas, y la consistencia es una garantía del sistema, no algo que el aplicativo deba gestionar.

Por qué Zig

El equipo de TigerBeetle eligió Zig por varias razones concretas. Sin GC significa que la latencia es predecible: no hay pausas inesperadas del garbage collector que puedan afectar tiempos de respuesta en sistemas financieros donde cada milisegundo importa. El control explícito de la memoria permite gestionar exactamente cómo se organizan los datos en disco y en memoria para optimizar los patrones de acceso del motor de almacenamiento.

La interop nativa con C permitió reutilizar código existente de sistemas de almacenamiento y protocolos de consenso sin tener que portarlo. Y comptime se usa extensivamente para generar código de serialización y validación en tiempo de compilación.

Arquitectura de TigerBeetle

TigerBeetle usa un motor de almacenamiento propio llamado LSM (Log-Structured Merge-tree), similar al que usa RocksDB pero escrito en Zig y adaptado para los patrones de acceso específicos de datos financieros. El sistema de consenso es Viewstamped Replication, una alternativa a Raft con propiedades ligeramente diferentes para sistemas de alta disponibilidad.

El modelo de datos de TigerBeetle es intencionalmente simple:

  • Account: una cuenta con saldo. Tiene campos para saldo pendiente y saldo contabilizado, separados para gestionar transferencias en dos fases.
  • Transfer: una transferencia entre dos cuentas. Puede ser simple (inmediata) o en dos fases (con una fase de pending y una de post).

No hay tablas, no hay SQL, no hay esquemas flexibles. El API es binario y muy específico.

Cómo usar TigerBeetle desde una aplicación

TigerBeetle tiene clientes oficiales para varios lenguajes. El siguiente ejemplo usa el cliente de Node.js, pero la estructura es la misma en todos:

# Descargar y arrancar TigerBeetle
wget https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-x86_64-linux.zip
unzip tigerbeetle-x86_64-linux.zip

# Crear un fichero de datos (el cluster 0 con un solo nodo)
./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 0_0.tigerbeetle

# Arrancar el servidor
./tigerbeetle start --addresses=3000 0_0.tigerbeetle
// Ejemplo con cliente JavaScript
const { createClient } = require("tigerbeetle-node");

async function main() {
    const client = createClient({
        cluster_id: 0n,
        replica_addresses: ["3000"],
    });

    // Crear dos cuentas
    const errores = await client.createAccounts([
        {
            id: 1n,
            ledger: 1,      // divisa o tipo de cuenta
            code: 718,      // código de tipo de cuenta (definido por el aplicativo)
            flags: 0,
            user_data_128: 0n,
            user_data_64: 0n,
            user_data_32: 0,
        },
        {
            id: 2n,
            ledger: 1,
            code: 718,
            flags: 0,
            user_data_128: 0n,
            user_data_64: 0n,
            user_data_32: 0,
        },
    ]);

    if (errores.length > 0) {
        console.error("Error creando cuentas:", errores);
        return;
    }

    // Transferir 100 unidades de la cuenta 1 a la cuenta 2
    const erroresTransf = await client.createTransfers([
        {
            id: 1n,
            debit_account_id: 1n,
            credit_account_id: 2n,
            amount: 100n,
            ledger: 1,
            code: 720,
            flags: 0,
            user_data_128: 0n,
            user_data_64: 0n,
            user_data_32: 0,
            timeout: 0,
            pending_id: 0n,
        },
    ]);

    if (erroresTransf.length > 0) {
        console.error("Error en transferencia:", erroresTransf);
        return;
    }

    // Consultar los saldos
    const cuentas = await client.lookupAccounts([1n, 2n]);
    for (const cuenta of cuentas) {
        console.log(`Cuenta ${cuenta.id}: débito=${cuenta.debits_posted}, crédito=${cuenta.credits_posted}`);
    }

    client.destroy();
}

main().catch(console.error);

Rendimiento y garantías

TigerBeetle publica benchmarks de hasta un millón de transferencias por segundo en hardware moderno. La latencia de escritura es muy baja porque el diseño evita las capas de abstracción innecesarias: no hay ORM, no hay capa de SQL que parsear, no hay query planner. Cada operación es directamente una escritura o lectura estructurada en el motor de almacenamiento.

Las garantías de consistencia son totales: TigerBeetle nunca pierde una transferencia confirmada, incluso ante fallos de disco o reinicios inesperados del sistema. El diseño de las escrituras es determinista y el replay del log siempre produce el mismo resultado.

Cuándo usar TigerBeetle

TigerBeetle tiene sentido cuando el núcleo del negocio es mover dinero: fintechs, pasarelas de pago, sistemas de clearing, wallets digitales. Para esos casos, gestionar la contabilidad encima de una base de datos de propósito general (PostgreSQL, MySQL) implica construir muchas garantías que TigerBeetle da de forma nativa.

No es para todos los casos. Si tu aplicación tiene datos financieros pero no son el núcleo del sistema, probablemente no necesitas un motor especializado. Pero si el volumen de transacciones es alto y la consistencia financiera es crítica, TigerBeetle es una opción seria.

TigerBeetle es también uno de los mejores ejemplos de por qué Zig existe: código de sistemas de baja latencia donde cada detalle de la gestión de memoria importa. Si quieres profundizar en cómo Zig gestiona la memoria en proyectos así, el artículo sobre allocators y arenas explica el modelo en detalle, y el artículo de introducción a Zig da el contexto completo del lenguaje.

Imagen: Pexels / panumas nikhomkhai

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP