Architecture et modèle d’acteur

Rivet implémente le modèle d’acteur où chaque Actor représente un processus long‑vivant doté d’un état en mémoire et d’une base SQLite intégrée. Les acteurs sont adressés par une clé unique et partagent le même processus Node.js, Bun ou Rust, ce qui évite le coût d’un sandbox par acteur. Cette conception permet de déployer des centaines de milliers d’acteurs sur un seul serveur, chaque acteur consommant seulement 72 KB de RAM. Le contrôle de planification et la communication se font via WebSockets et appels HTTP, offrant une interface unifiée pour le backend et le frontend.

Performances et coûts de stockage

Le benchmark officiel (Rivet 3.0.3) indique un p99 de 4,99 ms pour les démarrages à froid, grâce à l’absence de pull d’image ou de boot de VM. Le stockage persistant repose sur SQLite répliqué sur S3, avec des temps de 1,07 ms pour les écritures durables et 1,36 ms pour les lectures au p99, soit environ 10,4 × plus rapides que le stockage SSD répliqué standard. Le calcul de coût compare S3 Standard à trois copies EBS gp3, montrant une réduction de dépenses significative pour les charges à grande échelle.

Gestion de l’état et tolérance aux pannes

Chaque acteur conserve son état dans la base SQLite locale, qui est synchronisée avec S3. En cas d’inactivité, l’acteur hiberne et ne consomme aucune ressource ; il se réactive instantanément à la prochaine requête sans perte de données. La résilience est assurée par le déplacement automatique des acteurs vers un nœud disponible si le nœud d’origine échoue, la base SQLite étant déjà stockée de façon durable. Le système expose également OpenTelemetry pour le suivi des traces, métriques et logs, facilitant le diagnostic en production.

import { actor } from "rivetkit";
import { db } from "rivetkit/db";
const agent = actor({
  db: db({
    onMigrate: async (db) => {
      await db.execute(`CREATE TABLE IF NOT EXISTS messages (role TEXT, content TEXT)`);
    },
  }),
  actions: {
    chat: async (c, text: string) => {
      await c.db.execute("INSERT INTO messages VALUES (?, ?)", "user", text);
      const messages = await c.db.execute("SELECT role, content FROM messages");
      const response = streamText({ model: openai("gpt-5"), messages });
      for await (const delta of response.textStream) {
        c.broadcast("token", delta);
      }
      await c.db.execute("INSERT INTO messages VALUES (?, ?)", "assistant", await response.text);
    },
  },
});

Cas d’usage et limites

Rivet cible les charges « agentic » : assistants IA, jeux multijoueurs, documents collaboratifs ou tout service nécessitant un état persistant et du temps réel. La possibilité de créer des « Dynamic Apps » où chaque utilisateur possède son propre backend montre la flexibilité du modèle. Cependant, la dépendance à S3 introduit une latence réseau inhérente et nécessite une configuration réseau fiable. De plus, le modèle repose sur un contrôle de planification centralisé ; une surcharge du plan de contrôle pourrait devenir un goulot d’étranglement à très grande échelle. Malgré ces contraintes, les mesures publiées démontrent que Rivet offre une alternative efficace aux solutions serverless traditionnelles pour les workloads fortement stateful.