3 points par GN⁺ 5 시간 전 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • Il y a encore quelques années, la synchronisation de données multijoueur en temps réel était l’un des problèmes les plus difficiles, exigeant des spécialistes et des investissements à l’échelle d’une entreprise. Aujourd’hui, un simple npm install suffit pour implémenter une UI multijoueur, même dans un projet amateur.
  • Automerge est un outil de construction de modèles de données local-first, sûrs pour le multijoueur et dotés de gestion de versions. Il gère automatiquement la persistance des données, l’historique, la diffusion aux collaborateurs et la résolution de conflits, d’une manière proche du pattern useState de React, sans que l’UI ait à s’en soucier.
  • Dans le cas de Ducking, un éditeur audio multijoueur dans le navigateur, l’essentiel est de concevoir le modèle de données de façon à ce qu’il se mappe naturellement aux opérations CRDT.
  • Pour les cas qu’Automerge ne garantit pas, comme le réordonnancement de listes, il faut implémenter soi-même des invariants plus forts via du code au niveau applicatif.
  • Le point clé est que l’édition collaborative en temps réel, autrefois une sorte de magie réservée aux usages industriels, peut désormais s’appliquer librement à de petites applications destinées à un cercle restreint.

Contexte — le projet Ducking

  • Ces derniers mois, l’auteur a développé Ducking, un éditeur audio multijoueur dans le navigateur, pour le podcast de sa partenaire.
  • Il trouvait étrange que le montage audio reste bloqué dans un modèle vieux de 20 ans, fait d’applications desktop mono-utilisateur et d’échanges de fichiers.
    • Il fallait un workflow collaboratif à la Google Docs ou Figma, où l’un édite un clip pendant qu’un autre corrige la transcription ou ajuste les réglages d’EQ.
    • Des outils modernes de collaboration comme les commentaires, l’historique ou le suivi des modifications étaient aussi nécessaires.
  • Un article précédent couvrait le design UI original et le modèle de mise en page audio, qui rendaient un éditeur solo plus efficace, mais le vrai objectif était un workflow plus collaboratif.

Comment fonctionne Automerge

  • Toutes les données de Ducking, hors blobs audio, sont stockées dans des documents Automerge.
  • Le pattern central est familier aux développeurs React : on récupère les données avec un hook pour les rendre, puis on envoie des demandes de modification asynchrones ; une fois les données modifiées, le hook déclenche un rerender.
    • Exemple avec le hook useDocument : const [doc, changeDoc] = useDocument<Episode>(docUrl) ; lors d’un changement de champ, on met à jour avec changeDoc((d) => { d.title = e.target.value }).
  • Les opérations de mise à jour semblent impératives, mais diffèrent des objets et tableaux JS natifs.
    • Elles ont moins de méthodes, ne mutent pas immédiatement, et interceptent les mutations pour les convertir en entrées de changelist dans l’historique du document.
  • Pour les usages simples, Automerge fait ce qu’il faut, mais ce n’est pas magique : ses invariants ne correspondent pas toujours au sens métier recherché, d’où l’importance d’une conception prudente du modèle de données.
    • Il faut que la plupart des actions utilisateur sémantiquement unitaires correspondent à une opération unique fournie par Automerge.
    • Les actions distinctes portant sur des données liées doivent se résoudre naturellement du point de vue des invariants de cette opération Automerge.
    • Il faut séparer clairement les données canoniques stockées des données dérivées calculées.

Modéliser les données pour le multijoueur

  • Dans le modèle de données de Ducking, un clip est une fenêtre qui joue une portion d’une source audio sous-jacente immuable ; il définit la plage de lecture, l’application d’effets et l’occupation de l’espace sur la timeline.
    • L’effet le plus courant consiste à faire varier le volume de l’audio sous-jacent dans le temps pour créer des crossfades ou supprimer du bruit.
  • Au départ, chaque clip possédait une liste de niveaux de volume indexés dans le temps à partir du début du clip, mais cela posait problème car la plupart des changements de volume concernaient l’audio source, pas le clip.
    • Si l’on avançait légèrement le début d’un clip, tous les changements de volume s’appliquaient soudain à une autre partie de l’audio.
    • Écrire du code pour mettre à jour tous les horodatages de volume à chaque changement de début de clip aurait été un mauvais choix.
  • Si deux collaborateurs éditent en même temps le début d’un clip, chaque modification embarque à la fois le changement du point de départ et tous les horodatages d’automation de volume.
    • Automerge ne connaît pas la relation causale entre ces changements, donc lors de la fusion ils peuvent se résoudre de manière incohérente.
    • C’est un problème typique quand une action sémantiquement unique essaie de mettre à jour plusieurs données persistées selon une causalité qu’un CRDT ne comprend pas.
  • La solution a consisté à migrer les données d’effets audio pour les attacher non plus au clip, mais au repère temporel de l’audio source.
    • Ainsi, modifier le début ou la durée d’un clip ne nécessite plus de mise à jour, et si plusieurs éditeurs changent le début, l’automation de volume ou d’autres effets, ces changements restent indépendants et fusionnent correctement avec plus de chances.
  • Différence entre UI mono-utilisateur et UI multijoueur :
    • Dans une UI solo, on peut conserver le modèle de données existant et ajouter des calculs au moment de l’écriture.
    • Dans une UI multijoueur, il est bien plus fréquent de migrer le modèle pour que toutes les données persistées restent orthogonales.
  • Cela pousse à fortement préférer la simplification à l’écriture et le calcul à la lecture, afin de tirer au maximum parti de la fusion automatique d’Automerge.
  • Conseils sur les migrations de forme de données :
    • Il faut accepter qu’un build puisse nécessiter une migration de schéma, et s’entraîner tôt pour ne pas redouter la première grosse migration.
    • Il existe plusieurs patterns : traitement à la lecture côté client, upgrade en lot côté serveur, etc.
    • Trouver un invariant pratique pour vérifier que l’état avant/après migration est équivalent facilite énormément le travail.
    • Dans Ducking, tous les projets ont été exportés avant/après migration pour vérifier les changements via une empreinte audio (audio fingerprint), ce qui a permis de déployer même de gros changements de schéma sans crainte.

Implémenter le réordonnancement de listes

  • Parfois, il faut écrire soi-même des invariants plus forts via du code applicatif pour obtenir des garanties qu’Automerge ne fournit pas.
  • C’est ce qui est arrivé en implémentant la magnetic timeline de Ducking, c’est-à-dire la liste triée des clips à lire.
    • Automerge fournit des opérations de tableau pour supprimer et insérer des éléments par index, mais pas d’opération pour réordonner atomiquement un élément existant.
  • Des solutions connues existent :
    • Martin Kleppmann a publié un article sur les opérations atomiques de réordonnancement de listes.
    • Avec Liangrun Da, il a aussi publié Extending JSON CRDTs with Move Operations.
    • Il existe une draft PR pour l’ajouter à Automerge, mais elle n’a pas encore été fusionnée.
  • Problème de l’approche simple :
    • Supprimer l’objet à son index actuel puis le réinsérer à l’index cible.
    • Même combinés, les invariants de ces deux opérations ne garantissent pas l’invariant souhaité : « en cas de réordonnancements concurrents, l’objet apparaît exactement une fois dans la liste ».
    • En présence de suppressions/réinsertions concurrentes, un objet peut apparaître à plusieurs positions de la liste. Si Alice et Bob déplacent chacun B avec delete+insert, les deux suppressions fusionnent en un seul tombstone, mais les deux insertions créent chacune un nouvel élément, si bien que B apparaît deux fois.
  • Implémentation maison de l’invariant « exactement une fois » au niveau applicatif :
    • Un semantic id est attribué à chaque clip lors de son insertion dans la timeline.
    • Lors d’un réordonnancement, les opérations de suppression et d’insertion décrites plus haut sont déclenchées.
    • À la lecture, l’application parcourt les doublons ayant le même semantic id, choisit arbitrairement le premier élément non supprimé et ignore les autres.
    • Cela garantit qu’un objet n’existe qu’une seule fois dans la liste et que tous les lecteurs convergent vers le même état final.
  • Le réordonnancement de listes est la seule opération dont Ducking a eu besoin et qu’Automerge ne fournissait pas ; si la PR est fusionnée, cette logique applicative ne sera plus nécessaire.

Historique du document

  • Une bonne UI multijoueur a besoin d’outils de gestion d’historique : les collaborateurs veulent voir les changements survenus pendant leur absence, laisser des commentaires sur des diffs, comparer des versions anciennes et revenir en arrière.
  • Automerge suit l’historique des versions d’un document et fournit d’excellents primitives pour manipuler historique et comparaison.
    • En revanche, c’est au développeur de l’application de décider comment exposer ces informations et quels concepts montrer à l’utilisateur.
  • Les Patchwork lab notes d’Ink & Switch sont recommandées.
    • Le travail sur l’exposition des branches aux utilisateurs et sur les commentaires universels est particulièrement intéressant.
  • Ducking a finalement adopté un modèle relativement simple de collaboration et d’historique :
    • Un historique de versions linéaire avec des checkpoints nommés par l’utilisateur ; un checkpoint sert à la fois d’unité de regroupement des changements et d’unité de discussion, de diff et de rollback.
    • Des fils de commentaires peuvent être rattachés à un point précis de l’audio, à une zone de transcription ou à un checkpoint de version.
  • Il n’y a pas encore eu de raison suffisante d’introduire des branches, mais cela pourrait devenir utile à l’avenir.

Texte et marks

  • Le travail sur le rich text est particulièrement délicat lorsqu’on veut superposer une logique personnalisée à du texte éditable.
    • Le papier Peritext est recommandé pour comprendre les difficultés du rich text et, plus largement, des logiciels multijoueurs.
  • Le schéma rich text d’Automerge inclut des marks, c’est-à-dire des annotations appliquées à des plages de texte et qui restent cohérentes même pendant l’édition.
    • On les utilise le plus souvent pour la mise en forme, comme le gras ou l’italique, mais on peut aussi créer des marks spécifiques à l’application.
  • Ducking utilise des marks personnalisés de deux façons :
    • Pour suivre les zones de transcription ciblées par des fils de commentaires.
    • Pour suivre l’horodatage des mots dans la transcription tout en autorisant leur édition.
      • Le service de transcription stocke la transcription dans Automerge sous forme d’objet rich text où chaque mot reçoit un mark contenant ses informations temporelles.
      • Si l’on corrige juste une petite faute dans un mot, le mark est conservé, donc toutes les informations temporelles restent intactes.
      • Si l’on réécrit une phrase entière, certains marks intermédiaires disparaissent, mais ceux du début et de la fin restent, ce qui permet au moins de conserver une information temporelle approximative minimale.
  • Une limite des marks est que leur datum doit être une valeur simple, généralement une chaîne, et qu’il ne fusionne pas en mode multijoueur.
    • Pour des données petites et immuables comme l’horodatage de transcription, Ducking sérialise du JSON en chaîne.
    • Pour des données plus complexes ou mutables, comme les fils de commentaires, le mark ne stocke qu’un id, et les données réelles sont conservées ailleurs dans le document.
  • Les marks offrent une excellente base pour construire des fonctionnalités applicatives par-dessus du rich text multijoueur.

Prochain article — structure de la série

  • Cet article est la deuxième partie d’une trilogie sur la création de Ducking.
    • Partie 1 : explication du design UI original du logiciel.
    • Partie 2 (cet article) : pourquoi Automerge mérite d’être envisagé et comment il permet de construire des projets multijoueurs amateurs.
    • Partie 3, à venir : retour d’expérience sur la création de Ducking.
  • À propos de cette future troisième partie :
    • Le support des LLM a été utilisé non pas pour augmenter la productivité, mais pour dégager plus de temps pour esquisser et se reposer dans un hamac.
    • Le plaisir de créer un logiciel narrowcast, pensé pour satisfaire seulement un petit nombre de personnes.

Questions attendues

Et les données audio ?

  • Toutes les données multijoueurs sont stockées dans Automerge, mais les blobs audio sous-jacents sont traités à part afin d’assurer une lecture rapide.
  • L’objectif est qu’un nouveau collaborateur puisse commencer à écouter et éditer en moins de 4 secondes après le chargement de la page — plus vite que lancer une application desktop, et bien plus vite que télécharger tout le fichier projet.
    • Un épisode d’une heure peut dépendre d’environ 1 Go d’audio, avec 4 heures d’enregistrement studio haute qualité, des effets et de la musique de fond.
  • Traitements effectués par le service audio à l’upload pour garantir un démarrage à froid rapide :
    • Sauvegarde de l’audio source.
    • Transcription de la voix pour la vue transcription.
    • Génération de la forme d’onde pour la vue timeline.
    • Découpage en petites fenêtres pour que, si une minute seulement est utilisée sur 40 minutes d’enregistrement, la plupart des clients n’aient à récupérer qu’un ou deux petits fragments.
    • Transcodage des fragments dans un format compressé afin de fournir une version lossy lisible immédiatement pendant que l’audio haute qualité se télécharge en arrière-plan.
  • La couche de données de l’UI gère, en fonction de l’intention utilisateur, le chargement d’une version rapide des données immédiatement nécessaires, puis de la version haute qualité complète de l’audio effectivement utilisé.
    • L’API IndexedDB du navigateur est utile pour le cache multi-niveaux et le stockage content-addressable, avec une éviction automatique : ce qui est utilisé reste, ce qui ne l’est pas disparaît.
  • Une fois tout ce traitement et ce cache local en place, le reste de l’UI peut partir du principe que l’accès aléatoire rapide à l’audio est acquis et se concentrer sur le workflow d’édition.

Pourquoi avoir créé une UI navigateur + serveur plutôt qu’une app local-first ?

  • L’auteur préfère les apps local-first de type Obsidian, capables de fonctionner complètement sans serveur, surtout quand elles offrent en même temps un bon chemin de sortie et une expérience cloud payante.
  • Au départ, il était parti sur l’option d’une app Tauri avec stockage dans le système de fichiers local et synchronisation serveur optionnelle.
    • L’UI était conçue autour d’une interface de données pouvant être fournie aussi bien par un serveur que par une app locale.
    • Cela servait de garde-fou pour qu’aucun futur financement ne pousse à verrouiller davantage l’application à des fins de monétisation.
  • Ensuite, il a estimé qu’il ne s’agissait pas d’un SaaS, mais simplement de quelque chose qu’il voulait utiliser avec sa partenaire et quelques amis.
    • Le risque de mauvais usage disparaissait, les coûts d’exploitation permanents restaient faibles, et il a donc choisi la solution la plus simple.
  • Une fois un démarrage à froid d’environ 3 secondes atteint, plus personne n’avait envie de perdre du temps à télécharger et installer une application native.
  • L’espoir est que les applications audio passent directement de leur monde desktop actuel à un monde local-first avec options de synchronisation, sans traverser entre-temps 10 à 20 ans de lock-in SaaS.

Automerge est-il sûr et web-scale ? Une startup devrait-elle l’utiliser ?

  • L’auteur répond avec joie qu’il ne sait pas — ce n’est pas un refus, mais vraiment une absence de certitude.
  • Lorsqu’il a commencé à travailler, l’édition multijoueur temps réel sans conflit relevait de la magie ; il y a 10 ans, il existait des solutions connues pour certains problèmes, mais elles nécessitaient une équipe financée et une expertise variée.
    • Aujourd’hui, on peut installer une dépendance, construire une UI de manière globalement intuitive et collaborer en temps réel avec ses amis.
  • Côté sécurité, Ducking est aujourd’hui protégé par un accès réseau limité et une étape d’autorisation lors de l’ouverture de connexions websocket au serveur Automerge.
    • Un utilisateur ne peut ni découvrir ni éditer un projet auquel il n’a pas été invité.
    • L’attribution des modifications et commentaires à des utilisateurs n’est sécurisée que partiellement, et repose en partie sur l’idée que les amis ne feront pas n’importe quoi.
    • Des permissions fines — commenter sans éditer, n’éditer qu’une partie du projet, contrôler la découvrabilité — nécessiteraient un vrai travail de conception.
  • Keyhive, en développement chez Ink & Switch, propose un modèle de contrôle d’accès par capabilities, sécurisé cryptographiquement.
    • Cela devrait faciliter le partage public d’apps Automerge même avec des utilisateurs non fiables, mais ce n’est pas encore prêt.

Automerge est-il meilleur ?

  • Une autre solution du domaine est Yjs, mais l’auteur ne peut pas décider à la place du lecteur de ce qui convient le mieux.
  • Le conseil qui ne change pas :
    • Réfléchir profondément au problème, faire quelques estimations grossières, prototyper avec plusieurs alternatives, et admettre honnêtement que son propre problème n’est peut-être pas si difficile et ne nécessite pas forcément la solution la plus moderne ou la plus sophistiquée.
  • Dans le cas de Ducking, un prototype rapide et l’exploration de la documentation ont montré qu’Automerge était suffisamment mature et performant pour cet usage.
  • Plus important encore, l’écosystème Ink & Switch lui paraît esthétiquement séduisant.
    • Automerge n’est pas seulement un moteur de synchronisation et de gestion de versions, mais fait partie d’une vision plus large d’un logiciel plus sûr, plus collaboratif, plus flexible, plus plaisant et plus personnel.
    • L’auteur espère voir réussir Keyhive et d’autres projets du même type, et voir se diffuser des logiciels petits mais magiques, conçus pour un petit nombre de personnes.

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.