Die Herausforderung
Eine nordamerikanische Universität betrieb über 30 akademische und administrative Systeme gegen Canvas LMS über eine nächtliche Batch-Synchronisierung, die zu einem fragilen Job von 6 Stunden angewachsen war. Immer wenn Canvas eine Funktion hinzufügte, brach der Job; immer wenn die Einschreibungen anstiegen, lief der Job in die Rate Limits der Canvas-API und scheiterte lautlos auf halber Strecke; und immer wenn ein nachgelagertes System aktuellere Daten brauchte, lautete die Antwort „morgen früh, vielleicht“.
Das zentrale IT-Team hatte drei konkrete Probleme. Erstens beklagten sich Dozenten, dass Änderungen im Notenbuch bis zu 24 Stunden brauchten, um die Analytics-Dashboards zu erreichen. Zweitens verbrauchte der Sync-Job so viel vom API-Kontingent, dass andere Integrationen von Canvas gedrosselt wurden. Drittens gab es keine Wiederherstellungsstrategie – schlug der Job um 03:14 Uhr fehl, musste ein Ingenieur aufstehen, die Abbruchstelle finden und den Lauf manuell wiederholen. Das Team brauchte eine Integration, die für Dozenten echtzeitnah genug war, Canvas so schonend behandelte, dass sie innerhalb der gemeinsamen Rate Limits blieb, und so beobachtbar war, dass ein Bereitschaftsingenieur ihr vertrauen konnte.
Unsere Lösung
Wir haben den nächtlichen Job durch eine ereignisgesteuerte Canvas-LMS-Integration auf Python und FastAPI ersetzt, die auf drei Bausteinen beruht: einem Canvas-Live-Events-Consumer, einem Canvas-API-Client mit kontingentbewusster Nebenläufigkeitssteuerung und einem idempotenten Change-Event-Bus, den die nachgelagerten Systeme abonnieren.
Canvas Live Events fließen in einen FastAPI-Webhook-Empfänger, werden verifiziert, in einer Inbox-Tabelle gespeichert und von einem Celery-Worker verarbeitet, der sie an die richtigen nachgelagerten Handler verteilt. Für Zustände, die Canvas nicht von sich aus sendet (Kursinhalte, detaillierte Einschreibedaten, große Teilnehmerlisten), nutzen wir eine intelligente Polling-Schicht, die GraphQL einsetzt, wo es günstiger ist, und REST, wo nicht – abgerufen über einen einzigen Canvas-Client, der den Header `X-Rate-Limit-Remaining` beachtet und dynamisch drosselt, bevor Canvas es verlangt.
Die nachgelagerten Systeme sprechen nicht mehr direkt mit Canvas – sie abonnieren unseren internen Change-Event-Bus, der idempotent und pro Entität (pro Studierendem, pro Kurs) geordnet ist. Diese eine Architekturentscheidung hat das Problem doppelter Aufrufe beseitigt: Über 30 Systeme stellten Canvas jede Nacht dieselbe Frage; jetzt konsumieren alle dasselbe normalisierte Ereignis. Das tägliche API-Aufrufvolumen pendelte sich bei rund 50.000 ein – deutlich innerhalb des Kontingents der Universität mit planbarem Spielraum –, während die Verzögerung bei der Weitergabe einer Notenbuch-Änderung von 24 Stunden auf unter 90 Sekunden sank.
- Canvas-Live-Events-Consumer mit Verifizierung signierter Payloads und Inbox-Persistenz
- Kontingentbewusster Canvas-API-Client, der X-Rate-Limit-Remaining beachtet, mit adaptiver Nebenläufigkeit
- Gemischte REST- + GraphQL-Strategie – GraphQL, wo es das Anfragevolumen senkt
- OAuth2-Token-Lebenszyklus mit automatischer Rotation und Refresh-on-401-Fallback
- Idempotenter, pro Entität geordneter Change-Event-Bus, den nachgelagerte Systeme abonnieren
- Integriertes Replay-Tool – beliebige Zeitfenster von Canvas-Ereignissen ohne Abstimmung erneut verarbeiten
- Paginierungsoptimierung (Cursor im Lesezeichen-Stil), die tiefe Offset-N+1-Muster eliminiert
- Datadog-Dashboards für Ende-zu-Ende-Latenz, Canvas-Kontingentreserve und Verzögerung pro System
- PagerDuty-Alarme für Kontingentverbrauchsrate, Ereignis-Rückstau und OAuth-Token-Zustand