web-dev-qa-db-fra.com

Comment puis-je réaliser la clustering de lignes sans le verrouillage exclusif et la journalisation sur le dessus de la commande `cluster`?

La commande CLUSTER sur une grande table peut prendre beaucoup de temps et blocs à la fois lit et écrit à la table pendant qu'il s'exécute.

Je n'ai pas besoin que les données dans ma table soient strictement triées dans l'ordre d'index, je veux juste que des lignes couramment interrogées ensemble pour être plus susceptibles d'être dans les mêmes blocs de base de données que dispersés uniformément à travers la table (qui est la distribution qu'ils Naturellement, dues à la nature de la manière dont la date est insérée à la table).

Cela peut faire une grande différence. Dans l'exemple ci-dessous, la seule différence est que le insert a une fonction supplémentaire order by mod(g,10) afin que les données de test soient pré-groupées par Host_id. De loin, moins de blocs doivent être lus lors de l'obtention de toutes les données d'un Host_id.

Existe-t-il un moyen de réaliser ce type de clustering sans la serrure exclusive et la transmission de la tête de la commande cluster?

create schema stack;
set search_path=stack;
--
create table foo(Host_id integer, bar text default repeat('a',400));
insert into foo(Host_id) select mod(g,10) from generate_series(1,500000) g;
create index nu_foo on foo(Host_id);
explain analyze select count(bar) from foo where Host_id=1;
/*
                                                            QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=30188.66..30188.67 rows=1 width=404) (actual time=1129.858..1129.859 rows=1 loops=1)
   ->  Bitmap Heap Scan on foo  (cost=919.27..30066.46 rows=48883 width=404) (actual time=253.149..1110.013 rows=50000 loops=1)
         Recheck Cond: (Host_id = 1)
         Rows Removed by Index Recheck: 320257
         ->  Bitmap Index Scan on nu_foo  (cost=0.00..907.04 rows=48883 width=0) (actual time=251.863..251.863 rows=50000 loops=1)
               Index Cond: (Host_id = 1)
 Total runtime: 1129.893 ms
*/
--
drop table foo;
--
create table foo(Host_id integer, bar text default repeat('a',400));
insert into foo(Host_id) select mod(g,10) from generate_series(1,500000) g order by mod(g,10);
create index nu_foo on foo(Host_id);
explain analyze select count(bar) from foo where Host_id=1;
/*
                                                         QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=7550.20..7550.21 rows=1 width=32) (actual time=24.397..24.397 rows=1 loops=1)
   ->  Bitmap Heap Scan on foo  (cost=47.80..7543.95 rows=2500 width=32) (actual time=3.988..16.189 rows=50000 loops=1)
         Recheck Cond: (Host_id = 1)
         ->  Bitmap Index Scan on nu_foo  (cost=0.00..47.17 rows=2500 width=0) (actual time=3.649..3.649 rows=50000 loops=1)
               Index Cond: (Host_id = 1)
 Total runtime: 24.437 ms
*/
--
drop schema stack cascade;

Vous pouvez le faire sans utiliser la commande cluster et avoir la table verrouillée ou générer WAL pour toute la table. Le coût est que vous devez analyser la table régulièrement.

L'idée de base est:

  1. éteindre l'autovacuum pour la table
  2. vérifiez chaque bloc pour déterminer le degré de clustering
  3. supprimer et réinsérer toutes les lignes des blocs en dessous d'un seuil de clustering
  4. aspirer manuellement pour libérer ces blocs (complètes)
  5. répétez les étapes 2-4 aussi régulièrement que nécessaire

test Schema Sama Sample Data initialement 'Part-clustered':

create schema stack;
set search_path=stack;
create type t_tid as (blkno bigint, rowno integer);
create table foo(Host_id integer, bar text default repeat('a',400)) with (autovacuum_enabled=false);
insert into foo(Host_id) select mod(g,10) from generate_series(1,500000) g order by mod(g,10);
insert into foo(Host_id) select mod(g,10) from generate_series(1,500000) g;
create index nu_foo on foo(Host_id);

statistiques initiales de clustering:

select cn, count(*)
from ( select count(*) cn
       from (select distinct (ctid::text::t_tid).blkno, Host_id from foo) z
       group by blkno ) z
group by cn
order by cn;
/*
 cn | count
----+-------
  1 | 27769  <---- half clustered
  2 |     8
  5 |     1
 10 | 27778  <---- half un-clustered
*/
select count(distinct (ctid::text::t_tid).blkno) from foo where Host_id=1;
/*
 count
-------
 30558  <--------- lots of blocks to read for `Host_id=1`
*/

analyse initiale ( 2146.503 ms ):

explain analyze select count(bar) from foo where Host_id=1;
/*
                                                           QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=15097.30..15097.31 rows=1 width=32) (actual time=2146.157..2146.158 rows=1 loops=1)
   ->  Bitmap Heap Scan on foo  (cost=95.17..15084.80 rows=5000 width=32) (actual time=21.586..2092.379 rows=100000 loops=1)
         Recheck Cond: (Host_id = 1)
         Rows Removed by Index Recheck: 286610
         ->  Bitmap Index Scan on nu_foo  (cost=0.00..93.92 rows=5000 width=0) (actual time=19.232..19.232 rows=100000 loops=1)
               Index Cond: (Host_id = 1)
 Total runtime: 2146.503 ms
*/

supprimez et réinsérez les lignes non groupées:

with w as ( select blkno
            from (select distinct (ctid::text::t_tid).blkno, Host_id from foo) z
            group by blkno
            having count(*)>2 )
   , d as ( delete from foo
            where (ctid::text::t_tid).blkno in (select blkno from w)
            returning * )
insert into foo(Host_id,bar) select Host_id,bar from d order by Host_id;
--
vacuum foo;

nouvelles statistiques de clustering:

select cn, count(*)
from ( select count(*) cn
       from (select distinct (ctid::text::t_tid).blkno, Host_id from foo) z
       group by blkno ) z
group by cn
order by cn;
/*
 cn | count
----+-------
  1 | 55541  <---- fully clustered
  2 |    16
*/
select count(distinct (ctid::text::t_tid).blkno) from foo where Host_id=1;
/*
 count
-------
  5558  <--------- far fewer blocks to read for `Host_id=1`
*/

nouvelle analyse ( 48.804 ms ):

explain analyze select count(bar) from foo where Host_id=1;
/*
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=16110.64..16110.65 rows=1 width=32) (actual time=48.760..48.761 rows=1 loops=1)
   ->  Bitmap Heap Scan on foo  (cost=131.18..16098.14 rows=5000 width=32) (actual time=8.402..32.439 rows=100000 loops=1)
         Recheck Cond: (Host_id = 1)
         ->  Bitmap Index Scan on nu_foo  (cost=0.00..129.93 rows=5000 width=0) (actual time=7.636..7.636 rows=100000 loops=1)
               Index Cond: (Host_id = 1)
 Total runtime: 48.804 ms
*/

nettoyer:

drop schema stack cascade;

Ce qui précède est fonctionnel maintenant, mais est un peu bizarre (nécessite de désactiver l'aspirateur automatique pour la table) et nécessite une numérisation complète régulière de la table. Je pense que quelque chose de similaire sans inconvénients pourrait être intégré à Postgres. Vous auriez besoin de:

  1. Un index spatial efficace au grappe sur (ceci à 9,4 avec compression gin ou mieux encore en 9.5 avec le nouveau type d'index Brin)
  2. Un processus de "aspirateur" qui scannerait cet indice pour détecter quels blocs doit être supprimé/réinserté (cela serait idéalement capable de réinsérer les lignes dans des blocs frais afin que le vide automatique soit laissé à l'auto-aspirum. défaut)