De laatste tijd heb ik me regelmatig positief uitgelaten over PostgreSQL en de vele mogelijkheden die je daarmee hebt. Hoogste tijd dus om eens een functionaliteit van PostgreSQL onder de loep te nemen.

Deze week moest ik wat stresstests uitvoeren en had doorvoor grote hoeveelheden data nodig. Normaal had ik daarvoor in php een lusje geschreven die een berg insert-queries naar de database zou sturen. Ik werd er echter op gewezen dat je dit veel sneller kunt doen door even een functie in PostgreSQL te schrijven die dat zelf regelt. En jawel, dat ging verbazingwekkend snel! 100.000 records inserten in minder dan 5 seconden.

Op zich is het niet zo spannend, maar het geeft wel aan dat je door het gebruik van functies binnen de database, de data een stuk sneller kunt verwerken dan wanneer je iedere keer een nieuwe query naar de database moet sturen. Daarnaast is het een stuk veiliger, je kunt de gebruiker de toegang tot de tabel ontzeggen en uitsluitend toegang geven tot de functies.

Ga er eens mee spelen en doe er je voordeel mee.

http://www.postgresql.org/docs/8.1/interactive/server-programming.html

Gebruikte tabel:

CREATE TABLE stress
(
  id bigserial NOT NULL,
  content text, 
  CONSTRAINT pk_id PRIMARY KEY (id)
)

Gebruikte PostgreSQL-functie (kan beter, output wordt niet gebruikt):

CREATE OR REPLACE FUNCTION stresstest(int8, int8)
  RETURNS int8 AS
$BODY$
DECLARE
	ii int8;
BEGIN
	FOR ii IN $1..$2
	LOOP
INSERT INTO stress(content) VALUES(MD5(CURRENT_TIMESTAMP));
	END LOOP;
	RETURN $2;
END;
$BODY$
LANGUAGE 'plpgsql';

Gebruikte PHP-code:
<?php
error_reporting(E_ALL);
// Hier jouw eigen gegevens invullen:
$dbname = 'test';
$dbuser = '*****';
$dbpasswd = '*****';
$dbhost = 'localhost';
$dbport = '5432';

$avgQuery = 0;
$avgFunction= 0;

// aanpassen naar eigen wens:
$repeat = 5;
$records = 100000;

if(!pg_connect('host='.$dbhost.' dbname='.$dbname.' user='.$dbuser.' password='.$dbpasswd.' port='.$dbport)){
echo 'Geen databaseverbinding!';
}
else {
echo 'Er wordt '.$repeat.' keer een snelheidstest uitgevoerd waarbij '.number_format($records, 0, ',', '.'). ' records worden aangemaakt. Resultaten:';
echo '<table border=1>'.PHP_EOL;

## insert met een gewone insert-query rechtstreeks in de tabel
echo '<th colspan=2>INSERT query:</th>';
for ($x = 1; $x <= $repeat; $x++){
$start = microtime(true);
for ($i = 0; $i < $records; $i++ ){
// md5-functie van de database maakt de boel nog langzamer, php doet dit sneller
$query = "INSERT INTO stress(content) VALUES('".md5(time())."')";
if(!pg_exec($query)){
echo pg_last_error().PHP_EOL;
echo $query.PHP_EOL;
}
}
$end = microtime(true);
$time = $end - $start;
$avgQuery += $time;
echo '<tr><td>'.$x.'</td><td>'.$time.'</td></tr>'.PHP_EOL;
}

## En nu een insert met de functie uitvoeren:
echo '<th colspan=2>Function:</th>';
for ($x = 1; $x <= $repeat; $x++){
$start = microtime(true);

$query = "SELECT stresstest(1, ".$records.")";
if(!pg_exec($query)){
echo pg_last_error().PHP_EOL;
echo $query.PHP_EOL;
}

$end = microtime(true);
$time = $end - $start;
$avgFunction += $time;
echo '<tr><td>'.$x.'</td><td>'.$time.'</td></tr>'.PHP_EOL;
}

$gemQuery = $avgQuery/$repeat;
$gemFunction = $avgFunction/$repeat;
$gemVerschil = number_format($gemQuery/$gemFunction, 1, ',', '.');
echo '<th colspan=2>Gemiddelden:</th>';
echo '<tr><td>Query:</td><td>'.$gemQuery.'</td></tr>';
echo '<tr><td>Functie:</td><td>'.$gemFunction.'</td></tr>';

echo '</table>'.PHP_EOL;
echo 'Het gebruik van de functie was gemiddeld '.$gemVerschil.' keer zo snel.';
pg_close();
}
?>

Resultaten:

Er wordt 5 keer een snelheidstest uitgevoerd waarbij 100.000 records worden aangemaakt. Resultaten:

INSERT query:
1: 127.626353979
2: 127.663748026
3: 129.388574123
4: 128.646729946
5: 125.241792917
Function:
1: 4.24825119972
2: 5.77088189125
3: 4.0562710762
4: 4.04982805252
5: 5.46645998955
Gemiddelden:
Query: 127.713439798
Functie: 4.71833844185
Het gebruik van de functie was gemiddeld 27,1 keer zo snel.

Ps. Er valt vast nog wel het e.e.a. te optimaliseren, daar heb ik me niet of nauwelijks mee beziggehouden. De functie kan in elk geval nog wat eenvoudiger.
Zou het kunnen zijn dat je in het geval van de interne INSERT hij eerst alles netjes klaar zet, dan alle data in 1 keer invoegt en dan weer lekker zijn indexen gaat bijwerken en dat bij het openen van een query hij dat bij iedere aparte INSERT doet? Zoja, dan zou de eerste ronde (die door PHP ingevoerd wordt) sneller gaan waneer je meerdere queries bij 1 enkele pg_exec opstuurt.

Niet dat het er op zich toe doet..

Veel interessanter zijn natuurlijk de SELECT queries, aangezien die vele malen vaker uitgevoerd worden, veel meer rekenkracht vereisen en veel meer data moeten verschuiven... en dat dan vergelijkend met MySQL om zo even lekker te benadrukken hoe 'belabberd' die MyISAM engine wel niet is/zou kunnen zijn...

edit: indeling post veranderd :/
Ik wil het op zich wel testen.. ik zit alleen even te denken wat voor select queries je moet verzinnen om het spannend te maken? Eentje die meerdere tabellen op verschillende manieren importeert? Ik kan in perl wel een aardig testje schrijven, waarin gebruik gemaakt wordt van DBI (een database layer met verschillende drivers)

Ik heb zowel een default mysql als een default postgresql versie in debian draaien. Lijkt me interessant om eens te testen. Hebben jullie suggesties voor de database die we er op los gaan laten? Misschien een test met een postcode database die ik nog heb rondhangen ofzo? Veel data in elk geval.
De resultaten vallen me nog wel mee. Ik heb bijvoorbeeld ervaring met een crawler die van 40 seconden terug ging naar 0,001 seconde.

Maar ik had dan de data in één keer in de function gestopt dus zonder for-loop. En de function explode dan de resultaten en doet de inserts. Dan hoeft de SQL maar één keer geparst te worden plus dat alle data in 1 keer wordt verstuurd.
Bij mij ging het trouwens ook nog eens om een unique index. Ik deed in de function eerst een SELECT voor elke query omdat het sneller is dan de exception van de UNIQUE afwachten. En het ging om 1000 queries per test in een database met 5 miljoen records.

Overigens is een goed gescripte procedure of function ook nog rustig 100x zo snel als eentje waar net iets aan ontbreekt of minder optimaal is, maar dat is soms even testen.
Gooi er inderdaad een een poscodetabel in

Is ook nog relationeel
Met de select-queries heb ik me hier even niet bezig gehouden, maar wat Jelmer al zegt, daar heb je veel meer aan snelheid dan bij een insert. Het is alleen wel zo dat schrijven in de database (insert of update) langer duurt dan een select. De vergelijking die ik hier maak, is een vergelijking tussen php die de data aanmaakt en ieder resultaat in een aparte query naar de database stuurt, en PostgreSQL die de data zelf aanmaakt en opslaat.

Bij een select spelen hele andere zaken een rol, met als belangrijkste onderdeel de indexen. En daar heeft MySQL inderdaad een ernstig probleem mee... Maar goed, die vergelijking ga ik hier niet nogmaals maken, het is algemeen bekend dat MySQL (ernstige!) beperkingen heeft.

Ik denk dat ik eens ga stoeien met een forumpje, een datamodel met users, topics en reacties bestaat uit een aantal fk's en pk's en je kunt vast leuk met indexen gaan stoeien. 'Lorem ipsum' zal wel voor de content zorgen...

Reageren