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 ist | Wer es auslöst | |
|---|---|---|
| Aktualisierung auf Abruf | Einen Datensatz jetzt erneut abrufen | Sie, Ihr Agent oder Ihre CI — POST /v1/datasets/{dataset_id}/refresh |
| Inkrementeller Ladevorgang | Nur das abrufen, was sich seit dem letzten Lauf geändert hat | Derselbe Aufruf, mit mode: "incremental" |
| Geplante Aktualisierung | Ein Cron-Ausdruck, den die Plattform für Sie ausführt | Niemand — 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.
completedisttrue, unddatasetist 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
PROCESSINGmarkiert, und ein Konnektor läuft Sekunden später.completedistfalse. Pollen Sie den Datensatz oder hören Sie auf dem Websocket, bisprocessingStatusPROCESSINGverlä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.backendliest sich alsdb_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.
executionCountsteigt,lastStatusistsuccess, und die neueste Zeile ist noch aus dem März. - Ein Datensatz kann überhaupt keinen Zeitplan haben, und dann hat
executionCountkeine 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
| Feld | Was es ist |
|---|---|
asOf | Der 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. |
lastLoadedAt | Wann 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. |
refresh | Ob 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_refreshunddelete_refreshdecken 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).