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 richiesta | Ripreleva subito un dataset | Tu, il tuo agente, o la tua CI — POST /v1/datasets/{dataset_id}/refresh |
| Caricamento incrementale | Preleva solo ciò che è cambiato dall'ultima esecuzione | La stessa chiamata, con mode: "incremental" |
| Aggiornamento pianificato | Un'espressione cron che la piattaforma esegue per te | Nessuno — 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ètrueedatasetè 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éprocessingStatusnon esce daPROCESSING.
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.backendriportadb_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.
executionCountsale,lastStatusèsuccess, e la riga più recente è ancora di marzo. - Un dataset può non avere alcuna pianificazione, e allora
executionCountnon 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
| Campo | Che cos'è |
|---|---|
asOf | Il 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. |
lastLoadedAt | Quando 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. |
refresh | Se 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_refreshedelete_refreshcoprono 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).