Quelles sont les approches courantes pour augmenter les performances de lecture/écriture d'une table avec jusqu'à 100 millions de lignes?
La table a columnSEGMENT_ID INT NOT NULL
, où chaque segment comprend environ 100 000 à 1 000 000 lignes. Écrit - toutes les lignes pour SEGMENT_ID
sont insérés en une seule fois, aucune mise à jour pour SEGMENT_ID
ensuite. Lit - sont assez souvent, j'ai besoin de bonnes performances pour SELECT * FROM table WERE SEGMENT_ID = ?
.
L'approche la plus évidente consiste à créer une nouvelle table pour SEGMENT_ID
dynamiquement, mais les tables dynamiques signifient des hacks avec ORM ou même un framework de requête SQL natif. En d'autres termes, vous finissez avec un code qui sent.
Vous pouvez également utiliser le sharding, non? La base de données crée-t-elle de nouvelles tables sous le capot?
Je peux regrouper la table par SEGMENT_ID
. Mais mes insertions seront-elles groupées si j'insère toutes les données liées aux segments en même temps?
Postgres propose également de tiliser le partitionnement pour gérer de très grandes tables .
Peut-être existe-t-il une sorte d'index magique qui m'aidera à éviter de créer dynamiquement de nouvelles tables ou de configurer le sharding?
D'autres options?
BRIN
indexTIAS.
Voici un tableau exactement comme vous l'avez décrit, dans le pire des cas, 100 millions de lignes avec 1 million de lignes par SEGMENT_ID
explain analyze
CREATE TABLE foo AS
SELECT (x::int%100)::int AS SEGMENT_ID
FROM generate_series(1,100e6) AS gs(x);
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series gs (cost=0.00..15.00 rows=1000 width=32) (actual time=21740.904..57589.405 rows=100000000 loops=1)
Planning time: 0.043 ms
Execution time: 96685.350 ms
(3 rows)
Cela signifie que nous avons créé la table en 1,5 min. Ici, nous ajoutons un index.
CREATE INDEX ON foo
USING brin (SEGMENT_ID);
VACUUM ANALYZE foo;
Ensuite, nous ajoutons un autre million de lignes. SEGMENT_ID = 142
explain analyze
INSERT INTO foo(SEGMENT_ID)
SELECT 142
FROM generate_series(1,1e6) AS gs(x);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Insert on foo (cost=0.00..10.00 rows=1000 width=0) (actual time=1489.958..1489.958 rows=0 loops=1)
-> Function Scan on generate_series gs (cost=0.00..10.00 rows=1000 width=0) (actual time=174.690..286.331 rows=1000000 loops=1)
Planning time: 0.043 ms
Execution time: 1499.529 ms
(4 rows)
L'ajout d'un million de lignes a pris 1,5 seconde. Maintenant, nous sélectionnons,
explain analyze
SELECT *
FROM foo
WHERE SEGMENT_ID=142;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on foo (cost=52.00..56.01 rows=1 width=4) (actual time=4.401..140.874 rows=1000000 loops=1)
Recheck Cond: (segment_id = 142)
Rows Removed by Index Recheck: 24832
Heap Blocks: lossy=4535
-> Bitmap Index Scan on foo_segment_id_idx (cost=0.00..52.00 rows=1 width=0) (actual time=1.504..1.504 rows=46080 loops=1)
Index Cond: (segment_id = 142)
Planning time: 0.059 ms
Execution time: 176.902 ms
(8 rows)
La sélection d'un million de lignes a pris 176 ms.
C'est sur un x230 de 5 ans avec un "Intel (R) Core (TM) i5-3230M CPU @ 2.60GHz" et un seul SSD. Vous pouvez en acheter un pour quelques centaines de dollars et l'installer Xubuntu. Pas vraiment une science difficile non plus. Je compile Angular apps en arrière-plan.
Quelles sont les approches courantes pour augmenter les performances de lecture/écriture d'une table avec jusqu'à 100 millions de lignes?
Vous ne l'utilisez pas sur un téléphone? Je veux dire, sérieusement, des centaines de millions de lignes ne sont pas particulièrement importantes sur le matériel moderne de milieu de gamme. Cela signifierait - hm, voyons. Dual Socket, 16 cœurs (je me contente de la licence minimale Windows Standard ici, mais elle correspond par exemple au bas de gamme d'un AMD EPYC), peut-être 128 Go RAM et une configuration entièrement SSD, à moins une chose fortement en cache SSD.
Je veux dire, mon âge VM (serveur SQL, utilisant 48 Go de mémoire, 6 cœurs et environ 10 SSD dédiés) gère 64 millions de travaux d'insertion/suppression de lignes en moins d'une seconde SANS rien de particulier.
L'approche la plus évidente consiste à créer une nouvelle table pour SEGMENT_ID
C'est une chose où les bases de données professionnelles ont quelque chose appelé partitionnement. Une sorte de google me dit que postgres l'a également - https://www.postgresql.org/docs/current/static/ddl-partitioning.html - le savez-vous? D'après ce que je vois, il est un peu moins élégant que SQL Server (semble créer des index sur chaque partition, au lieu de cela géré par la base de données de manière transparente).
Il ne sera pas plus rapide à lire ou à écrire, mais les suppressions de partitions entières peuvent accélérer considérablement. Pas besoin d'être dynamique ici, bien que vous puissiez le faire - le point principal est que vous ne travaillez jamais avec les sous-tables, donc l'ORM et les requêtes restent les mêmes.
Vous pouvez également utiliser le sharding, non?
Ce que vous devriez faire - une fois que vous avez atteint des centaines de milliards de lignes.
C'est vraiment du partitionnement, mais seulement si vos scénarios d'insertion/suppression le rendent efficace. Sinon, la réponse est vraiment le matériel, en particulier parce que 100 millions, ce n'est pas beaucoup. Et le partitionnement est à peu près la seule solution qui fonctionne bien avec les ORM.
Tout vraiment, pourquoi dynamiquement? Prégénéré. Oh et...
J'ai besoin de bonnes performances pour SELECT * FROM table WERE SEGMENT_ID =?
Les partitions ne sont PAS utiles ici. Ok, voici le problème - les partitions vous aident à rechercher moins de données, mais utiliser un index avec le segment_id comme premier champ et filtrer par celui-ci - fait exactement la même chose. Assez RAM et FAST IO sont la seule solution pour une lecture rapide des données. Les partitions sont fondamentalement une chose "supprimer une partition rapidement" - tout le reste obtient au mieux un petit gain.