55. Écrire une méthode d'échantillonnage de table

55.1. Fonctions de support d'une méthode d'échantillonnage

L'implémentation de la clause TABLESAMPLE de PostgreSQL™ supporte l'utilisation de méthodes personnalisées d'échantillonnage de table, en plus des méthodes BERNOULLI et SYSTEM qui sont requises par le standard SQL. La méthode d'échantillonnage détermine les lignes de la table sélectionnées lorsque la clause TABLESAMPLE est utilisée.

Au niveau SQL, une méthode d'échantillonnage de table est représentée par une simple fonction, classiquement implémentée en C, et qui a la signature suivante :

method_name(internal) RETURNS tsm_handler

Le nom de la fonction est le même que le nom de la méthode apparaissant dans la clause TABLESAMPLE. L'argument internal est factice (il a toujours une valeur de zéro) qui sert uniquement à interdire que cette fonction soit appelée directement à partir d'une commande SQL. Le résultat de cette fonction doit être une structure allouée avec palloc de type TsmRoutine, qui contient des pointeurs de fonction supportant la méthode d'échantillonnage. Ces fonctions sont des fonctions C pleines et entières qui ne sont ni visibles ni appellables au niveau SQL. Les fonctions de support sont décrites dans le Section 55.1, « Fonctions de support d'une méthode d'échantillonnage ».

En plus des pointeurs de fonction, la structure TsmRoutine doit fournir ces champs additionnels :

List *parameterTypes

Il s'agit d'une liste d'OID contenant les OID des types de données du ou des paramètre(s) qui seront acceptés par la clause TABLESAMPLE lorsque cette méthode d'échantillonnage sera utilisée. Par exemple, pour les méthodes incluses, cette liste contient un simple élément avec la valeur FLOAT4OID, qui représente le pourcentage d'échantillonnage. Les méthodes d'échantillonnage personnalisées peuvent avoir des paramètres en plus ou différents.

bool repeatable_across_queries

Si true, la méthode d'échantillonnage peut renvoyer des échantillons identiques pour des requêtes successives, si les mêmes paramètres et la valeur de graine de la clause REPEATABLE sont fournis à chaque fois et que le contenu de la table n'a pas changé. Lorsque positionné à false, la clause REPEATABLE n'est pas acceptée comme valable pour la méthode d'échantillonnnage.

bool repeatable_across_scans

Si true, la méthode d'échantillonnage peut renvoyer des échantillons identiques pour des parcours successifs dans la même requête (en supposant des paramètres, une graine et une image de la base inchangés). Lorsque positionné à false, le planificateur ne sélectionnera pas des plans qui requièrent de parcourir la table échantillonnée plus d'une fois, dans la mesure où ceci pourrait entraîner des résultats de sortie incohérents.

La structure TsmRoutine est déclarée dans le fichier src/include/access/tsmapi.h, auquel il convient de se référer pour des détails supplémentaires.

Les méthodes d'échantillonnage de table incluses dans la distribution standard sont de bonnes références pour écrire la vôtre. Jeter un œil dans le répertoire src/backend/access/tablesample de l'arbre des sources pour les méthodes incluses, et dans le répertoire contrib pour des méthodes additionnelles.

55.1. Fonctions de support d'une méthode d'échantillonnage

La fonction du gestionnaire TSM renvoie une structure TsmRoutine allouée avec palloc contenant des pointeurs vers les fonctions de support décrites ci-dessous. La plupart des fonctions sont obligatoires, mais certaines sont optionnelles, et leurs pointeurs peuvent être NULL.

void
SampleScanGetSampleSize (PlannerInfo *root,
                         RelOptInfo *baserel,
                         List *paramexprs,
                         BlockNumber *pages,
                         double *tuples);

Cette fonction est appelée durant la planification. Elle doit estimée le nombre de pages de la relation qui seront lues lors d'un simple parcours, et le nombre de lignes qui seront sélectionnées lors du parcours. (Par exemple, cela pourrait être déterminé en estimant la fraction échantillonnée, puis en multipliant baserel->pages et baserel->tuples par ce chiffre, après s'être assuré d'avoir arrondi ces chiffres à des valeurs entières.) La liste paramexprs contient les expressions qui sont les paramètres de la clause TABLESAMPLE. Il est recommandé d'utiliser la fonction estimate_expression_value pour essayer de réduire ces expressions à des constantes, si leurs valeurs sont nécessaires pour les besoins de l'estimation ; mais la fonction doit renvoyer les estimations des tailles même si elles ne peuvent être réduites, et elle ne devrait pas échouer même si les valeurs apparaissent invalides (rappelez-vous qu'il s'agit uniquement d'une estimation de valeurs futures à l'exécution). Les paramètres pages et tuples sont les valeurs de sorties.

void
InitSampleScan (SampleScanState *node,
                int eflags);

Initialise pour l'exécution d'un nœud du plan SampleScan. La fonction est appelée au démarrage de l'exécuteur. Elle devrait effectuer toutes les initialisations nécessaires avant que le traitement ne puisse commencer. Le nœud SampleScanState a déjà été créé, mais son champ tsm_state est NULL. La fonction peut allouer via palloc les données internes d'état nécessaires à la fonction d'échantillonnage, et enregistrer un pointeur dans node->tsm_state. Des informations à propos de la table à parcourir sont accessibles via d'autres champs du nœud SampleScanState (mais veuillez noter que le descripteur du parcours node->ss.ss_currentScanDesc n'est pas encore positionné à ce stade). eflags contient un ensemble de bits décrivant le mode opératoire de l'exécuteur pour ce nœud du plan.

Lorsque (eflags & EXEC_FLAG_EXPLAIN_ONLY) est true, le parcours ne sera pas encore effectué. Dans ce cas, cette fonction devrait effectuer uniquement le minimum requis pour mettre dans un état valide le nœud pour la commande EXPLAIN et la fonction EndSampleScan.

Cette fonction est optionnelle (positionnez alors le pointeur sur NULL), auquel cas la fonction BeginSampleScan doit effectuer toutes les initialisations nécessaires à la méthode d'échantillonnage.

void
BeginSampleScan (SampleScanState *node,
                 Datum *params,
                 int nparams,
                 uint32 seed);

Débute l'exécution d'un parcours d'échantillonnage. Cette fonction est appelée juste avant la première tentative de récupération d'une ligne, et peut être appelée à nouveau si le parcours a besoin d'être relancé. Des informations sur la table à parcourir sont accessibles via les champs de la structure du nœud SampleScanState (mais notez que le descripteur du parcours node->ss.ss_currentScanDesc n'est pas encore positionné à ce stade). Le tableau params, de longueur nparams, contient les valeurs des paramètres indiqués dans la clause TABLESAMPLE. Ces paramètres seront en nombre et de types spécifiés par la méthode d'échantillonnage dans la liste parameterTypes, et ont été vérifiés comme n'étant pas null. seed> contient une graine à usage de la méthode d'échantillonnage pour générer des nombres aléatoires ; il s'agit d'un hash dérivé de la valeur de la clause REPEATABLE si fournie, ou du résultat de la fonction random() dans le cas contraire.

Cette fonction peut ajuster les champs node->use_bulkread et node->use_pagemode. Si node->use_bulkread est true, ce qui est le cas par défaut, le parcours utilisera une stratégie d'accès aux tampons mémoires qui encourage le recyclage des tampons après usage. Il peut être raisonnable de mettre cette valeur à false si le parcours doit visiter seulement une petite fraction des pages de la table. Si node->use_pagemode est true, ce qui est la valeur par défaut, le parcours effectuera une vérification de la visibilité avec un unique passage pour l'ensemble des lignes composant chaque page visitée. Il peut être raisonnable de mettre cette valeur à false si le parcours doit sélectionner seulement une petite fraction des lignes de chaque page visitée. Ceci aura pour conséquence un nombre moindre de vérifications de visibilité effectuées, mais chacune sera plus coûteuse car elle demandera plus de verrouillages.

Si la méthode d'échantillonnage est marquée comme repeatable_across_scans, elle doit être capable de sélectionner le même ensemble de lignes lors d'un parcours relancé à nouveau comme elle l'a fait à l'origine, c'est-à-dire qu'un nouvel appel à la fonction BeginSampleScan doit engendrer la sélection des mêmes lignes que précédemment (dans la mesure où les paramètres de la clause TABLESAMPLE et la graine ne changent pas).

BlockNumber
NextSampleBlock (SampleScanState *node);

Renvoie le numéro du bloc de la page suivante à parcourir, ou InvalidBlockNumber si il n'y a plus de pages à parcourir.

Cette fonction peut être omise (mettez le pointeur à la valeur NULL), auquel cas le code du serveur effectuera un parcours séquentiel de l'ensemble de la relation. Un tel parcours peut utiliser un parcours synchronisé, aussi la méthode d'échantillonnage ne peut pas supposer que les pages de la relation sont visitées dans le même ordre à chaque parcours.

OffsetNumber
NextSampleTuple (SampleScanState *node,
                 BlockNumber blockno,
                 OffsetNumber maxoffset);

Renvoie le décalage de la ligne suivante à echantillonner sur la page spécifiée, ou InvalidOffsetNumber si il n'y a plus de lignes à échantillonner. maxoffset est le décalage le plus grand utilisé sur la page.

[Note]

Note

Il n'est pas explicitement indiqué à la fonction NextSampleTuple les décalages dans l'intervalle 1 .. maxoffset qui contiennent des lignes valides. Ce n'est normalement pas un problème dans la mesure où le code du serveur ignore les requêtes pour échantillonner des lignes manquantes ou non visibles ; ceci ne devrait pas entraîner de biais dans l'échantillon. Cependant, si nécessaire, la fonction peut examiner node->ss.ss_currentScanDesc->rs_vistuples[] pour identifier les lignes valides et visibles. (Ceci requiert que node->use_pagemode soit true.)

[Note]

Note

La fonction NextSampleTuple ne doit pas assumer que blockno est le même numéro de page que celui renvoyé par le plus récent appel à la fonction NextSampleBlock. Le numéro a été renvoyé par un précédent appel à la fonction NextSampleBlock, mais le code du serveur est autorisé à appeler NextSampleBlock en amont du parcours des pages, pour rendre possible la récupération en avance. Il est acceptable d'assumer qu'une fois le parcours d'une page débuté, les appels successifs à la fonction NextSampleTuple se réfèrent tous à la même page jusqu'à ce que InvalidOffsetNumber soit retourné.

void
EndSampleScan (SampleScanState *node);

Termine le parcours et libère les ressources. Il n'est normalement pas important de libérer la mémoire allouée via palloc, mais toutes les ressources visibles à l'extérieur doivent être nettoyées. Cette fonction peut être omise (positionnez le pointeur sur la valeur NULL) dans la plupart des cas où de telles ressources n'existent pas.