- Apps local-first prometem respostas rápidas e privacidade garantida por padrão, mas têm a limitação de que implementar suporte offline de verdade é extremamente difícil
- O principal motivo é a complexidade da sincronização; quando vários dispositivos alteram os dados ao mesmo tempo, surge o problema de que eles precisam convergir exatamente para o mesmo estado no fim
- Existem dois grandes desafios técnicos: incerteza na ordem temporal e conflitos
- Para resolver isso, é necessário aplicar abordagens de sistemas distribuídos como Hybrid Logical Clocks(HLCs) e CRDTs
- Ao aproveitar extensões baseadas em SQLite, é possível oferecer uma arquitetura de sincronização simples e confiável, utilizável em todas as plataformas
A promessa e a realidade dos apps offline-first
- Apps offline-first se apresentam como capazes de oferecer resposta imediata, privacidade garantida por padrão e uso sem espera de carregamento mesmo em redes instáveis
- Na prática, porém, a maioria dos apps não implementa suporte offline adequadamente; a maior parte apenas salva mudanças temporariamente no dispositivo local e as envia depois quando a conexão volta
- Esse tipo de implementação é pouco confiável e acaba levando a mensagens de aviso como "suas alterações podem não ter sido salvas"
A dificuldade fundamental da sincronização
- Ao criar um app local-first, você inevitavelmente está construindo um sistema distribuído
- Vários dispositivos podem alterar os dados de forma independente enquanto estão offline e, quando se reconectarem depois, precisam convergir exatamente para o mesmo estado
- Para isso, existem dois grandes desafios
- Incerteza na ordem dos eventos
- Conflitos sobre os mesmos dados
1. Incerteza na ordem dos eventos
- Eventos acontecem em momentos diferentes em vários dispositivos, e o estado pode mudar conforme a ordem aplicada
- Exemplo: o dispositivo A define x=3, o dispositivo B define x=5 → após mudanças offline em cada um, a sincronização pode produzir resultados diferentes
- Bancos de dados centralizados tradicionais resolvem isso com consistência forte, mas isso exige sincronização global e não se encaixa em sistemas local-first
- No fim, é preciso determinar a ordem apropriada para cada evento mesmo em um ambiente dinâmico e distribuído; é necessário um modo de ordenar sem depender de um relógio central
Introdução dos Hybrid Logical Clocks(HLCs)
- Hybrid Logical Clocks(HLCs) são um algoritmo simples e eficaz que permite chegar a um acordo prático sobre a ordem dos eventos entre dispositivos individuais
- O HLC combina informação de tempo físico com um contador lógico
- Por exemplo:
- O dispositivo A registra um evento às 10:00:00.100, e o HLC fica em (10:00:00.100, 0)
- O dispositivo B, ao receber a mensagem, eleva o HLC para (10:00:00.100, 1), mesmo que seu relógio esteja atrasado
- Com isso, é possível determinar corretamente a ordem dos eventos independentemente da diferença entre os relógios físicos dos dois dispositivos
2. O problema dos conflitos
- Aplicar a ordem correta não é suficiente; quando dispositivos diferentes modificam os mesmos dados de forma independente, conflitos inevitavelmente surgem
- A maioria dos sistemas exige que o desenvolvedor escreva manualmente o código de resolução de conflitos, o que traz risco de erros e custo de manutenção
Uso de CRDTs
- A melhor abordagem é aplicar Conflict-Free Replicated Data Types(CRDTs)
- CRDTs garantem que independentemente da ordem de sincronização, ou mesmo com aplicações duplicadas, o estado final em cada dispositivo sempre será igual
- A estratégia de CRDT mais simples é Last-Write-Wins(LWW)
- Cada atualização recebe um timestamp
- Na sincronização, o valor com o timestamp mais recente é escolhido
As vantagens do SQLite
- Para construir um app local-first, é essencial ter um banco de dados local leve e confiável, e o SQLite é a escolha ideal
- Ao implementar sincronização com extensões baseadas em SQLite, há os seguintes benefícios
- Aplicar mensagens é simples: consultar o valor atual → sobrescrever se o novo timestamp for mais recente → caso contrário, ignorar
- Essa abordagem garante convergência de estado em todos os dispositivos, independentemente da ordem da sincronização
O significado dessa arquitetura
- Essa estrutura viabiliza uma sincronização simples e confiável
- Confiabilidade sem perda de dados, mesmo após semanas offline
- Característica determinística de sempre convergir para o estado final
- Solução apenas com uma extensão leve de SQLite, sem dependências pesadas
- Suporte a todas as principais plataformas, como iOS, Android, macOS, Windows, Linux e WASM
Recomendações para desenvolvedores
- É preciso evitar abordagens que apenas "simulam" suporte offline com uma fila simples de requisições
- É necessário adotar o conceito de eventual consistency e usar tecnologias comprovadas de sistemas distribuídos como HLC e CRDT
- Em vez de frameworks grandes e complexos, é preferível buscar uma estrutura pequena e sem dependências
- Como resultado, o app pode aproveitar vantagens como execução imediata, uso offline e privacidade garantida por padrão
Referência ao open source SQLite-Sync
- Se houver interesse em um engine offline-first multiplataforma pronto para uso em produção, é possível consultar a extensão open source SQLite-Sync
1 comentários
Opiniões no Hacker News
Cache-Controlcorretamente nas respostas da API e respeitando isso na camada de rede. Assim, mesmo se a validade do cache mudar no servidor, isso pode ser aplicado imediatamente sem atualizar o app