<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>True Solutions &#187; Bazy danych</title>
	<atom:link href="http://www.truesolutions.pl/blog/category/bazy-danych/feed" rel="self" type="application/rss+xml" />
	<link>http://www.truesolutions.pl/blog</link>
	<description>Startup w Polsce. Refleksje, przemyślenia, rozwiązania.</description>
	<lastBuildDate>Thu, 07 Jan 2010 23:28:18 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<image>
  <link>http://www.truesolutions.pl/blog</link>
  <url>http://www.truesolutions.pl/blog/favicon.ico</url>
  <title>True Solutions</title>
</image>
		<item>
		<title>SELECT MAX(id) w PostgreSQL</title>
		<link>http://www.truesolutions.pl/blog/select-max_id_-w-postgresql</link>
		<comments>http://www.truesolutions.pl/blog/select-max_id_-w-postgresql#comments</comments>
		<pubDate>Thu, 01 Oct 2009 22:48:20 +0000</pubDate>
		<dc:creator>Wiktor</dc:creator>
				<category><![CDATA[Bazy danych]]></category>

		<guid isPermaLink="false">http://www.truesolutions.pl/blog/?p=182</guid>
		<description><![CDATA[Jak znaleźć najnowszą (czyt. o największym id) krotkę? Wydawałoby się, że nic prostrzego &#8211; wystarczy użyć funkcji agregującej MAX():
SELECT MAX( id ) FROM tab_a;
I rzeczywiście w większości przypadków MAX() zdaje egzamin i to bardzo dobrze. Na testowych danych zapytanie wykonywało się 0.05ms. Rozważmy jednak bardziej skomplikowany przypadek:
SELECT MAX( id ) FROM tab_a a JOIN tab_b [...]]]></description>
			<content:encoded><![CDATA[<p>Jak znaleźć najnowszą (czyt. o największym id) krotkę? Wydawałoby się, że nic prostrzego &#8211; wystarczy użyć funkcji agregującej MAX():</p>
<blockquote><p>SELECT MAX( id ) FROM tab_a;</p></blockquote>
<p>I rzeczywiście w większości przypadków MAX() zdaje egzamin i to bardzo dobrze. Na testowych danych zapytanie wykonywało się 0.05ms. Rozważmy jednak bardziej skomplikowany przypadek:</p>
<blockquote><p>SELECT MAX( id ) FROM tab_a a JOIN tab_b b ON ( a.xx = b.yy );</p></blockquote>
<p>I tutaj przestaje być wesoło. Powyższy przykład wykonywał się ponad 22ms, czyli prawie 500 razy dłużej! Oczywiście zarówno id jak i kolumny z warunku połączeniowego mają prawidłowe indeksy. Problem polega na tym, że wynik połączenia zaindeksowany już nie jest i MAX() wykonuje seq scan na tych danych.</p>
<p>Rozwiązanie jest trywialne &#8211; wystarczy nie używać MAX():</p>
<blockquote><p>SELECT id FROM tab_a a JOIN tab_b b ON ( a.xx = b.yy ) ORDER BY id DESC LIMIT 1;</p></blockquote>
<p>Rezultat: 0.12ms.</p>
<p>Poniżej dowód w postaci screenshotów z &#8220;życiowej&#8221; bazy (wybaczcie cenzurę &#8211; nie chcę ujawniać struktury bazy):</p>
<p><a href="http://www.truesolutions.pl/blog/wp-content/uploads/2009/09/max.png"><img class="alignnone size-medium wp-image-185" title="max()" src="http://www.truesolutions.pl/blog/wp-content/uploads/2009/09/max-300x221.png" alt="" width="300" height="221" /></a></p>
<p><a href="http://www.truesolutions.pl/blog/wp-content/uploads/2009/09/join-max.png"><img class="alignnone size-medium wp-image-186" title="max() z join" src="http://www.truesolutions.pl/blog/wp-content/uploads/2009/09/join-max-300x182.png" alt="" width="300" height="182" /></a></p>
<p><a href="http://www.truesolutions.pl/blog/wp-content/uploads/2009/09/join-limit1.png"><img class="alignnone size-medium wp-image-188" title="join z order desc limit 1" src="http://www.truesolutions.pl/blog/wp-content/uploads/2009/09/join-limit1-300x185.png" alt="" width="300" height="185" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.truesolutions.pl/blog/select-max_id_-w-postgresql/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Benchmark procedur w PostgreSQL</title>
		<link>http://www.truesolutions.pl/blog/benchmark-procedur-w-postgresql</link>
		<comments>http://www.truesolutions.pl/blog/benchmark-procedur-w-postgresql#comments</comments>
		<pubDate>Tue, 09 Oct 2007 09:53:02 +0000</pubDate>
		<dc:creator>True Solutions</dc:creator>
				<category><![CDATA[Bazy danych]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[plperl]]></category>
		<category><![CDATA[plpgsql]]></category>
		<category><![CDATA[plpython]]></category>
		<category><![CDATA[pltcl]]></category>
		<category><![CDATA[postgresql]]></category>

		<guid isPermaLink="false">http://www.truesolutions.pl/blog/?p=4</guid>
		<description><![CDATA[Podczas pisania jednej z naszych aplikacji webowych chcieliśmy mieć możliwość dostępu do numeru IP oraz ID użytkownika w każdej z funkcji PostgreSQL. Aby uniknąć przekazywania tych wartości do każdej z funkcji korzystając z listy argumentów (mamy tych funkcji około 110) skorzystaliśmy z tabel tymczasowych w PostgreSQL. Przy każdym połączeniu z bazą danych, uruchamiana była następująca [...]]]></description>
			<content:encoded><![CDATA[<p>Podczas pisania jednej z naszych aplikacji webowych chcieliśmy mieć możliwość dostępu do numeru IP oraz ID użytkownika w każdej z funkcji PostgreSQL. Aby uniknąć przekazywania tych wartości do każdej z funkcji korzystając z listy argumentów (mamy tych funkcji około 110) skorzystaliśmy z tabel tymczasowych w PostgreSQL. Przy każdym połączeniu z bazą danych, uruchamiana była następująca procedura:</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">CREATE</span> <span style="color: #993333; font-weight: bold;">OR</span> <span style="color: #993333; font-weight: bold;">REPLACE</span> <span style="color: #993333; font-weight: bold;">FUNCTION</span> set_connection_data_old<span style="color: #66cc66;">&#40;</span>_ip text<span style="color: #66cc66;">,</span> _user_id integer<span style="color: #66cc66;">&#41;</span>
RETURNS <span style="color: #993333; font-weight: bold;">BOOLEAN</span> <span style="color: #993333; font-weight: bold;">AS</span>
$BODY$
BEGIN
EXECUTE <span style="color: #ff0000;">'CREATE TEMP TABLE user_connection_data (ip text, user_id integer)'</span>;
<span style="color: #993333; font-weight: bold;">INSERT</span> <span style="color: #993333; font-weight: bold;">INTO</span> user_connection_data <span style="color: #66cc66;">&#40;</span>ip<span style="color: #66cc66;">,</span> user_id<span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">VALUES</span> <span style="color: #66cc66;">&#40;</span>_ip<span style="color: #66cc66;">,</span> _user_id<span style="color: #66cc66;">&#41;</span>;
<span style="color: #993333; font-weight: bold;">IF</span> FOUND THEN
<span style="color: #993333; font-weight: bold;">RETURN</span> true;
ELSE <span style="color: #993333; font-weight: bold;">RETURN</span> false;
END <span style="color: #993333; font-weight: bold;">IF</span>;
END;
$BODY$
<span style="color: #993333; font-weight: bold;">LANGUAGE</span> <span style="color: #ff0000;">'plpgsql'</span> VOLATILE;</pre></td></tr></table></div>

<p>Zainstalowany skrypt do <a href="http://pgfouine.projects.postgresql.org/">analizowania logów PostgreSQL</a> wykazał, iż procedura ta jest najczęściej wywoływaną instrukcją (co oczywiście nie jest odkrywcze), jednak przeciętny czas jej wywołania (60-70ms) wydawał się nam zbyt duży. W przypadku <a href="http://www.postgresql.org/docs/current/interactive/sql-vacuum.html">nieposprzątanej</a> bazy danych czas potrafił się wydłużyć do kilku &#8211; kilkunastu sekund!</p>
<p>Oczywiście, można przekazywać te dwie informacje (IP oraz ID użytkownika) do każdej funkcji osobno, jednak zależało nam na bardziej eleganckim rozwiązaniu. Po zaakceptowaniu straty wynikającej z konieczności uruchamiania dodatkowego zapytania, rozpoczęliśmy poszukiwania innych rozwiązań dla naszego głównego problemu, czyli przekazywania wspólnych (globalnych) zmiennych pomiędzy wywołaniami funkcji. Możliwości z którymi się spotkaliśmy są następujące:</p>
<ul>
<li>tworzenie tabeli tymczasowej oraz</li>
<li>stworzenie zmiennej globalnej.</li>
</ul>
<p>Niestety, <a href="http://www.postgresql.org/docs/current/static/plpgsql.html">pl/pgsql</a> nie dysponuje natywnie możliwością tworzenia zmiennych globalnych. Możliwości takie posiada jednak zarówno <a href="http://www.postgresql.org/docs/8.2/static/pltcl.html">pl/tcl</a>, <a href="http://www.postgresql.org/docs/8.2/static/plperl.html">pl/perl</a> oraz <a href="http://www.postgresql.org/docs/8.2/interactive/plpython.html">pl/python</a>.</p>
<p><strong>Oto przeprowadzone testy:</strong></p>
<p><strong>1.</strong> Pierwsze wywołanie pustej funkcji:</p>
<ul>
<li>pl/pgsql (~4ms)</li>
<li>pl/tcl (~20ms)</li>
<li>pl/pyhon (~65ms)</li>
<li>pl/perl (~120ms)</li>
</ul>
<p>Wyniki mówią same za siebie &#8211; pl/pgsql jest wyraźnym zwycięzcą. 5x szybszy od pl/tcl, ponad 16x szybszy od pl/pythona oraz 30x (!) szybszy od pl/perla. Kolejne wywołania funkcji w każdym przypadku były oczywiście szybsze.</p>
<p><a href="http://www.truesolutions.pl/blog/wp-content/uploads/2007/10/picture-1.png" title="wykres1"><img src="http://www.truesolutions.pl/blog/wp-content/uploads/2007/10/picture-1.png" alt="wykres1" /></a></p>
<p><strong>2.</strong> Wywołanie 5x pod rząd tej samej pustej funkcji wraz z przykładowymi czasami wywołania</p>
<ul>
<li>pl/pgsql</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 3.870 ms  statement: SELECT test_pgsql( )
LOG:  duration: 0.412 ms  statement: SELECT test_pgsql( )
LOG:  duration: 0.272 ms  statement: SELECT test_pgsql( )
LOG:  duration: 0.267 ms  statement: SELECT test_pgsql( )
LOG:  duration: 0.264 ms  statement: SELECT test_pgsql( )</pre></div></div>

<ul>
<li>pl/tcl</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 25.939 ms  statement: SELECT test_tcl( )
LOG:  duration: 0.440 ms  statement: SELECT test_tcl( )
LOG:  duration: 0.336 ms  statement: SELECT test_tcl( )
LOG:  duration: 0.330 ms  statement: SELECT test_tcl( )
LOG:  duration: 0.330 ms  statement: SELECT test_tcl( )</pre></div></div>

<ul>
<li>pl/python:</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 66.549 ms  statement: SELECT test_python( )
LOG:  duration: 0.497 ms  statement: SELECT test_python( )
LOG:  duration: 0.312 ms  statement: SELECT test_python( )
LOG:  duration: 0.307 ms  statement: SELECT test_python( )
LOG:  duration: 0.307 ms  statement: SELECT test_python( )</pre></div></div>

<ul>
<li>pl/perl</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 122.523 ms  statement: SELECT test_perl( )
LOG:  duration: 0.428 ms  statement: SELECT test_perl( )
LOG:  duration: 0.278 ms  statement: SELECT test_perl( )
LOG:  duration: 0.274 ms  statement: SELECT test_perl( )
LOG:  duration: 0.274 ms  statement: SELECT test_perl( )</pre></div></div>

<p>Zgodnie z tym czego mogliśmy się spodziewać najbardziej kosztowne jest pierwsze wywołanie funkcji (w tym przypadku rodzaj języka ma znaczenie), natomiast kolejne wywołania (niezależnie od języka) to zazwyczaj 0,2 &#8211; 0,4 ms.</p>
<p><strong>3.</strong> Pięciokrotne, pojedyncze funkcji zapisującej/odczytującej w zależności od języka dwie zmienne &#8211; do tablicy tymczasowej (pl/pgsql) lub zapis jako zmienne globalne (pl/tcl, pl/perl, pl/python)</p>
<ul>
<li>pl/tcl (zapis, średni czas ~26,7ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 26.209 ms  statement: SELECT test_store_tcl( '127.0.0.1', 234 )
LOG:  duration: 26.366 ms  statement: SELECT test_store_tcl( '127.0.0.1', 234 )
LOG:  duration: 26.108 ms  statement: SELECT test_store_tcl( '127.0.0.1', 234 )
LOG:  duration: 29.043 ms  statement: SELECT test_store_tcl( '127.0.0.1', 234 )
LOG:  duration: 26.030 ms  statement: SELECT test_store_tcl( '127.0.0.1', 234 )</pre></div></div>

<ul>
<li>pl/tcl (odczyt, średni czas ~0,86 ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 0.864 ms  statement: SELECT test_get_tcl( )
LOG:  duration: 0.867 ms  statement: SELECT test_get_tcl( )
LOG:  duration: 0.863 ms  statement: SELECT test_get_tcl( )
LOG:  duration: 0.864 ms  statement: SELECT test_get_tcl( )
LOG:  duration: 0.862 ms  statement: SELECT test_get_tcl( )</pre></div></div>

<ul>
<li>pl/perl (zapis, średni czas ~122,1 ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 121.790 ms  statement: SELECT test_store_perl( '127.0.0.1', 234 )
LOG:  duration: 122.007 ms  statement: SELECT test_store_perl( '127.0.0.1', 234 )
LOG:  duration: 122.095 ms  statement: SELECT test_store_perl( '127.0.0.1', 234 )
LOG:  duration: 122.428 ms  statement: SELECT test_store_perl( '127.0.0.1', 234 )
LOG:  duration: 122.211 ms  statement: SELECT test_store_perl( '127.0.0.1', 234 )</pre></div></div>

<ul>
<li>pl/perl (odczyt, średni czas ~1,66 ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 1.642 ms  statement: SELECT test_get_perl( )
LOG:  duration: 1.688 ms  statement: SELECT test_get_perl( )
LOG:  duration: 1.662 ms  statement: SELECT test_get_perl( )
LOG:  duration: 1.670 ms  statement: SELECT test_get_perl( )
LOG:  duration: 1.654 ms  statement: SELECT test_get_perl( )</pre></div></div>

<ul>
<li>pl/python (zapis, średni czas ~66 ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 66.196 ms  statement: SELECT test_store_python( '127.0.0.1', 234 )
LOG:  duration: 66.026 ms  statement: SELECT test_store_python( '127.0.0.1', 234 )
LOG:  duration: 66.012 ms  statement: SELECT test_store_python( '127.0.0.1', 234 )
LOG:  duration: 65.915 ms  statement: SELECT test_store_python( '127.0.0.1', 234 )
LOG:  duration: 66.228 ms  statement: SELECT test_store_python( '127.0.0.1', 234 )</pre></div></div>

<ul>
<li>pl/python (odczyt, średni czas ~1,52 ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 1.474 ms  statement: SELECT test_get_python( )
LOG:  duration: 1.718 ms  statement: SELECT test_get_python( )
LOG:  duration: 1.446 ms  statement: SELECT test_get_python( )
LOG:  duration: 1.477 ms  statement: SELECT test_get_python( )
LOG:  duration: 1.475 ms  statement: SELECT test_get_python( )</pre></div></div>

<ul>
<li>pl/pgsql (zapis, średni czas ~55,8 ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 57.036 ms  statement: SELECT test_store_pgsql( '127.0.0.1', 234 )
LOG:  duration: 65.635 ms  statement: SELECT test_store_pgsql( '127.0.0.1', 234 )
LOG:  duration: 52.147 ms  statement: SELECT test_store_pgsql( '127.0.0.1', 234 )
LOG:  duration: 57.006 ms  statement: SELECT test_store_pgsql( '127.0.0.1', 234 )
LOG:  duration: 47.622 ms  statement: SELECT test_store_pgsql( '127.0.0.1', 234 )</pre></div></div>

<ul>
<li>pl/pgsql (odczyt, średni czas ~3,34 ms)</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">LOG:  duration: 2.980 ms  statement: SELECT test_get_pgsql( )
LOG:  duration: 4.691 ms  statement: SELECT test_get_pgsql( )
LOG:  duration: 3.018 ms  statement: SELECT test_get_pgsql( )
LOG:  duration: 3.022 ms  statement: SELECT test_get_pgsql( )
LOG:  duration: 3.002 ms  statement: SELECT test_get_pgsql( )</pre></div></div>

<p>Wyniki zapisu: (im mniej ms tym lepiej)</p>
<p>1. pl/tcl<br />
2. pl/pgsql<br />
3. pl/python<br />
4. pl/perl</p>
<p><a href="http://www.truesolutions.pl/blog/wp-content/uploads/2007/10/picture-3.png" title="picture2"><img src="http://www.truesolutions.pl/blog/wp-content/uploads/2007/10/picture-3.png" alt="picture2" /></a></p>
<p>Wyniki odczytu: (im mniej ms tym lepiej)</p>
<p>1. pl/tcl<br />
2. pl/python<br />
3. pl/perl<br />
4. pl/pgsql</p>
<p><a href="http://www.truesolutions.pl/blog/wp-content/uploads/2007/10/picture-4.png" title="picture3"><img src="http://www.truesolutions.pl/blog/wp-content/uploads/2007/10/picture-4.png" alt="picture3" /></a></p>
<p>Jak widać, w naszym konkretnym przypadku, <strong>najszybszy zarówno w zapisie, jak i odczycie danych okazał się pl/tcl.</strong></p>
<p>W efekcie nasza funkcja została przyspieszona o 100% &#8211; średni czas wykonania został skrócony z ~60ms do ~30ms.</p>
<p>Oczywiście, benchmark dotyczył specyficznej funkcjonalności, stąd nie zawsze nasze wynika sprawdzą się w innym kontekście. Zapewne też najszybsze będą funkcje napisane w C, jednak w standardowej dystrybucji PostgreSQL język ten nie jest umieszczony (przynajmniej do wersji 8.2). Oto krótka specyfikacja naszego piwnicznego serwera deweloperskiego:</p>
<ul>
<li>2x Pentium III Katmai</li>
<li>512 MB RAM</li>
<li>2x SCSI Storage Controller (Adaptec + IBM) RAID 5</li>
<li>Slackware 10.2.0</li>
<li>PostgreSQL 8.1</li>
<li>PHP 5.2.0</li>
<li>Apche 2.0.59</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.truesolutions.pl/blog/benchmark-procedur-w-postgresql/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
