# 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[​](#die-drei-wege-wie-daten-vorankommen "Direkter Link zur 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[​](#refresh-a-dataset-now "Direkter Link zur 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`](#how-old-are-the-numbers) — 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[​](#inkrementelle-ladevorgänge-und-der-lauf-der-noch-nicht-inkrementell-ist "Direkter Link zur 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[​](#geplante-aktualisierung "Direkter Link zur 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[​](#lesen-was-passiert-ist "Direkter Link zur 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?[​](#how-old-are-the-numbers "Direkter Link zur 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[​](#die-drei-felder-und-warum-es-drei-sind "Direkter Link zur 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[​](#was-nicht-das-alter-der-daten-ist "Direkter Link zur 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[​](#wo-jede-geste-lebt "Direkter Link zur 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](/de/reference/vibedasher-sdk-api.md)**.
* **dbt-Projekte** tragen ihre eigenen Ausführungs- und Aktualisierungsrouten (`/v1/dbt/projects/{project_id}/run`, `/v1/dbt/projects/{project_id}/refreshes`).
