Zum Hauptinhalt springen

Ihre Daten aktuell halten

Ein Dashboard ist seine URL nur wert, wenn die Zahlen darauf aktuell sind. Das ist der Teil, den eine Agent-Sitzung nicht zurücklassen kann: Ihr Agent baut das Dashboard und verschwindet, und die Daten darunter müssen weiterhin nach einem Zeitplan eintreffen, monatelang, ohne dass jemand im Kreislauf bleibt.

Aktualität ist daher Plattformzustand, kein Dashboard-Code. Sie richten sie einmal gegen einen Datensatz ein; sie läuft weiter, egal ob jemand eine Sitzung offen hält.

Die drei Wege, wie Daten vorankommen​

Was es istWer es auslöst
Aktualisierung auf AbrufEinen Datensatz jetzt erneut abrufenSie, Ihr Agent oder Ihre CI — POST /v1/datasets/{dataset_id}/refresh
Inkrementeller LadevorgangNur das abrufen, was sich seit dem letzten Lauf geändert hatDerselbe Aufruf, mit mode: "incremental"
Geplante AktualisierungEin Cron-Ausdruck, den die Plattform für Sie ausführtNiemand — der Cron-Poller der Plattform

Einen Datensatz jetzt aktualisieren​

result = client.datasets.refresh(dataset_id)
print(result.completed, result.effective_mode, result.message)
const result = await client.datasets.refresh(datasetId);
console.log(result.completed, result.effectiveMode, result.message);

Zwei Ergebnisse, und der Unterschied ist wichtig:

  • 200 OK — die Arbeit ist tatsächlich innerhalb der Anfrage abgeschlossen worden. completed ist true, und dataset ist die endgültige Zeile. Das ist, was ein erneuter Download eines öffentlichen Datensatzes bewirkt.
  • 202 Accepted — die Aktualisierung wurde gestartet. Die Zeile ist gesperrt und als PROCESSING markiert, und ein Konnektor läuft Sekunden später. completed ist false. Pollen Sie den Datensatz oder hören Sie auf dem Websocket, bis processingStatus PROCESSING verlässt.

Ein 202 ist kein Fehler und kein Erfolg — es ist ein Versprechen. Behandeln Sie „der Aufruf ist zurückgekehrt" und „die Daten sind aktuell" als zwei verschiedene Fakten, und prüfen Sie den zweiten, bevor Sie jemandem sagen, das Dashboard sei aktuell. Der Aufruf, der das prüft, ist get_data_freshness — alles vor diesem Abschnitt handelt davon, Daten zu bewegen, nicht davon, wie alt sie sind.

Inkrementelle Ladevorgänge, und der Lauf, der noch nicht inkrementell ist​

Übergeben Sie einen Modus, um nur neue Zeilen abzurufen:

from vibedasher.models import RefreshRequest

result = client.datasets.refresh(dataset_id, RefreshRequest(mode="incremental"))
print(result.requested_mode, "->", result.effective_mode)

requestedMode gibt wieder, was Sie angefragt haben. effectiveMode ist, was tatsächlich gelaufen ist, und beide sind nicht immer gleich. Eine inkrementelle Anfrage gegen einen Datensatz ohne bisherigen Checkpoint meldet effectiveMode: "bootstrap": Sie läuft als vollständiges Überschreiben und zeichnet das Wasserzeichen auf, sodass der nächste Lauf der erste wirklich inkrementelle ist.

Verzweigen Sie nach effectiveMode, nie nach dem, was Sie angefragt haben. (Das ist dieselbe Regel wie bei client.query, das die tatsächlich verwendete Ausführungsspur meldet statt der angefragten — ein Muster, das es sich zu verinnerlichen lohnt: Die Plattform teilt Ihnen mit, was passiert ist, und es ist Ihre Aufgabe, das zu lesen.)

Um den nächsten Lauf zu zwingen, wieder alles abzurufen, löschen Sie das Wasserzeichen:

client.datasets.reset_checkpoint(dataset_id)

Geplante Aktualisierung​

Ein Zeitplan ist eine Zeile, die Sie einmal erstellen. Sie trägt einen Cron-Ausdruck und das dbt-Projekt, das sie aktualisiert:

from vibedasher.models import RefreshCreate

schedule = client.refreshes.create_refresh(
RefreshCreate(name="nightly", project_id=project_id, cron="0 3 * * *")
)
print(schedule.refresh.next_run, schedule.schedule_registration.registered)

Die Antwort trägt einen scheduleRegistration-Block. Prüfen Sie registered. Er ist nur dann true, wenn der Zeitplan tatsächlich aktiv ist; wenn er false ist, sagen reason und message, warum, und nextRun ist null.

Dieses Feld existiert wegen eines echten Ausfalls, und die Geschichte ist das beste Argument für das aktuelle Design. Die ursprüngliche Implementierung registrierte jeden Zeitplan als eigene Cloud-Regel, umschloss den Aufruf mit einem breiten except und antwortete mit 201 Created und einem selbstbewussten nextRun. Die API-Rolle hatte keine Berechtigung, diese Regeln zu erstellen, sodass nie etwas registriert wurde — und jedem Kunden gesagt wurde, seine Daten würden aktualisiert. Es wurde am 2026‑09‑05 entdeckt: Es existierten null Regeln, und der eine Zeitplan in der Datenbank hatte ein nextRun fünf Monate in der Vergangenheit und eine Ausführungsanzahl von null.

Die Lösung war nicht bessere Fehlerbehandlung. Es war die Entfernung dessen, was fehlschlagen konnte: Die Zeile ist der Zeitplan. Ein Cron-Poller läuft alle fünf Minuten, beansprucht jede fällige Zeile per Compare-and-Set und führt sie aus. Es gibt keine externe Registrierung, die gelingen oder scheitern könnte, sodass das Einzige, was bei der Erstellung schiefgehen kann, ein Cron-Ausdruck ist, der nicht ausgewertet werden kann — und der wird jetzt vorab abgelehnt, nicht erst beim Auslösen.

Zwei Konsequenzen, mit denen Sie planen sollten:

  • Ein Zeitplan löst innerhalb von etwa fünf Minuten nach seiner Fälligkeit aus, nicht auf die Sekunde genau. Cron bedeutet hier „nicht früher als", nicht „genau um".
  • scheduleRegistration.backend liest sich als db_cron. Es bleibt in der Antwort erhalten, weil Clients danach verzweigen.

Lesen, was passiert ist​

Jede Zeitplanzeile trägt ihre eigene Historie — lastRun, lastStatus, lastError, executionCount, nextRun:

for r in client.refreshes.iter_all():
print(r.name, r.last_status, r.last_run, r.execution_count, r.last_error)

executionCount ist das Feld, das Sie beobachten sollten. Ein Zeitplan, der seit einer Woche existiert mit executionCount: 0, ist nicht langsam — er läuft nicht, und genau das ist der Fehler, den das obige Design sichtbar statt still machen sollte.

Wie alt sind diese Zahlen?​

Alles oben beantwortet „läuft die Pipeline?". Es beantwortet nicht „ist das, was dieses Dashboard zeigt, aktuell?", und die beiden fallen in beide Richtungen auseinander:

  • Ein Zeitplan kann einwandfrei laufen und nichts Neues laden. executionCount steigt, lastStatus ist success, und die neueste Zeile ist noch aus dem März.
  • Ein Datensatz kann überhaupt keinen Zeitplan haben, und dann hat executionCount keine Zeile zu lesen. Ein statischer Upload ist hier der Regelfall: seine Zeilen ändern sich, wenn jemand neue hochlädt, und sonst nie — eine gesunde Zeitplanliste kann also neben einem neun Tage alten Datensatz stehen und nichts darüber sagen. Das Fehlen eines Zeitplans ist nicht ein Zeitplan mit schlechtem Zähler.

Ein Aufruf beantwortet die eigentliche Frage des Lesers, gemessen an den Zeilen statt an der Buchführung der Plattform:

f = client.datasets.get_freshness(dataset_id).freshness
print(f.as_of, f.age_days, f.status)
const { freshness } = await client.datasets.getFreshness(datasetId);
console.log(freshness.asOf, freshness.ageDays, freshness.status);

Über MCP ist es get_data_freshness, das genau eines von viz_id oder dataset_id nimmt. Mit einer viz_id löst es jeden Datensatz auf, den das Dashboard liest, und meldet den ältesten — ein Dashboard ist nur so aktuell wie seine älteste Eingabe. Die REST-Route ist GET /v1/datasets/{dataset_id}/freshness.

Die drei Felder, und warum es drei sind​

FeldWas es ist
asOfDer neueste Wert in der eigenen Zeitspalte der Daten. Das ist die Antwort, und sie kann nicht schmeicheln: sie wird aus den Zeilen gelesen. ageDays ist, wie alt das ist; negativ bedeutet vorwärtsdatierte Daten.
lastLoadedAtWann Zeilen zuletzt angekommen sind. Ein Pipeline-Fakt, kein Alter — eine heute Morgen geladene Datei kann nichts Neueres als März enthalten. Zitieren Sie es niemals als Stand.
refreshOb etwas geplant ist, das die Zahlen neuer macht, einschließlich der Antwort „nichts, und nichts kann es" für eine flache Datei. Dieses Feld deckt den Fall „kein Zeitplan" von oben ab.

status sagt, wie weit asOf zu trauen ist: MEASURED (er ist echt), NO_TIME_COLUMN (die Daten tragen keine Datumsspalte, haben also keinen Stand, den sie haben könnten), oder UNKNOWN — was bedeutet, dass der Fakt nicht festgestellt wurde, und niemals ein Bestehen ist.

lastLoadedSource nennt, welche Uhr lastLoadedAt beantwortet hat. iceberg_snapshot ist die physische Schreibzeit des aktuellen Snapshots der Tabelle — der einzige echte Ankunftszeitpunkt, und einer, den ein Umbenennen nicht verschieben kann. not_established bedeutet, dass lastLoadedAt absichtlich null ist: dieser Lesepfad hat den Snapshot nicht konsultiert, und statt mit dem updatedAt der Zeile zu antworten — einer Schreibuhr, die jede Änderung vorstellt, und die einmal Zeilen neun Tage nach ihrer Ankunft meldete — verweigert das Feld die Antwort. Ein Feld, das verweigert, ist strikt besser als eines, das mit der falschen Uhr antwortet. Die Route oben ist der Aufruf, der die richtige liest. get_viz und list_vizzes tragen ein kostenloses dataFreshness, das über die Aktualisierungslage eindeutig und über den Stand UNKNOWN ist; dieser Aufruf löst genau dieses UNKNOWN auf.

Was nicht das Alter der Daten ist​

Weder publishStatus, noch lastBuild.createdAt, noch processingStatus, noch updatedAt beantworten diese Frage. Sie sind, in dieser Reihenfolge: ob ein Bundle existiert, wann der Code kompiliert wurde, ob die Datei einmal geparst wurde, und wann die Zeile zuletzt geändert wurde. Jedes davon liest sich aktuell auf einem Dashboard, dessen Zahlen Wochen alt sind. Wenn Sie gerade Aktualität aus einem von ihnen berichten wollen, rufen Sie stattdessen die Freshness-Route auf.

Es kostet ein SELECT MAX(<Datumsspalte>) pro Datensatz — dieselbe Sonde, die das gerenderte Dashboard beim Mounten schon ausführt.

Wo jede Geste lebt​

  • Zeitpläne sind die MCP-Oberfläche. create_refresh, list_refreshes, get_refresh und delete_refresh decken den gesamten Lebenszyklus einer wiederkehrenden Aktualisierung aus Ihrem Agenten heraus ab.
  • „Jetzt aktualisieren" ist das SDK und die REST-API. Ein einmaliger Lauf ist dort ein einzelner Aufruf — siehe die API Reference.
  • dbt-Projekte tragen ihre eigenen Ausführungs- und Aktualisierungsrouten (/v1/dbt/projects/{project_id}/run, /v1/dbt/projects/{project_id}/refreshes).