<?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>radek.cc &#187; Technical</title>
	<atom:link href="http://radek.cc/category/tech/feed/" rel="self" type="application/rss+xml" />
	<link>http://radek.cc</link>
	<description>Radosław Zieliński</description>
	<lastBuildDate>Fri, 25 Jun 2010 05:22:08 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>You can get your laptop stolen too, smartass.</title>
		<link>http://radek.cc/2010/05/18/bye-bye-eee-pc/</link>
		<comments>http://radek.cc/2010/05/18/bye-bye-eee-pc/#comments</comments>
		<pubDate>Mon, 17 May 2010 23:47:51 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[Travel]]></category>
		<category><![CDATA[ecuador]]></category>
		<category><![CDATA[fail]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=382</guid>
		<description><![CDATA[I thought I´m so fucking awesome, nobody can steal from me.  Well, I just waved my Eee PC and cell phone goodbye.
How: 3h bus ride from Montanita to Guayaquil (Ecuador).  Modern, clean bus.  Two of us,  a bit of a hangover, but mostly awake (13:00).  Keeping our bags in the overhead storage space.  Nobody was walking [...]]]></description>
			<content:encoded><![CDATA[<p>I thought I´m so fucking awesome, <strong>nobody</strong> can steal from me.  Well, I just waved my Eee PC and cell phone goodbye.</p>
<p>How: 3h bus ride from <a href="http://maps.google.com/maps?f=q&amp;source=s_q&amp;hl=en&amp;q=Montanita,+Ecuador,+Ecuador&amp;sll=-1.493971,-79.40918&amp;sspn=9.018419,21.42334&amp;ie=UTF8&amp;cd=3&amp;geocode=FXgW5P8dJc8v-w&amp;split=0&amp;doflg=ptk&amp;hq=&amp;hnear=Montanita,+Ecuador,+Ecuador&amp;z=11">Montanita</a> to Guayaquil (Ecuador).  Modern, clean bus.  Two of us,  a bit of a hangover, but mostly awake (13:00).  Keeping our bags in the overhead storage space.  Nobody was walking around, as far as I remember.  A man tried to help me with putting the bag into the storage space, but I stopped him and did that myself.  After the ride, one of the passengers passed me the bag, when I was standing up.</p>
<p>Outside of the bus, I noticed the bag is too light and too soft &#8212; the netbook is gone.  I thought I left it in Montanita, have excluded the possibility of theft on the bus, seemed too difficult&#8230; but then I noticed the loss of the cell phone, and everything clicked together&#8230; the man with the guinea pig (distraction), the helpful passenger&#8230;</p>
<p>Fuck, fuck, fuck.  I´m lucky they left the passport.</p>
<p>Before I figured things out and notified the police (a helpful lady from the tourist information office was kind enough to translate), the bus was gone to another destination&#8230; I bet the driver knew what´s going on, but the friendly cops couldn´t do anything more.</p>
<p>I mark this <a href="http://radek.cc/category/tech/">Technical</a>, for it´s a good lesson about backups.  The last one I (probably) can recover is from 2006, and that´s just the most critical stuff (my SSH and PGP keys, etc).</p>
<p>The phrase ¨epic fail¨ somehow doesn´t seem appropriate.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2010/05/18/bye-bye-eee-pc/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>PLD: perl v5.12.0</title>
		<link>http://radek.cc/2010/04/13/pld-perl-v5-12-0/</link>
		<comments>http://radek.cc/2010/04/13/pld-perl-v5-12-0/#comments</comments>
		<pubDate>Tue, 13 Apr 2010 20:40:17 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[perl]]></category>
		<category><![CDATA[pld]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=372</guid>
		<description><![CDATA[I&#8217;ve updated perl.spec to v5.12.0 (on HEAD).
Not sent to builders &#8212; this will require more testing and rebuilding of whatever depends on libperl.so and the directories, which usually results in a temporary mess.  I&#8217;m not going to do that: EBUSY.
Packages for i686 (just bare perl &#38; perl-dirs) can be found at:
poldek -s http://carme.pld-linux.org/~radek/perl-512-i686/
Notes:

suidperl has been [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve updated <a href="http://cvs.pld-linux.org/packages/perl/perl.spec">perl.spec</a> to v5.12.0 (on <code>HEAD</code>).</p>
<p>Not sent to builders &#8212; this will require more testing and rebuilding of whatever depends on <code>libperl.so</code> and the directories, which usually results in a temporary mess.  I&#8217;m not going to do that: <a href="http://radek.cc/category/travel/">EBUSY</a>.</p>
<p>Packages for i686 (just bare perl &amp; perl-dirs) can be found at:</p>
<pre style="padding-left: 30px;">poldek -s <a href="http://carme.pld-linux.org/~radek/perl-512-i686/">http://carme.pld-linux.org/~radek/perl-512-i686/</a></pre>
<p>Notes:</p>
<ul>
<li>suidperl has been removed</li>
<li>microperl is currently <a href="http://perl5.git.perl.org/perl.git/commitdiff/01be0729981136a058cce07a897ccdb94609e1c0">broken</a> (does anyone care?  if yes, speak up)</li>
<li><code>@INC</code> order has been changed to the one PLD has since v5.8.0 (site, vendor, priv), therefore rendering the <a href="http://cvs.pld-linux.org/packages/perl/perl_581-INC.patch">-INC</a> patch obsolete; please be testing it (<code>perl -le 'map print, @INC'</code>)</li>
</ul>
<p>See <a href="http://search.cpan.org/~jesse/perl-5.12.0/pod/perl5120delta.pod">perl5120delta.pod</a> for the release notes.  Official release information is <a href="http://lwn.net/Articles/383203/">here</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2010/04/13/pld-perl-v5-12-0/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>(Free)DNS trouble ahead&#8230;</title>
		<link>http://radek.cc/2010/04/09/freedns-trouble-ahead/</link>
		<comments>http://radek.cc/2010/04/09/freedns-trouble-ahead/#comments</comments>
		<pubDate>Fri, 09 Apr 2010 14:00:02 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[dns]]></category>
		<category><![CDATA[fail]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=367</guid>
		<description><![CDATA[DNS hosting, it seems so simple, yet it&#8217;s so tricky&#8230;
All domain registrars I know of provide the service, but:

the interface is usually too crude and simplistic (I do want the TXT, SRV, IPv6 support and overall fine-grained control over the zone&#8217;s content),
I want to manage the domains from different registrars in one place,
I don&#8217;t trust [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://en.wikipedia.org/wiki/Domain_Name_System">DNS</a> hosting, it seems so simple, yet it&#8217;s so tricky&#8230;</p>
<p>All domain registrars I know of provide the service, but:</p>
<ol>
<li>the interface is usually too crude and simplistic (I do want the TXT, <a href="http://en.wikipedia.org/wiki/SRV_record">SRV</a>, IPv6 support and overall fine-grained control over the zone&#8217;s content),</li>
<li>I want to manage the domains from different registrars in one place,</li>
<li>I don&#8217;t trust them; they&#8217;re in the business of making money from registering domains, not providing a reliable DNS service.</li>
</ol>
<p><a href="http://freedns.sgh.waw.pl/">FreeDNS</a> has been the perfect solution so far: operated by <a href="http://42.pl/">Piotr `Beeth&#8217; Kucharski</a>, whom I trust, and hosted on <a title="Warsaw School of Economics" href="http://www.sgh.waw.pl/index_en.html">SGH</a>&#8217;s servers, an entity I <em>have trusted</em>.</p>
<p>Recently, SGH has demanded a copy of the database, and when Piotr refused, they have blocked his access and took over the service, claiming rights to the data.  Trust FAIL.</p>
<p>If you&#8217;re keeping your domains there, it&#8217;s time to find an alternative.  Frankly, I don&#8217;t know of one at the moment.</p>
<p style="padding-left: 30px;"><em><strong>Update</strong></em>: <a href="https://freedns.42.pl/">FreeDNS::42</a>.</p>
<p>Source link (in Polish) with information about the issue: <a href="http://42.pl/freedns.html">http://42.pl/freedns.html</a>; the <a href="http://www.wykop.pl/link/343223/ci-sgh-wyrzuca-freedns/">comments on Wykop</a> are also useful.  The only thing I can infer from the discussion is that someone at <a href="http://www.panoramio.com/photo/1370534">SGH</a> is up to something evil.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2010/04/09/freedns-trouble-ahead/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>IRC: XChat geolocation plugin</title>
		<link>http://radek.cc/2009/09/06/irc-xchat-geolocation-plugin/</link>
		<comments>http://radek.cc/2009/09/06/irc-xchat-geolocation-plugin/#comments</comments>
		<pubDate>Sun, 06 Sep 2009 15:20:08 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[irc]]></category>
		<category><![CDATA[macros]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=172</guid>
		<description><![CDATA[A geolocation plugin for the XChat IRC client.]]></description>
			<content:encoded><![CDATA[<p>The <a href="http://www.postgresql.org/community/irc">#postgresql</a> IRC channel on <a href="http://freenode.net/">Freenode</a> can be an interesting source of new ideas and insight into how people are using my <a href="http://www.postgresql.org/">favourite RDBMS</a>; I find myself hanging around there quite frequently.  With ~400 people from all over the world on the channel, I&#8217;m sometimes curious which part of the globe the person I&#8217;m talking to is from.</p>
<p>Enter <a href="http://www.xchat.org/">XChat</a> and its <a href="http://www.perl.org/">Perl</a> scripting capability.</p>
<p>I wrote a simple plugin to look up the IP address in the <a href="http://www.maxmind.com/app/geolitecity">Maxmind</a> GeoIP lookup database, you can find it here: <a href="http://radek.cc/xchat-geo.pl">xchat-geo.pl</a>.  See &#8220;perldoc xchat-geo.pl&#8221; for the installation instructions.  Usage:</p>
<pre style="padding-left: 30px;">/geo some_nickname
[17:33] ==&gt; some_nickname | US San Francisco CA 37.7525 -122.3194  n=somenick@some.host</pre>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2009/09/06/irc-xchat-geolocation-plugin/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>~/.psqlrc tricks: missing and unused indexes</title>
		<link>http://radek.cc/2009/09/05/psqlrc-tricks-indexes/</link>
		<comments>http://radek.cc/2009/09/05/psqlrc-tricks-indexes/#comments</comments>
		<pubDate>Sat, 05 Sep 2009 15:16:57 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[macros]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[psql]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=156</guid>
		<description><![CDATA[psql macros for finding missing and unused indexes.]]></description>
			<content:encoded><![CDATA[<p>Two more <a href="http://radek.cc/tag/psql+macros/">~/.psqlrc tricks</a> with extra long lines:</p>
<pre style="padding-left: 30px; white-space: pre-wrap;"><span style="text-decoration: underline;">\set trashindexes</span> '( select s.schemaname as sch, s.relname as rel, s.indexrelname as idx, s.idx_scan as scans, pg_size_pretty(pg_relation_size(s.relid)) as ts, pg_size_pretty(pg_relation_size(s.indexrelid)) as "is" from pg_stat_user_indexes s join pg_index i on i.indexrelid=s.indexrelid left join pg_constraint c on i.indrelid=c.conrelid and array_to_string(i.indkey, '' '') = array_to_string(c.conkey, '' '') where i.indisunique is false and pg_relation_size(s.relid) &gt; 1000000 and s.idx_scan &lt; 100000 and c.confrelid is null order by s.idx_scan asc, pg_relation_size(s.relid) desc )'
<span style="text-decoration: underline;">\set missingindexes</span> '( select src_table, dst_table, fk_name, pg_size_pretty(s_size) as s_size, pg_size_pretty(d_size) as d_size, d from ( select distinct on (1,2,3,4,5) textin(regclassout(c.conrelid)) as src_table, textin(regclassout(c.confrelid)) as dst_table, c.conname as fk_name, pg_relation_size(c.conrelid) as s_size, pg_relation_size(c.confrelid) as d_size, array_upper(di.indkey::int[], 1) + 1 - array_upper(c.conkey::int[], 1) as d from pg_constraint c left join pg_index di on di.indrelid = c.conrelid and array_to_string(di.indkey, '' '') ~ (''^'' || array_to_string(c.conkey, '' '') || ''( |$)'') join pg_stat_user_tables st on st.relid = c.conrelid where c.contype = ''f'' order by 1,2,3,4,5,6 asc) mfk where mfk.d is distinct from 0 and mfk.s_size &gt; 1000000 order by mfk.s_size desc, mfk.d desc )'</pre>
<p>The first one displays <strong>unused indexes</strong>.  Why would you care?</p>
<ol>
<li>wasted RAM and disk space,</li>
<li>longer restore,</li>
<li>slower INSERT, UPDATE and DELETE statements.</li>
</ol>
<p>Running this is useless after a fresh restore (or after calling <code><a href="http://www.postgresql.org/docs/current/static/monitoring-stats.html">pg_stat_reset</a>(), </code>or &#8212; in case you&#8217;re running something ancient, like 8.2 or older, with <code><a href="http://www.postgresql.org/docs/8.2/static/runtime-config-statistics.html#GUC-STATS-RESET-ON-SERVER-START">stats_reset_on_server_start</a>=on</code> &#8212; after restarting the server).</p>
<p>It excludes unique indexes and the ones on columns being foreign keys (used when deleting / updating rows in the tables the FK points to).</p>
<p>Tables smaller than 1MB are excluded as well.</p>
<p>Example usage:</p>
<pre style="padding-left: 30px;">radek=# :trashindexes limit 5;
  sch   |  rel   |            idx            | scans |   ts    |   is
--------+--------+---------------------------+-------+---------+---------
 public | tracks | i_tracks_theme            |     0 | 15 MB   | 4664 kB
 public | tracks | i_tracks_album_theme      |     0 | 15 MB   | 9696 kB
 public | tracks | i_tracks_theme_album      |     0 | 15 MB   | 6616 kB
 public | albums | i_albums_theme            |     0 | 5296 kB | 1336 kB
 public | albums | i_albums_collection_theme |     0 | 5296 kB | 1664 kB
 public | albums | i_albums_theme_collection |     0 | 5296 kB | 1664 kB
(5 rows</pre>
<p>Columns: schema name, table name, index name, number of index scans, table size, index size.</p>
<p>The second one suggests <strong>missing indexes</strong>.</p>
<p>Imagine a large table (large enough that you wouldn&#8217;t like any <a href="http://www.postgresql.org/docs/current/static/using-explain.html">sequential scans</a> on it) with a <a href="http://www.postgresql.org/docs/current/static/tutorial-fk.html">foreign key</a> to another table.  Now, when you DELETE a referenced row from the second table, Postgres has to enforce the constraint somehow (raise an exception, delete the row from the first table or set the FK column(s) to <code>NULL</code>).  For that, the first table has to be scanned somehow, to find out if any action is needed.</p>
<p>It&#8217;s not worth it if the referencing table is small, or if it&#8217;s not-so-small but always in RAM, or if the rows in the second table are never updated or deleted.  The query omits small tables using an arbitrary number; tweak for your needs.</p>
<p>Example usage:</p>
<pre style="padding-left: 30px;">msawi11=# set search_path to "$user"; -- demonstrate lack of separate columns for schemas
SET
msawi11=# select * from :missingindexes x where src_table ~ 'promoc|usl';
    src_table    |     dst_table     |         fk_name         | s_size  |   d_size   | d
-----------------+-------------------+-------------------------+---------+------------+---
 public.promocje | public.operatorzy | promocje_fk_operator_id | 4736 kB | 8192 bytes |
 public.uslugi   | public.taryfy     | uslugi_fk_taryfa_id     | 1032 kB | 552 kB     | 2
(2 rows</pre>
<p>Last three columns: source table size, destination table size, distance.  If &#8220;distance&#8221; is not null, it means there already is an index starting with the FK columns; &#8220;distance&#8221; constains the number of extra columns.</p>
<p>In this example, &#8220;uslugi.taryfa_id&#8221; is a FK to the &#8220;taryfy&#8221; table.  There is an unique index <code>on uslugi (taryfa_id, foo, bar)</code>, so the distance = 2.</p>
<p>The textin(regclassout(oid)) calls are there for Pg 8.2 compatibility (lacks a regclass::text cast).  Indented query looks like this:</p>
<pre style="padding-left: 30px;">select src_table, dst_table, fk_name, pg_size_pretty(s_size) as s_size, pg_size_pretty(d_size) as d_size, d
    from (
        select
                distinct on (1,2,3,4,5)
                textin(regclassout(c.conrelid))  as src_table,
                textin(regclassout(c.confrelid)) as dst_table,
                c.conname                        as fk_name,
                pg_relation_size(c.conrelid)     as s_size,
                pg_relation_size(c.confrelid)    as d_size,
                array_upper(di.indkey::int[], 1) + 1 - array_upper(c.conkey::int[], 1) as d
        from pg_constraint c
        left join pg_index di on di.indrelid = c.conrelid
                and array_to_string(di.indkey, ' ') ~ ('^' || array_to_string(c.conkey, ' ') || '( |$)')
        join pg_stat_user_tables st on st.relid = c.conrelid
        where c.contype = 'f'
        order by 1,2,3,4,5,6 asc
    ) mfk
    where mfk.d is distinct from 0 and mfk.s_size &gt; 1000000
    order by mfk.s_size desc, mfk.d desc;</pre>
<p>Note: don&#8217;t blindly add indexes basing on the results of this query.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2009/09/05/psqlrc-tricks-indexes/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>PostgreSQL tuning: temp_buffers</title>
		<link>http://radek.cc/2009/09/02/postgresql-tuning-temp_buffers/</link>
		<comments>http://radek.cc/2009/09/02/postgresql-tuning-temp_buffers/#comments</comments>
		<pubDate>Wed, 02 Sep 2009 12:22:38 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[gotcha]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[postgresql]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=146</guid>
		<description><![CDATA[A client of mine wanted to significantly increase the number of users connecting to his database.  All of these are long-running sessions (the whole working day), running complex queries (lots of joins, also to set returning functions (ab)using temporary tables, business logic kept mostly on the DB side).
A new beefy machine has been bought, postgresql.conf [...]]]></description>
			<content:encoded><![CDATA[<p>A client of mine wanted to significantly increase the number of users connecting to his database.  All of these are long-running sessions (the whole working day), running complex queries (lots of joins, also to <a href="http://www.postgresql.org/docs/current/static/functions-srf.html">set returning</a> functions (ab)using temporary tables, business logic kept mostly on the DB side).</p>
<p>A new beefy machine has been bought, postgresql.conf got reviewed independently by several seasoned DBAs, some performance tests were run&#8230; and we figured it&#8217;s going to be OK.</p>
<p>On the big day, the database started swapping and grinded to a halt.  After a long night of adding hacks to the application and more RAM to the machine, we got it to +- working state, but the performance was far from acceptable.</p>
<p>Then, Szymon the Project Manager has asked the Right Question™: why are the idle backends not freeing the memory, actually?  This got me back to studying the config file and I noticed &#8220;<a href="http://www.postgresql.org/docs/current/static/runtime-config-resource.html#GUC-TEMP-BUFFERS">temp_buffers</a> = 8GB&#8221;.  We all missed it before &#8212; the default is also 8, but MB, not GB.  Also, I thought the allocated memory is being freed after the temporary table is dropped&#8230;</p>
<p>Wrong.  The temp_buffers memory is not being freed.  It&#8217;s being kept <span style="text-decoration: underline;">until the end of the session</span>.</p>
<p>So, when the users log in, they usually run a heavy &#8220;what&#8217;s going on today&#8221; query, which includes a function creating a large temporary table.  Each backend has been allocating ~1GB of RAM for it and keeping it through the day.</p>
<p>Epic fail, but with a happy ending &#8212; everything is humming nicely now. :-)</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2009/09/02/postgresql-tuning-temp_buffers/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>~/.psqlrc tricks: table sizes</title>
		<link>http://radek.cc/2009/08/15/psqlrc-tricks-table-sizes/</link>
		<comments>http://radek.cc/2009/08/15/psqlrc-tricks-table-sizes/#comments</comments>
		<pubDate>Sat, 15 Aug 2009 11:08:35 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[macros]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[psql]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=131</guid>
		<description><![CDATA[~/.psqlrc macros for listing table sizes in the current database.]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.postgresql.org/docs/current/static/app-psql.html">psql</a> from PostgreSQL 8.4.0 contains an useful addition: <code>\d+</code> shows table sizes.  That&#8217;s handy, but:</p>
<ul>
<li>doesn&#8217;t help much with 8.3 and 8.2 databases,</li>
<li>doesn&#8217;t let me run aggregates on these,</li>
<li>the view is cluttered by table comments,</li>
<li>can&#8217;t be sorted by size.</li>
</ul>
<p>So, my <code>~/.psqlrc</code> contains these two lines:</p>
<pre style="padding-left: 30px; white-space: pre-wrap">$ tail -n2 .psqlrc
<span style="text-decoration: underline;">\set rtsize</span> '(select table_schema, table_name, pg_relation_size( quote_ident( table_schema ) || \'.\' || quote_ident( table_name ) ) as size, pg_total_relation_size( quote_ident( table_schema ) || \'.\' || quote_ident( table_name ) ) as total_size  from information_schema.tables where table_type = \'BASE TABLE\' and table_schema not in (\'information_schema\', \'pg_catalog\') order by pg_relation_size( quote_ident( table_schema ) || \'.\' || quote_ident( table_name ) ) desc, table_schema, table_name)'
<span style="text-decoration: underline;">\set tsize</span> '(select table_schema, table_name, pg_size_pretty(size) as size, pg_size_pretty(total_size) as total_size from (:rtsize) x order by x.size desc, x.total_size desc, table_schema, table_name)'</pre>
<p>Both queries return the same data; &#8220;<code>:rtsize</code>&#8221; outputs raw numbers (mnemonic: raw table size), and &#8220;<code>:tsize</code>&#8221; formats them with <a href="http://www.postgresql.org/docs/current/static/functions-admin.html#FUNCTIONS-ADMIN-DBSIZE">pg_size_pretty()</a>.  Usage and the benefits will be best illustrated by examples; this one shows the 5 largest tables:</p>
<pre style="padding-left: 30px;"><a href="http://www.redmine.org/">redmine</a>=# :tsize limit 5;
 table_schema |      table_name       |  size  | total_size
--------------+-----------------------+--------+------------
 public       | journals              | 192 kB | 360 kB
 public       | issues                | 128 kB | 248 kB
 public       | wiki_content_versions | 48 kB  | 304 kB
 public       | journal_details       | 48 kB  | 152 kB
 public       | attachments           | 32 kB  | 88 kB
(5 rows)</pre>
<p>Sum of table sizes from the &#8220;log&#8221; schema, without pricing-related tables; note the &#8220;<code>x</code>&#8221; after &#8220;<code>:rtsize</code>&#8221; &#8212; since the macro is expanded to &#8220;<code>( select ... )</code>&#8220;, it has to be aliased:</p>
<pre style="padding-left: 30px;">msawi11=# select pg_size_pretty( sum(size)::int ), pg_size_pretty( sum(total_size)::int )
          from :rtsize x where table_schema='log' and table_name !~ 'ceny';
 pg_size_pretty | pg_size_pretty
----------------+----------------
 18 MB          | 32 MB
(1 row)</pre>
<p>Useful.</p>
<p>It&#8217;s a pity PostgreSQL doesn&#8217;t come with such <a href="http://pgfoundry.org/projects/newsysviews/">views</a> in the standard installation; <code>psql</code>&#8217;s code would have been much simpler, some incompatibilities of <code>\X</code> commands could have been avoided, and other client application developers wouldn&#8217;t have to copy/paste code from &#8220;<code>psql -E</code>&#8221; output&#8230;</p>
<p>Update: more <a href="http://radek.cc/tag/psql+macros/">~/.psqlrc tricks</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2009/08/15/psqlrc-tricks-table-sizes/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>PostgreSQL: selecting from views with DISTINCT ON (&#8230;)</title>
		<link>http://radek.cc/2009/08/03/pg-views-with-distinct-on/</link>
		<comments>http://radek.cc/2009/08/03/pg-views-with-distinct-on/#comments</comments>
		<pubDate>Mon, 03 Aug 2009 20:37:20 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[gotcha]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[postgresql]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=116</guid>
		<description><![CDATA[Solution for / description of a tricky PostgreSQL planner behaviour, involving DISTINCT ON and views.]]></description>
			<content:encoded><![CDATA[<p>Took me a half day to figure this out on a complex query, so I took the time to prepare a simple case for a demonstration.</p>
<p>First, see the documentation on <a href="http://www.postgresql.org/docs/current/static/sql-select.html#SQL-DISTINCT"><code>DISTINCT ON</code></a>; it&#8217;s a <a href="http://postgresql.org/">PostgreSQL</a>-only extension.</p>
<p>Consider a table structure: a <em>collection</em> has many <em>albums</em>, an album has many <em>tracks</em>.  Both an album and a track can have an optional <em>theme</em> attribute.  The task: find the list of tracks from a (single) given collection, and for each track, suggest one other album with the same theme from the same collection.</p>
<p>A full example with create table / create index statements is here: <a href="http://radek.cc/wp-content/uploads/2009/08/pg-view-distinct-on.sql">pg-view-distinct-on.sql</a>.  This example could be solved with a <code>GROUP BY</code> instead of a <code>DISTINCT ON ()</code>, but in my real query it wouldn&#8217;t work so great.</p>
<p>Let&#8217;s start with a simple view, giving us all the tracks from all collections:</p>
<pre style="padding-left: 30px;">create or replace view v_test as
    select a.collection_id, c.id as c_id, t.*
    from tracks      t
    join albums      a on a.id = t.album_id
    join collections c on c.id = a.collection_id
    left join albums r on
            r.id &lt;&gt; a.id and r.id &lt;&gt; t.album_id  -- we don't want the same album
        and r.collection_id = a.collection_id
    --  and r.collection_id = c.id               -- or would this be better...?
        and r.theme = t.theme;</pre>
<p>This is very nice, but duplicates the &#8220;track&#8221; rows, if more than one album with the same theme is found.  My approach looked like this:</p>
<pre style="padding-left: 30px;">create or replace view v_test_bad as
    select distinct on (id) *
    from v_test
    order by id;</pre>
<p>Now, &#8220;<code>select * from v_test_bad where collection_id = 42</code>&#8221; gives the correct results.  The problem is, it takes way too long &#8212; see the <a href="http://explain.depesz.com/s/s8">query plan</a>.  Indexes are not used at all.</p>
<p>Why, wtf?</p>
<p>The magic answer is: since we&#8217;re sorting the result (order by), then picking the <em>first row</em> from the <em>group</em> defined by the list of columns we&#8217;re <em>sorting with</em> (order by id, distinct on (id)), the planner doesn&#8217;t push the <code>WHERE</code> condition downwards, to the <code>v_test</code> view.  It doesn&#8217;t figure out, that pushing (using) it there won&#8217;t make any difference to the results.</p>
<p>Since there is no usable search condition in the <code>v_test</code> view, it has to do sequential scans, and then filter out the results&#8230; on bigger tables and longer sorting conditions that would take hours to weeks.</p>
<p>Solution:</p>
<pre style="padding-left: 30px;">create or replace view v_test_good as
    select distinct on (id, <strong>c_id, collection_id</strong>) *
    from v_test
    where collection_id = c_id
    order by id, <strong>c_id, collection_id</strong>;</pre>
<p>Adding columns to the &#8220;order by&#8221; and &#8220;distinct on&#8221; allows it to push the conditions on these columns &#8220;downwards&#8221;.  The &#8220;<code>collection_id = c_id</code>&#8221; part is a hint, which lets us avoid specifying both columns in the outer <code>WHERE</code> to get index scans on both [edit: now I'm not sure where I saw benefit in that].  The query time is now 4 miliseconds instead of 4 seconds; compare <a href="http://explain.depesz.com/s/UGE">the new plan</a> for &#8220;<code>select * from v_test_good where collection_id = 12</code>&#8220;.</p>
<p>Obvious, when you think about it.  Not so obvious with a complex query joining a dozen tables, though.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2009/08/03/pg-views-with-distinct-on/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Building SQL statements from PL/pgSQL</title>
		<link>http://radek.cc/2009/07/28/building-sql-statements-from-plpgsql/</link>
		<comments>http://radek.cc/2009/07/28/building-sql-statements-from-plpgsql/#comments</comments>
		<pubDate>Tue, 28 Jul 2009 09:22:36 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[plpgsql]]></category>
		<category><![CDATA[postgresql]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=86</guid>
		<description><![CDATA[PL/pgSQL functions for building INSERT, UPDATE and DELETE statements with hstore.]]></description>
			<content:encoded><![CDATA[<p>I need to build INSERT and UPDATE statements, then send them over <a href="http://www.postgresql.org/docs/current/static/dblink.html">dblink</a> to another database.  Seems like a fairly common task, but I couldn&#8217;t find anything ready-made anywhere&#8230; Below is a version using <span style="text-decoration: line-through;"><a href="http://www.postgresql.org/docs/current/static/hstore.html">hstore</a></span> hstore-new (you&#8217;ll <span style="text-decoration: line-through;">probably</span> need <a href="http://pgfoundry.org/projects/hstore-new/">hstore-new</a> with its neat new operators and the ability to cast RECORD variables to hstore to do anything useful this way).</p>
<p>Example calls:</p>
<pre style="padding-left: 30px;">radek=# select build_insert( 'foo', 'a=&gt;1, b=&gt;2009-01-02, c=&gt;"foo bar baz", d=&gt;NULL'::hstore );
                                    build_insert
-------------------------------------------------------------------------------------
 INSERT INTO foo ( a, b, c, d ) VALUES ( '1', '2009-01-02', 'foo bar baz', DEFAULT )
(1 row)

radek=# select build_update( 'foo', 'a=&gt;1, b=&gt;2009-01-02, c=&gt;null'::hstore, '{a,b}'::text[] );
                     build_update
------------------------------------------------------
 UPDATE foo SET c=NULL WHERE a='1' AND b='2009-01-02'
(1 row)

radek=# select build_delete( 'foo', 'a=&gt;1, b=&gt;2009-01-02, c=&gt;null'::hstore, '{c}'::text[] );
          build_delete
---------------------------------
 DELETE FROM foo WHERE c IS NULL
(1 row)</pre>
<p>You should quote the table / schema name (the first argument) with <a href="http://www.postgresql.org/docs/current/static/functions-string.html">quote_ident()</a>, like this:</p>
<pre style="padding-left: 30px;">quote_ident( 'public' ) || '.' || quote_ident( 'foo' )</pre>
<p>Tests and comments most welcome; here&#8217;s the code:</p>
<pre style="padding-left: 30px;">create or replace function build_insert( _table text, _record hstore ) returns text as $$
declare
	_sql	text;
	_values	text;
	_tmp	record;
begin
	for _tmp in ( select * from each( _record ) order by key ) loop
		_sql := coalesce( _sql || ', ', 'INSERT INTO ' || _table || ' ( ' )
			|| quote_ident( _tmp.key );
		_values := coalesce( _values || ', ', 'VALUES ( ' )
			|| coalesce( quote_literal( _tmp.value ), 'DEFAULT' );
	end loop;
	return _sql || ' ) ' || _values || ' )';
end;
$$ language plpgsql immutable strict;

comment on function build_insert( text, hstore ) is
  'build an INSERT statement from the hstore, the table name should be quoted';

create or replace function build_update( _table text, _record hstore, _pk text[] ) returns text as $$
declare
	_sql	text;
	_where	text;
	_tmp	record;
begin
	for _tmp in ( select * from each( _record - _pk ) order by key ) loop
		_sql := coalesce( _sql || ', ', 'UPDATE ' || _table || ' SET ' )
			|| quote_ident( _tmp.key ) || '='
			|| coalesce( quote_literal( _tmp.value ), 'NULL' );
	end loop;
	for _tmp in ( select * from each( _record =&gt; _pk ) order by key ) loop
		_where := coalesce( _where || ' AND ', '' )
			|| quote_ident( _tmp.key )
			|| coalesce( '=' || quote_literal( _tmp.value ), ' IS NULL' );
	end loop;
	return _sql || ' WHERE ' || _where;
end;
$$ language plpgsql immutable strict;

comment on function build_update( text, hstore, text[] ) is
  'build an UPDATE statement, pass the list of primary key columns in the last argument';

create or replace function build_delete( _table text, _record hstore, _pk text[] ) returns text as $$
declare
	_where	text;
	_tmp	record;
begin
	for _tmp in ( select * from each( _record =&gt; _pk ) order by key ) loop
		_where := coalesce( _where || ' AND ', '' )
			|| quote_ident( _tmp.key )
			|| coalesce( '=' || quote_literal( _tmp.value ), ' IS NULL' );
	end loop;
	return 'DELETE FROM ' || _table || ' WHERE ' || _where;
end;
$$ language plpgsql immutable strict;

comment on function build_delete( text, hstore, text[] ) is
  'builds a DELETE statement with WHERE columns specified as the last argument';</pre>
<p>Edit: noted the need for quote_ident() and hstore-new.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2009/07/28/building-sql-statements-from-plpgsql/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Gnome 2.26 &#8212; business as usual&#8230;</title>
		<link>http://radek.cc/2009/03/19/gnome-226-business-as-usual/</link>
		<comments>http://radek.cc/2009/03/19/gnome-226-business-as-usual/#comments</comments>
		<pubDate>Thu, 19 Mar 2009 16:11:04 +0000</pubDate>
		<dc:creator>radek</dc:creator>
				<category><![CDATA[Technical]]></category>
		<category><![CDATA[fail]]></category>
		<category><![CDATA[gnome]]></category>
		<category><![CDATA[pld]]></category>
		<category><![CDATA[whine]]></category>

		<guid isPermaLink="false">http://radek.cc/?p=73</guid>
		<description><![CDATA[Gnome 2.26: FAIL again.]]></description>
			<content:encoded><![CDATA[<p>I had a bad feeling about <a href="http://room-303.com/blog/2009/03/19/gnome-226/">this</a>&#8230; but have decided to proceed anyway.  My laptop just froze completely somewhere during the upgrade.</p>
<p>Result, after the reboot, finishing the upgrade and rpm -Va:</p>
<p style="padding-left: 30px;"><img class="size-full wp-image-74 alignnone" title="gnome 2.26" src="http://radek.cc/wp-content/uploads/2009/03/gnome-226-small.png" alt="gnome 2.26" width="500" height="313" /></p>
<p>That&#8217;s right, it&#8217;s just <a href="http://www.gkrellm.net/">gkrellm</a> and nothing else (not counting the obligatory useless, utterly annoying gnome-keyring-daemon, the volume control applet and pulseaudio).  Same with the &#8220;Gnome failsafe&#8221; session.</p>
<p>So, business <a href="http://www.mail-archive.com/pld-devel-en@lists.pld-linux.org/msg04629.html">as usual</a>; it&#8217;s not like I expected it to work <em>anyway</em>.</p>
<p>Ctrl+Alt+F1, &#8220;DISPLAY=:0 xterm&#8221; and I could run gnome-panel and metacity.  No luck with working Alt+Tab or the gnome-terminal:</p>
<pre style="padding-left: 30px;">$ gnome-terminal
Xlib:  extension "Generic Event Extension" missing on display ":0.0".
[...]
Failed to contact the GConf daemon; exiting.</pre>
<p>The stunning result:</p>
<div id="attachment_75" class="wp-caption alignnone" style="width: 510px"><img class="size-full wp-image-75" title="gnome-226-2" src="http://radek.cc/wp-content/uploads/2009/03/gnome-226-2.png" alt="gnome-226-2" width="500" height="313" /><p class="wp-caption-text">Incomplete window controls, missing icons.  Meh.</p></div>
<p>To waste time for debugging, or switching; that is the question&#8230;</p>
<p>Anyway, I&#8217;m looking forward to 2.28.  We had the locked keyboards already, now the broken desktop&#8230; screen blinking in rainbow colours and &#8220;cat /dev/urandom &gt; /dev/dsp&#8221; is likely to be next, I suppose.</p>
]]></content:encoded>
			<wfw:commentRss>http://radek.cc/2009/03/19/gnome-226-business-as-usual/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
