Notre système écrit beaucoup de données (type de système Big Data). Les performances en écriture suffisent à nos besoins, mais les performances en lecture sont vraiment trop lentes.
La structure de la clé primaire (contrainte) est similaire pour toutes nos tables:
timestamp(Timestamp) ; index(smallint) ; key(integer).
Une table peut avoir des millions de lignes, voire des milliards de lignes, et une demande de lecture concerne généralement une période (timestamp / index) et une balise spécifiques. Il est courant d'avoir une requête qui renvoie environ 200 000 lignes. Actuellement, nous pouvons lire environ 15 000 lignes par seconde, mais nous devons être 10 fois plus rapides. Est-ce possible, et si oui comment?
Remarque: PostgreSQL est fourni avec notre logiciel, le matériel est donc différent d’un client à l’autre.
C'est une machine virtuelle utilisée pour les tests. L'hôte de la machine virtuelle est Windows Server 2008 R2 x64 avec 24,0 Go de RAM.
Spécification du serveur (VMWare de la machine virtuelle)
Server 2008 R2 x64
2.00 GB of memory
Intel Xeon W3520 @ 2.67GHz (2 cores)
postgresql.conf
optimisations
shared_buffers = 512MB (default: 32MB)
effective_cache_size = 1024MB (default: 128MB)
checkpoint_segment = 32 (default: 3)
checkpoint_completion_target = 0.9 (default: 0.5)
default_statistics_target = 1000 (default: 100)
work_mem = 100MB (default: 1MB)
maintainance_work_mem = 256MB (default: 16MB)
Définition de la table
CREATE TABLE "AnalogTransition"
(
"KeyTag" integer NOT NULL,
"Timestamp" timestamp with time zone NOT NULL,
"TimestampQuality" smallint,
"TimestampIndex" smallint NOT NULL,
"Value" numeric,
"Quality" boolean,
"QualityFlags" smallint,
"UpdateTimestamp" timestamp without time zone, -- (UTC)
CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag" ),
CONSTRAINT "FK_AnalogTransition_Tag" FOREIGN KEY ("KeyTag")
REFERENCES "Tag" ("Key") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE,
autovacuum_enabled=true
);
Question
La requête prend environ 30 secondes pour s'exécuter dans pgAdmin3, mais nous aimerions avoir le même résultat sous 5 secondes si possible.
SELECT
"AnalogTransition"."KeyTag",
"AnalogTransition"."Timestamp" AT TIME ZONE 'UTC',
"AnalogTransition"."TimestampQuality",
"AnalogTransition"."TimestampIndex",
"AnalogTransition"."Value",
"AnalogTransition"."Quality",
"AnalogTransition"."QualityFlags",
"AnalogTransition"."UpdateTimestamp"
FROM "AnalogTransition"
WHERE "AnalogTransition"."Timestamp" >= '2013-05-16 00:00:00.000' AND "AnalogTransition"."Timestamp" <= '2013-05-17 00:00:00.00' AND ("AnalogTransition"."KeyTag" = 56 OR "AnalogTransition"."KeyTag" = 57 OR "AnalogTransition"."KeyTag" = 58 OR "AnalogTransition"."KeyTag" = 59 OR "AnalogTransition"."KeyTag" = 60)
ORDER BY "AnalogTransition"."Timestamp" DESC, "AnalogTransition"."TimestampIndex" DESC
LIMIT 500000;
Expliquer 1
"Limit (cost=0.00..125668.31 rows=500000 width=33) (actual time=2.193..3241.319 rows=500000 loops=1)"
" Buffers: shared hit=190147"
" -> Index Scan Backward using "PK_AnalogTransition" on "AnalogTransition" (cost=0.00..389244.53 rows=1548698 width=33) (actual time=2.187..1893.283 rows=500000 loops=1)"
" Index Cond: (("Timestamp" >= '2013-05-16 01:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-16 15:00:00-04'::timestamp with time zone))"
" Filter: (("KeyTag" = 56) OR ("KeyTag" = 57) OR ("KeyTag" = 58) OR ("KeyTag" = 59) OR ("KeyTag" = 60))"
" Buffers: shared hit=190147"
"Total runtime: 3863.028 ms"
Expliquer 2
Lors de mon dernier test, il m'a fallu 7 minutes pour sélectionner mes données! Voir ci-dessous:
"Limit (cost=0.00..313554.08 rows=250001 width=35) (actual time=0.040..410721.033 rows=250001 loops=1)"
" -> Index Scan using "PK_AnalogTransition" on "AnalogTransition" (cost=0.00..971400.46 rows=774511 width=35) (actual time=0.037..410088.960 rows=250001 loops=1)"
" Index Cond: (("Timestamp" >= '2013-05-22 20:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-24 20:00:00-04'::timestamp with time zone) AND ("KeyTag" = 16))"
"Total runtime: 411044.175 ms"
la source
Ainsi, je vois une chose dans les plans: votre index est soit gonflé (puis à côté de la table sous-jacente) ou n’est tout simplement pas très bon pour ce type de requête (j’ai essayé d’aborder ceci dans mon dernier commentaire ci-dessus).
Une ligne de l'index contient 14 octets de données (et quelques-uns pour l'en-tête). Maintenant, calculons à partir des nombres donnés dans le plan: vous avez 500 000 lignes sur 190147 pages, ce qui signifie en moyenne moins de 3 lignes utiles par page, soit environ 37 octets par page. C'est un très mauvais ratio, n'est-ce pas? Étant donné que la première colonne de l'index est le
Timestamp
champ et qu'il est utilisé dans la requête en tant que plage, le planificateur peut choisir - et choisit - l'index pour rechercher les lignes correspondantes. MaisTimestampIndex
lesWHERE
conditions ne mentionnent rien , aussi le filtrage surKeyTag
n'est-il pas très efficace, car ces valeurs sont supposées apparaître de manière aléatoire dans les pages d'index.Ainsi, une possibilité est de changer la définition de l'index en
(ou, étant donné la charge de votre système, créez cet index en tant que nouvel index:
L'autre possibilité qu'une grande partie des pages d'index soit occupée par des rangées mortes, ce qui pourrait être éliminé par aspiration. Vous avez créé la table avec le réglage
autovacuum_enabled=true
- mais avez-vous déjà commencé l'autovacuuming? Ou courirVACUUM
manuellement?la source