TigerBeetle — Banco de Dados Financeiro de Alta Performance em Zig

TigerBeetle — Banco de Dados Financeiro de Alta Performance em Zig

O TigerBeetle é um dos projetos mais ambiciosos e bem-sucedidos construídos com Zig. Trata-se de um banco de dados distribuído projetado especificamente para contabilidade financeira, capaz de processar milhões de transações por segundo com garantias ACID rigorosas. O TigerBeetle demonstra que Zig é capaz de competir com C e C++ em sistemas de missão crítica.

O Que É o TigerBeetle

O TigerBeetle é um banco de dados OLTP (Online Transaction Processing) otimizado para o caso de uso específico de ledgers financeiros — registros contábeis que rastreiam saldos, transferências e transações monetárias. Diferente de bancos de dados genéricos como PostgreSQL ou MySQL, o TigerBeetle é projetado do zero para garantir:

  • Correção absoluta: Nunca perder dinheiro por bugs de software
  • Performance extrema: Milhões de transações por segundo
  • Durabilidade: Dados nunca são perdidos, mesmo em falhas de hardware
  • Auditabilidade: Cada transação é rastreável e imutável

Por Que Zig

A equipe do TigerBeetle escolheu Zig por razões técnicas específicas:

  1. Controle de memória determinístico: Sem GC significa latência previsível
  2. Compilação cruzada: Deploy fácil em qualquer arquitetura
  3. Interoperabilidade C: Reutilização de código existente quando necessário
  4. Segurança de memória em tempo de compilação: Detecção de bugs antes da produção
  5. Performance comparável a C: Sem overhead de runtime

Joren Spit, CEO do TigerBeetle, descreveu a escolha: “Zig nos dá a performance de C com a segurança e produtividade que precisamos para construir infraestrutura financeira confiável.”

Arquitetura

Motor de Armazenamento

O TigerBeetle usa um motor de armazenamento customizado chamado LSM-Forest (Log-Structured Merge Forest), uma variação otimizada do LSM-Tree:

┌─────────────────────────────────┐
│         WAL (Write-Ahead Log)    │
├─────────────────────────────────┤
│         Memtable (RAM)           │
├─────────────────────────────────┤
│         Level 0 (Disco)          │
├─────────────────────────────────┤
│         Level 1 (Disco)          │
├─────────────────────────────────┤
│         Level N (Disco)          │
└─────────────────────────────────┘

Esse design permite:

  • Escritas sequenciais no disco (otimizado para SSDs)
  • Compactação em background sem impactar latência
  • Recuperação rápida após falhas

Consenso Distribuído

O TigerBeetle implementa o protocolo Viewstamped Replication (VSR) para consenso distribuído, garantindo que o cluster continue operando mesmo com falhas de nós:

        ┌──────────┐
        │  Primary  │
        │   (Líder) │
        └────┬─────┘
    ┌────────┼────────┐
    │        │        │
┌───▼──┐ ┌──▼───┐ ┌──▼───┐
│Backup│ │Backup│ │Backup│
│  1   │ │  2   │ │  3   │
└──────┘ └──────┘ └──────┘

O cluster tolera f falhas com 2f + 1 nós. Um cluster de 5 nós tolera 2 falhas simultâneas.

I/O Direto

O TigerBeetle utiliza Direct I/O (O_DIRECT no Linux) e io_uring para I/O assíncrono de alta performance, bypassando o cache do sistema operacional:

// Exemplo conceitual do I/O direto usado pelo TigerBeetle
const file = try std.fs.openFileAbsolute("/data/tigerbeetle.dat", .{
    .mode = .read_write,
    .intended_io_mode = .direct,
});

// Writes alinhados para I/O direto
const aligned_buffer = try std.heap.page_allocator.alignedAlloc(
    u8,
    std.mem.page_size,
    block_size,
);

Modelo de Dados

O TigerBeetle trabalha com dois tipos fundamentais:

Accounts (Contas)

Account {
    id:              u128,  // Identificador único
    debits_pending:  u128,  // Débitos pendentes
    debits_posted:   u128,  // Débitos confirmados
    credits_pending: u128,  // Créditos pendentes
    credits_posted:  u128,  // Créditos confirmados
    user_data_128:   u128,  // Dados customizados
    user_data_64:    u64,
    user_data_32:    u32,
    ledger:          u32,   // Ledger ao qual pertence
    code:            u16,   // Tipo da conta
    flags:           u16,   // Flags de comportamento
    timestamp:       u64,   // Timestamp de criação
}

Transfers (Transferências)

Transfer {
    id:                u128,  // Identificador único
    debit_account_id:  u128,  // Conta debitada
    credit_account_id: u128,  // Conta creditada
    amount:            u128,  // Valor da transferência
    pending_id:        u128,  // ID da transferência pendente
    user_data_128:     u128,
    user_data_64:      u64,
    user_data_32:      u32,
    timeout:           u32,   // Timeout em segundos
    ledger:            u32,
    code:              u16,
    flags:             u16,
    timestamp:         u64,
}

Usando o TigerBeetle

Instalação

# Download do binário
curl -Lo tigerbeetle https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-linux-x86_64
chmod +x tigerbeetle

# Criar arquivo de dados
./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 0_0.tigerbeetle

# Iniciar o servidor
./tigerbeetle start --addresses=3001 0_0.tigerbeetle

Cliente em Zig

const tb = @import("tigerbeetle");

pub fn main() !void {
    var client = try tb.Client.init(
        std.heap.page_allocator,
        0, // cluster_id
        &.{std.net.Address.parseIp4("127.0.0.1", 3001) catch unreachable},
    );
    defer client.deinit();

    // Criar contas
    const accounts = [_]tb.Account{
        .{
            .id = 1,
            .ledger = 700,
            .code = 10,
            .flags = .{},
        },
        .{
            .id = 2,
            .ledger = 700,
            .code = 10,
            .flags = .{},
        },
    };
    _ = try client.createAccounts(&accounts);

    // Criar transferência
    const transfers = [_]tb.Transfer{
        .{
            .id = 1,
            .debit_account_id = 1,
            .credit_account_id = 2,
            .amount = 1000, // R$ 10,00 em centavos
            .ledger = 700,
            .code = 1,
            .flags = .{},
        },
    };
    _ = try client.createTransfers(&transfers);

    // Consultar saldos
    const results = try client.lookupAccounts(&.{1, 2});
    for (results) |account| {
        std.debug.print("Conta {}: débitos={}, créditos={}\n", .{
            account.id,
            account.debits_posted,
            account.credits_posted,
        });
    }
}

Clientes em Outras Linguagens

O TigerBeetle oferece clientes oficiais para diversas linguagens:

  • Go: go get github.com/tigerbeetle/tigerbeetle-go
  • Java: Via Maven Central
  • C#/.NET: Via NuGet
  • Node.js: npm install tigerbeetle-node
  • Python: pip install tigerbeetle

Performance

Os benchmarks do TigerBeetle são impressionantes:

  • ~3.4 milhões de transações/segundo em um único nó
  • Latência p99 < 1ms para operações de transferência
  • Throughput linear com hardware adicional no cluster
  • Zero downtime durante failover de líder

Casos de Uso

O TigerBeetle é usado em cenários como:

  • Fintechs e neobancos: Processamento de pagamentos em tempo real
  • Marketplaces: Split de pagamentos e escrow
  • Exchanges de criptomoedas: Registro de ordens e saldos
  • Sistemas de fidelidade: Pontos e recompensas
  • Jogos online: Economias virtuais

Confira o case detalhado do TigerBeetle para mais informações sobre adoção em produção.

Boas Práticas

  1. Use transferências com dois passos (pending + posting) para operações complexas
  2. Projete seus ledgers cuidadosamente — cada ledger é um namespace isolado
  3. Use user_data para vincular transferências a entidades do seu sistema
  4. Configure cluster com pelo menos 3 réplicas para produção
  5. Monitore latência e throughput com as métricas exportadas pelo TigerBeetle

Próximos Passos

Explore outros drivers de banco de dados do ecossistema Zig, aprenda sobre as bibliotecas de rede que suportam comunicação eficiente, e veja como fintechs usam Zig em produção. Para aprender mais sobre Zig, visite nossos tutoriais.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.