J'aime le fait que PostgreSQL résiste aux plantages, car je ne veux pas perdre de temps réparer une base de données . Cependant, je suis sûr que je peux désactiver/modifier certains éléments pour que les insertions/mises à jour fonctionnent plus rapidement, même si je perd quelques enregistrements avant une panne de courant/un crash. Je ne m'inquiète pas de quelques enregistrements - juste de la base de données dans son ensemble.
J'essaie d'optimiser PostgreSQL pour de grandes quantités d'écritures. Il faut actuellement 22 minutes pour insérer 1 million de lignes, ce qui semble un peu lent.
Comment puis-je accélérer les écritures PostgreSQL?
Certaines des options que j'ai examinées (comme full_page_writes) semblent également risquer de corrompre des données, ce que je ne souhaite pas. Cela ne me dérange pas que des données soient perdues - je ne veux simplement pas de corruption.
Voici le tableau que j'utilise - ceci puisque la plupart des tableaux contiendront des ints et des petites chaînes, ce tableau "sample" semble être le meilleur exemple de ce à quoi je devrais m'attendre.
CREATE TABLE "user"
(
id serial NOT NULL,
username character varying(40),
email character varying(70),
website character varying(100),
created integer,
CONSTRAINT user_pkey PRIMARY KEY (id)
)
WITH ( OIDS=FALSE );
CREATE INDEX id ON "user" USING btree (id);
J'ai environ 10 scripts émettant chacun 100 000 demandes à la fois à l'aide d'instructions préparées. Ceci simule une charge réelle que mon application donnera à la base de données. Dans mon application, chaque page a 1+ insertions.
J'utilise déjà les commits asynchrones, car j'ai
synchronous_commit = off
dans le fichier de configuration principal.
1M enregistrements insérés en 22 minutes s’élève à 758 enregistrements/seconde. Chaque INSERT représente un commit individuel sur le disque, avec à la fois un journal à écriture anticipée et des composants de base de données. Normalement, je m'attends à ce que même un bon matériel avec un cache sauvegardé sur batterie et tout ce que vous aurez la chance d'atteindre 3000 commit/seconde. Vous ne vous en tirez donc pas vraiment mal s'il s'agit d'un matériel standard sans une telle accélération d'écriture. La limite normale ici est comprise entre 500 et 1 000 commits/seconde dans la situation dans laquelle vous vous trouvez, sans réglage particulier pour cette situation.
En ce qui concerne ce à quoi cela pourrait ressembler, si vous ne pouvez pas inclure plus d’enregistrements dans les commits, vous avez le choix entre:
Désactive synchronous_commit (déjà Terminé)
Augmentez wal_writer_delay. Lorsque Synchronous_commit est désactivé, les spools de la base de données S'engagent à être écrit Toutes les 200 ms. Vous pouvez faire en sorte que Un certain nombre de secondes à la place de Si vous le souhaitez en ajustant ce Vers le haut, cela augmente simplement la taille De la perte de données après un crash.
Augmentez wal_buffers à 16 Mo, juste pour Rendre cette opération plus efficace .
Augmentez checkpoint_segments, pour réduire de La fréquence à laquelle les données standard sont Écrites sur le disque. Vous voulez probablement Au moins 64 ici. Les inconvénients sont une utilisation plus importante de l’espace disque et un temps de récupération plus long Après une panne.
Augmentez shared_buffers. La valeur par défaut Ici est minuscule, généralement 32 Mo. Vous devez Augmenter la quantité de mémoire Partagée par UNIX que le système doit allouer. Une fois que cela est fait, les valeurs utiles sont , Typiquement> 1/4 de la RAM totale, jusqu'à 8GB. Le taux de gain diminue ici de plus de 256 Mo au-dessus de 256 Mo. Toutefois, le passage de la valeur par défaut de À Peut être vraiment utile.
C'est à peu près tout. Tout ce qui pourrait vous aider pourrait potentiellement endommager les données en cas de plantage; ce sont tous complètement sûrs.
22 minutes pour 1 million de lignes ne semble pas que lent, surtout si vous avez beaucoup d'index.
Comment faites-vous les inserts? Je suppose que vous utilisez des insertions par lots, pas une ligne par transaction.
Est-ce que PG prend en charge certains types de chargement en bloc, comme la lecture d'un fichier texte ou la fourniture d'un flux de données CSV? Si tel est le cas, vous seriez probablement mieux avisé de l'utiliser.
S'il vous plaît postez le code que vous utilisez pour charger les enregistrements 1M, et les gens le conseilleront.
S'il vous plaît poster:
EDIT: Il semble que le PO ne soit pas intéressé par les encarts en vrac, mais effectue un test de performance pour de nombreux encarts à une seule rangée. Je suppose que chaque insertion est dans sa propre transaction.
Eh bien, vous ne nous donnez pas beaucoup pour continuer. Mais on dirait que vous recherchez les commits asynchrones .
Ne négligez pas une mise à niveau matérielle - un matériel plus rapide signifie généralement une base de données plus rapide.
Je pense que le problème ne peut pas être résolu en traitant uniquement avec le serveur.
J'ai découvert que PostgreSQL pouvait valider plus de 3 000 lignes par seconde. Le serveur et le client n'étaient pas occupés, mais le temps passait. En revanche, SQL Server peut atteindre plus de 5 000 lignes par seconde, et Oracle est encore plus rapide: il peut atteindre 12 000+ par seconde, soit environ 20 champs à la suite.
Je suppose que le problème réside dans les allers-retours: envoyez une ligne au serveur et recevez la réponse du serveur. SQL Server et Oracle prennent en charge les opérations par lots: envoyez plusieurs lignes dans un appel de fonction et attendez la réponse.
Il y a de nombreuses années, j'ai travaillé avec Oracle: J'essayais d'améliorer les performances d'écriture à l'aide d'OCI. Je lisais des documents et trouvais que trop d'allers-retours diminueraient les performances. Enfin, je l'ai résolu en utilisant des opérations par lots: envoyez 128 lignes ou plus au serveur dans un lot et attendez la réponse. Il a atteint plus de 12 000 lignes par seconde. Si vous n'utilisez pas de lots et envoyez toutes les lignes individuellement (y compris l'attente), la quantité de données atteinte est d'environ 2 000 lignes par seconde.
Vous devez également augmenter checkpoint_segments
(par exemple, jusqu'à 32 ou même plus) et très probablement aussi wal_buffers
.
Modifier:
S'il s'agit d'un chargement en bloc, vous devez utiliser COPY pour insérer les lignes. C'est beaucoup plus rapide qu'un INSERT simple.
Si vous avez besoin d'utiliser INSERT, avez-vous envisagé d'utiliser des insertions par lots (pour JDBC) ou à plusieurs lignes?
1M commits en 22 minutes semble raisonnable, même avec synchronous_commit = off
, mais si vous pouvez éviter le besoin de commettre sur chaque insertion, vous pouvez obtenir beaucoup plus rapidement que cela. Je viens d'essayer d'insérer dans votre exemple de table 1M lignes (identiques) à partir de 10 rédacteurs simultanés, à l'aide de la commande bulk-insert COPY
:
$ head -n3 users.txt | cat -A # the rest of the file is just this another 99997 times
Random J. User^[email protected]^Ihttp://example.org^I100$
Random J. User^[email protected]^Ihttp://example.org^I100$
Random J. User^[email protected]^Ihttp://example.org^I100$
$ wc -l users.txt
100000 users.txt
$ time (seq 10 | xargs --max-procs=10 -n 1 bash -c "cat users.txt | psql insertspeed -c 'COPY \"user\" (username, email, website, created) FROM STDIN WITH (FORMAT text);'")
real 0m10.589s
user 0m0.281s
sys 0m0.285s
$ psql insertspeed -Antc 'SELECT count(*) FROM "user"'
1000000
Clairement, il n’ya que 10 commits, ce qui n’est pas exactement ce que vous recherchez, mais cela vous donne, espérons-le, une indication de la vitesse qui pourrait être possible en regroupant vos insertions. C’est sur une machine virtuelle VirtualBox VM exécutant Linux sur un hôte de bureau Windows assez standard, donc pas exactement le matériel le plus performant possible.
Pour vous donner un peu moins de chiffres, nous avons un service en cours de production qui a un seul thread qui transmet les données à Postgres via une commande COPY
semblable à la précédente. Il termine un lot et valide après un certain nombre de lignes ou si la transaction atteint un certain âge (selon la première éventualité). Il peut gérer 11 000 insertions par seconde avec une latence maximale de ~ 300 ms en effectuant environ 4 commits par seconde. Si nous réduisions l'âge maximal autorisé des transactions, nous obtenions plus de validations par seconde, ce qui réduirait la latence mais également le débit. Encore une fois, ce n’est pas du matériel terriblement impressionnant.
Sur la base de cette expérience, je vous recommande fortement d'essayer d'utiliser COPY
plutôt que INSERT
et d'essayer de réduire autant que possible le nombre de validations tout en atteignant votre objectif de latence.
Une chose que vous pouvez faire pour accélérer les choses est de supprimer l'index que vous créez manuellement - la contrainte primary key
crée déjà automatiquement un index unique sur cette colonne, comme vous pouvez le voir ci-dessous (je teste en 8.3):
postgres=> CREATE TABLE "user"
postgres-> (
postgres(> id serial NOT NULL,
postgres(> username character varying(40),
postgres(> email character varying(70),
postgres(> website character varying(100),
postgres(> created integer,
postgres(> CONSTRAINT user_pkey PRIMARY KEY (id)
postgres(> )
postgres-> WITH ( OIDS=FALSE );
NOTICE: CREATE TABLE will create implicit sequence "user_id_seq" for serial column "user.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "user_pkey" for table "user"
CREATE TABLE
postgres=> CREATE INDEX id ON "user" USING btree (id);
CREATE INDEX
postgres=> \d user
Table "stack.user"
Column | Type | Modifiers
----------+------------------------+---------------------------------------------------
id | integer | not null default nextval('user_id_seq'::regclass)
username | character varying(40) |
email | character varying(70) |
website | character varying(100) |
created | integer |
Indexes:
"user_pkey" PRIMARY KEY, btree (id)
"id" btree (id)
En outre, envisagez de remplacer wal_sync_method
par une option qui utilise O_DIRECT
- ce n'est pas la valeur par défaut sous Linux
Une possibilité serait d'utiliser le jeu de clés DEFERRABLE pour différer les contraintes car celles-ci sont vérifiées pour toutes les lignes.
L'idée serait donc de demander à postgresql de vérifier les contraintes juste avant de vous engager.