0003 — Coleta coerente: ler duas vezes e conferir¶
Decisão obsoleta — superada pela decisão 0004
Status: Obsoleto · Responsável: Gustavo Madruga · Atualizado em: 2026-09-08 · Decidido em: 2026-09-08
Obsoleta desde 2026-09-09, superada pela 0004. A premissa desta decisão estava errada: ela trata o pedido órfão como tendo UMA causa (o sync aterrissando entre os SELECTs) e conclui que ler duas vezes basta. Há um segundo modo — a linha que ainda não chegou ao banco local — em que as duas leituras concordam e o lote sai órfão do mesmo jeito. Foi o que três incidentes medidos mostraram (
260079421,260079618,260078864). A leitura dupla permanece no código, agora como rede do caminho por presença; o que caiu foi a premissa de que ela era suficiente.
Contexto¶
O contrato pAbast (§3.7) manda escrever o lote na ordem de dependência referencial
(propriedades → fones → estoque → contratos → itensped → formulas), e o Fluxo já garantia
essa ordem. Só que a ordem garante o arquivo, não o conteúdo: nada obrigava o cliente a
ter as linhas do pai no lote.
Incidente do pedido 260079421 (2026-09-08). O coletarPendentes fazia um SELECT por
tabela. O PowerSync grava no SQLite local a qualquer momento — inclusive no meio da coleta — e o
Agendador acorda justamente no gatilho de mudança, ou seja, coleta enquanto o dado aterrissa.
Medido no log:
12:14:39.018 push do pedido aplicado no servidor (6 tabelas, 1 transação)
12:14:39.073 coleta: SELECT propriedades/fones/estoque → o pedido ainda não chegou
...as linhas aterrissam no SQLite...
coleta: SELECT contratos/itensped/formulas → chegaram
12:14:43 próximo ciclo pega propriedades/fones/estoque
O arquivo saiu com o pedido sem o cliente. O ZIM cadastrou cliente e produto no lote seguinte,
mas o pedido já tinha levado 120 -Cliente CPF ... não cadastrado. — e 1XX é terminal, não
retenta. Pedido órfão, sem caminho de volta pelo cliente.
Do lado servidor não havia o que corrigir: o applyPut do integrador-server aplica o payload
inteiro em uma transação e o PowerSync tem um stream só com as 6 tabelas. A publicação já
era atômica; quem quebrava a atomicidade era a leitura.
Decisão¶
Ler duas vezes e só aceitar o lote quando as duas leituras batem (Processador.coletarPendentes).
Duas leituras iguais significam que nada aterrissou entre elas — logo a segunda é uma foto
coerente. Diferentes significam que aterrissou: vale a segunda e confere de novo, até três vezes.
Banco que não sossega devolve lote vazio e espera o próximo ciclo; o dado não some, só espera
um momento mais calmo.
A comparação é por id + cod_retorno na ordem — o par que decide quem entra no lote. Mudança só
de campo de dado (o servidor reescrevendo um valor de linha que já estava lá) não é detectada: ela
não cria pedido órfão, que é o que esta conferência existe para impedir.
O laço de leitura (lerTodasAsTabelas) é exatamente o de antes. O custo é uma passada extra de
SELECTs por ciclo, em tabelas locais pequenas — irrelevante perto de rodar o zimrtmu.exe.
O que foi descartado¶
- Transação de leitura no wrapper (
readTransactiondo SDK). Resolveria igual, mas oPowerSyncWrapperé ponte e ponte fica magra — regra da casa, no cabeçalho da própria classe. SELECTcomposto comUNION ALLdas 6 tabelas (um statement = um instante). Correto e sem tocar no wrapper, mas exige padding deNULLaté a maior largura e colunas de controle: SQL gerado bem mais difícil de ler que o laço de duas leituras, para um público que programa em ZIM.executar("BEGIN")…COMMIT")em volta dos SELECTs. Não funciona: o SDK avisa que statement no nível do banco pode cair em conexão diferente (pool de leitura) — o BEGIN abriria transação numa conexão e os SELECTs rodariam noutra, sem snapshot e deixando transação órfã.- Gate de dependência referencial (segurar a linha cujo pai não está gravado nem no lote).
Chegou a ser implementado e foi removido: resolvia o mesmo bug que a leitura dupla já
resolve, ao custo de ~100 linhas, cinco regras FK — uma delas apoiada numa suposição sobre o dado
(
formulas→ kit) — e de um modo de falha novo: filho de pai morto em1XXficava segurado indefinidamente, com só um INFO por ciclo. Duas mecânicas para um bug é uma a mais.
Consequências¶
- Um pedido cujo cliente ainda não chegou não sai — sai inteiro no ciclo seguinte.
- Ciclo com o banco recebendo sync sem parar é adiado, não processado pela metade. Fica no log
(
coleta não estabilizou em N leituras). - Não há defesa contra órfão de causa diferente desta (ex.: o servidor publicar um pedido sem o
cliente). Se aparecer, o
1XXdo ZIM denuncia — e aí a decisão se revisita com o caso na mão, não por antecipação.
Lacuna conhecida (fora deste repo)¶
Uma linha em 1XX não tem caminho de volta: o re-importar do painel PIED reenvia o payload, mas o
upsert do integrador-server preserva o write-back por regra ("mão única") e não toca o
cod_retorno — a linha segue 1XX e o cliente nunca a recoleta. O botão do painel, hoje, não
re-arma nada. Correção pertence ao par int-pied/integrador-server; enquanto não vier, o desempate é
UPDATE ... SET cod_retorno = '000' na mão.