Passa al contenuto principale

Mantieni i tuoi dati aggiornati

Una dashboard vale il suo URL solo se i numeri che mostra sono aggiornati. Questa è la parte che una sessione dell'agente non può lasciarsi alle spalle: il tuo agente costruisce la dashboard e se ne va, e i dati sottostanti devono continuare ad arrivare secondo una pianificazione, per mesi, senza che nessuno sia coinvolto.

La freschezza è quindi stato della piattaforma, non codice della dashboard. La configuri una volta su un dataset; continua a funzionare indipendentemente dal fatto che qualcuno tenga aperta una sessione.

I tre modi in cui i dati avanzano​

Cos'èChi lo attiva
Aggiornamento su richiestaRipreleva subito un datasetTu, il tuo agente, o la tua CI — POST /v1/datasets/{dataset_id}/refresh
Caricamento incrementalePreleva solo ciò che è cambiato dall'ultima esecuzioneLa stessa chiamata, con mode: "incremental"
Aggiornamento pianificatoUn'espressione cron che la piattaforma esegue per teNessuno — il poller cron della piattaforma

Aggiorna subito un dataset​

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);

Due esiti, e la differenza conta:

  • 200 OK — il lavoro è effettivamente terminato durante la richiesta. completed è true e dataset è la riga finale. È ciò che fa un ri-download di un dataset pubblico.
  • 202 Accepted — l'aggiornamento è stato avviato. La riga è bloccata e contrassegnata come PROCESSING, e un connettore viene eseguito pochi secondi dopo. completed è false. Effettua polling sul dataset, oppure ascolta sul websocket, finché processingStatus non esce da PROCESSING.

Un 202 non è un fallimento e non è un successo — è una promessa. Tratta "la chiamata è tornata" e "i dati sono aggiornati" come due fatti distinti, e verifica il secondo prima di dire a qualcuno che la dashboard è aggiornata. La chiamata che lo verifica è get_data_freshness — tutto ciò che precede quella sezione riguarda il far muovere i dati, non quanto siano vecchi.

Caricamenti incrementali, e l'esecuzione che non è ancora incrementale​

Passa una modalità per prelevare solo le righe nuove:

from vibedasher.models import RefreshRequest

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

requestedMode ripete ciò che hai richiesto. effectiveMode è ciò che è effettivamente stato eseguito, e non sono sempre uguali. Una richiesta incrementale contro un dataset senza ancora un checkpoint riporta effectiveMode: "bootstrap": viene eseguita come una sovrascrittura completa e registra il watermark, quindi l'esecuzione successiva è la prima genuinamente incrementale.

Ramifica in base a effectiveMode, mai in base a ciò che hai richiesto. (Questa è la stessa regola di client.query, che riporta la corsia di esecuzione effettivamente usata piuttosto che quella richiesta — uno schema da interiorizzare: la piattaforma ti dice cosa è successo, ed è compito tuo leggerlo.)

Per forzare la prossima esecuzione a recuperare di nuovo tutto, azzera il watermark:

client.datasets.reset_checkpoint(dataset_id)

Aggiornamento pianificato​

Una pianificazione è una riga che crei una volta. Porta con sé un'espressione cron e il progetto dbt che aggiorna:

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)

La risposta porta un blocco scheduleRegistration. Controlla registered. È true solo quando la pianificazione è realmente attiva; quando è false, reason e message dicono perché, e nextRun è null.

Quel campo esiste a causa di un'interruzione reale, e la storia è il miglior argomento a favore del design attuale. L'implementazione originale registrava ogni pianificazione come una propria regola cloud, avvolgeva la chiamata in un except generico, e rispondeva 201 Created con un sicuro nextRun. Il ruolo API non aveva il permesso di creare quelle regole, quindi non veniva mai registrato nulla — e a ogni cliente veniva detto che i suoi dati si sarebbero aggiornati. È stato scoperto il 2026‑09‑05: non esisteva alcuna regola, e l'unica pianificazione nel database aveva un nextRun di cinque mesi nel passato e un conteggio di esecuzioni pari a zero.

La correzione non è stata una migliore gestione degli errori. È stata la rimozione della cosa che poteva fallire: la riga è la pianificazione. Un poller cron viene eseguito ogni cinque minuti, rivendica ogni riga scaduta con un compare-and-set, e la esegue. Non c'è alcuna registrazione esterna che possa riuscire o fallire, quindi l'unica cosa che può andare storta al momento della creazione è un'espressione cron che non può essere valutata — e ora viene rifiutata subito, non al momento dell'esecuzione.

Due conseguenze da tenere in conto:

  • Una pianificazione scatta entro circa cinque minuti dal suo orario previsto, non al secondo esatto. Cron qui significa "non prima di", non "esattamente alle".
  • scheduleRegistration.backend riporta db_cron. Viene mantenuto nella risposta perché i client si ramificano in base ad esso.

Leggere cosa è successo​

Ogni riga di pianificazione porta la propria cronologia — 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 è il campo da tenere d'occhio. Una pianificazione esistente da una settimana con executionCount: 0 non è lenta — non sta girando, ed è esattamente il fallimento che il design sopra è stato costruito per rendere visibile invece che silenzioso.

Quanto sono vecchi questi numeri?​

Tutto quanto precede risponde a «la pipeline funziona?». Non risponde a «ciò che questa dashboard mostra è aggiornato?», e le due cose divergono in entrambe le direzioni:

  • Una pianificazione può girare perfettamente e non caricare nulla di nuovo. executionCount sale, lastStatus è success, e la riga più recente è ancora di marzo.
  • Un dataset può non avere alcuna pianificazione, e allora executionCount non ha alcuna riga da leggere. Un caricamento statico è il caso comune: le sue righe cambiano quando qualcuno ne carica di nuove e in nessun altro momento, così un elenco di pianificazioni sano può convivere con un dataset vecchio di nove giorni senza dirne nulla. L'assenza di una pianificazione non è una pianificazione con un contatore sbagliato.

Una sola chiamata risponde alla vera domanda del lettore, misurata sulle righe e non sulla contabilità interna della piattaforma:

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);

Via MCP è get_data_freshness, che accetta esattamente uno tra viz_id e dataset_id. Con un viz_id risolve ogni dataset che la dashboard legge e riporta il più vecchio — una dashboard è aggiornata solo quanto il suo input più vecchio. La rotta REST è GET /v1/datasets/{dataset_id}/freshness.

I tre campi, e perché sono tre​

CampoChe cos'è
asOfIl valore più recente nella colonna temporale dei dati stessi. Questa è la risposta, e non può lusingarti: è letta dalle righe. ageDays è quanto è vecchio; negativo significa dati post-datati.
lastLoadedAtQuando le righe sono arrivate l'ultima volta. Un fatto di pipeline, non un'età — un file caricato stamattina può non contenere nulla più recente di marzo. Non citarlo mai come data di riferimento.
refreshSe qualcosa è pianificato per rendere i numeri più nuovi, inclusa la risposta «niente, e niente può esserlo» per un file piatto. È il campo che copre il caso «nessuna pianificazione» descritto sopra.

status dice quanto fidarsi di asOf: MEASURED (è reale), NO_TIME_COLUMN (i dati non portano alcuna colonna di data, quindi non hanno una data di riferimento da avere), oppure UNKNOWN — che significa che il fatto non è stato stabilito e non è mai un via libera.

lastLoadedSource nomina quale orologio ha risposto a lastLoadedAt. iceberg_snapshot è l'ora di scrittura fisica dello snapshot corrente della tabella — il solo vero istante di arrivo, e uno che una rinomina non può spostare. not_established significa che lastLoadedAt è null di proposito: quel percorso di lettura non ha consultato lo snapshot, quindi invece di rispondere con l'updatedAt della riga — un orologio di scrittura che qualsiasi modifica fa avanzare, e che una volta ha riportato righe arrivate nove giorni dopo il fatto — il campo rinuncia. Un campo che rinuncia è strettamente meglio di uno che risponde con l'orologio sbagliato. La rotta sopra è la chiamata che legge quello giusto. get_viz e list_vizzes portano un dataFreshness gratuito, definito sulla postura di aggiornamento e UNKNOWN sulla data di riferimento; questa chiamata è ciò che risolve quell'UNKNOWN.

Ciò che non è l'età dei dati​

Né publishStatus, né lastBuild.createdAt, né processingStatus, né updatedAt rispondono a questa domanda. Sono, nell'ordine: se esiste un bundle, quando il codice è stato compilato, se il file è stato analizzato una volta, e quando la riga è cambiata l'ultima volta. Ognuno di essi si legge come aggiornato su una dashboard i cui numeri hanno settimane. Se stai per riportare l'attualità da uno di questi, chiama invece la rotta di freschezza.

Costa un SELECT MAX(<colonna di data>) per dataset — la stessa sonda che la dashboard renderizzata esegue già al montaggio.

Dove vive ogni gesto​

  • Le pianificazioni sono la superficie MCP. create_refresh, list_refreshes, get_refresh e delete_refresh coprono tutto il ciclo di vita di un aggiornamento ricorrente dal tuo agente.
  • «Aggiorna ora» è l'SDK e la REST API. Un'esecuzione una tantum è una sola chiamata lì — vedi il Riferimento API.
  • I progetti dbt portano le proprie rotte di esecuzione e aggiornamento (/v1/dbt/projects/{project_id}/run, /v1/dbt/projects/{project_id}/refreshes).