Na Yooga, os terminais POS precisam funcionar offline. Restaurante não pode parar de tirar pedido porque o WiFi caiu. O terminal tem banco local, processa transação localmente e sincroniza com a nuvem quando tem internet. Fazer isso funcionar com dinheiro de verdade dá bem mais trabalho do que parece.
O desafio central é resolução de conflito. Terminal A vende a última picanha enquanto tá offline. Terminal B, também offline, vende a mesma última picanha. As duas transações são válidas localmente. Quando sincronizam, tu tem um conflito: duas vendas pro mesmo item. Num app de chat tu mergeia as duas mensagens e foda-se. Num sistema POS tu acabou de vender estoque que não existe. Alguém vai ficar sem picanha.
A gente explorou CRDTs no começo. Conflict-free Replicated Data Types são lindos na teoria. Cada terminal mantém o próprio estado, operações são comutativas e associativas, merge é automático. Pra coisa tipo metadado de pedido, nota de cliente e mudança de config, CRDT funciona muito bem. A gente usa Last-Writer-Wins pra maioria das configs e Grow-Only Set pros logs de auditoria.
CRDT desmorona pra dado de estoque e financeiro. Um counter CRDT rastreia incrementos e decrementos de múltiplos nós, mas pode ficar negativo. Em estoque, isso significa vender o que não tem; na conciliação financeira, livros errados. Esses domínios precisam de coordenação, o que exige estar online e contraria o propósito do offline-first.
Nossa solução é híbrida. Dado não crítico usa CRDT e sincroniza automático. Dado de estoque e financeiro usa abordagem otimista local-first com reconciliação server-side. O terminal registra a venda localmente com status "pendente_sync." Quando volta online, manda a transação pro servidor. O servidor valida contra o estado atual. Se o estoque tá disponível, confirma. Se não, dispara fluxo de reconciliação que envolve o gerente do restaurante.
O protocolo de sync usa vector clock pra ordenação. Cada terminal mantém um relógio lógico que incrementa a cada operação local. Na hora de sincronizar, o terminal manda o relógio e o servidor compara com o último estado conhecido. Operações são replayadas em ordem causal. Isso é importante porque "adicionar item ao pedido" precisa acontecer antes de "aplicar desconto ao pedido" mesmo se chegarem fora de ordem.
Batching é crítico pra performance. Restaurante lotado gera centenas de operações por hora por terminal. Sincronizar uma por uma ia destruir a rede e o servidor. A gente agrupa operações em chunks de 50 ou janelas de 30 segundos, o que vier primeiro. O batch é comprimido, assinado com a chave do terminal e enviado como payload único.
Consistência eventual funciona pra 95% das operações. Ninguém nota 30 segundos de delay numa atualização de cardápio, atribuição de mesa ou escala de funcionário. Confirmação de pagamento precisa de consistência forte. Quando o cliente paga, o terminal manda o evento com requisito de confirmação síncrona. Se não alcança o servidor, enfileira o pagamento como "confirmacao_pendente" e imprime uma observação no cupom. É um custo na UX que prefiro a processar pagamento no escuro.
Esse sistema tá rodando há mais de um ano. Taxa de conflito abaixo de 0.3%. A maioria dos conflitos é de estoque e resolve automático porque o restaurante tinha mais estoque do que o sistema mostrava. O resto vai pra um dashboard onde o gerente resolve na mão. Não é perfeito, mas funciona em escala e funciona quando o WiFi inevitavelmente morre no rush de sábado à noite.