Polls incrémentaux
Transmettez un curseur comme `since_id` d'une exécution d'une source poll à la suivante, pour que le fournisseur ne renvoie que les nouveautés
Par défaut, chaque exécution d'une source de données déclarative en mode poll envoie la même requête, et upsert sur l'id de l'élément absorbe le chevauchement. Beaucoup d'API fournisseur font mieux : elles renvoient un repère avec chaque réponse (le meta.newest_id de X) et l'acceptent à l'appel suivant (since_id), si bien que chaque exécution ne récupère que les nouveautés.
Le bloc cursor de l'ingest d'un poll déclare ce repère :
param— le paramètre de requête sous lequel la valeur stockée est envoyée.from— l'endroit de la réponse où la valeur suivante est lue.fromLink— à la place defrom, pour un fournisseur qui répond par un lien vers sa requête suivante : l'endroit de la réponse où se trouve ce lien. La valeur suivante est le paramètre de requêteparamde ce lien.morePath— facultatif : l'endroit où le fournisseur indique que d'autres résultats attendent.
La plateforme stocke le curseur par source installée et ne le fait avancer qu'une fois les lignes de l'exécution écrites.
Déclarer un curseur
ingest: {
mode: 'poll',
intervalSec: 300,
request: {
url: 'https://api.x.com/2/tweets/search/recent',
query: { query: 'from:scrydon', max_results: '100' },
authRef: 'token',
},
response: { itemsPath: '$.data', format: 'json', itemsOptional: true },
mapping: {
id: { path: 'id' },
text: { path: 'text' },
createdAt: { path: 'created_at' },
},
writeMode: 'upsert',
cursor: { param: 'since_id', from: '$.meta.newest_id', morePath: '$.meta.next_token' },
}| Champ | Signification |
|---|---|
param | Nom du paramètre de requête, de 1 à 128 caractères. Ajouté après request.query et avant l'identifiant. |
from | Chemin dans la réponse décodée, dans la même syntaxe que itemsPath ($.meta.newest_id). |
fromLink | À la place de from : chemin vers un lien de la réponse décodée, dont le paramètre de requête param est la valeur suivante. Voir Un curseur dans un lien. |
morePath | Chemin facultatif que le fournisseur remplit quand il détient plus de résultats que cette réponse n'en contient. |
Un curseur déclare exactement l'un de from et fromLink. cursor n'est accepté que sur une source poll. Tout autre mode d'ingestion refuse ce champ.
Un curseur dans un lien avec fromLink
Certains fournisseurs renvoient la requête suivante sous forme de lien complet plutôt que de valeur. Les requêtes delta de Microsoft Graph terminent leur réponse par @odata.deltaLink, et le jeton à envoyer la fois suivante est le paramètre $deltatoken de ce lien :
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,mail)",
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?$deltatoken=oEcOySpF_hWYmTIUZBOIfPzcwisr…",
"value": [{ "id": "87d349ed-…", "displayName": "Adele Vance", "mail": "adelev@contoso.com" }]
}fromLink désigne le lien ; param nomme le paramètre de requête lu dans ce lien, et c'est ce même paramètre qui est envoyé à l'exécution suivante :
request: {
url: 'https://graph.microsoft.com/v1.0/users/delta',
query: { $select: 'displayName,mail' },
authRef: 'token',
},
response: { itemsPath: '$.value', format: 'json', itemsOptional: true },
writeMode: 'upsert',
cursor: {
param: '$deltatoken',
fromLink: "$['@odata.deltaLink']",
morePath: "$['@odata.nextLink']",
},- Les clés de Graph contiennent un
.: le chemin les met entre guillemets dans des crochets,$['@odata.deltaLink']. Un chemin à points ($.@odata.deltaLink) chercheraitdeltaLinkdans@odataet ne trouverait rien. - Le lien doit être une URL absolue qui porte
param. La valeur est stockée décodée et envoyée encodée : un jeton qui contient%3ddans le lien est renvoyé tel quel. - Seul
paramest pris dans le lien. Le reste de la requête suivante est celle que vous avez déclarée — son url,request.queryet l'identifiant — : déclarez donc la requête vers laquelle pointe le lien. - Un cycle delta qui ne tient pas dans une réponse n'a pas de
@odata.deltaLinksur sa première page, seulement@odata.nextLink. Quand ce lien de page suivante porte aussiparam, le curseur y avance : chaque exécution lit la page suivante du cycle. Quand il porte un autre paramètre, le curseur ne bouge pas et l'exécution suivante recommence le cycle. Voir Pages supplémentaires. - Un fournisseur peut faire expirer un jeton. Graph répond HTTP 410 Gone à une requête dont le
$deltatokena expiré. Cette exécution échoue et le curseur reste en place : chaque exécution suivante échoue de la même façon jusqu'à ce qu'un administrateur de l'organisation réinitialise le curseur ; l'exécution suivante commence alors un nouveau cycle.
Comment une exécution l'utilise
- La première exécution n'a aucun curseur stocké et envoie la requête exactement telle que déclarée — sans
param. - Une exécution suivante ajoute
param=<valeur stockée>à la chaîne de requête, tant que la valeur stockée a été prise avec la même connexion et la même requête (voir Ce qui réinitialise un curseur). - Une fois la réponse décodée, la valeur à
fromdevient le curseur suivant si c'est une chaîne non vide ou un nombre fini. Un nombre est envoyé sous forme de texte. Un id au-delà de 2^53 perd en précision sous forme de nombre JSON : faites pointerfromvers la forme chaîne du fournisseur quand il en a une (les ids de X sont des chaînes). - Le curseur suivant n'est stocké qu'après l'écriture des lignes de l'exécution, dans la même mise à jour qui enregistre l'exécution comme réussie.
Tout le reste laisse le curseur où il était :
fromne résout rien, ou résoutnull, une chaîne vide, un objet ou une liste. X ometmeta.newest_idquand rien n'est nouveau : l'exécution suivante repart du même point.fromLinkrésout autre chose qu'une URL absolue, ou une URL sansparamou avec unparamvide.- La valeur dépasse 2 048 octets (UTF-8). L'exécution journalise
cursor_too_longet le curseur ne bouge pas. PourfromLink, la limite porte sur la valeur décodée du paramètre, pas sur le lien entier. - La récupération a échoué, la réponse n'a pas pu être décodée, ou les lignes n'ont pas pu être écrites. Un curseur n'est jamais stocké au-delà de lignes qu'une exécution en échec a perdues.
Une exécution réussie fait tout de même avancer le curseur au-delà des lignes qu'elle n'a pas écrites : les lignes refusées par la validation de la source et celles que la politique de données de l'organisation bloque. Les récupérer à nouveau aboutirait au même résultat. Si une mise à niveau corrige le mapping ou la validation qui les refusait, le curseur est conservé (voir Ce qui réinitialise un curseur) : réinitialisez-le pour que l'exécution suivante reparte comme une première exécution.
Comme le curseur n'avance qu'après une écriture réussie, une exécution qui échoue en cours de route est récupérée à nouveau depuis le même point. Le mode d'écriture absorbe les lignes déjà récupérées — c'est pourquoi un curseur exige upsert ou changed-only.
Mode d'écriture
Un curseur exige writeMode: 'upsert' (la valeur par défaut) ou 'changed-only'. Les deux autres sont refusés à la compilation :
writeMode | Pourquoi il ne peut pas porter de curseur |
|---|---|
append | Une exécution récupérée à nouveau après un échec écrirait deux fois les mêmes lignes. |
replace | La réponse de chaque exécution ne contient que les nouveautés : la table ne garderait que les lignes de la dernière exécution. |
Le paramètre doit lui appartenir
param ne peut entrer en collision avec rien d'autre qui définit un paramètre de requête : une clé de request.query, un paramètre déjà écrit dans request.url, ou le nom de requête de l'identifiant quand auth.placement vaut query. L'une ou l'autre valeur déciderait du curseur que voit le fournisseur, ou serait décidée par lui. Renommez l'un des deux.
Pages supplémentaires : morePath
Une exécution envoie une requête et lit une réponse. Quand morePath résout autre chose que rien, null, une chaîne vide ou false, le fournisseur garde des pages que cette exécution n'a pas lues. L'exécution journalise cursor_more_pages, écrit les lignes qu'elle a lues et n'est pas comptée comme complète : l'état de synchronisation de la source indique que d'autres pages attendent, et cette exécution ne certifie pas sa santé. Une exécution n'est complète qu'une fois la dernière page lue.
Ce que fait le curseur dépend de l'endroit où il est lu :
from. Le curseur ne bouge pas. La valeur àfromdésigne l'élément le plus récent, et les pages derrière lui sont plus anciennes ; y avancer les sauterait pour de bon. L'exécution suivante redemande depuis le même point.fromLink. Le curseur avance au paramètre de requêteparamdu lien de page suivante àmorePath: l'exécution suivante lit la page suivante du cycle. Le delta d'un drive (?token=) se pagine ainsi. Quand le lien de page suivante porte un autre paramètre (le delta des utilisateurs de Graph pagine avec$skiptokenmais reprend avec$deltatoken), le curseur ne bouge pas, et chaque exécution recommence le cycle.
Augmentez la taille de page là où le fournisseur le permet (l'en-tête Prefer: odata.maxpagesize=… de Graph, dans request.headers), pour qu'un cycle ou une rafale tienne dans une réponse.
Si le journal affiche cursor_more_pages à la plupart des exécutions, le fournisseur produit plus de résultats par intervalle qu'une réponse n'en contient : raccourcissez l'intervalle, ou augmentez la taille de page du fournisseur dans les limites de ressources. Avec un curseur from, c'est obligatoire et pas seulement plus rapide : tant qu'une réponse ne contient pas toute la rafale, le curseur ne peut pas avancer.
Ce qui réinitialise un curseur
Un curseur appartient à la requête sous laquelle il a été pris. La plateforme le stocke avec une portée : une empreinte de la connexion résolue par l'exécution, des endpoints admis de cette connexion, de la request déclarée, des intervalSec et minIntervalSec de la définition, et du bloc cursor lui-même. Une exécution dont la portée diffère n'envoie pas la valeur stockée ; elle démarre comme une première exécution et journalise cursor_reset. Si cette exécution ne peut pas faire avancer le curseur, celui qui était stocké est effacé.
| Événement | Curseur |
|---|---|
| La source est dirigée vers une autre connexion | Effacé dans la même modification |
| Une source sans connexion épinglée est épinglée à la connexion qu'elle lit déjà | Conservé |
| Une source sans connexion épinglée est épinglée à une autre connexion | Non envoyé par l'exécution suivante, qui démarre sans lui |
| Une instance supplémentaire est renommée sur la même connexion | Conservé |
Un administrateur modifie l'endpoint de la connexion, ou une mise à niveau modifie la request, le bloc cursor, ou les intervalSec ou minIntervalSec de la définition | Non envoyé par l'exécution suivante, qui démarre sans lui |
| Une personne modifie la planification de la source dans Analytics | Conservé — cette valeur n'entre pas dans la portée |
| Une mise à niveau qui ne modifie que le mapping ou la table | Conservé |
| La source est mise en pause, puis reprise | Conservé |
| La source est supprimée | Supprimé avec elle ; une source ajoutée à nouveau démarre sans curseur |
| Un administrateur de l'organisation le réinitialise | Effacé ; l'exécution suivante démarre sans lui |
Réinitialiser un curseur à la main
Un administrateur de l'organisation peut effacer le curseur d'une source dans Analytics → Sources de données : ouvrez la source, allez dans l'onglet Planification et conservation et choisissez Réinitialiser le curseur, puis confirmez. L'action n'est proposée que pour une source qui déclare cursor, et seulement aux administrateurs de l'organisation. Une réinitialisation qui efface un curseur stocké est enregistrée dans le journal d'audit sous data_source.cursor.reset, au nom de l'administrateur ; une source sans curseur stocké n'a rien à effacer : Analytics indique Rien à réinitialiser, et rien n'est enregistré.
Utilisez-la quand la table a perdu des lignes que le fournisseur détient encore — après une suppression par erreur, par exemple. L'exécution suivante n'envoie pas de param et récupère ce que le fournisseur renvoie pour une première requête ; upsert absorbe les lignes déjà présentes dans la table.
Une exécution déjà en cours n'est pas arrêtée. Si une exécution a démarré avant la réinitialisation, elle peut encore stocker le curseur auquel elle est parvenue en se terminant, et la réinitialisation ne prend alors effet qu'à l'exécution d'après. Si une exécution était en cours au moment de la réinitialisation, réinitialisez à nouveau une fois que l'onglet Activité de la source la montre terminée.
Récapitulatif des validations
Ce sont des erreurs de compilation, détectées par extension build avant qu'une archive ne soit téléversée :
| Refusé | Pourquoi |
|---|---|
cursor sur un autre mode que poll | Seul un poll HTTP planifié transmet un curseur d'une exécution à l'autre |
writeMode append ou replace avec un cursor | Une nouvelle récupération dupliquerait des lignes, ou la table ne garderait que les nouvelles lignes de chaque exécution |
param égal à une clé de request.query, à un paramètre de request.url, ou au name de requête de l'identifiant | Le curseur écraserait un autre paramètre, ou serait écrasé par lui |
Un param, from, fromLink ou morePath vide, ou un param de plus de 128 caractères | Chacun désigne quelque chose que l'exécution doit trouver |
from et fromLink ensemble, ou aucun des deux | La valeur suivante se lit à un seul endroit |
Tout champ non listé dans cursor | Le bloc est .strict() — une faute de frappe est une erreur, pas un no-op silencieux |