4. 4/25
Quel problème ?
rjuju=# EXPLAIN ANALYZE SELECT id2, id1, id3
FROM t1 ORDER BY id1;
QUERY PLAN
---------------------------------------------------------------------
Sort (rows=10000 width=16) (actual rows=10000)
Sort Key: id1
Sort Method: quicksort Memory: 787kB
-> Seq Scan on t1 (rows=10000 width=16) (actual rows=10000)
Julien Rouhaud Tri & PostgreSQL
5. 5/25
Quicksort
Algorithme de tri classique
Tri "en place"
Implémenté dans src/backend/utils/sort/tuplesort.c
Se fera en mémoire s’il peut stocker toutes les
lignes à trier en mémoire
Julien Rouhaud Tri & PostgreSQL
6. 6/25
top-N heapsort
Autre implémentation de tri en mémoire
ORDER BY X LIMIT Y
Utilisé automatiquement si
On a déjà stocké au moins Y lignes, et que le
work_mem est épuisé
On a déjà stocké au moins Y * 2 lignes
Peut largement dépasser le work_mem !
Julien Rouhaud Tri & PostgreSQL
7. 7/25
Allocation mémoire
Généralités
Postgres est écrit en C, pas de GC ni de destructeur
À la place, des contextes mémoires
Plusieurs implémentation, généralement AllocSet
palloc() / pfree()
Julien Rouhaud Tri & PostgreSQL
8. 8/25
Allocation mémoire
Utilisation courante de la mémoire pour le tri
Un variable maintient la quantité de mémoire
utilisée
Deux macro pour mettre à jour cette quantité
(spécifique tuplesort)
Une fonction de l’allocateur mémoire pour savoir la
quantité de mémoire vraiment utilisée
#define USEMEM(state,amt) ((state)->availMem -= (amt))
#define FREEMEM(state,amt) ((state)->availMem += (amt))
state->gros_tableau = palloc(taille);
USEMEM(state, GetMemoryChunkSpace(state->gros_tableau));
Julien Rouhaud Tri & PostgreSQL
10. 10/25
AllocSet
Un peu d’implémentation
Implémenté dans src/backend/utils/mmgr/aset.c
typedef struct AllocChunkData {
Size size;
void *aset;
} AllocChunkData;
#define ALLOC_CHUNKHDRSZ sizeof(struct AllocChunkData)
static Size
AllocSetGetChunkSpace(MemoryContext context, void *pointer) {
AllocChunk chunk = AllocPointerGetChunk(pointer);
Size result;
result = chunk->size + ALLOC_CHUNKHDRSZ;
return result;
}
taille contexte associé Les données
Julien Rouhaud Tri & PostgreSQL
11. 11/25
Stockage des données à trier
1 - tableau contenant les lignes
1. Un gros tableau, contenant les pointeurs des
lignes à trier
Ces pointeurs sont stockés dans une struct :
SortTuple (taille : 24B)
typedef struct {
void *tuple; /* the tuple itself */
Datum datum1; /* value of first key column */
bool isnull1; /* is first key column NULL? */
int tupindex; /* see notes above */
} SortTuple;
Naivement, on devrait avoir besoin de
(nb_ligne * sizeof(SortTuple)) + ALLOC_CHUNKHDRSZ
(nb_ligne * 24B) + 16B
Sauf que...
Julien Rouhaud Tri & PostgreSQL
12. 12/25
Stockage des données à trier
1 - tableau contenant les lignes
PostgreSQL alloue par défaut de quoi stocker 1024
lignes
struct Tuplesortstate {
[...]
/* This array holds the tuples now in sort memory... */
SortTuple *memtuples; /* array of SortTuple structs */
int memtupcount; /* number of tuples currently present */
int memtupsize; /* allocated length of memtuples array */
[...]
}
state->memtupcount = 0;
state->memtupsize = 1024;
state->memtuples = palloc(state->memtupsize * sizeof(SortTuple));
[...]
Julien Rouhaud Tri & PostgreSQL
13. 13/25
Stockage des données à trier
1 - tableau contenant les lignes
Et croît en doublant la capacité, avec une limite
/*
* Grow the memtuples[] array, if possible within our memory
* constraint. We must not exceed INT_MAX tuples in memory or
* the caller-provided.memory limit. [...]
* Normally, at each increment we double the size of the array.
* [...]
*/
static bool grow_memtuples(Tuplesortstate *state)
INT_MAX (2147483647), ~2.1 milliards d’entrées,
~48GB
Minimum de (1024 * 24B) + 16B, environ 24kB
Nombre de lignes arrondi à la puissance de 2
supérieure
ex pour 10000 lignes : (16384 * 24B) + 16B,
environ 384kB
Julien Rouhaud Tri & PostgreSQL
14. 14/25
Stockage des données à trier
2 - ajout des lignes à trier
2. Chaque ligne est allouée séparément, puis
stockée dans le tableau
on stock des MinimalTuple, une version épurée
d’une ligne sans les information de visibilité
struct MinimalTupleData
{
uint32 t_len; /* actual length of minimal tuple */
char mt_padding[MINIMAL_TUPLE_PADDING];
/* Fields below here must match HeapTupleHeaderData! */
uint16 t_infomask2; /* number of attributes + various flags */
uint16 t_infomask; /* various flag bits, see below */
uint8 t_hoff; /* sizeof header incl. bitmap, padding */
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]; /* bitmap of NULLs */
/* MORE DATA FOLLOWS AT END OF STRUCT */
};
typedef MinimalTupleData *MinimalTuple;
Julien Rouhaud Tri & PostgreSQL
15. 15/25
Stockage des données à trier
2 - ajout des lignes à trier
On ne stocke pas une ligne entière d’une table
Mais la TargetList (clause SELECT) d’une relation à
trier (table, résultat d’une jointure)
Les règles de stockage d’une ligne s’appliquent
Julien Rouhaud Tri & PostgreSQL
16. 16/25
Stockage d’un MinimalTuple
Généralités
Un header fixe de 15B aligné, 16B
Un bitmapset de NULL si au moins une colonne est
nulle
Un champ Oid si la table a été créée avec WITH
OIDS (la plupart des catalogues)
Les colonnes non NULL, avec leurs règles
d’alignement
Le tout arrondi à la puissance de 2 supérieure (si
moins de 8kB)
et 16B d’overhead, la ligne étant allouée dans un
chunk
Julien Rouhaud Tri & PostgreSQL
17. 17/25
Stockage d’un MinimalTuple
Stockage des NULL
bitmapset présent si au moins une colonne de la
ligne est NULL
Un bitmapset, 1 bit par colonne, arrondi à l’octet
supérieur
* BITMAPLEN(NATTS) -
* Computes size of null bitmap given number of data columns.
*/
#define BITMAPLEN(NATTS) (((int)(NATTS) + 7) / 8)
MinimalTuple
heap_form_minimal_tuple(TupleDesc tupleDescriptor,
Datum *values,
bool *isnull)
{
int numberOfAttributes = tupleDescriptor->natts;
[...]
if (hasnull)
len += BITMAPLEN(numberOfAttributes);
Julien Rouhaud Tri & PostgreSQL
18. 18/25
Stockage d’un MinimalTuple
Stockage des attributs
Règles d’alignement des types standard
rjuju=# SELECT attname, attlen, attalign
FROM pg_class c JOIN pg_attribute a on a.attrelid = c.oid
WHERE c.relname = 't1' AND a.attnum > 0 ORDER BY attnum;
attname | attlen | attalign
---------+--------+----------
id1 | 4 | i
id2 | 8 | d
id3 | 4 | i
catalog-pg-type.html
c = char alignment, i.e., no alignment needed.
s = short alignment (2 bytes on most machines).
i = int alignment (4 bytes on most machines).
d = double alignment (8 bytes on many machines,
but by no means all).
Julien Rouhaud Tri & PostgreSQL
19. 19/25
Stockage d’un MinimalTuple
Stockage des attributs
Ordre de colonne non optimal
SELECT id1, id2, id3 FROM t1 ORDER BY id;
id1 ---- id2 id3
16B de données, 4B perdus, 20B
total ligne : 16B (header) + 20B = 36B, arrondi à
64B
total alloué : 64B + 16B (overhead palloc) = 80B
Ordre de colonne optimal
SELECT id2, id1, id3 FROM t1 ORDER BY id;
id2 id1id1id1id1id1id1id1id1 id3
16B de données, pas d’espace perdu
total ligne : 16B (header) + 16B = 32B, arrondi à
32B
total alloué : 32B + 16B (overhead palloc) = 48B
Julien Rouhaud Tri & PostgreSQL
20. 20/25
Mémoire totale
Soit N1 le nombre de lignes
Soit N2 le nombre de lignes, arrondi à la puissance de 2 supérieure,
minimum 1024
1 - Espace pour le tableau :
(N2 ∗ 24) + 16
Soit L1 la taille moyenne d’une ligne *
L1 =
nbAtt
attnum=1
attlen(attnum)
Soit L2 la taille totale allouée, arrondi à la puissance de 2
supérieure :
L2 = L1 + 16
2 - Espace pour les lignes :
(L2 + 16) ∗ N1
* Case simple : pas de NULL ni OID, < 8kB, sans tenir compte de
l’alignement, types de taille fixe
Julien Rouhaud Tri & PostgreSQL
21. 21/25
pg_sortstats
extension pour instrumenter les tris
github.com/powa-team/pg_sortstats
estimation du work_mem requis, type de tris,
mémoire utilisée...
compteurs cumulés
aggrégé par (querid, dbid, userid, sort_key)
fonctionelle, mais perfectible (NULL pas pris en
compte, ...)
Julien Rouhaud Tri & PostgreSQL
22. 22/25
pg_sortstats
Métriques retournées
lines nombre totale de lignes en entrée du tri
lines_to_sort nombre de lignes à trier réellement
work_mems work_mem estimé pour trier en mémoire
topn_sorts nombre de tri effectué avec un top-N heapsort
quicksorts nombre de tri effectué avec un quicksort
external_sorts nombre de tri effectué avec un external sort
external_merges nombre de tri effectué avec un external merge
nbtapes nombre de tapes utilisés pour les external merge
space_disk espace disque utilisé
space_memory espace mémoire utilisé
non_parallels nombre de tri non parallèles
nb_workers nombre de workers total utilisés (si parallèle)
Julien Rouhaud Tri & PostgreSQL
23. 23/25
pg_sortstats
Idées d’utilisation
avoir une idée du nombre de tris dans son
application
détecter des index manquants pour satisfaire les
tris
aider à configurer le work_mem
capturer régulièrement la sortie (avec PoWA par
exemple), et
avoir une idée du work_mem nécessaire pour
satisfaire 80% des tris entre 9h et 18h
combien de tris passeraient en RAM si on
augmentait le work_mem de 10%
. . .
log_temp_files & pgBadger restent indispensables !
Julien Rouhaud Tri & PostgreSQL
24. 24/25
pg_sortstats
Et la suite ?
ajouter le support pour les Hash et HashAgg
stocker le work_mem effectif ?
meilleure représentation des données ?
autre ?
Julien Rouhaud Tri & PostgreSQL