Dans les articles précédents, nous avons discuté du
moteur d'indexation PostgreSQL, de l'interface des méthodes d'accÚs et des méthodes suivantes:
index de hachage ,
arbres B ,
GiST ,
SP-GiST ,
GIN et
RUM . Le sujet de cet article est les index BRIN.
Brin
Concept général
Contrairement aux index avec lesquels nous avons déjà été rassemblés, l'idée de BRIN est d'éviter de parcourir des lignes définitivement inadaptées plutÎt que de trouver rapidement celles qui correspondent. Il s'agit toujours d'un index inexact: il ne contient pas du tout de TID de lignes de table.
Simplement, BRIN fonctionne bien pour les colonnes oĂč les valeurs sont en corrĂ©lation avec leur emplacement physique dans le tableau. En d'autres termes, si une requĂȘte sans clause ORDER BY renvoie les valeurs de colonne virtuellement dans l'ordre croissant ou dĂ©croissant (et qu'il n'y a pas d'index sur cette colonne).
Cette méthode d'accÚs a été créée dans le cadre d'
Axle , le projet europĂ©en de trĂšs grandes bases de donnĂ©es analytiques, avec un Ćil sur des tables de plusieurs tĂ©raoctets ou des dizaines de tĂ©raoctets. Une caractĂ©ristique importante de BRIN qui nous permet de crĂ©er des index sur de telles tables est une petite taille et des frais gĂ©nĂ©raux minimaux de maintenance.
Cela fonctionne comme suit. Le tableau est divisé en
plages de plusieurs pages (ou de plusieurs blocs, ce qui est le mĂȘme) - d'oĂč le nom: Block Range Index, BRIN. L'index stocke
des informations rĂ©capitulatives sur les donnĂ©es de chaque plage. En rĂšgle gĂ©nĂ©rale, ce sont les valeurs minimales et maximales, mais il se trouve qu'elles sont diffĂ©rentes, comme indiquĂ© plus loin. Supposons qu'une requĂȘte soit exĂ©cutĂ©e contenant la condition d'une colonne; si les valeurs recherchĂ©es n'entrent pas dans l'intervalle, toute la plage peut ĂȘtre ignorĂ©e; mais s'ils obtiennent, toutes les lignes de tous les blocs devront ĂȘtre parcourues pour choisir celles qui correspondent entre elles.
Ce ne sera pas une erreur de traiter BRIN non pas comme un index, mais comme un accélérateur de balayage séquentiel. Nous pouvons considérer BRIN comme une alternative au partitionnement si nous considérons chaque plage comme une partition "virtuelle".
Voyons maintenant la structure de l'index plus en détail.
La structure
La premiÚre page (plus exactement, zéro) contient les métadonnées.
Les pages contenant les informations récapitulatives sont situées à un certain décalage par rapport aux métadonnées. Chaque ligne d'index de ces pages contient des informations récapitulatives sur une plage.
Entre la méta page et les données récapitulatives, des pages avec la
carte de plage inversée (abrégée en "revmap") sont localisées. En fait, il s'agit d'un tableau de pointeurs (TID) vers les lignes d'index correspondantes.

Pour certaines plages, le pointeur dans "revmap" peut conduire à aucune ligne d'index (une est marquée en gris sur la figure). Dans un tel cas, la plage est considérée comme n'ayant pas encore d'informations récapitulatives.
Numérisation de l'index
Comment l'index est-il utilisé s'il ne contient pas de références aux lignes de table? Cette méthode d'accÚs ne peut certainement pas renvoyer des lignes TID par TID, mais elle peut créer un bitmap. Il peut y avoir deux types de pages bitmap: précises, sur la ligne et inexactes, sur la page. C'est un bitmap inexact qui est utilisé.
L'algorithme est simple. La carte des plages est balayée séquentiellement (c'est-à -dire que les plages sont parcourues dans l'ordre de leur emplacement dans le tableau). Les pointeurs sont utilisés pour déterminer les lignes d'index avec des informations récapitulatives sur chaque plage. Si une plage ne contient pas la valeur recherchée, elle est ignorée et si elle peut contenir la valeur (ou si les informations récapitulatives ne sont pas disponibles), toutes les pages de la plage sont ajoutées au bitmap. Le bitmap résultant est ensuite utilisé comme d'habitude.
Mise Ă jour de l'index
Il est plus intéressant de savoir comment l'index est mis à jour lorsque la table est modifiée.
Lors de l'
ajout d' une nouvelle version d'une ligne à une page de table, nous déterminons dans quelle plage elle est contenue et utilisons la carte des plages pour trouver la ligne d'index avec les informations récapitulatives. Ce sont toutes de simples opérations arithmétiques. Soit, par exemple, la taille d'une plage de quatre et à la page 13, une version de ligne avec la valeur 42 se produit. Le numéro de la plage (commençant par zéro) est 13/4 = 3, par conséquent, dans "revmap", nous prenons le pointeur avec le décalage de 3 (son numéro d'ordre est quatre).
La valeur minimale pour cette plage est 31, et la valeur maximale est 40. Puisque la nouvelle valeur de 42 est hors de l'intervalle, nous mettons Ă jour la valeur maximale (voir la figure). Mais si la nouvelle valeur est toujours dans les limites stockĂ©es, l'index n'a pas besoin d'ĂȘtre mis Ă jour.

Tout cela concerne la situation oĂč la nouvelle version de la page se produit dans une plage pour laquelle les informations rĂ©capitulatives sont disponibles. Lorsque l'index est créé, les informations rĂ©capitulatives sont calculĂ©es pour toutes les plages disponibles, mais tandis que le tableau est davantage dĂ©veloppĂ©, de nouvelles pages peuvent apparaĂźtre qui sortent des limites. Deux options sont disponibles ici:
- GĂ©nĂ©ralement, l'index n'est pas mis Ă jour immĂ©diatement. Ce n'est pas un gros problĂšme: comme dĂ©jĂ mentionnĂ©, lors de la numĂ©risation de l'index, toute la plage sera examinĂ©e. La mise Ă jour rĂ©elle est effectuĂ©e pendant le "vide", ou elle peut ĂȘtre effectuĂ©e manuellement en appelant la fonction "brin_summarize_new_values".
- Si nous créons l'index avec le paramÚtre "autosummarize", la mise à jour se fera immédiatement. Mais lorsque les pages de la plage sont remplies de nouvelles valeurs, les mises à jour peuvent se produire trop souvent, par conséquent, ce paramÚtre est désactivé par défaut.
Lorsque de nouvelles plages se produisent, la taille de "revmap" peut augmenter. Chaque fois que la carte, situĂ©e entre la mĂ©ta-page et les donnĂ©es rĂ©capitulatives, doit ĂȘtre Ă©tendue d'une autre page, les versions de lignes existantes sont dĂ©placĂ©es vers d'autres pages. Ainsi, la carte des plages est toujours situĂ©e entre la mĂ©ta-page et les donnĂ©es rĂ©capitulatives.
Lorsqu'une ligne est
supprimĂ©e , ... rien ne se passe. On peut remarquer que parfois la valeur minimale ou maximale sera supprimĂ©e, auquel cas l'intervalle pourrait ĂȘtre rĂ©duit. Mais pour dĂ©tecter cela, il faudrait lire toutes les valeurs de la plage, ce qui est coĂ»teux.
L'exactitude de l'index n'est pas affectĂ©e, mais la recherche peut nĂ©cessiter de parcourir plus de plages que ce qui est rĂ©ellement nĂ©cessaire. En gĂ©nĂ©ral, les informations rĂ©capitulatives peuvent ĂȘtre recalculĂ©es manuellement pour une telle zone (en appelant les fonctions "brin_desummarize_range" et "brin_summarize_new_values"), mais comment dĂ©tecter un tel besoin? Quoi qu'il en soit, aucune procĂ©dure conventionnelle n'est disponible Ă cette fin.
Enfin, la
mise Ă jour d'une ligne est simplement une suppression de la version obsolĂšte et l'ajout d'une nouvelle.
Exemple
Essayons de construire notre propre mini-entrepÎt de données pour les données des tables de la
base de données de démonstration . Supposons que, aux fins du reporting BI, un tableau dénormalisé soit nécessaire pour refléter les vols au départ d'un aéroport ou atterris à l'aéroport avec la précision d'un siÚge en cabine. Les données de chaque aéroport seront ajoutées au tableau une fois par jour, quand il est minuit dans le fuseau horaire approprié. Les données ne seront ni mises à jour ni supprimées.
Le tableau se présente comme suit:
demo=# create table flights_bi( airport_code char(3), airport_coord point,
Nous pouvons simuler la procédure de chargement des données à l'aide de boucles imbriquées: une externe par jours (nous considérerons
une grande base de donnĂ©es , donc 365 jours), et une boucle interne - par fuseaux horaires (de UTC + 02 Ă UTC + 12) . La requĂȘte est assez longue et n'a pas d'intĂ©rĂȘt particulier, je vais donc la cacher sous le spoiler.
Simulation de chargement des données dans le stockage DO $$ <<local>> DECLARE curdate date := (SELECT min(scheduled_departure) FROM flights); utc_offset interval; BEGIN WHILE (curdate <= bookings.now()::date) LOOP utc_offset := interval '12 hours'; WHILE (utc_offset >= interval '2 hours') LOOP INSERT INTO flights_bi WITH flight ( airport_code, airport_coord, flight_id, flight_no, scheduled_time, actual_time, aircraft_code, flight_type ) AS ( ;
demo=# select count(*) from flights_bi;
count ---------- 30517076 (1 row)
demo=# select pg_size_pretty(pg_total_relation_size('flights_bi'));
pg_size_pretty ---------------- 4127 MB (1 row)
Nous obtenons 30 millions de lignes et 4 Go. Pas si grand, mais assez bon pour un ordinateur portable: le balayage séquentiel m'a pris environ 10 secondes.
Sur quelles colonnes devons-nous créer l'index?
Ătant donnĂ© que les index BRIN ont une petite taille et des frais gĂ©nĂ©raux modĂ©rĂ©s et que les mises Ă jour se produisent rarement, le cas Ă©chĂ©ant, une occasion rare se prĂ©sente de crĂ©er de nombreux index «au cas oĂč», par exemple, sur tous les domaines sur lesquels les utilisateurs analystes peuvent crĂ©er leurs requĂȘtes ad hoc. . Cela ne sera pas utile - tant pis, mais mĂȘme un index qui n'est pas trĂšs efficace fonctionnera mieux que le scan sĂ©quentiel. Bien sĂ»r, il y a des champs sur lesquels il est absolument inutile de construire un index; le pur bon sens les incitera.
Mais il devrait ĂȘtre Ă©trange de se limiter Ă ce conseil, essayons donc d'Ă©noncer un critĂšre plus prĂ©cis.
Nous avons déjà mentionné que les données doivent quelque peu correspondre à leur emplacement physique. Ici, il est logique de se rappeler que PostgreSQL collecte des statistiques de colonne de table, qui incluent la valeur de corrélation. Le planificateur utilise cette valeur pour choisir entre un scan d'index normal et un scan bitmap, et nous pouvons l'utiliser pour estimer l'applicabilité de l'index BRIN.
Dans l'exemple ci-dessus, les donnĂ©es sont Ă©videmment ordonnĂ©es par jours (par "horaire_programmĂ©", ainsi que par "heure_rĂ©elle" - il n'y a pas beaucoup de diffĂ©rence). En effet, lorsque des lignes sont ajoutĂ©es Ă la table (sans suppressions ni mises Ă jour), elles sont disposĂ©es l'une aprĂšs l'autre dans le fichier. Dans la simulation du chargement des donnĂ©es, nous n'avons mĂȘme pas utilisĂ© la clause ORDER BY, par consĂ©quent, les dates dans une journĂ©e peuvent gĂ©nĂ©ralement ĂȘtre mĂ©langĂ©es de maniĂšre arbitraire, mais l'ordre doit ĂȘtre en place. VĂ©rifions ceci:
demo=# analyze flights_bi; demo=# select attname, correlation from pg_stats where tablename='flights_bi' order by correlation desc nulls last;
attname | correlation --------------------+------------- scheduled_time | 0.999994 actual_time | 0.999994 fare_conditions | 0.796719 flight_type | 0.495937 airport_utc_offset | 0.438443 aircraft_code | 0.172262 airport_code | 0.0543143 flight_no | 0.0121366 seat_no | 0.00568042 passenger_name | 0.0046387 passenger_id | -0.00281272 airport_coord | (12 rows)
La valeur qui n'est pas trop proche de zéro (idéalement, prÚs de plus-moins un, comme dans ce cas), nous indique que l'indice BRIN sera approprié.
La classe de voyage "fare_condition" (la colonne contient trois valeurs uniques) et le type de vol "flight_type" (deux valeurs uniques) semblaient ĂȘtre inopinĂ©ment aux deuxiĂšme et troisiĂšme places. C'est une illusion: formellement, la corrĂ©lation est Ă©levĂ©e, alors qu'en rĂ©alitĂ© sur plusieurs pages successives toutes les valeurs possibles seront rencontrĂ©es Ă coup sĂ»r, ce qui signifie que BRIN ne fera aucun bien.
Le fuseau horaire "airport_utc_offset" va ensuite: dans l'exemple considéré, dans un cycle de jour, les aéroports sont classés par fuseaux horaires "par construction".
Ce sont ces deux domaines, le temps et le fuseau horaire, que nous allons approfondir avec.
Affaiblissement possible de la corrélation
La corrĂ©lation qui se fait "par construction" peut ĂȘtre facilement affaiblie lorsque les donnĂ©es sont modifiĂ©es. Et le problĂšme ici n'est pas dans une modification d'une valeur particuliĂšre, mais dans la structure du contrĂŽle d'accĂšs simultanĂ© multiversion: la version de ligne obsolĂšte est supprimĂ©e sur une page, mais une nouvelle version peut ĂȘtre insĂ©rĂ©e partout oĂč de l'espace libre est disponible. Pour cette raison, des lignes entiĂšres se mĂ©langent lors des mises Ă jour.
Nous pouvons contrÎler partiellement cet effet en réduisant la valeur du paramÚtre de stockage "fillfactor" et en laissant ainsi de l'espace libre sur une page pour les futures mises à jour. Mais voulons-nous augmenter la taille d'une table déjà énorme? En outre, cela ne résout pas le problÚme des suppressions: ils "définissent également des interruptions" pour les nouvelles lignes en libérant de l'espace quelque part à l'intérieur des pages existantes. Pour cette raison, les lignes qui autrement arriveraient à la fin du fichier seront insérées à un endroit arbitraire.
Soit dit en passant, c'est un fait curieux. Ătant donnĂ© que l'index BRIN ne contient pas de rĂ©fĂ©rences aux lignes de table, sa disponibilitĂ© ne devrait pas du tout entraver les mises Ă jour HOT, mais c'est le cas.
Ainsi, BRIN est principalement conçu pour des tables de grandes et mĂȘme de grandes tailles qui ne sont pas du tout mises Ă jour ou trĂšs lĂ©gĂšrement mises Ă jour. Cependant, il s'adapte parfaitement Ă l'ajout de nouvelles lignes (Ă la fin du tableau). Cela n'est pas surprenant puisque cette mĂ©thode d'accĂšs a Ă©tĂ© créée en vue des entrepĂŽts de donnĂ©es et des rapports analytiques.
Quelle taille de gamme devons-nous sélectionner?
Si nous traitons une table de téraoctets, notre principale préoccupation lors du choix de la taille d'une plage sera probablement de ne pas rendre l'index BRIN trop grand. Cependant, dans notre situation, nous pouvons nous permettre d'analyser les données plus précisément.
Pour ce faire, nous pouvons sélectionner des valeurs uniques d'une colonne et voir sur combien de pages elles apparaissent. La localisation des valeurs augmente les chances de succÚs dans l'application de l'indice BRIN. De plus, le nombre de pages trouvé demandera la taille d'une plage. Mais si la valeur est "répartie" sur toutes les pages, BRIN est inutile.
Bien sĂ»r, nous devons utiliser cette technique en gardant un Ćil vigilant sur la structure interne des donnĂ©es. Par exemple, cela n'a aucun sens de considĂ©rer chaque date (plus exactement, un horodatage, y compris l'heure) comme une valeur unique - nous devons l'arrondir aux jours.
Techniquement, cette analyse peut ĂȘtre effectuĂ©e en regardant la valeur de la colonne "ctid" cachĂ©e, qui fournit le pointeur vers une version de ligne (TID): le numĂ©ro de la page et le numĂ©ro de la ligne Ă l'intĂ©rieur de la page. Malheureusement, il n'y a pas de technique conventionnelle pour dĂ©composer le TID en ses deux composants, par consĂ©quent, nous devons transtyper les types Ă travers la reprĂ©sentation textuelle:
demo=# select min(numblk), round(avg(numblk)) avg, max(numblk) from ( select count(distinct (ctid::text::point)[0]) numblk from flights_bi group by scheduled_time::date ) t;
min | avg | max ------+------+------ 1192 | 1500 | 1796 (1 row)
demo=# select relpages from pg_class where relname = 'flights_bi';
relpages ---------- 528172 (1 row)
Nous pouvons voir que chaque jour est réparti de maniÚre assez uniforme sur les pages et que les jours sont légÚrement mélangés les uns aux autres (1500 et 365 fois = 547500, ce qui n'est que légÚrement supérieur au nombre de pages du tableau 528172). C'est en fait clair "par construction" de toute façon.
Des informations prĂ©cieuses ici sont un nombre spĂ©cifique de pages. Avec une taille de plage conventionnelle de 128 pages, chaque jour remplira 9 Ă 14 plages. Cela semble rĂ©aliste: avec une requĂȘte pour un jour spĂ©cifique, on peut s'attendre Ă une erreur autour de 10%.
Essayons:
demo=# create index on flights_bi using brin(scheduled_time);
La taille de l'index est aussi petite que 184 Ko:
demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_scheduled_time_idx'));
pg_size_pretty ---------------- 184 kB (1 row)
Dans ce cas, il n'est guÚre logique d'augmenter la taille d'une plage au prix de perdre la précision. Mais nous pouvons réduire la taille si nécessaire, et la précision augmentera, au contraire (avec la taille de l'index).
Voyons maintenant les fuseaux horaires. Ici, nous ne pouvons pas non plus utiliser une approche par force brute. Toutes les valeurs doivent plutĂŽt ĂȘtre divisĂ©es par le nombre de cycles journaliers, car la distribution est rĂ©pĂ©tĂ©e chaque jour. De plus, comme il n'y a que peu de fuseaux horaires, nous pouvons regarder toute la distribution:
demo=# select airport_utc_offset, count(distinct (ctid::text::point)[0])/365 numblk from flights_bi group by airport_utc_offset order by 2;
airport_utc_offset | numblk --------------------+-------- 12:00:00 | 6 06:00:00 | 8 02:00:00 | 10 11:00:00 | 13 08:00:00 | 28 09:00:00 | 29 10:00:00 | 40 04:00:00 | 47 07:00:00 | 110 05:00:00 | 231 03:00:00 | 932 (11 rows)
En moyenne, les données pour chaque fuseau horaire peuplent 133 pages par jour, mais la distribution est trÚs non uniforme: Petropavlovsk-Kamchatskiy et Anadyr ne comptent que six pages, tandis que Moscou et ses environs en nécessitent des centaines. La taille par défaut d'une plage n'est pas bonne ici; Par exemple, définissons-le sur quatre pages.
demo=# create index on flights_bi using brin(airport_utc_offset) with (pages_per_range=4); demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_airport_utc_offset_idx'));
pg_size_pretty ---------------- 6528 kB (1 row)
Plan d'exécution
Voyons comment fonctionnent nos index. Sélectionnons un jour, disons, il y a une semaine (dans la base de données de démonstration, "aujourd'hui" est déterminé par la fonction "booking.now"):
demo=# \set d 'bookings.now()::date - interval \'7 days\'' demo=# explain (costs off,analyze) select * from flights_bi where scheduled_time >= :d and scheduled_time < :d + interval '1 day';
QUERY PLAN -------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=10.282..94.328 rows=83954 loops=1) Recheck Cond: ... Rows Removed by Index Recheck: 12045 Heap Blocks: lossy=1664 -> Bitmap Index Scan on flights_bi_scheduled_time_idx (actual time=3.013..3.013 rows=16640 loops=1) Index Cond: ... Planning time: 0.375 ms Execution time: 97.805 ms
Comme nous pouvons le voir, le planificateur a utilisĂ© l'index créé. Quelle est sa prĂ©cision? Le rapport entre le nombre de lignes qui remplissent les conditions de requĂȘte ("lignes" du nĆud Bitmap Heap Scan) et le nombre total de lignes renvoyĂ©es Ă l'aide de l'index (la mĂȘme valeur plus les lignes supprimĂ©es par la vĂ©rification de l'index) nous en dit long. Dans ce cas 83954 / (83954 + 12045), ce qui reprĂ©sente environ 90%, comme prĂ©vu (cette valeur changera d'un jour Ă l'autre).
D'oĂč vient le numĂ©ro 16640 dans les "lignes rĂ©elles" du nĆud Bitmap Index Scan? Le fait est que ce nĆud du plan crĂ©e un bitmap inexact (page par page) et ignore complĂštement le nombre de lignes que le bitmap touchera, tandis que quelque chose doit ĂȘtre affichĂ©. Par consĂ©quent, au dĂ©sespoir, une page est censĂ©e contenir 10 lignes. Le bitmap contient 1664 pages au total (cette valeur est indiquĂ©e dans "Heap Blocks: lossy = 1664"); nous obtenons donc 16640. Au total, il s'agit d'un nombre insensĂ© auquel nous ne devons pas prĂȘter attention.
Et les aéroports? Par exemple, prenons le fuseau horaire de Vladivostok, qui remplit 28 pages par jour:
demo=# explain (costs off,analyze) select * from flights_bi where airport_utc_offset = interval '8 hours';
QUERY PLAN ---------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=75.151..192.210 rows=587353 loops=1) Recheck Cond: (airport_utc_offset = '08:00:00'::interval) Rows Removed by Index Recheck: 191318 Heap Blocks: lossy=13380 -> Bitmap Index Scan on flights_bi_airport_utc_offset_idx (actual time=74.999..74.999 rows=133800 loops=1) Index Cond: (airport_utc_offset = '08:00:00'::interval) Planning time: 0.168 ms Execution time: 212.278 ms
Le planificateur utilise à nouveau l'index BRIN créé. La précision est pire (environ 75% dans ce cas), mais cela est attendu car la corrélation est plus faible.
Plusieurs index BRIN (comme tous les autres) peuvent certainement ĂȘtre joints au niveau du bitmap. Par exemple, voici les donnĂ©es sur le fuseau horaire sĂ©lectionnĂ© pour un mois (notez le nĆud "BitmapAnd"):
demo=# \set d 'bookings.now()::date - interval \'60 days\'' demo=# explain (costs off,analyze) select * from flights_bi where scheduled_time >= :d and scheduled_time < :d + interval '30 days' and airport_utc_offset = interval '8 hours';
QUERY PLAN --------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=62.046..113.849 rows=48154 loops=1) Recheck Cond: ... Rows Removed by Index Recheck: 18856 Heap Blocks: lossy=1152 -> BitmapAnd (actual time=61.777..61.777 rows=0 loops=1) -> Bitmap Index Scan on flights_bi_scheduled_time_idx (actual time=5.490..5.490 rows=435200 loops=1) Index Cond: ... -> Bitmap Index Scan on flights_bi_airport_utc_offset_idx (actual time=55.068..55.068 rows=133800 loops=1) Index Cond: ... Planning time: 0.408 ms Execution time: 115.475 ms
Comparaison avec b-tree
Et si nous crĂ©ons un index B-tree rĂ©gulier sur le mĂȘme champ que BRIN?
demo=# create index flights_bi_scheduled_time_btree on flights_bi(scheduled_time); demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_scheduled_time_btree'));
pg_size_pretty ---------------- 654 MB (1 row)
Il semblait ĂȘtre
plusieurs milliers de fois plus grand que notre BRIN! Cependant, la requĂȘte est effectuĂ©e un peu plus rapidement: le planificateur a utilisĂ© des statistiques pour dĂ©terminer que les donnĂ©es sont physiquement ordonnĂ©es et qu'il n'est pas nĂ©cessaire de crĂ©er un bitmap et, principalement, que la condition d'index n'a pas besoin d'ĂȘtre revĂ©rifiĂ©e:
demo=# explain (costs off,analyze) select * from flights_bi where scheduled_time >= :d and scheduled_time < :d + interval '1 day';
QUERY PLAN ---------------------------------------------------------------- Index Scan using flights_bi_scheduled_time_btree on flights_bi (actual time=0.099..79.416 rows=83954 loops=1) Index Cond: ... Planning time: 0.500 ms Execution time: 85.044 ms
C'est ce qui est si merveilleux avec BRIN: nous sacrifions l'efficacité, mais gagnons beaucoup d'espace.
Classes d'opérateur
minmax
Pour les types de donnĂ©es dont les valeurs peuvent ĂȘtre comparĂ©es, les informations rĂ©capitulatives sont constituĂ©es des
valeurs minimale et maximale . Les noms des classes d'opérateurs correspondantes contiennent "minmax", par exemple, "date_minmax_ops". En fait, ce sont des types de données que nous envisagions jusqu'à présent, et la plupart des types sont de ce type.
inclus
Les opérateurs de comparaison ne sont pas définis pour tous les types de données. Par exemple, ils ne sont pas définis pour les points (type "point"), qui représentent les coordonnées géographiques des aéroports. Soit dit en passant, c'est pour cette raison que les statistiques ne montrent pas la corrélation pour cette colonne.
demo=# select attname, correlation from pg_stats where tablename='flights_bi' and attname = 'airport_coord';
attname | correlation ---------------+------------- airport_coord | (1 row)
Mais beaucoup de ces types nous permettent d'introduire un concept de "zone de délimitation", par exemple, un rectangle de délimitation pour les formes géométriques. Nous avons expliqué en détail comment l'index
GiST utilise cette fonctionnalitĂ©. De mĂȘme, BRIN permet Ă©galement de collecter des informations rĂ©capitulatives sur les colonnes ayant des types de donnĂ©es comme ceux-ci:
la zone de délimitation pour toutes les valeurs à l'intérieur d'une plage est juste la valeur récapitulative.
Contrairement Ă GiST, la valeur rĂ©capitulative pour BRIN doit ĂȘtre du mĂȘme type que les valeurs indexĂ©es. Par consĂ©quent, nous ne pouvons pas construire l'index des points, bien qu'il soit clair que les coordonnĂ©es pourraient fonctionner dans BRIN: la longitude est Ă©troitement liĂ©e au fuseau horaire. Heureusement, rien n'empĂȘche la crĂ©ation de l'index sur une expression aprĂšs avoir transformĂ© des points en rectangles dĂ©gĂ©nĂ©rĂ©s. Dans le mĂȘme temps, nous allons dĂ©finir la taille d'une plage sur une page, juste pour montrer le cas limite:
demo=# create index on flights_bi using brin (box(airport_coord)) with (pages_per_range=1);
La taille de l'indice est aussi petite que 30 Mo, mĂȘme dans une situation aussi extrĂȘme:
demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_box_idx'));
pg_size_pretty ---------------- 30 MB (1 row)
Nous pouvons maintenant crĂ©er des requĂȘtes qui limitent les aĂ©roports par des coordonnĂ©es. Par exemple:
demo=# select airport_code, airport_name from airports where box(coordinates) <@ box '120,40,140,50';
airport_code | airport_name --------------+----------------- KHV | Khabarovsk-Novyi VVO | Vladivostok (2 rows)
Le planificateur refusera cependant d'utiliser notre index.
demo=# analyze flights_bi; demo=# explain select * from flights_bi where box(airport_coord) <@ box '120,40,140,50';
QUERY PLAN --------------------------------------------------------------------- Seq Scan on flights_bi (cost=0.00..985928.14 rows=30517 width=111) Filter: (box(airport_coord) <@ '(140,50),(120,40)'::box)
Pourquoi? Désactivons le scan séquentiel et voyons ce qui se passe:
demo=# set enable_seqscan = off; demo=# explain select * from flights_bi where box(airport_coord) <@ box '120,40,140,50';
QUERY PLAN -------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (cost=14079.67..1000007.81 rows=30517 width=111) Recheck Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box) -> Bitmap Index Scan on flights_bi_box_idx (cost=0.00..14072.04 rows=30517076 width=0) Index Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box)
Il semble que l'index
peut ĂȘtre utilisĂ©, mais le planificateur suppose que le bitmap devra ĂȘtre construit sur toute la table (regardez les "lignes" du nĆud Bitmap Index Scan), et il n'est pas Ă©tonnant que le planificateur choisisse le balayage sĂ©quentiel dans ce cas. Le problĂšme ici est que pour les types gĂ©omĂ©triques, PostgreSQL ne collecte aucune statistique et le planificateur doit y aller aveuglĂ©ment:
demo=# select * from pg_stats where tablename = 'flights_bi_box_idx' \gx
-[ RECORD 1 ]----------+------------------- schemaname | bookings tablename | flights_bi_box_idx attname | box inherited | f null_frac | 0 avg_width | 32 n_distinct | 0 most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram |
Hélas. Mais il n'y a rien à redire sur l'indice - il fonctionne et fonctionne bien:
demo=# explain (costs off,analyze) select * from flights_bi where box(airport_coord) <@ box '120,40,140,50';
QUERY PLAN ---------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=158.142..315.445 rows=781790 loops=1) Recheck Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box) Rows Removed by Index Recheck: 70726 Heap Blocks: lossy=14772 -> Bitmap Index Scan on flights_bi_box_idx (actual time=158.083..158.083 rows=147720 loops=1) Index Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box) Planning time: 0.137 ms Execution time: 340.593 ms
La conclusion doit ĂȘtre la suivante: PostGIS est nĂ©cessaire si quelque chose de non trivial est requis de la gĂ©omĂ©trie. Il peut de toute façon recueillir des statistiques.
Internes
L'extension conventionnelle "pageinspect" nous permet de regarder à l'intérieur de l'index BRIN.
Tout d'abord, la métainformation nous demandera la taille d'une plage et le nombre de pages allouées pour "revmap":
demo=# select * from brin_metapage_info(get_raw_page('flights_bi_scheduled_time_idx',0));
magic | version | pagesperrange | lastrevmappage ------------+---------+---------------+---------------- 0xA8109CFA | 1 | 128 | 3 (1 row)
Les pages 1-3 ici sont allouées pour "revmap", tandis que les autres contiennent des données récapitulatives. De "revmap", nous pouvons obtenir des références aux données récapitulatives pour chaque plage. Disons que les informations sur la premiÚre gamme, comprenant les 128 premiÚres pages, se trouvent ici:
demo=# select * from brin_revmap_data(get_raw_page('flights_bi_scheduled_time_idx',1)) limit 1;
pages --------- (6,197) (1 row)
Et voici les donnĂ©es rĂ©capitulatives elles-mĂȘmes:
demo=# select allnulls, hasnulls, value from brin_page_items( get_raw_page('flights_bi_scheduled_time_idx',6), 'flights_bi_scheduled_time_idx' ) where itemoffset = 197;
allnulls | hasnulls | value ----------+----------+---------------------------------------------------- f | f | {2016-08-15 02:45:00+03 .. 2016-08-15 17:15:00+03} (1 row)
Gamme suivante:
demo=# select * from brin_revmap_data(get_raw_page('flights_bi_scheduled_time_idx',1)) offset 1 limit 1;
pages --------- (6,198) (1 row)
demo=# select allnulls, hasnulls, value from brin_page_items( get_raw_page('flights_bi_scheduled_time_idx',6), 'flights_bi_scheduled_time_idx' ) where itemoffset = 198;
allnulls | hasnulls | value ----------+----------+---------------------------------------------------- f | f | {2016-08-15 06:00:00+03 .. 2016-08-15 18:55:00+03} (1 row)
Et ainsi de suite.
Pour les classes "inclusion", le champ "valeur" affichera quelque chose comme
{(94.4005966186523,69.3110961914062),(77.6600036621,51.6693992614746) .. f .. f}
La premiÚre valeur est le rectangle d'intégration, et les lettres "f" à la fin indiquent qu'il manque des éléments vides (le premier) et des valeurs non fusionnables (le second). En fait, les seules valeurs non fusionnables sont les adresses "IPv4" et "IPv6" (type de données "inet").
Propriétés
Vous rappelant les requĂȘtes qui
ont déjà été fournies .
Voici les propriétés de la méthode d'accÚs:
amname | name | pg_indexam_has_property --------+---------------+------------------------- brin | can_order | f brin | can_unique | f brin | can_multi_col | t brin | can_exclude | f
Les index peuvent ĂȘtre créés sur plusieurs colonnes. Dans ce cas, ses propres statistiques rĂ©capitulatives sont collectĂ©es pour chaque colonne, mais elles sont stockĂ©es ensemble pour chaque plage. Bien sĂ»r, cet indice est logique si une seule et mĂȘme taille de plage convient Ă toutes les colonnes.
Les propriétés de couche d'index suivantes sont disponibles:
name | pg_index_has_property ---------------+----------------------- clusterable | f index_scan | f bitmap_scan | t backward_scan | f
Ăvidemment, seul le scan bitmap est pris en charge.
Cependant, le manque de clustering peut sembler déroutant. Apparemment, puisque l'index BRIN est sensible à l'ordre physique des lignes, il serait logique de pouvoir regrouper les données en fonction de l'index. Mais ce n'est pas le cas. On ne peut que créer un index «régulier» (B-tree ou GiST, selon le type de données) et le clusterer en fonction de celui-ci. Soit dit en passant, souhaitez-vous regrouper une table supposée énorme en tenant compte des verrous exclusifs, du temps d'exécution et de la consommation d'espace disque lors de la reconstruction?
Les propriétés des couches de colonnes sont les suivantes:
name | pg_index_column_has_property --------------------+------------------------------ asc | f desc | f nulls_first | f nulls_last | f orderable | f distance_orderable | f returnable | f search_array | f search_nulls | t
La seule propriété disponible est la possibilité de manipuler des valeurs NULL.
Continuez Ă lire .