Travailler en mémoire vive

Mémoire vive, disques durs, SSD

      Les performances des solutions de stockage de masse ont largement évolué depuis les années 2000 avec l’introduction des SSD en complément, voire en remplacement complet des disques durs, dans certaines baies. Cependant, travailler sur "disque" est toujours bien plus lent que travailler en "mémoire vive".
      Lorsque vous voyez le mot clé MATERIALIZE dans un plan, cela signifie qu’un jeu de résultats intermédiaire est constitué. Est-ce indispensable ou est-il possible de travailler en mémoire s’il n’est pas trop volumineux ? Il est préférable de chercher à optimiser les requêtes avant d’augmenter les ressources matérielles allouées. Mais il est parfois impossible de modifier le code voire même de modifier le schéma en ajoutant des index. Augmenter les ressources peut dans ce cas être intéressant comme le montre aussi cet article sur le parallélisme. Il ne faut pas être trop radin !
      Voici un exemple rencontré dans un batch d’une application tournant actuellement sous Oracle 11.2, en cours de test sous PostgreSQL 9.6. Les noms des tables et des colonnes ont été modifiés.

SELECT version(); version ------------------------------------------------------------------------------------------ PostgreSQL 9.6.0 on x86_64-pc-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bit (1 ligne) Temps : 0,550 ms \d+ Liste des relations Schéma | Nom | Type | Propriétaire | Taille | Description ------------+-----------+-------+--------------+------------+----------------------------------- admpatient | t1 | table | admpatient | 890 MB | admpatient | t2 | table | admpatient | 172 MB | admpatient | t3 | table | admpatient | 8192 bytes | (3 lignes) EXPLAIN SELECT count(*) FROM t2 , t3 WHERE coalesce(t2_c1, 'N') <> 'I' AND ( (t2_c2 >= TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)-3), 'DDMMYYYY') OR t2_c2 IS NULL) and t2_c3 < TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)), 'DDMMYYYY') ) OR ( t2_c1 IN (SELECT t1_c1 FROM t1 WHERE t1_c2 >= (EXTRACT (YEAR FROM CURRENT_DATE)-3)) ); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate (cost=75453479919.99..75453479919.99 rows=1 width=8) -> Nested Loop (cost=0.00..75453477823.30 rows=838675 width=0) -> Seq Scan on t3 (cost=0.00..1.01 rows=1 width=0) -> Seq Scan on t2 (cost=0.00..75453469435.54 rows=838675 width=0) Filter: ((((COALESCE(t2_c1, 'N'::character varying))::text <> 'I'::text) AND ((t2_c2 >= to_date(('0101'::text || ((date_part('year'::text, (('now'::cstring)::date)::timestamp without time zone) - '3'::double precision))::text), 'DDMMYYYY'::text)) OR (t2_c2 IS NULL)) AND (t2_c3 < to_date(('0101'::text || (date_part('year'::text, (('now'::cstring)::date)::timestamp without time zone))::text), 'DDMMYYYY'::text))) OR (SubPlan 1)) SubPlan 1 -> Materialize (cost=0.00..155285.83 rows=448398 width=9) -> Seq Scan on t1 (cost=0.00..150853.84 rows=448398 width=9) Filter: ((t1_c2)::double precision >= (date_part('year'::text, (('now'::cstring)::date)::timestamp without time zone) - '3'::double precision)) (9 lignes) SHOW work_mem; work_mem ---------- 4MB (1 ligne) Temps : 0,363 ms SELECT count(*) FROM t2 , t3 WHERE coalesce(t2_c1, 'N') <> 'I' AND ( (t2_c2 >= TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)-3), 'DDMMYYYY') OR t2_c2 IS NULL) and t2_c3 < TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)), 'DDMMYYYY') ) OR ( t2_c1 IN (SELECT t1_c1 FROM t1 WHERE t1_c2 >= (EXTRACT (YEAR FROM CURRENT_DATE)-3)) ); Cancel request sent ERREUR: annulation de la requête à la demande de l'utilisateur Temps : 81207,020 ms

      Ouille ! Le coût annoncé était gigantesque (75453479919) et j’ai interrompu la requête au bout d’une minute et vingt secondes. Il serait sans doute intéressant de s’interroger sur l’écriture de cette requête mais, pour débloquer la situation, est-il possible de travailler en mémoire vive plutôt que sur disque ?

SET work_mem='64MB'; SET EXPLAIN SELECT count(*) FROM t2 , t3 WHERE coalesce(t2_c1, 'N') <> 'I' AND ( (t2_c2 >= TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)-3), 'DDMMYYYY') OR t2_c2 IS NULL) and t2_c3 < TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)), 'DDMMYYYY') ) OR ( t2_c1 IN (SELECT t1_c1 FROM t1 WHERE t1_c2 >= (EXTRACT (YEAR FROM CURRENT_DATE)-3)) ); QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate (cost=244807.47..244807.48 rows=1 width=8) -> Nested Loop (cost=151974.83..242710.78 rows=838675 width=0) -> Seq Scan on t3 (cost=0.00..1.01 rows=1 width=0) -> Seq Scan on t2 (cost=151974.83..234323.02 rows=838675 width=0) Filter: ((((COALESCE(t2_c1, 'N'::character varying))::text <> 'I'::text) AND ((t2_c2 >= to_date(('0101'::text || ((date_part('year'::text, (('now'::cstring)::date)::timestamp without time zone) - '3'::double precision))::text), 'DDMMYYYY'::text)) OR (t2_c2 IS NULL)) AND (t2_c3 < to_date(('0101'::text || (date_part('year'::text, (('now'::cstring)::date)::timestamp without time zone))::text), 'DDMMYYYY'::text))) OR (hashed SubPlan 1)) SubPlan 1 -> Seq Scan on t1 (cost=0.00..150853.84 rows=448398 width=9) Filter: ((t1_c2)::double precision >= (date_part('year'::text, (('now'::cstring)::date)::timestamp without time zone) - '3'::double precision)) (8 lignes) SELECT count(*) FROM t2 , t3 WHERE coalesce(t2_c1, 'N') <> 'I' AND ( (t2_c2 >= TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)-3), 'DDMMYYYY') OR t2_c2 IS NULL) and t2_c3 < TO_DATE('0101' || (EXTRACT (YEAR FROM CURRENT_DATE)), 'DDMMYYYY') ) OR ( t2_c1 IN (SELECT t1_c1 FROM t1 WHERE t1_c2 >= (EXTRACT (YEAR FROM CURRENT_DATE)-3)) ); count -------- 960687 (1 ligne) Temps : 11209,623 ms

Bingo ! En passant la mémoire de travail de 4 mégaoctets (la valeur par défaut) à 64 mégaoctets, l’opération de matérialisation d’un jeu de resultats intermédiaire a disparu et le coût estimé a diminué drastiquement (244807). Et, en pratique, la requête est exécutée en 11 secondes. C’est un résultat qui n’est pas optimal mais qui est "acceptable".

Mise à jour : 08/10/2016