cron-jobs

Par rivet-dev · skills

Tâches cron durables avec les Rivet Actors : les timers `schedule.after` et `schedule.at` survivent aux redémarrages et aux crashs, ainsi que le ré-armement des tâches récurrentes et les handlers idempotents.

npx skills add https://github.com/rivet-dev/skills --skill cron-jobs

Tâches Cron et Planification

IMPORTANT : Avant de faire quoi que ce soit, vous DEVEZ lire BASE_SKILL.md dans le répertoire de cette compétence. Il contient des conseils essentiels sur le débogage, la gestion des erreurs, la gestion d'état, le déploiement et la configuration du projet. Ces règles et patterns s'appliquent à tout travail RivetKit. Tout ce qui suit suppose que vous ayez déjà lu et compris ce document.

Exemples Fonctionnels

Si vous avez besoin d'une implémentation de référence, consultez le code d'exemple fonctionnel brut dans ces modèles :

Patterns pour exécuter des tâches cron durables et des tâches planifiées sur les Actors Rivet. Les planifications d'Actor sont des minuteurs persistants possédés par le moteur, donc une tâche conserve son délai limite à travers la mise en veille, les redémarrages, les mises à niveau, les déploiements et les crashs des actors.

Code de Démarrage

Commencez par l'exemple Scheduling fonctionnel sur GitHub. Il implémente un service de rappels avec des minuteurs uniques, un frontend React et des événements de déclenchement en direct.

L'API Scheduling

L'API complète est documentée dans Scheduling. Il y a deux méthodes, toutes deux disponibles sur le contexte de l'actor :

Méthode Comportement
c.schedule.after(duration, actionName, ...args) Exécute l'action nommée après duration millisecondes.
c.schedule.at(timestamp, actionName, ...args) Exécute l'action nommée à un timestamp d'époque exact en millisecondes.

Propriétés clés :

  • Durable : La planification est persistée par le moteur et le minuteur survit à la mise en veille, redémarrage, mise à niveau et crash de l'actor. Si l'actor est en veille à la limite, le moteur le réveille pour exécuter l'action. Voir Lifecycle.
  • Actions ordinaires comme callbacks : Le callback planifié est une action ordinaire sur le même actor, invoquée par nom. Les arguments après le nom de l'action sont transmis positivement, par exemple c.schedule.after(delayMs, "triggerReminder", reminder.id).
  • Pas d'API d'annulation : Rivet ne supporte pas actuellement l'annulation d'une action planifiée. Le pattern est une garde tombstone : supprimez l'entrée de l'état et faites que l'action planifiée ne fasse rien quand elle ne peut pas trouver son entrée. Les actions cancelReminder et triggerReminder de l'exemple implémentent exactement cela.

Tâches Récurrentes Via Réarmement

c.schedule est unaire, donc les tâches récurrentes sont construites en ayant l'action planifiée se réarmer à la fin de chaque exécution :

import { actor } from "rivetkit";

const DAY_MS = 24 * 60 * 60 * 1000;

export const dailyReport = actor({
  state: { lastRunAt: 0 },
  actions: {
    runReport: (c) => {
      // Do the job's work, then record the run.
      c.state.lastRunAt = Date.now();
      // Re-arm the next run before returning.
      c.schedule.after(DAY_MS, "runReport");
    },
  },
});

Armez la première exécution à partir de onCreate ou d'une action de configuration ; après cela, l'action maintient la chaîne vivante en se replanifiant elle-même.

Se réarmer avec after mesure la prochaine exécution à partir de la fin de la courante, donc la cadence dérive plus tard par le temps d'exécution du travail à chaque cycle. Si les exécutions doivent rester alignées sur une cadence fixe, réarmez plutôt avec c.schedule.at(c.state.lastRunAt + DAY_MS, "runReport").

L'exemple Scheduling lui-même n'utilise que des rappels uniques. Une implémentation réelle de réarmement se trouve dans l'actor monde inactif, où l'action collectProduction crédite la production, met à jour lastCollectedAt et appelle un helper scheduleCollection qui réarme avec c.schedule.after(delayMs, "collectProduction", { buildingId }). Il montre aussi la gestion du rattrapage : si l'action s'exécute tard, elle calcule combien d'intervalles entiers se sont écoulés depuis la dernière exécution et les crédite en un seul lot avant de réarmer.

Pour les tâches multi-étapes qui nécessitent des tentatives et un suivi de la progression dans une seule exécution, considérez les Workflows au lieu de chaîner les planifications.

Comparaison de Durabilité

Approche Durabilité du Minuteur Mise à l'Échelle Horizontale Déploiements et Redémarrages
setTimeout / setInterval Mémoire en processus uniquement Chaque réplica arme son propre minuteur, donc les tâches s'exécutent une fois par instance Tous les minuteurs en attente sont perdus au redémarrage ou au crash
node-cron et bibliothèques similaires Mémoire en processus uniquement Chaque instance exécute la tâche sauf si vous ajoutez un verrouillage externe La planification se réinitialise au déploiement ; les exécutions manquées pendant la panne sont ignorées
Service cron externe Vit en dehors de votre app Nécessite un endpoint HTTP public plus son propre état de dédupliquage et de tentative Survit à vos déploiements mais est une infrastructure séparée à exploiter
Planification d'Actor Rivet Persiste par le moteur en tant que minuteur durable Exactement un actor par clé, donc le minuteur est armé une fois au lieu d'une fois par réplica Survit à la mise en veille, redémarrage, mise à niveau et crash de l'actor

Idempotence

Une action planifiée peut se déclencher plus que prévu : un crash entre la réalisation du travail et le réarmement peut faire que l'action s'exécute à nouveau, et parce que les planifications ne peuvent pas être annulées, une action peut se déclencher pour une entrée qui a déjà été supprimée. Concevez les handlers pour qu'un déclenchement dupliqué soit inoffensif :

  • Stocker un marqueur d'exécution en état : Gardez un timestamp lastRunAt ou un numéro de séquence en état d'actor et mettez-le à jour dans l'action. À chaque déclenchement, calculez le temps écoulé depuis le marqueur et ignorez ou traitez par lot en conséquence. L'action collectProduction de l'actor monde inactif fait cela avec lastCollectedAt et le traitement par lot d'intervalles entiers.
  • Garde tombstone pour les entrées annulées : La triggerReminder de l'exemple recherche le rappel dans c.state.reminders en premier et retourne avec un avertissement s'il a disparu, donc un déclenchement après annulation est une no-op sûre.
  • Garder le travail et les mises à jour de marqueur dans la même action : Les écritures d'état d'actor sont persistées avec l'action, donc mettre à jour le marqueur dans le même handler qui fait le travail maintient les deux cohérentes.

Topologie

Topologie À Utiliser Quand Clé d'Exemple
Actor de tâche singleton Une tâche globale comme un rapport nocturne ou un passage de nettoyage job["daily-report"]
Actor par entité planifiée Minuteurs par utilisateur ou par ressource tels que rappels, essais ou périodes de facturation reminder[userId]

L'exemple Scheduling utilise une clé reminderActor["main"] partagée unique pour la simplicité de la démo. Pour les systèmes de rappels de production, préférez un actor par utilisateur pour que les minuteurs, l'état et la charge soient isolés par entité. Voir Keys.

Exemple de Service de Rappels

Sujet Résumé
Planification Minuteurs uniques armés avec c.schedule.after(delayMs, "triggerReminder", reminder.id) ou c.schedule.at(timestamp, "triggerReminder", reminder.id).
État État JSON contenant reminders et completedCount ; l'action planifiée mute l'état quand elle se déclenche.
Événements triggerReminder diffuse un événement reminderTriggered à tous les clients connectés. Voir Events.
Annulation cancelReminder ne supprime que le rappel de l'état ; l'action planifiée peut encore se déclencher et faire une no-op via une garde de recherche d'état.

Actors

  • Clé : reminderActor["main"]
  • Responsabilité : Stocke les rappels en état persistant, arme une future auto-action par rappel via c.schedule, marque les rappels comme complétés quand l'action planifiée se déclenche, et diffuse reminderTriggered aux clients connectés.
  • Actions
    • scheduleReminder
    • scheduleReminderAt
    • triggerReminder
    • getReminders
    • cancelReminder
    • getStats
  • Queues
    • Aucune
  • État
    • JSON
    • reminders
    • completedCount

Lifecycle

sequenceDiagram
    participant C as Client
    participant R as reminderActor
    participant E as Engine

    C->>R: scheduleReminder(message, delayMs)
    R->>E: schedule.after(delayMs, triggerReminder, id)
    Note over E: schedule persisted + alarm armed
    R-->>C: reminder
    Note over R: actor sleeps
    E->>R: alarm fires, actor wakes
    Note over R: triggerReminder(id) runs
    R-->>C: reminderTriggered event
    Note over R: recurring jobs re-arm here with schedule.after

Liste de Sécurité

L'exemple est intentionnellement ouvert : tout client peut se connecter à la clé partagée ["main"] et planifier ou annuler n'importe quoi. Traitez tout ce qui suit comme des extensions requises pour la production :

  • Valider les entrées de planification : Limitez delayMs et timestamp des clients. Rejetez les délais négatifs, les timestamps dans le passé et les délais limite absurdement lointains, et limitez les tailles de message ou de payload.
  • Ne jamais planifier des actions choisies par le client : Exposez des actions spécifiques comme scheduleReminder qui arment en interne un callback fixe. Ne passez pas un nom d'action fourni par le client ou des args non vérifiés dans c.schedule.
  • Authentifier et définir la portée des clés : Ajoutez l'authentification de connexion et utilisez des clés d'actor par utilisateur au lieu d'une clé globale unique, pour que les utilisateurs ne puissent pas lire ou annuler les planifications les uns des autres.
  • Élaguer les entrées complétées : Le tableau reminders de l'exemple croît sans limite. Supprimez ou archivez les entrées complétées pour que l'état reste petit.
  • Utiliser des IDs stables : Générez des IDs d'entrée avec crypto.randomUUID() plutôt que des chaînes timestamp-plus-aléatoire.

Carte de Référence

Actors

Agent Os

Clients

Connect

Cookbook

Général

Auto-Hébergement

Skills similaires