implémenter un éditeur collaboratif temps réel dans un SaaS (Yjs, WebSocket, PostgreSQL)

Vous êtes CTO ou lead dev et vous devez ajouter une fonctionnalité d’édition collaborative (comme Google Docs) à votre logiciel métier ou SaaS ? Ce guide technique explique une architecture robuste et évolutive basée sur CRDT (Yjs), WebSocket, persistance PostgreSQL et montée en charge via Redis. À la fin vous aurez un plan clair, des snippets, des commandes Docker et les pièges à éviter.

pourquoi ce choix d’architecture

  • Yjs (CRDT) évite les serveurs de résolution de conflits et fonctionne bien en offline-first.
  • WebSocket pour la faible latence et la synchronisation temps réel des opérations.
  • Redis pub/sub pour propager les mises à jour entre plusieurs instances de serveur WebSocket.
  • PostgreSQL pour la persistance des états/versions (historique, snapshots) et requêtes relationnelles.

Si vous voulez explorer Yjs : documentation officielle Yjs. Pour la persistance relationnelle, la doc PostgreSQL est une bonne référence : PostgreSQL documentation.

architecture proposée (vue d’ensemble)

  1. Clients (navigateur / desktop) utilisent Yjs + y-websocket client pour produire/consommer opérations CRDT.
  2. Un cluster d’instances WebSocket (Node.js) reçoit les messages clients. Chaque instance applique localement les opérations Yjs.
  3. Redis pub/sub (ou Redis Streams) relaye les mises à jour entre instances pour garder tout le cluster cohérent.
  4. Un composant de persistance sérialise périodiquement (ou à checkpoint) l’état Yjs vers PostgreSQL (snapshots et logs d’édition).
  5. Authentification + autorisation tenant-aware (JWT / session) pour restreindre l’accès aux documents.

étapes de mise en œuvre avec exemples

1) Prototype local : lancer les dépendances

Commandes Docker pour monter rapidement Redis et PostgreSQL :

docker run -d --name redis -p 6379:6379 redis
docker run -d --name postgres -e POSTGRES_PASSWORD=secret -p 5432:5432 postgres

2) serveur WebSocket minimal (Node.js) — principe

Le serveur reçoit les opérations Yjs côté serveur et publie sur Redis. Ci‑dessous un pseudo‑snippet (conceptuel) ; adaptez selon vos packages (ws / uWebSockets / y-websocket).

// pseudo code - Node.js
const WebSocket = require('ws');
const Redis = require('redis');
const { applyUpdate, encodeStateAsUpdate } = require('yjs');

const wss = new WebSocket.Server({ port: 1234 });
const redisPub = Redis.createClient();
const redisSub = Redis.createClient();

wss.on('connection', (ws, req) => {
  // authentifier l'utilisateur (JWT) et charger le document Yjs
  ws.on('message', msg => {
    // msg contient update Yjs : appliquer localement puis publier sur Redis
    applyUpdate(doc, new Uint8Array(msg));
    redisPub.publish('doc:1234', msg);
  });
});

// recevoir les updates des autres instances
redisSub.subscribe('doc:1234');
redisSub.on('message', (channel, msg) => {
  // diffuser aux clients connectés sur cette instance
  wss.clients.forEach(client => client.send(msg));
});

Remarque : préférez des bibliothèques Yjs officielles comme y-websocket pour la production ; ici on montre le flux.

3) persistance : snapshots et journalisation

Stratégie recommandée :

  • Enregistrer périodiquement un snapshot binaire Yjs dans PostgreSQL (colonne bytea) pour restauration rapide.
  • Archiver les updates (opérationnel) si besoin d’audit ou de replay.
  • Conserver index de version + métadonnées (user, tenant, timestamp).
-- schéma PostgreSQL exemple (simplifié)
CREATE TABLE doc_snapshot (
  id uuid PRIMARY KEY,
  document_id text,
  tenant_id text,
  snapshot bytea,
  version bigint,
  created_at timestamptz DEFAULT now()
);

Lors de la restauration, on charge le snapshot dans un document Yjs côté serveur puis on applique les updates récents.

4) montée en charge

  • Utiliser plusieurs instances WebSocket derrière un load balancer. Si votre LB ne gère pas sticky sessions, Redis pub/sub assure la cohérence.
  • Mesurez latence de bout en bout et visez <100 ms pour une bonne sensation utilisateur (objet métier dépendant).
  • Surveillez ops/sec et mémoire par document : un document populaire peut devenir hot‑spot, shardlez-le (split par sous-documents ou sections).

exemples d’erreurs fréquentes et comment les éviter

  • Fuite mémoire : ne conservez pas des documents Yjs en mémoire indéfiniment. Déchargez les documents inactifs et sauvegardez l’état.
  • Storm d’événements : appliquer debouncing ou batching des updates pour persistance afin d’éviter pic IO.
  • Perte de message Redis : si la durabilité est critique, considérez Redis Streams ou stockez les updates dans une file persistante.
  • Conflits d’accès multi‑tenant : validez et vérifiez tenant_id à chaque requête WebSocket et avant d’appliquer/restaurer un snapshot.

sécurité et conformité

  • Authentification forte sur le canal WebSocket (JWT avec vérification serveur).
  • Chiffrement TLS pour les connexions WebSocket (wss://).
  • Autorisation fine : vérifier que l’utilisateur a accès au document (tenant + roles).
  • Audit : journalisez les événements d’écriture (utile pour conformité et rollback).

bonnes pratiques opérationnelles

  1. Tests de charge ciblés sur documents "hot" pour dimensionner Redis et instances WebSocket.
  2. Surveillance : métriques sur latence applyUpdate, taille des messages, mémoire par document, nombre de clients par document.
  3. Plan de sauvegarde/restauration : snapshots réguliers et testés sur des environnements staging.
  4. Favoriser modularité : séparer couche temps réel (WS + Redis) de la couche métier/Persistence (PostgreSQL).

outil et technologies conseillées

  • Yjs (CRDT) pour la logique de merge côté client et serveur. Voir yjs.dev.
  • Redis pour la propagation entre instances et pour limiter la latence inter‑nœud.
  • PostgreSQL pour snapshots/queries relationnelles — utile si vous avez déjà un backoffice basé sur Postgres. (voir la doc officielle).
  • Conteneurisation (Docker) et orchestrateur (Kubernetes) pour scalabilité ; utilisez des probes et autoscaling pour les instances temps réel.
  • Stack observabilité (Prometheus + Grafana) pour métriques temps réel.

exemples concrets / métriques à viser

Indicateurs à surveiller lors d’un pilote :

  • Latence de propagation (client→server→autres clients) : objectif <100 ms.
  • Opérations par seconde par document : surveillez le seuil à partir duquel la mémoire ou le CPU explose.
  • Taille moyenne des messages : gardez les updates compressés si >100 KB fréquemment.

conclusion

Implémenter un éditeur collaboratif dans un SaaS exige de combiner une logique de synchronisation résiliente (CRDT), une couche temps réel optimisée (WebSocket + Redis) et une persistance sûre (PostgreSQL). Le prototype commence simple (y-websocket + Docker) puis évolue vers un cluster avec monitoring et stratégies de snapshot. Cette approche vous donne un éditeur scalable, tolérant aux conflits et adapté à un contexte ERP/SaaS multitenant.

Pour aller plus loin, Novane accompagne la mise en production sécurisée et scalable de fonctionnalités temps réel dans des SaaS et logiciels métiers — découvrez nos services SaaS et nos réalisations techniques pour évaluer votre roadmap : services SaaS, PostgreSQL, Docker.

Besoin d’un audit ou d’un prototype rapide ? Contactez‑nous pour une séance de consulting.