J'ai besoin d'aide pour analyser les mauvaises performances d'une requête exécutée sur une grande table contenant 83.660.142 millions de lignes, ce qui prend jusqu'à 25 minutes à plus d'une heure , en fonction de la charge du système, pour le calcul.postgresql 9.6.4: la requête de plage d'horodatage prend une éternité
J'ai créé le tableau suivant qui se compose d'une clé composite et 3 indices:
CREATE TABLE IF NOT EXISTS ds1records(
userid INT DEFAULT 0,
clientid VARCHAR(255) DEFAULT '',
ts TIMESTAMP,
site VARCHAR(50) DEFAULT '',
code VARCHAR(400) DEFAULT '');
CREATE UNIQUE INDEX IF NOT EXISTS primary_idx ON records (userid, clientid, ts, site, code);
CREATE INDEX IF NOT EXISTS userid_idx ON records (userid);
CREATE INDEX IF NOT EXISTS ts_idx ON records (ts);
CREATE INDEX IF NOT EXISTS userid_ts_idx ON records (userid ASC,ts DESC);
Dans une application de traitement par lots de printemps, je suis l'exécution d'une requête qui se présente comme suit:
SELECT *
FROM records
WHERE userid = ANY(VALUES (2), ..., (96158 more userids))
AND (ts < '2017-09-02' AND ts >= '2017-09-01'
OR ts < '2017-08-26' AND ts >= '2017-08-25'
OR ts < '2017-08-19' AND ts >= '2017-08-18'
OR ts < '2017-08-12' AND ts >= '2017-08-11')
Les ID utilisateur sont déterminés lors de l'exécution (le nombre d'ID est compris entre 95 000 et 110 000). Pour chaque utilisateur, j'ai besoin d'extraire les pages vues de la journée en cours et les trois derniers jours de la même semaine. La requête renvoie toujours des lignes entre des lignes 3-4M. L'exécution de la requête avec l'option EXPLAIN ANALYZE
renvoie le plan d'exécution suivant.
Nested Loop (cost=1483.40..1246386.43 rows=3761735 width=70) (actual time=108.856..1465501.596 rows=3643240 loops=1)
-> HashAggregate (cost=1442.38..1444.38 rows=200 width=4) (actual time=33.277..201.819 rows=96159 loops=1)
Group Key: "*VALUES*".column1
-> Values Scan on "*VALUES*" (cost=0.00..1201.99 rows=96159 width=4) (actual time=0.006..11.599 rows=96159 loops=1)
-> Bitmap Heap Scan on records (cost=41.02..6224.01 rows=70 width=70) (actual time=8.865..15.218 rows=38 loops=96159)
Recheck Cond: (userid = "*VALUES*".column1)
Filter: (((ts < '2017-09-02 00:00:00'::timestamp without time zone) AND (ts >= '2017-09-01 00:00:00'::timestamp without time zone)) OR ((ts < '2017-08-26 00:00:00'::timestamp without time zone) AND (ts >= '2017-08-25 00:00:00'::timestamp without time zone)) OR ((ts < '2017-08-19 00:00:00'::timestamp without time zone) AND (ts >= '2017-08-18 00:00:00'::timestamp without time zone)) OR ((ts < '2017-08-12 00:00:00'::timestamp without time zone) AND (ts >= '2017-08-11 00:00:00'::timestamp without time zone)))
Rows Removed by Filter: 792
Heap Blocks: exact=77251145
-> Bitmap Index Scan on userid_ts_idx (cost=0.00..41.00 rows=1660 width=0) (actual time=6.593..6.593 rows=830 loops=96159)
Index Cond: (userid = "*VALUES*".column1)
J'ai ajusté les valeurs de certains paramètres de réglage Postgres (malheureusement sans succès):
- effective_cache_size = 15 Go (probablement inutile que la requête est exécutée juste une fois)
- shared_buffers = 15Go
- work_mem = 3Go
L'application exécute des tâches coûteuses informatiquement (par exemple . fusion de données/injection de données) et consomme environ 100 Go de mémoire, de sorte que le matériel du système est suffisamment dimensionné avec 125 Go de RAM et 16 cœurs (OS: Debian).
Je me demande pourquoi postgres n'utilise pas l'index combiné userid_ts_idx
dans son plan d'exécution? Comme la colonne timestamp de l'index est triée dans l'ordre inverse, je demanderais à postgres de l'utiliser pour trouver les tuples correspondants pour la partie range de la requête car elle pourrait passer séquentiellement dans l'index jusqu'à ce que la condition ts < '2017-09-02 00:00:00
soit vraie et retourner toutes les valeurs jusqu'à la condition ts >= 2017-09-01 00:00:00
est rempli. Au lieu de cela postgres utilise le coûteux Bitmap Heap Scan qui effectue un balayage de table linéaire si j'ai bien compris. Ai-je mal configuré les paramètres db ou ai-je un malentendu conceptuel?
Mise à jour
Le CTE comme suggéré dans les commentaires n'a malheureusement pas apporter des améliorations. Le scan de tas bitmap a été remplacé par un balayage séquentiel mais les performances sont encore médiocres. Voici le plan d'exécution mis à jour:
Merge Join (cost=20564929.37..20575876.60 rows=685277 width=106) (actual time=2218133.229..2222280.192 rows=3907472 loops=1)
Merge Cond: (ids.id = r.userid)
Buffers: shared hit=2408684 read=181785
CTE ids
-> Values Scan on "*VALUES*" (cost=0.00..1289.70 rows=103176 width=4) (actual time=0.002..28.670 rows=103176 loops=1)
CTE ts
-> Values Scan on "*VALUES*_1" (cost=0.00..0.05 rows=4 width=32) (actual time=0.002..0.004 rows=4 loops=1)
-> Sort (cost=10655.37..10913.31 rows=103176 width=4) (actual time=68.476..83.312 rows=103176 loops=1)
Sort Key: ids.id
Sort Method: quicksort Memory: 7909kB
-> CTE Scan on ids (cost=0.00..2063.52 rows=103176 width=4) (actual time=0.007..47.868 rows=103176 loops=1)
-> Sort (cost=20552984.25..20554773.54 rows=715717 width=102) (actual time=2218059.941..2221230.585 rows=8085760 loops=1)
Sort Key: r.userid
Sort Method: quicksort Memory: 1410084kB
Buffers: shared hit=2408684 read=181785
-> Nested Loop (cost=0.00..20483384.24 rows=715717 width=102) (actual time=885849.043..2214665.723 rows=8085767 loops=1)
Join Filter: (ts.r @> r.ts)
Rows Removed by Join Filter: 707630821
Buffers: shared hit=2408684 read=181785
-> Seq Scan on records r (cost=0.00..4379760.52 rows=178929152 width=70) (actual time=0.024..645616.135 rows=178929147 loops=1)
Buffers: shared hit=2408684 read=181785
-> CTE Scan on ts (cost=0.00..0.08 rows=4 width=32) (actual time=0.000..0.000 rows=4 loops=178929147)
Planning time: 126.110 ms
Execution time: 2222514.566 ms
Toute chance il est contraint par lit le disque? Veuillez utiliser 'EXPLAIN (ANALYZE, BUFFERS)' la prochaine fois. Cela vous donnera un aperçu de la mise en mémoire tampon. Comme nous le voyons, le scan du tas consomme la plupart du temps. https://explain.depesz.com/s/wJBk – filiprem
Merci pour l'indice ... Selon * iotop * les lectures de disque fluctuent entre 1 et 7 M/s mais j'ai aussi vu des pics à 17M/s . Quelle devrait être la moyenne pour une bonne performance? – user35934
Avec plusieurs ORs dans cet ensemble de plages de dates, je ne pense pas qu'il sera "vrai" la dernière date en remontant à la date la plus proche. Le filtre indique qu'il traite chaque plage '(... ou ...) et (... ou ...) et (... ou ...) et (... ou ...)' –