web-dev-qa-db-fra.com

Optimisation des requêtes sur une gamme d'horodatages (une colonne)

J'utilise Postgres 9.3 à travers Heroku.

J'ai une table, "Trafic", avec des enregistrements de 1 M + qui a de nombreux inserts et mises à jour chaque jour. J'ai besoin d'effectuer des opérations de somme sur cette table sur différentes gammes de temps et que ces appels peuvent prendre jusqu'à 40 secondes et aimeraient entendre des suggestions sur la manière de l'améliorer.

J'ai l'index suivant en place sur cette table:

CREATE INDEX idx_traffic_partner_only ON traffic (dt_created) WHERE campaign_id IS NULL AND uuid_self <> uuid_partner;

Voici un exemple de sélection de sélection:

SELECT SUM("clicks") AS clicks, SUM("impressions") AS impressions
FROM "traffic"
WHERE "uuid_self" != "uuid_partner"
AND "campaign_id" is NULL
AND "dt_created" >= 'Sun, 29 Mar 2015 00:00:00 +0000'
AND "dt_created" <= 'Mon, 27 Apr 2015 23:59:59 +0000' 

Et c'est l'analyse expliquée:

Aggregate  (cost=21625.91..21625.92 rows=1 width=16) (actual time=41804.754..41804.754 rows=1 loops=1)
  ->  Index Scan using idx_traffic_partner_only on traffic  (cost=0.09..20085.11 rows=308159 width=16) (actual time=1.409..41617.976 rows=302392 loops=1)
      Index Cond: ((dt_created >= '2015-03-29'::date) AND (dt_created <= '2015-04-27'::date))
Total runtime: 41804.893 ms

http://explain.depesz.com/s/gga

Cette question est très similaire à celle d'une autre sur le SE, mais celle-ci utilisait un indice sur deux gammes d'horodatage de colonne et que le planificateur d'index de cette requête avait des estimations. La principale suggestion Il y avait pour créer un indice multi-colonnes trié, mais pour les index de colonne unique qui n'a pas beaucoup d'effet. Les autres suggestions étaient d'utiliser des indices de cluster/pg_repack et de gist, mais je ne les ai pas encore essayés, car j'aimerais voir s'il y a une meilleure solution à l'aide d'index ordinaires.

optimiser les requêtes sur une gamme d'horodatages (deux colonnes)

Pour référence, j'ai essayé les index suivants, qui n'ont pas été utilisés par la DB:

INDEX idx_traffic_2 ON traffic (campaign_id, uuid_self, uuid_partner, dt_created);
INDEX idx_traffic_3 ON traffic (dt_created);
INDEX idx_traffic_4 ON traffic (uuid_self);
INDEX idx_traffic_5 ON traffic (uuid_partner);

[~ # ~] éditer [~ # ~] : a expliqué expliquer (analyser, verbeuse, coûts, tampons) et ces résultats ont été des résultats:

Aggregate  (cost=20538.62..20538.62 rows=1 width=8) (actual time=526.778..526.778 rows=1 loops=1)
  Output: sum(clicks), sum(impressions)
  Buffers: shared hit=47783 read=29803 dirtied=4
  I/O Timings: read=184.936
  ->  Index Scan using idx_traffic_partner_only on public.traffic  (cost=0.09..20224.74 rows=313881 width=8) (actual time=0.049..431.501 rows=302405 loops=1)
      Output: id, uuid_self, uuid_partner, impressions, clicks, dt_created... (other fields redacted)
      Index Cond: ((traffic.dt_created >= '2015-03-29'::date) AND (traffic.dt_created <= '2015-04-27'::date))
      Buffers: shared hit=47783 read=29803 dirtied=4
      I/O Timings: read=184.936
Total runtime: 526.881 ms

http://explain.depesz.com/s/7gu6

Définition de la table:

CREATE TABLE traffic (
    id              serial,
    uuid_self       uuid not null,
    uuid_partner    uuid not null,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
)

iD est la clé principale et UUID_SELF, UUID_PARTNER et Campagne_ID sont toutes des clés étrangères. Le champ DT_Updated est mis à jour avec une fonction Postgres.

8
Evan Appleby

Deux choses qui sont TRES impair ici:

  1. La requête Sélectionnez 300k rangées d'une table avec des rangées de 1 m +. Pour 30% (ou quelque chose de plus de 5% - dépend de la taille de la ligne et des autres facteurs), il ne paie pas généralement d'utiliser un index du tout. Nous devrions voir A Scan séquentielle.

    L'exception serait des analyses d'index uniquement, ce que je ne vois pas ici. L'indice multicolumn @ Craig a suggéré serait la meilleure option si Vous obtenez des scans de l'index uniquement. Avec beaucoup de mises à jour comme vous l'avez mentionnée, cela peut ne pas fonctionner, auquel cas vous êtes mieux à l'abri sans les colonnes supplémentaires - et juste l'index que vous avez déjà. Vous pourriez être capable de faire Cela fonctionne pour vous avec paramètres AutoVacuum plus agressifs pour la table. Vous pouvez ajuster les paramètres pour des tables individuelles.

  2. Pendant que Postgres va utiliser l'index, je m'attendrais certainement à voir A Numérisation de l'index bitmap pour que de nombreuses lignes, pas un index brin Scan, qui est généralement le meilleur choix pour A LOW Pourcentage de lignes. Dès que Postgres attend plusieurs hits par page de données (à en juger de ses statistiques sur la table), il passera généralement à une analyse d'index bitmap.

Juger de cela, je soupçonnerais que vos Paramètres de coûts sont insuffisants (et éventuellement les statistiques de la table, aussi). Vous pouvez avoir défini random_page_cost et/ou cpu_index_Tuple_cost trop bas, par rapport à seq_page_cost. Suivez les liens et lisez le manuel.

S'adapterait également à l'observation que cache froid est un facteur important, car nous avons travaillé dans des commentaires. Soit vous accédez à des tables (parties de) que personne n'a touché depuis longtemps ou que vous exécutez sur un système de test où le cache n'est pas peuplé (encore)?
[.____] sinon, vous n'avez tout simplement pas assez RAM=== Disponible pour mettre en cache la plupart des données pertinentes de votre DB. Par conséquent, un accès aléatoire est beaucoup plus coûteux que l'accès séquentiel lorsque les données résident dans Cache. En fonction de la situation réelle, vous devrez peut-être vous adapter pour obtenir de meilleurs plans de requête.

Un autre facteur doit être mentionné pour une réponse lente sur la première lecture uniquement: bits d'indice. Lire Détails dans le Wiki Postgres et cette question connexe:

ou la table est extrêmement gonflée, auquel cas une analyse d'index aurait un sens et je vais renvoyer à _ CLUSTER/pg_repack dans ma réponse précédente que vous avez citée. (ou juste VACUUM FULL) et étudie vos paramètres VACUUM. Ceux-ci sont importants avec many inserts and updates every day.

Selon les schémas UPDATE Considérons également un FILLFACTOR inférieur à 100. Si vous ne mettez généralement à jour que des lignes nouvellement ajoutées, définissez le bas FILLFACTER après Compactage Votre table, de sorte que seules les nouvelles pages gardent une salle de guérison pour les mises à jour.

Schéma

campaign_id est 99% + NULL et dt_updated est 0% NULL.

Ajustez légèrement la séquence des colonnes pour économiser 8 octets par rangée (dans les 99% des cas où campaign_id est null):

CREATE TABLE traffic (
    uuid_self       uuid not null REFERENCES ... ,
    uuid_partner    uuid not null REFERENCES ... ,
    id              serial PRIMARY KEY,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
);

Explication détaillée et liens vers plus:

Mesurer:

3
Erwin Brandstetter

Il me ressemble comme si vous interrogez beaucoup de données dans un grand index, il est donc lent. Rien ne va notamment mal là-bas.

Si vous êtes sur PostgreSQL 9.3 ou 9.4, vous pouvez essayer de voir si vous pouvez obtenir une analyse de l'index en la faisant dans un index de couverture.

CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created, clicks, impressions)
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

PostgreSQL n'a pas de véritable index de couverture ni de support pour les termes d'index qui ne sont que des valeurs, ne faisant pas partie de l'arborescence B, il est donc plus lent et plus coûteux qu'il ne pouvait être avec ces fonctionnalités. Il pourrait encore s'agir d'une victoire sur une analyse indexable Si Vacuum est assez souvent suffisamment pour garder la carte de visibilité à jour.


Idéalement, PostgreSQL prendrait en charge les champs de données auxiliaires dans un indice comme dans MS-SQL Server ( Cette syntaxe ne fonctionnera pas dans PostgreSQL):

-- This will not work in PostgreSQL (at least 9.5)
-- it's an example of what I wish did work. Don't
-- comment to say it doesn't work.
--
CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created)
INCLUDING (clicks, impressions) -- auxillary data columns
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;
2
Craig Ringer