3 pontos por GN⁺ 5 시간 전 | Ainda não há comentários. | Compartilhar no WhatsApp
  • Até poucos anos atrás, a sincronização de dados multijogador em tempo real era um dos problemas mais difíceis, exigindo especialistas e investimento em nível empresarial, mas agora dá para implementar uma UI multijogador até em projetos de hobby com um único npm install
  • Automerge é uma ferramenta para construir modelos de dados com prioridade local, segurança para multijogador e controle de versões; ela lida automaticamente com persistência de dados, gestão de histórico, broadcast para colaboradores e resolução de conflitos de um jeito parecido com o padrão useState do React, sem que a UI precise se preocupar com isso
  • No caso do editor de áudio multijogador baseado em navegador Ducking, o ponto-chave é projetar o modelo de dados para que ele se mapeie naturalmente para operações de CRDT
  • Em casos que o Automerge não garante, como reordenação de listas, é preciso implementar invariantes mais fortes diretamente com código na camada da aplicação
  • O significado central é que a edição colaborativa em tempo real, que antes parecia magia de nível industrial, agora pode ser aplicada livremente até em pequenos apps para poucos usuários

Contexto — projeto Ducking

  • Nos últimos meses, foi criado o Ducking, um editor de áudio multijogador baseado em navegador, para o podcast da parceira do autor
  • Parecia estranho que a edição de áudio ainda estivesse presa a apps desktop de usuário único com 20 anos de idade e à troca manual de arquivos
    • Enquanto uma pessoa edita clipes, outra deveria poder corrigir a transcrição ou ajustar configurações de EQ, em um fluxo de trabalho colaborativo como Google Docs ou Figma
    • Ferramentas modernas de colaboração, como comentários, histórico e rastreamento de mudanças, também eram necessárias
  • O design de UI peculiar e o modelo de layout de áudio discutidos em um texto anterior tornaram o editor individual mais eficaz, mas o que realmente se queria era um fluxo de trabalho mais colaborativo

Como o Automerge funciona

  • Todos os dados do Ducking, exceto os blobs de áudio, são armazenados em um documento Automerge
  • O padrão central é familiar para desenvolvedores React: busca-se os dados com um hook, renderiza-se a UI e despacha-se uma solicitação assíncrona de mudança; quando os dados mudam, o hook dispara um rerender
    • Exemplo com o hook useDocument: recebe-se o documento como const [doc, changeDoc] = useDocument<Episode>(docUrl) e, ao mudar um campo de entrada, atualiza-se com changeDoc((d) => { d.title = e.target.value })
  • As operações de atualização parecem imperativas, mas não funcionam como objetos e arrays nativos de JS
    • Há menos métodos, não há mutação imediata, e o sistema intercepta as mudanças para convertê-las em itens de changelist do histórico do documento
  • Em usos simples, o Automerge cuida do necessário, mas não é magia; como seus invariantes nem sempre coincidem com o significado desejado, o projeto cuidadoso do modelo de dados é importante
    • A maior parte das ações semânticas do usuário deve corresponder a uma única operação oferecida pelo Automerge
    • Ações separadas do usuário sobre dados relacionados devem se resolver naturalmente do ponto de vista dos invariantes daquela operação do Automerge
    • É importante separar claramente os dados canônicos armazenados dos dados derivados calculados

Modelagem de dados para multijogador

  • No modelo de dados do Ducking, um clip é uma janela que reproduz parte de uma fonte de áudio base imutável, sendo responsável pelo trecho reproduzido, pela aplicação de efeitos e pelo espaço ocupado na timeline
    • O efeito mais comum é o clip ajustar o volume do áudio base ao longo do tempo para fazer crossfade ou remover ruído
  • No início, cada clip tinha uma lista de níveis de volume indexados por tempo relativo ao início do clip, mas isso gerava problemas porque a maioria das mudanças de volume dizia respeito ao áudio base, não ao clip
    • Se o início do clip fosse adiantado um pouco, todas as mudanças de volume passavam a ser aplicadas a outra parte do áudio
    • Escrever código para atualizar todos os timestamps de volume sempre que o início do clip mudasse seria uma má escolha
  • Se dois colaboradores editassem ao mesmo tempo o início do clip, cada edição agruparia a mudança do início com a atualização de todos os timestamps da automação de volume
    • O Automerge não conhece a relação causal entre essas mudanças e, ao fazer merge, elas poderiam se resolver de forma caótica
    • Esse é um problema típico de quando uma única ação semântica tenta atualizar vários dados persistidos de uma forma causal que o CRDT não entende
  • A solução foi migrar os dados de efeitos de áudio para o referencial temporal do áudio base, em vez de deixá-los no clip
    • Assim, não é mais necessário atualizar nada ao mudar o início ou a duração do clip, e múltiplos editores podem alterar início, automação de volume e outros efeitos de forma mais independente, com maior chance de merge correto
  • Diferença entre UI de usuário único e UI multijogador
    • Em uma UI de usuário único, às vezes dá para manter o modelo de dados existente e compensar isso com cálculo extra na hora de gravar
    • Em uma UI multijogador, migrar o modelo de dados é muito mais comum para manter todos os dados persistidos em estado ortogonal
  • Passa-se a preferir fortemente simplificar a escrita e calcular mais na leitura, maximizando o uso do merge automático do Automerge
  • Conselhos sobre migração de formatos de dados
    • Vale aceitar que formatos de dados precisarão ser migrados ao longo do desenvolvimento e praticar isso cedo para não ter medo da primeira grande migração
    • Existem vários padrões, como tratamento no momento da leitura pelo cliente ou upgrades em lote no servidor
    • Se houver um invariante conveniente para verificar que antes e depois da migração são equivalentes, o trabalho fica muito mais fácil
    • No Ducking, exportava-se o áudio de todos os projetos antes e depois da migração para verificar mudanças com uma impressão digital de áudio, o que tornou segura até a implantação de grandes mudanças de esquema

Implementando reordenação de listas

  • Às vezes, para obter garantias que o Automerge não oferece, é necessário escrever invariantes mais fortes diretamente com código na camada da aplicação
  • Isso aconteceu ao implementar a magnetic timeline do Ducking, uma lista ordenada dos clips a reproduzir
    • O Automerge oferece operações de array para remover e inserir itens por índice, mas não fornece uma operação para reordenação atômica de itens existentes
  • Há soluções conhecidas
    • Martin Kleppmann publicou um artigo sobre operação atômica de reordenação de listas
    • Também publicou com Liangrun Da o artigo "Extending JSON CRDTs with Move Operations"
    • Existe ainda um draft PR para adicionar isso ao Automerge, mas ele ainda não foi mesclado
  • Problema da forma simples de reordenar
    • Remover o objeto do índice atual e adicioná-lo de novo no índice de destino
    • Mesmo combinando os invariantes dessas duas operações, isso não garante o invariante desejado de que, mesmo com muitas reordenações concorrentes, o objeto apareça exatamente uma vez na lista
    • Se houver várias remoções e inserções concorrentes, o objeto pode aparecer em vários lugares da lista ao mesmo tempo; se Alice e Bob moverem B com delete+insert, as duas remoções viram um único tombstone, mas as duas inserções criam novos elementos distintos, então B aparece duas vezes
  • Implementação própria do invariante de "exatamente uma vez" na camada da aplicação
    • Quando um clip é inserido na timeline, recebe um semantic id
    • Na reordenação, são disparadas as operações de remoção e inserção descritas acima
    • Na leitura, a aplicação procura duplicatas com o mesmo semantic id, escolhe arbitrariamente o primeiro item não removido e ignora os demais
    • Assim, o objeto aparece apenas uma vez na lista e todos os leitores chegam sempre ao mesmo estado final
  • A reordenação de listas foi a única operação de que o Ducking precisou e que o Automerge não oferecia; quando o PR for mesclado, essa lógica em nível de aplicação poderá deixar de ser necessária

Histórico do documento

  • Uma boa UI multijogador precisa de ferramentas de gestão de histórico; colaboradores querem ver o que mudou enquanto estiveram fora, comentar diffs, comparar versões antigas e fazer rollback
  • O Automerge rastreia o histórico de versões do documento e oferece excelentes primitivas para lidar com histórico e comparação
    • Mas cabe ao desenvolvedor da aplicação decidir como expor isso e quais conceitos apresentar ao usuário
  • Recomendação das Patchwork lab notes da Ink & Switch
    • Especialmente interessantes são o trabalho de expor branches aos usuários e o de comentários universais
  • O Ducking acabou adotando um modelo relativamente simples de colaboração e histórico
    • Um histórico linear de versões com checkpoints nomeados pelo usuário, em que o checkpoint é a unidade de agrupamento de mudanças e também a unidade de discussão, diff e rollback
    • Threads de comentários que podem ser ligadas a um ponto específico do áudio, a uma região da transcrição ou a um checkpoint de versão
  • Ainda não houve motivo suficiente para introduzir branches, mas isso pode ser útil no futuro

Texto e marks

  • Trabalhar com texto rico é especialmente complicado quando se tenta adicionar lógica customizada sobre texto editável
    • Recomenda-se o artigo Peritext, que explica bem as dificuldades de rich text e de software multijogador em geral
  • O esquema de rich text do Automerge inclui marks, anotações aplicadas a intervalos de texto que permanecem consistentes mesmo durante a edição
    • O uso mais comum é para formatação como negrito e itálico, mas também é possível criar marks customizados da própria aplicação
  • Dois usos de marks customizados no Ducking
    • Rastrear regiões da transcrição que são alvo de threads de comentários
    • Rastrear timestamps de palavras na transcrição, permitindo edição normal
      • O serviço de transcrição armazena a transcrição no Automerge como um objeto rich text com marks de informação temporal em cada palavra
      • Se um pequeno erro de digitação for corrigido em apenas uma palavra, o mark é preservado e toda a informação de timing continua intacta
      • Se uma frase inteira for reescrita, alguns marks do meio podem se perder, mas os do início e do fim permanecem, garantindo ao menos uma noção aproximada do timing
  • Uma limitação dos marks é que seu datum precisa ser um valor simples, normalmente uma string, e ele não faz merge multijogador
    • Para dados pequenos e imutáveis, como timing de transcrição, usa-se JSON serializado como string
    • Para dados mais complexos ou mutáveis, como threads de comentários, guarda-se apenas o id no mark e os dados reais ficam em outra parte do documento
  • Os marks oferecem uma ótima base para construir recursos da aplicação sobre rich text multijogador

Próximo texto — estrutura da série

  • Este texto é a parte 2 de uma trilogia sobre a criação do Ducking
    • Parte 1: explica o design de UI peculiar do software
    • Parte 2 (este texto): recomenda olhar para o Automerge e mostra que é possível construir projetos multijogador de hobby
    • Parte 3 final, ainda por vir: retrospectiva da experiência de criar o Ducking
  • Menções sobre a parte 3 final
    • Uso de suporte de LLM não para intensificar o trabalho, mas para liberar mais tempo de rascunho e descanso na rede
    • O prazer de criar software narrowcast, feito para satisfazer apenas um pequeno grupo

Perguntas esperadas

E os dados de áudio?

  • Todos os dados multijogador ficam no Automerge, mas os blobs de áudio base não ficam nele; eles precisam ser tratados separadamente para permitir reprodução rápida
  • O objetivo é que um novo colaborador consiga começar a ouvir e editar em menos de 4 segundos após carregar a página, mais rápido do que abrir um app desktop e muito mais rápido do que baixar o arquivo inteiro do projeto
    • Um episódio de 1 hora pode depender de cerca de 1 GB de áudio, somando 4 horas de gravação de estúdio em alta qualidade, efeitos e música de fundo
  • O serviço de áudio faz várias tarefas no upload para garantir cold start rápido
    • Backup do áudio original
    • Transcrição da fala para a visualização de transcrição
    • Geração de waveform para a visualização da timeline
    • Divisão em janelas curtas para que, se apenas 1 minuto de uma gravação de 40 minutos for usado, a maioria dos clientes receba só um ou dois pequenos trechos
    • Transcodificação dos trechos para formatos comprimidos, oferecendo uma versão com perdas que possa tocar imediatamente enquanto o áudio de alta qualidade é baixado em segundo plano
  • A camada de dados da UI gerencia o carregamento da versão rápida do que é necessário naquele momento, seguindo a intenção do usuário, e também da versão de alta qualidade do áudio realmente usado
    • A API IndexedDB do navegador é útil para cache em múltiplas camadas e armazenamento content-addressable; com eviction automática, o que é usado fica e o que não é usado desaparece
  • Depois de todo esse processamento e cache local, o restante da UI pode assumir acesso aleatório rápido ao áudio e focar no fluxo de edição

Por que fazer uma UI de servidor + navegador em vez de um app local-first?

  • Há preferência por apps local-first como o Obsidian, especialmente os que oferecem uma rota de saída confiável junto com uma experiência paga baseada em nuvem
  • No início, a opção era começar com um app Tauri com armazenamento no sistema de arquivos local e sincronização opcional com servidor
    • A UI foi construída com base em uma interface de dados que poderia ser fornecida tanto pelo servidor quanto por um app local
    • Isso servia como proteção para que nenhum financiamento futuro levasse a uma monetização mais agressiva via lock-in
  • Depois, concluiu-se que isso não seria um SaaS, mas algo para usar com a parceira e alguns amigos
    • Sem incentivo para tratá-lo de forma errada e com baixo custo permanente de operação, decidiu-se fazer do jeito mais simples
  • Quando se alcançou um cold start de cerca de 3 segundos, ninguém mais quis perder tempo baixando e instalando um app nativo
  • A esperança é que apps de áudio saltem do mundo atual, ainda centrado em desktop, direto para um mundo local-first com opções de sincronização, evitando os 10 a 20 anos intermediários de lock-in de SaaS

O Automerge é seguro e web-scale? Vale usar em startup?

  • A resposta sincera é não sei, e isso é dito de forma bem-humorada; não é recusa, é literalmente não saber
  • Quando o autor entrou na área, a edição colaborativa em tempo real sem conflitos parecia magia, e há 10 anos existiam soluções conhecidas para certos problemas, mas elas exigiam equipes financiadas e especialização em várias áreas
    • Hoje, basta instalar uma dependência e, de forma relativamente intuitiva, criar uma UI em que amigos colaboram em tempo real
  • Em termos de segurança, o Ducking hoje é protegido por acesso restrito à rede e uma etapa de autorização ao criar a conexão websocket com o servidor Automerge
    • Usuários não conseguem descobrir nem editar projetos para os quais não foram convidados
    • A atribuição de autoria a edições e comentários é apenas parcialmente segura e depende da suposição de que os amigos não vão agir de má-fé
    • Permissões granulares, como poder comentar mas não editar, editar apenas parte do projeto ou controlar descoberta, exigem trabalho de design cuidadoso
  • O Keyhive, em desenvolvimento pela Ink & Switch, oferece um modelo de controle de acesso por capabilities com segurança criptográfica
    • Isso deve facilitar compartilhar apps Automerge publicamente até com usuários não confiáveis, mas ainda não está pronto

O Automerge é melhor?

  • Existe também o Yjs como outra solução nessa área, mas não dá para decidir pelos outros qual é a mais adequada
  • O conselho que não muda
    • Pensar profundamente no problema, fazer estimativas aproximadas sobre os limites que podem surgir, criar protótipos com várias alternativas e admitir honestamente que talvez o problema nem seja tão difícil a ponto de exigir a solução mais moderna e sofisticada
  • No caso do Ducking, protótipos rápidos e exploração da documentação mostraram que o Automerge era maduro o suficiente e com bom desempenho para esse uso
  • Mais importante ainda, o ecossistema da Ink & Switch atrai esteticamente
    • O Automerge não é apenas um motor de sincronização e versionamento, mas parte de uma visão maior de tornar o software mais seguro, colaborativo, flexível, prazeroso e pessoal
    • Há expectativa de sucesso de projetos como o Keyhive e de uma expansão de softwares pequenos, mágicos e feitos para poucos

Ainda não há comentários.

Ainda não há comentários.