Index PostgreSQL et BRIN
Testez-le vous-même. Ce n'est pas un problème sur un ordinateur portable de 5 ans avec un SSD.
EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
SELECT
x::int AS id,
(x::int % 20000)::int AS locid, -- fake location ids in the range of 1-20000
now() AS tsin, -- static timestmap
97.5::numeric(5,2) AS temp, -- static temp
x::int AS usage -- usage the same as id not sure what we want here.
FROM generate_series(1,1728000000) -- for 1.7 billion rows
AS gs(x);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series gs (cost=0.00..15.00 rows=1000 width=4) (actual time=173119.796..750391.668 rows=1728000000 loops=1)
Planning time: 0.099 ms
Execution time: 1343954.446 ms
(3 rows)
Il a donc fallu 22 minutes pour créer la table. En grande partie, parce que la table est un modeste 97GB. Ensuite, nous créons les index,
CREATE INDEX ON electrothingy USING brin (tsin);
CREATE INDEX ON electrothingy USING brin (id);
VACUUM ANALYZE electrothingy;
La création des index a également pris beaucoup de temps. Bien qu'ils soient BRIN, ils ne font que 2 à 3 Mo et se stockent facilement dans le bélier. La lecture de 96 Go n'est pas instantanée, mais ce n'est pas un réel problème pour mon ordinateur portable au moment de votre charge de travail.
Maintenant nous l'interrogeons.
explain analyze
SELECT max(temp)
FROM electrothingy
WHERE id BETWEEN 1000000 AND 1001000;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=5245.22..5245.23 rows=1 width=7) (actual time=42.317..42.317 rows=1 loops=1)
-> Bitmap Heap Scan on electrothingy (cost=1282.17..5242.73 rows=993 width=7) (actual time=40.619..42.158 rows=1001 loops=1)
Recheck Cond: ((id >= 1000000) AND (id <= 1001000))
Rows Removed by Index Recheck: 16407
Heap Blocks: lossy=128
-> Bitmap Index Scan on electrothingy_id_idx (cost=0.00..1281.93 rows=993 width=0) (actual time=39.769..39.769 rows=1280 loops=1)
Index Cond: ((id >= 1000000) AND (id <= 1001000))
Planning time: 0.238 ms
Execution time: 42.373 ms
(9 rows)
Mettre à jour avec les horodatages
Ici, nous générons une table avec différents horodatages afin de satisfaire la demande d'indexation et de recherche sur une colonne d'horodatage, la création prend un peu plus de temps car elle to_timestamp(int)
est nettement plus lente que now()
(mise en cache pour la transaction).
EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
SELECT
x::int AS id,
(x::int % 20000)::int AS locid,
-- here we use to_timestamp rather than now(), we
-- this calculates seconds since epoch using the gs(x) as the offset
to_timestamp(x::int) AS tsin,
97.5::numeric(5,2) AS temp,
x::int AS usage
FROM generate_series(1,1728000000)
AS gs(x);
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series gs (cost=0.00..17.50 rows=1000 width=4) (actual time=176163.107..5891430.759 rows=1728000000 loops=1)
Planning time: 0.607 ms
Execution time: 7147449.908 ms
(3 rows)
Maintenant, nous pouvons exécuter une requête sur une valeur d'horodatage à la place,
explain analyze
SELECT count(*), min(temp), max(temp)
FROM electrothingy WHERE tsin BETWEEN '1974-01-01' AND '1974-01-02';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=296073.83..296073.84 rows=1 width=7) (actual time=83.243..83.243 rows=1 loops=1)
-> Bitmap Heap Scan on electrothingy (cost=2460.86..295490.76 rows=77743 width=7) (actual time=41.466..59.442 rows=86401 loops=1)
Recheck Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
Rows Removed by Index Recheck: 18047
Heap Blocks: lossy=768
-> Bitmap Index Scan on electrothingy_tsin_idx (cost=0.00..2441.43 rows=77743 width=0) (actual time=40.217..40.217 rows=7680 loops=1)
Index Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
Planning time: 0.140 ms
Execution time: 83.321 ms
(9 rows)
Résultat:
count | min | max
-------+-------+-------
86401 | 97.50 | 97.50
(1 row)
Donc, en 83,321 ms, nous pouvons agréger 86 401 enregistrements dans une table avec 1,7 milliard de lignes. Cela devrait être raisonnable.
Heure se terminant
Calculer la fin de l'heure est assez facile aussi, tronquer l'horodatage vers le bas, puis ajoutez simplement une heure.
SELECT date_trunc('hour', tsin) + '1 hour' AS tsin,
count(*),
min(temp),
max(temp)
FROM electrothingy
WHERE tsin >= '1974-01-01'
AND tsin < '1974-01-02'
GROUP BY date_trunc('hour', tsin)
ORDER BY 1;
tsin | count | min | max
------------------------+-------+-------+-------
1974-01-01 01:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 02:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 03:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 04:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 05:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 06:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 07:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 08:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 09:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 10:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 11:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 12:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 13:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 14:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 15:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 16:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 17:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 18:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 19:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 20:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 21:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 22:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 23:00:00-06 | 3600 | 97.50 | 97.50
1974-01-02 00:00:00-06 | 3600 | 97.50 | 97.50
(24 rows)
Time: 116.695 ms
Il est important de noter qu'il n'utilise pas d'index sur l'agrégation, bien que ce soit le cas. Si c'est ce que vous voulez en général, vous voulez probablement que BRIN date_trunc('hour', tsin)
pose un petit problème, car il date_trunc
n'est pas immuable, vous devez donc d'abord l'envelopper pour qu'il en soit ainsi.
Partitionnement
Un autre point d’information important sur PostgreSQL est que PG 10 apporte le DDL de partitionnement . Ainsi, vous pouvez, par exemple, créer facilement des partitions pour chaque année. Décomposer votre base de données modeste en minuscules. Ce faisant, vous devriez pouvoir utiliser et gérer les index btree plutôt que BRIN, qui serait encore plus rapide.
CREATE TABLE electrothingy_y2016 PARTITION OF electrothingy
FOR VALUES FROM ('2016-01-01') TO ('2017-01-01');
Ou peu importe.