<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7997313029981170997</id><updated>2012-02-03T15:25:56.611-05:00</updated><category term='mobile'/><category term='Fedora'/><category term='postgres'/><category term='tools'/><category term='sysadmin'/><category term='pentaho'/><category term='community'/><category term='hosting'/><category term='analytics'/><category term='SELinux'/><category term='redhat'/><category term='audio'/><category term='travel'/><category term='css'/><category term='git'/><category term='tips'/><category term='nginx'/><category term='Spree'/><category term='cakephp'/><category term='email'/><category term='nosql'/><category term='social-networking'/><category term='unicode'/><category term='performance'/><category term='piggybak'/><category term='eye-candy'/><category term='reporting'/><category term='thrift'/><category term='facebook'/><category term='visualization'/><category term='openbsd'/><category term='mysql'/><category term='scalability'/><category term='riak'/><category term='security'/><category term='CentOS'/><category term='graphics'/><category term='cucumber'/><category term='rvm'/><category term='cloud'/><category term='django'/><category term='remote-work'/><category term='networking'/><category term='wordpress'/><category term='perlbrew'/><category term='optimization'/><category term='book review'/><category term='dropbox'/><category term='sinatra'/><category term='design'/><category term='mp3'/><category term='ruby-and-ruby-on-rails'/><category term='testing'/><category term='open-source'/><category term='json'/><category term='ruby'/><category term='yui'/><category term='virtualization'/><category term='COTS'/><category term='Camps'/><category term='javascript'/><category term='workflow'/><category term='sponsorship'/><category term='perl'/><category term='messaging'/><category term='environment'/><category term='gnu'/><category term='lua'/><category term='rpm'/><category term='browsers'/><category term='configuration-management'/><category term='python'/><category term='ecommerce'/><category term='Conference'/><category term='voldemort'/><category term='image'/><category term='jasper'/><category term='clients'/><category term='dbdpg'/><category term='Android'/><category term='database'/><category term='version-control'/><category term='linux'/><category term='USPS'/><category term='data-warehouse'/><category term='openafs'/><category term='cassandra'/><category term='mondaylinks'/><category term='php'/><category term='Debian'/><category term='monitoring'/><category term='liquid-galaxy'/><category term='audit'/><category term='Bucardo'/><category term='Java'/><category term='mongodb'/><category term='API'/><category term='seo'/><category term='SeniorNet'/><category term='company'/><category term='jquery'/><category term='jobs'/><category term='sql'/><category term='search'/><category term='Ubuntu'/><category term='Interchange'/><category term='caching'/><category term='ipv6'/><title type='text'>End Point Blog</title><subtitle type='html'>Ongoing observations by End Point people.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.endpoint.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/-/postgres'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/search/label/postgres'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/-/postgres/-/postgres?start-index=26&amp;max-results=25'/><author><name>Jon Jensen</name><uri>http://www.blogger.com/profile/18273388885281263476</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp3.blogger.com/_rFXHDrokbpE/SJHpPosaIQI/AAAAAAAAAAM/GnqeZuLItOA/S220/jon1.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>147</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-7734681975994677033</id><published>2012-01-30T22:55:00.001-05:00</published><updated>2012-01-30T22:55:59.092-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='security'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><category scheme='http://www.blogger.com/atom/ns#' term='audit'/><title type='text'>Protecting and auditing your secure PostgreSQL data</title><content type='html'>&lt;a href="http://4.bp.blogspot.com/-1R42QgfZvFs/TycvCoG8BtI/AAAAAAAAAPU/xJIL640LkBk/s1600/EIB.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 310px; height: 320px;" src="http://4.bp.blogspot.com/-1R42QgfZvFs/TycvCoG8BtI/AAAAAAAAAPU/xJIL640LkBk/s320/EIB.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5703579175260784338" /&gt;&lt;/a&gt;&lt;style&gt;span{font-family:sans-serif;}span.p{color:red;}span.d{color:gray;}span.c{color:green;font-family:monospace;}span.o{font-family:monospace;color:#0022cc;}span.oo{font-family:monospace;font-weight:bolder;}&lt;/style&gt;&lt;p&gt;PostgreSQL functions can be written in &lt;a href="http://www.postgresql.org/docs/9.1/static/xplang.html"&gt;many languages&lt;/a&gt;. These languages fall into two categories, 'trusted' and 'untrusted'. Trusted languages cannot do things "outside of the database", such as writing to local files, opening sockets, sending email, connecting to other systems, etc. Two such languages are &lt;a href="http://www.postgresql.org/docs/9.1/static/plpgsql.html"&gt;PL/pgSQL&lt;/a&gt; and and &lt;a href="http://www.postgresql.org/docs/9.1/static/plperl.html"&gt;PL/Perl&lt;/a&gt;. For "untrusted" languages, such as PL/PerlU, all bets are off, and they have no limitations placed on what they can do. Untrusted languages can be very powerful, and sometimes dangerous.&lt;/p&gt;&lt;p&gt;One of the reasons untrusted languages can be considered dangerous is that they can cause side effects outside of the normal transactional flow that cannot be rolled back. If your function writes to local disk, and the transaction then rolls back, the changes on disk are still there. Working around this is extremely difficult, as there is no way to detect when a transaction has rolled back at the level where you could, for example, undo your local disk changes.&lt;/p&gt;&lt;p&gt;However, there are times when this effect can be very useful. For example, in a &lt;a href="http://postgresql.1045698.n5.nabble.com/Logging-access-to-data-in-database-table-td5430079.html"&gt; recent thread&lt;/a&gt; on the PostgreSQL "general" mailing list (aka pgsql-general), somebody asked for a way to audit SELECT queries into a logging table that would survive someone doing a ROLLBACK. In other words, if you had a function named weapon_details() and wanted to have that function log all requests to it by inserting to a table, a user could simply run the query, read the data, and then rollback to thwart the auditing:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
BEGIN;

SELECT weapon_details('BFG 9000'); -- also inserts to an audit table

ROLLBACK;                          -- inserts to the audit table are now gone!
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Certainly there are other ways to track who is using this query, the most obvious being by enabling full Postgres logging (by setting log_statement = 'all' in your postgresql.conf file.) However, extracting that information from logs is no fun, so let's find a way to make that INSERT stick, even if the surrounding function was rolled back.&lt;/p&gt;&lt;p&gt;Stepping back for one second, we can see there are actually two problems here: restricting access to the data, and logging that access somewhere. The ultimate access restriction is to simply force everyone to go through your custom interface. However, in this example, we will assume that someone has &lt;a href="http://www.postgresql.org/docs/9.1/static/app-psql.html"&gt;psql&lt;/a&gt; access and needs to be able to run ad hoc SQL queries, as well as be able to BEGIN, ROLLBACK, COMMIT, etc.&lt;/p&gt;&lt;p&gt;Let's assume we have a table with some Very Important Data inside of it. Further, let's establish that regular users can only see some of that data, and that we need to know who asked for what data, and when. For this example, we will create a normal user named Alice:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE USER alice;
&lt;span class="d"&gt;CREATE ROLE&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;We need a way to tell which rows are suitable for people like Alice to view. We will set up a quick classification scheme using the nifty &lt;a href="http://www.postgresql.org/docs/9.1/static/datatype-enum.html"&gt;ENUM feature&lt;/a&gt; of PostgreSQL:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE TYPE classification AS ENUM (
 'unclassified',
 'restricted',
 'confidential',
 'secret',
 'top secret'
);
&lt;span class="d"&gt;CREATE TYPE&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Next, as a superuser, we create the table containing sensitive information, and populate it:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE TABLE weapon (
  id              SERIAL          PRIMARY KEY,
  name            TEXT            NOT NULL,
  cost            TEXT            NOT NULL,
  security_level  CLASSIFICATION  NOT NULL,
  description     TEXT            NOT NULL DEFAULT 'a fine weapon'
);
&lt;span class="d"&gt;NOTICE:  CREATE TABLE will create implicit sequence "weapon_id_seq" for serial column "weapon.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "weapon_pkey" for table "weapon"
CREATE TABLE&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;INSERT INTO weapon (name,cost,security_level) VALUES
 ('Crowbar',  10,  'unclassified'),
 ('M9',  200,  'restricted'),
 ('M16A2',  300,  'restricted'),
 ('M4A1',  400,  'restricted'),
 ('FGM-148 Javelin',  700,  'confidential'),
 ('Pulse Rifle',  50000,  'secret'),
 ('Zero Point Energy Field Manipulator',  'unknown',  'top secret');
&lt;span class="d"&gt;INSERT 0 7&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;We don't want anyone but ourselves to be able to access this table, so for safety, we make some explicit revocations. We'll examine the permissions before and after we do this:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;\dp weapon
&lt;span class="c"&gt;                           Access privileges
 Schema |  Name  | Type  | Access privileges | Column access privileges 
--------+--------+-------+-------------------+--------------------------
 public | weapon | table |                   | &lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;REVOKE ALL ON TABLE weapon FROM public;
&lt;span class="d"&gt;REVOKE&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;\dp weapon
&lt;span class="c"&gt;
                               Access privileges
 Schema |  Name  | Type  |     Access privileges     | Column access privileges 
--------+--------+-------+---------------------------+--------------------------
 public | weapon | table | postgres=arwdDxt/postgres | &lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;As you can see, what the REVOKE really does is remove the implicit "no permission" and grant explicit permissions to only the postgres user to view or modify the table. Let's confirm that Alice cannot do anything with that table:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;\c postgres alice
&lt;span class="c"&gt;You are now connected to database "postgres" as user "alice".&lt;/span&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;postgres=&gt; SELECT * FROM weapon;
&lt;span class="c"&gt;ERROR:  permission denied for relation weapon&lt;/span&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;postgres=&gt; UPDATE weapon SET id = id;
&lt;span class="c"&gt;ERROR:  permission denied for relation weapon&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Alice does need to have access to parts of this table, so we will create a "wrapper function" that will query the table for us and return some results. By declaring this function as SECURITY DEFINER, it will run as if the person who created the function invoked it  - in this case, the postgres user. For this example, we'll be letting Alice see the "cost and description" of exactly one item at a time. Further, we are not going to let her (or anyone else using this function) view certain items. Only those items classified as "confidential" or lower can be viewed (i.e. "confidential", "restricted", or "unclassified"). Here's the first version of our function:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE LANGUAGE plperlu;
&lt;span class="d"&gt;CREATE LANGUAGE&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE OR REPLACE FUNCTION weapon_details(TEXT)
RETURNS TABLE (name TEXT, cost TEXT, description TEXT)
LANGUAGE plperlu
SECURITY DEFINER
AS $bc$

use strict;
use warnings;

## The item they are looking for
my $name = shift;
## We will be nice and ignore the case and any whitespace
$name =~ s{^\s*(\S+)\s*$}{lc $1}e;

## What is the maximum security_level that people who are 
## calling this function can view?
my $seclevel = 'confidential';

## Query the table and pull back the matching row
## We need to differentiate between "not found" and "not allowed",
## by comparing a passed-in level to the security_level for that row.
my $SQL = q{
SELECT name,cost,description,
  CASE WHEN security_level &lt;= $1 THEN 1 ELSE 0 END AS allowed
FROM weapon
WHERE LOWER(name) = $2};

## Run the query, pull back the first row, as well as the allowed column value
my $sth = spi_prepare($SQL, 'CLASSIFICATION', 'TEXT');
my $rv = spi_exec_prepared($sth, $seclevel, $name);
my $row = $rv-&gt;{rows}[0];
my $allowed = delete $row-&gt;{allowed};

## Did we find anything? If not, simply return undef
if (! $rv-&gt;{processed}) {
   return undef;
}

## Throw an exception if we are not allowed to view this row
if (! $allowed) {
   die qq{Sorry, you are not allowed to view information on that weapon!\n};
}

## Return the requested data
return_next($row);

$bc$;
&lt;span class="d"&gt;CREATE FUNCTION&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The above should be fairly self-explanatory. We are using PL/Perl's &lt;a href="http://www.postgresql.org/docs/9.1/static/plperl-builtins.html"&gt;built-in database access functions&lt;/a&gt;, such as spi_prepare, to do the actual querying. Let's confirm that this works as it should for Alice:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;\c postgres alice
&lt;span class="c"&gt;You are now connected to database "postgres" as user "alice".&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;SELECT * FROM weapon_details('crowbar');
&lt;span class="c"&gt;  name   | cost |  description  
---------+------+---------------
 Crowbar | 10   | a fine weapon&lt;/span&gt;
&lt;span class="d"&gt;(1 row)&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;SELECT * FROM weapon_details('anvil');
&lt;span class="c"&gt; name | cost | description 
------+------+-------------&lt;/span&gt;
&lt;span class="d"&gt;(0 rows)&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;SELECT * FROM weapon_details('pulse rifle');
&lt;span class="c"&gt;ERROR:  Sorry, you are not allowed to view information on that weapon!
CONTEXT:  PL/Perl function "weapon_details"&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Now that we have solved the restricted access problem, let's move on the auditing. We will create a simple table to hold information about who accessed what and when:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE TABLE data_audit (
  tablename TEXT         NOT NULL,
  arguments TEXT             NULL,
  results   INTEGER          NULL,
  status    TEXT         NOT NULL  DEFAULT 'normal',
  username  TEXT         NOT NULL  DEFAULT session_user,
  txntime   TIMESTAMPTZ  NOT NULL  DEFAULT now(),
  realtime  TIMESTAMPTZ  NOT NULL  DEFAULT clock_timestamp()
);
&lt;span class="d"&gt;CREATE TABLE&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The 'tablename' column simply records which table they are getting data from. The 'arguments' is a free-form field describing what they were looking for. The 'results' column shows how many matching rows were found. The 'status' column will be used primarily to log unusual requests, such as the case where Alice looks for a forbidden item. The 'username' column records the name of the user doing the searching. Because we are using functions with SECURITY DEFINER set, this needs to be session_user, not current_user, as the latter will switch to 'postgres' within the function, and we want to log the real caller (e.g. 'alice'). The final two columns tell us then the current transaction started, and the exact time when an entry was made inside of this table. As a first attempt, we'll have our function do some simple inserts to this new data_audit table:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE OR REPLACE FUNCTION weapon_details(TEXT)
RETURNS TABLE (name TEXT, cost TEXT, description TEXT)
LANGUAGE plperlu
SECURITY DEFINER
AS $bc$

use strict;
use warnings;

## The item they are looking for
my $name = shift;
## We will be nice and ignore the case and any whitespace
$name =~ s{^\s*(\S+)\s*$}{lc $1}e;

## What is the maximum security_level that people who are 
## calling this function can view?
my $seclevel = 'confidential';

## Query the table and pull back the matching row
## We need to differentiate between "not found" and "not allowed",
## by comparing a passed-in level to the security_level for that row.
my $SQL = q{
SELECT name,cost,description,
  CASE WHEN security_level &lt;= $1 THEN 1 ELSE 0 END AS allowed
FROM weapon
WHERE LOWER(name) = $2};

## Run the query, pull back the first row, as well as the allowed column value
my $sth = spi_prepare($SQL, 'CLASSIFICATION', 'TEXT');
my $rv = spi_exec_prepared($sth, $seclevel, $name);
my $row = $rv-&gt;{rows}[0];
my $allowed = delete $row-&gt;{allowed};

&lt;span class="oo"&gt;
## Log this request
$SQL = 'INSERT INTO data_audit(tablename,arguments,results,status)
  VALUES ($1,$2,$3,$4)';
my $status = $rv-&gt;{rows}[0] ? $allowed ? 'normal' : 'forbidden' : 'na';
$sth = spi_prepare($SQL, 'TEXT', 'TEXT', 'INTEGER', 'TEXT');
spi_exec_prepared($sth, 'weapon', $name, $rv-&gt;{processed}, $status);
&lt;/span&gt;

## Did we find anything? If not, simply return undef
if (! $rv-&gt;{processed}) {
   return undef;
}

## Throw an exception if we are not allowed to view this row
if (! $allowed) {
   die qq{Sorry, you are not allowed to view information on that weapon!\n};
}

## Return the requested data
return_next($row);

$bc$;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;However, this fails the case pointed out in the original poster's email about viewing the data within a transaction that is then rolled back. It also fails to work at all when a forbidden item is requested, as that insert is rolled back by the die() call:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;\c postgres alice
&lt;span class="c"&gt;You are now connected to database "postgres" as user "alice".&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;SELECT * FROM weapon_details('crowbar');
&lt;span class="c"&gt;  name   | cost |  description  
---------+------+---------------
 Crowbar | 10   | a fine weapon
(1 row)&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;SELECT * FROM weapon_details('pulse rifle');
&lt;span class="c"&gt;ERROR:  Sorry, you are not allowed to view information on that weapon!
CONTEXT:  PL/Perl function "weapon_details"&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;BEGIN;
&lt;span class="d"&gt;BEGIN&lt;/span&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;SELECT * FROM weapon_details('m9');
&lt;span class="c"&gt; name | cost |  description  
------+------+---------------
 M9   | 200  | a fine weapon&lt;/span&gt;
&lt;span class="d"&gt;(1 row)&lt;/span&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;ROLLBACK;
&lt;span class="d"&gt;ROLLBACK&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;\c postgres postgres
&lt;span class="c"&gt;You are now connected to database "postgres" as user "postgres".&lt;/span&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;SELECT * FROM data_audit \x \g
&lt;span class="d"&gt;Expanded display is on.&lt;/span&gt;
&lt;span class="c"&gt;-[ RECORD 1 ]----------------------------
tablename | weapon
arguments | crowbar
results   | 1
status    | normal
username  | alice
txntime   | 2012-01-30 17:37:39.497491-05
realtime  | 2012-01-30 17:37:39.545891-05&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;How do we get around this? We need a way to commit something that will survive the surrounding transaction's rollback. The closest thing Postgres has to such a thing at the moment is to connect back to the database with a new and entirely separate connection. Two such popular ways to do so are with &lt;a href="http://www.postgresql.org/docs/9.1/static/dblink.html"&gt;the dblink program&lt;/a&gt; and &lt;a href="http://www.postgresql.org/docs/9.1/static/plperl.html"&gt;the PL/PerlU language&lt;/a&gt;. Obviously, we are going to focus on the latter, but all of this could be done with dblink as well. Here are the additional steps to connect back to the database, do the insert, and then leave again:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE OR REPLACE FUNCTION weapon_details(TEXT)
RETURNS TABLE (name TEXT, cost TEXT, description TEXT)
LANGUAGE plperlu
SECURITY DEFINER
&lt;span class="oo"&gt;VOLATILE&lt;/span&gt;
AS $bc$

use strict;
use warnings;
&lt;span class="oo"&gt;use DBI;&lt;/span&gt;

## The item they are looking for
my $name = shift;
## We will be nice and ignore the case and any whitespace
$name =~ s{^\s*(\S+)\s*$}{lc $1}e;

## What is the maximum security_level that people who are 
## calling this function can view?
my $seclevel = 'confidential';

## Query the table and pull back the matching row
## We need to differentiate between "not found" and "not allowed",
## by comparing a passed-in level to the security_level for that row.
my $SQL = q{
SELECT name,cost,description,
  CASE WHEN security_level &lt;= $1 THEN 1 ELSE 0 END AS allowed
FROM weapon
WHERE LOWER(name) = $2};

## Run the query, pull back the first row, as well as the allowed column value
my $sth = spi_prepare($SQL, 'CLASSIFICATION', 'TEXT');
my $rv = spi_exec_prepared($sth, $seclevel, $name);
my $row = $rv-&gt;{rows}[0];
my $allowed = defined $row ? delete $row-&gt;{allowed} : 1;

## Log this request
&lt;span class="oo"&gt;$SQL = 'INSERT INTO data_audit(username,tablename,arguments,results,status)
  VALUES (?,?,?,?,?)';
my $status = $rv-&gt;{rows}[0] ? $allowed ? 'normal' : 'forbidden' : 'na';
my $dbh = DBI-&gt;connect('dbi:Pg:service=auditor', '', '',
  {AutoCommit=&gt;0, RaiseError=&gt;1, PrintError=&gt;0});
$sth = $dbh-&gt;prepare($SQL);
my $user = spi_exec_query('SELECT session_user')-&gt;{rows}[0]{session_user};
$sth-&gt;execute($user, 'weapon', $name, $rv-&gt;{processed}, $status);
$dbh-&gt;commit();&lt;/span&gt;

## Did we find anything? If not, simply return undef
if (! $rv-&gt;{processed}) {
   return undef;
}

## Throw an exception if we are not allowed to view this row
if (! $allowed) {
   die qq{Sorry, you are not allowed to view information on that weapon!\n};
}

## Return the requested data
return_next($row);

$bc$;
&lt;span class="d"&gt;CREATE FUNCTION&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Note that because we are making external changes, we marked the function as VOLATILE, which ensures that it will always be run every time it is called, and not cached in any form. We are also using &lt;a href="http://www.postgresql.org/docs/9.1/static/libpq-pgservice.html"&gt;a Postgres service file&lt;/a&gt; with the 'db:Pg:service=auditor'. This means that the connection information (username, password, database) is contained in an external file. This is not only tidier than hard-coding those values into this function, but safer as well, as the function itself can be viewed by Alice. Finally, note that we are passing the 'username' directly into the function this time, as we have a brand new connection which is no longer linked to the 'alice' user, so we have to derive it ourselves from "SELECT session_user" and then pass it along.&lt;/p&gt;&lt;p&gt;Once this new function is in place, and we re-run the same queries as we did before, we see three entries in our audit table:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;\c postgres postgres
&lt;span class="c"&gt;You are now connected to database "postgres" as user "postgres".
&lt;span class="d"&gt;Expanded display is on.&lt;/span&gt;
-[ RECORD 1 ]----------------------------
tablename | weapon
arguments | crowbar
results   | 1
status    | normal
username  | alice
txntime   | 2012-01-30 17:56:01.544557-05
realtime  | 2012-01-30 17:56:01.54569-05
-[ RECORD 2 ]----------------------------
tablename | weapon
arguments | pulse rifle
results   | 1
status    | forbidden
username  | alice
txntime   | 2012-01-30 17:56:01.559532-05
realtime  | 2012-01-30 17:56:01.561225-05
-[ RECORD 3 ]----------------------------
tablename | weapon
arguments | m9
results   | 1
status    | normal
username  | alice
txntime   | 2012-01-30 17:56:01.573335-05
realtime  | 2012-01-30 17:56:01.574989-05&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;So that's the basic premise of how to solve the auditing problem. For an actual production script, you would probably want to cache the database connection by sticking things inside of the special &lt;a href="http://www.postgresql.org/docs/9.1/static/plperl-global.html"&gt;%_SHARED hash available to PL/Perl and Pl/PerlU&lt;/a&gt;. Note that each user gets their own version of that hash, so Alice will not be able to create a function and have access to the same %_SHARED hash that the postgres user has access to. It's probably a good idea to simply not let users like Alice use the language at all. Indeed, that's the default when we do the CREATE LANGUAGE call as above:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt; \c postgres alice
&lt;span class="c"&gt;You are now connected to database "postgres" as user "alice".&lt;/span&gt;

&lt;span class="p"&gt;postgres=&gt; &lt;/span&gt;CREATE FUNCTION showplatform()
RETURNS TEXT
LANGUAGE plperlu
AS $bc$
  return $^O;
$bc$;
&lt;span class="c"&gt;ERROR:  permission denied for language plperlu&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Further refinements to the actual script might include refactoring the logging bits to a separate function, writing some of the auditing data to a file on the local disk, recording the actual results returned to the user, and sending the data to another Postgres server entirely. For that matter, as we are using DBI, you could send it to other place entirely - such as a MySQL, Oracle, or DB2 database!&lt;/p&gt;&lt;p&gt;Another place for improvement would be associating each user with a security_level classification, such that any user could run the function and only see things at or below their level, rather than hard-coding the level as "confidential" as we have done here. Another nice refinement might be to always return undef (no matches) for items marked "top secret", to prevent the very existence of a top secret weapon from being deduced. :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-7734681975994677033?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/7734681975994677033/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=7734681975994677033' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/7734681975994677033'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/7734681975994677033'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2012/01/protecting-auditing-postgresql-data.html' title='Protecting and auditing your secure PostgreSQL data'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-1R42QgfZvFs/TycvCoG8BtI/AAAAAAAAAPU/xJIL640LkBk/s72-c/EIB.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-7157028004985963344</id><published>2012-01-05T13:28:00.006-05:00</published><updated>2012-01-05T14:10:55.506-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open-source'/><category scheme='http://www.blogger.com/atom/ns#' term='social-networking'/><category scheme='http://www.blogger.com/atom/ns#' term='ecommerce'/><category scheme='http://www.blogger.com/atom/ns#' term='hosting'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='Interchange'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Some great press for College District</title><content type='html'>&lt;p&gt;College District has been getting some positive press lately, the most recent being a &lt;a href="http://www.forbes.com/sites/chrissmith/2011/12/19/college-footballs-biggest-entrepreneur/" target="_blank"&gt;Forbes article&lt;/a&gt; which talks about the success they have been seeing in the last few years.&lt;/p&gt;

&lt;p&gt;College District is a company that sells collegiate merchandise to fans.  They got their start focusing on the LSU Tigers at &lt;a href="http://www.tigerdistrict.com" target="_blank"&gt;TigerDistrict.com&lt;/a&gt; and have branched out to teams such as the &lt;a href="http://www.duckdistrict.com" target="_blank"&gt;Oregon Ducks&lt;/a&gt; and &lt;a href="http://www.rolltidedistrict.com" target="_blank"&gt;Alabama Roll Tide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We've been working with Jared Loftus @ College District for more then four and a half years.  College District is running on a heavily modified Interchange system with some cool Postgres tricks.  The system can support a nearly unlimited number of sites, running on 2 catalogs (1 for the admin, 1 for the front end) and 1 database.  The key to the system is different schemas, fronted by views, that hide and expose records based on the database user that is connected.  The great thing about this system is that Jared can choose to launch a new store within a day and be ready for sales, something he has taken advantage of in the past when a team is on fire and he sees an opportunity he can't pass up.&lt;/p&gt;

&lt;p&gt;We are currently preparing for a re-launch of the College District site that will focus on crowd-sourced designs.  Artists and fans will submit their designs, have them voted on, some will be chosen to be sold and the folks that have their designs chosen will get paid for their efforts.  The goal here is to grow a community that guides what College District and the individual school sites ultimately sell.&lt;/p&gt;

&lt;p&gt;With College District's quick growth we've also been helping them improve their order fulfillment process.  This includes streamlining how orders are picked, packed and shipped.  The introduction of bar code scanners will help with the accuracy and speed of the process.&lt;/p&gt;

&lt;p&gt;We get a kick out of seeing our clients succeed, especially those that come to us with a clear vision and a good attitude, and then put the hard work in to make it happen.  It's an exciting year ahead for College District and we'll be right there supporting them on the journey.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-7157028004985963344?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/7157028004985963344/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=7157028004985963344' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/7157028004985963344'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/7157028004985963344'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2012/01/some-great-press-for-college-district.html' title='Some great press for College District'/><author><name>Ron Phipps</name><uri>http://www.blogger.com/profile/02106747784162431265</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='23' src='http://bp1.blogger.com/_qTrL2g2nY1M/SHFY1QqZ-YI/AAAAAAAAAAM/T6MCGHloJXU/S220/img_ron_240.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-5805577856505668568</id><published>2011-12-17T10:35:00.004-05:00</published><updated>2011-12-17T11:58:30.440-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tools'/><category scheme='http://www.blogger.com/atom/ns#' term='unicode'/><category scheme='http://www.blogger.com/atom/ns#' term='tips'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>Sanitizing supposed UTF-8 data</title><content type='html'>&lt;p&gt;As time passes, it's clear that Unicode has won the character set encoding wars, and UTF-8 is by far the most popular encoding, and the expected default. In a few more years we'll probably find discussion of different character set encodings to be arcane, relegated to "data historians" and people working with legacy systems.&lt;/p&gt;

&lt;p&gt;But we're not there yet! There's still lots of migration to do before we can forget about everything that's not UTF-8.&lt;/p&gt;

&lt;p&gt;Last week I again found myself converting data. This time I was taking data from a PostgreSQL database with no specified encoding (so-called "SQL_ASCII", really just raw bytes), and sending it via JSON to a remote web service. JSON uses UTF-8 by default, and that's what I needed here. Most of the source data was in either UTF-8, ISO Latin-1, or Windows-1252, but some was in non-Unicode Chinese or Japanese encodings, and some was just plain mangled.&lt;/p&gt;

&lt;p&gt;At this point I need to remind you about one of the most unusual aspects of UTF-8: It has limited valid forms. Legacy encodings typically used all or most of the 255 code points in their 8-byte space (leaving point 0 for traditional ASCII NUL). While UTF-8 is compatible with 7-bit ASCII, it does not allow any possible 8-bit byte in any position. See &lt;a href="http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences"&gt;the Wikipedia summary of invalid byte sequences&lt;/a&gt; to know what can be considered invalid.&lt;/p&gt;

&lt;p&gt;We had no need to try to fix the truly broken data, but we wanted to convert everything possible to UTF-8 and at the very least guarantee no invalid UTF-8 strings appeared in what we sent.&lt;/p&gt;

&lt;p&gt;I previously wrote about &lt;a href="http://blog.endpoint.com/2010/03/postgresql-utf-8-conversion.html"&gt;converting a PostgreSQL database dump to UTF-8&lt;/a&gt;, and used the Perl CPAN module &lt;a href="http://search.cpan.org/perldoc?IsUTF8"&gt;IsUTF8&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I was going to use that again, but looked around and found an even better module, exactly targeting this use case: &lt;a href="http://search.cpan.org/dist/Encoding-FixLatin/"&gt;Encoding::FixLatin&lt;/a&gt;, by Grant McLean. Its documentation says it "takes mixed encoding input and produces UTF-8 output" and that's exactly what it does, focusing on input with mixed UTF-8, Latin-1, and Windows-1252.&lt;/p&gt;

&lt;p&gt;It worked as advertised, very well. We would need to use a different module to convert some other legacy encodings, but in this case this was good enough and got the vast majority of the data right.&lt;/p&gt;

&lt;p&gt;There's even a standalone &lt;a href="http://search.cpan.org/dist/Encoding-FixLatin/script/fix_latin"&gt;fix_latin&lt;/a&gt; program designed specifically for processing Postgres pg_dump output from legacy encodings, with some nice examples of how to use it.&lt;/p&gt;

&lt;p&gt;One gotcha is similar to a catch that David Christensen reported with the Encode module in a &lt;a href="http://blog.endpoint.com/2010/12/character-encoding-in-perl-decodeutf8.html"&gt;blog post here about a year ago&lt;/a&gt;: If the Perl string already has the UTF-8 flag set, Encoding::FixLatin immediately returns it, rather than trying to process it. So it's important that the incoming data be a pure byte stream, or that you otherwise turn off the UTF-8 flag, if you expect it to change anything.&lt;/p&gt;

&lt;p&gt;Along the way I found some other CPAN modules that look useful for cases where I need more manual control than Encoding::FixLatin gives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/perldoc?Search::Tools::UTF8"&gt;Search::Tools::UTF8&lt;/a&gt; - test for and/or fix bad ASCII, Latin-1, Windows-1252, and UTF-8 strings&lt;/li&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/perldoc?Encode::Detect"&gt;Encode::Detect&lt;/a&gt; - use Mozilla's universal charset detector and convert to UTF-8&lt;/li&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/perldoc?Unicode::Tussle"&gt;Unicode::Tussle&lt;/a&gt; - ridiculously comprehensive set of Unicode tools that has to be seen to be believed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once again Perl's thriving open source/free software community made my day!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-5805577856505668568?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/5805577856505668568/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=5805577856505668568' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5805577856505668568'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5805577856505668568'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/12/sanitizing-supposed-utf-8-data.html' title='Sanitizing supposed UTF-8 data'/><author><name>Jon Jensen</name><uri>http://www.blogger.com/profile/18273388885281263476</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp3.blogger.com/_rFXHDrokbpE/SJHpPosaIQI/AAAAAAAAAAM/GnqeZuLItOA/S220/jon1.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-6386085831787430972</id><published>2011-11-10T13:20:00.002-05:00</published><updated>2011-11-10T15:46:20.965-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='monitoring'/><category scheme='http://www.blogger.com/atom/ns#' term='reporting'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Finding PostgreSQL temporary_file problems with tail_n_mail</title><content type='html'>&lt;style&gt;&lt;!-- span{font-family:sans-serif;}span.p{color:red;}span.t{color:green;}span.o{font-family:monospace;color:#0077cc;} --&gt;&lt;/style&gt;
&lt;div style="float:right; margin:0 0 40px 20px;cursor:pointer; cursor:hand;width: 222px; height: 320px;"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-HP7vf_sIClk/Trv9voBvMtI/AAAAAAAAAMs/EeszjaFE9YY/s1600/circus_elephant.jpg"&gt;&lt;img src="http://3.bp.blogspot.com/-HP7vf_sIClk/Trv9voBvMtI/AAAAAAAAAMs/EeszjaFE9YY/s320/circus_elephant.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5673407150243656402" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;center&gt;&lt;small&gt;Image by Flickr user &lt;a href="http://www.flickr.com/photos/dirkscircusimages/"&gt;dirkjanranzijn&lt;/a&gt;&lt;/small&gt;&lt;/center&gt;&lt;/div&gt;&lt;p&gt;PostgreSQL does as much work as it can in RAM, but sometimes it needs to (or thinks that it needs to) write things temporarily to disk. Typically, this happens on large or complex queries in which the required memory is greater than the &lt;a href="http://www.postgresql.org/docs/current/static/runtime-config-resource.html#GUC-WORK-MEM"&gt;&lt;code&gt;&lt;strong&gt;work_mem&lt;/strong&gt;&lt;/code&gt;&lt;/a&gt; setting.&lt;/p&gt;&lt;p&gt;This is usually an unwanted event: not only is going to disk much slower than keeping things in memory, but it can cause I/O contention. For very large, not-run-very-often queries, writing to disk can be warranted, but in most cases, you will want to adjust the &lt;code&gt;&lt;strong&gt;work_mem&lt;/strong&gt;&lt;/code&gt; setting. Keep in mind that this is very flexible setting, and can be adjusted globally (via the postgresql.conf file), per-user (via the ALTER USER command), and dynamically within a session (via the SET command). A good rule of thumb is to set it to something reasonable in your postgresql.conf (e.g. 8MB), and set it higher for specific users that are known to run complex queries. When you discover a particular query run by a normal user requires a lot of memory, adjust the work_mem for that particular query or set of queries.&lt;/p&gt;&lt;p&gt;How do you tell when you work_mem needs adjusting, or more to the point, when Postgres is writing files to disk? The key is the setting in postgresql.conf called &lt;a href="http://www.postgresql.org/docs/current/static/runtime-config-logging.html#GUC-LOG-TEMP-FILES"&gt;&lt;code&gt;&lt;strong&gt;log_temp_files&lt;/strong&gt;&lt;/code&gt;&lt;/a&gt;. By default it is set to &lt;strong&gt;-1&lt;/strong&gt;, which does no logging at all. Not very useful. A better setting is &lt;strong&gt;0&lt;/strong&gt;, which is my preferred setting: it logs &lt;em&gt;all&lt;/em&gt; temporary files that are created. Setting &lt;code&gt;&lt;strong&gt;log_temp_files&lt;/strong&gt;&lt;/code&gt; to a positive number will only log entries that have an on-disk size greater than the given number (in kilobytes). Entries about temporary files used by Postgres will appear like this in your log file:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
2011-01-12 16:33:34.175 EST LOG:  temporary file: path "base/pgsql_tmp/pgsql_tmp16501.0", size 130220032
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The only important part is the size, in bytes. In the example above, the size is 124 MB, which is not that small of a file, especially as it may be created many, many times. So the question becomes, how can we quickly parse the files and get a sense of which queries are causing excess writes to disk? Enter the &lt;a href="http://bucardo.org/wiki/Tail_n_mail"&gt;tail_n_mail program&lt;/a&gt;, which I recently tweaked to add a "tempfile" mode for just this purpose.&lt;/p&gt;&lt;p&gt;To enter this mode, just name your config file with "tempfile" in its name, and have it find the lines containing the temporary file information. It's also recommended you make use of the tempfile_limit parameter, which limits the results to the "top X" ones, as the report can get very verbose otherwise. An example config file and an example invocation via cron:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;$&lt;/span&gt; &lt;span class="t"&gt;cat tail_n_mail.tempfile.myserver.txt&lt;/span&gt;

## Config file for the tail_n_mail program
## This file is automatically updated
## Last updated: Thu Nov 10 01:23:45 2011
MAILSUBJECT: Myserver tempfile sizes
EMAIL: greg@endpoint.com
FROM: postgres@myserver.com
INCLUDE: temporary file
TEMPFILE_LIMIT: 5

FILE: /var/log/pg_log/postgres-%Y-%m-%d.log

&lt;span class="p"&gt;$&lt;/span&gt; &lt;span class="t"&gt;crontab -l | grep tempfile&lt;/span&gt;

## Mail a report each morning about tempfile usage:
0 5 * * * bin/tail_n_mail tnm/tail_n_mail.tempfile.myserver.txt --quiet

&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;For the client I wrote this for, we run this once a day and it mails us a nice report giving the worst tempfile offenders. The queries are broken down in three ways:&lt;/p&gt;&lt;p&gt;&lt;ul&gt;&lt;li&gt;Largest overall temporary file size&lt;/li&gt;&lt;li&gt;Largest arithmetic mean (average) size&lt;/li&gt;&lt;li&gt;Largest total size across all the same query&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;Here is a slightly edited version of an actual tempfile report email:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
Date: Mon Nov  7 06:39:57 2011 EST
Host: myserver.example.com
Total matches: 1342
Matches from [A] /var/log/pg_log/2011-11-08.log: 1241
Matches from [B] /var/log/pg_log/2011-11-09.log:  101
Not showing all lines: tempfile limit is 5

  Top items by arithmetic mean    |   Top items by total size
----------------------------------+-------------------------------
    860 MB (item 5, count is 1)   |   17 GB (item 4, count is 447)
    779 MB (item 1, count is 2)   |    8 GB (item 2, count is 71)
    597 MB (item 7, count is 1)   |    6 GB (item 334, count is 378)
    597 MB (item 8, count is 1)   |    6 GB (item 46, count is 104)
    596 MB (item 9, count is 1)   |    5 GB (item 3, count is 63)

[1] From file B Count: 2
Arithmetic mean is 779.38 MB, total size is 1.52 GB
Smallest temp file size: 534.75 MB (2011-11-08 12:33:14.312 EST)
Largest temp file size: 1024.00 MB (2011-11-08 16:33:14.121 EST)
First: 2011-11-08 05:30:12.541 EST
Last:  2011-11-09 03:12:22.162 EST
SELECT ab.order_number, TO_CHAR(ab.creation_date, 'YYYY-MM-DD HH24:MI:SS') AS order_date,
FROM orders o
JOIN order_summary os ON (os.order_id = o.id)
JOIN customer c ON (o.customer = c.id)
ORDER BY creation_date DESC

[2] From file A Count: 71
Arithmetic mean is 8.31 MB, total size is 654 MB
Smallest temp file size: 12.12 MB (2011-11-08 06:12:15.012 EST)
Largest temp file size: 24.23 MB (2011-11-08 19:32:45.004 EST)
First: 2011-11-08 06:12:15.012 EST
Last:  2011-11-09 04:12:14.042 EST
CREATE TEMPORARY TABLE tmp_sales_by_month AS SELECT * FROM sales_by_month_view;

&lt;/pre&gt;&lt;/span&gt;&lt;p&gt;While it still needs a little polishing (such as showing which file each smallest/largest came from), it has already been an indispensible tool forfinding queries that causing I/O problems via frequent and/or large temporary files.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-6386085831787430972?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/6386085831787430972/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=6386085831787430972' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6386085831787430972'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6386085831787430972'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/11/postgres-temporary-files-workmem-and.html' title='Finding PostgreSQL temporary_file problems with tail_n_mail'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-HP7vf_sIClk/Trv9voBvMtI/AAAAAAAAAMs/EeszjaFE9YY/s72-c/circus_elephant.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-3728351360508092223</id><published>2011-10-07T01:13:00.001-04:00</published><updated>2011-10-07T01:17:23.784-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bucardo'/><category scheme='http://www.blogger.com/atom/ns#' term='Conference'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>PG West 2011 Re-cap</title><content type='html'>&lt;p&gt;I just recently got back from PG West 2011, and have had some time to ruminate on the experience (do elephants chew a cud?&lt;tt&gt;&amp;lt;/note-to-self&amp;gt;&lt;/tt&gt;).  I definitely enjoyed San Jose as the location; it's always neat to visit new places and to meet new people, and I have to say that San Jose's weather was perfect for this time of year.  I was also glad to be able to renew professional relationships and meet others in the PostgreSQL community.&lt;/p&gt;

&lt;p&gt;Topic-wise, I noticed that quite a few talks had to do with replication and virtualization; this certainly seems to be a trend in the industry in general, and has definitely been a pet topic of mine for quite a while.  It's interesting to see the various problems that necessitate some form of replication, the tradeoffs/considerations for each specific problem, and wide variety of tools that are available in order to attack each of these problems (e.g. availability, read/write scaling, redundancy, etc).&lt;/p&gt;

&lt;p&gt;A few high points from each of the days:&lt;/p&gt;

&lt;h3&gt;Tuesday&lt;/h3&gt;
&lt;p&gt;I had dinner with fellow PostgreSQL contributors; some I knew ahead of time, others I got to know.  This was followed by additional socializing.&lt;/p&gt;

&lt;h3&gt;Wednesday&lt;/h3&gt;
&lt;p&gt;I attended a talk on &lt;i&gt;PostgreSQL HA&lt;/i&gt;, which covered the use of traditional cluster-level warm/hot standbys, as well as a solution using &lt;tt&gt;pg_pool&lt;/tt&gt; and &lt;tt&gt;slony&lt;/tt&gt;.  This was followed by the keynote address at the conference, given by Charles Fan, Senior Vice President from VMware.  This was a high-level overview of the type of work that VMware had been doing in order to support virtualizing PostgreSQL and optimizing for running multiple PostgreSQL instances on separate VMs efficiently.&lt;/p&gt;
&lt;p&gt;I was involved in some "lunch track" discussions, and followed this all up with several more talks covering VMWare's specific offerings in more detail.&lt;/p&gt;
&lt;p&gt;Evening was dinner and mandatory socializing.&lt;/p&gt;

&lt;h3&gt;Thursday&lt;/h3&gt;
&lt;p&gt;I went to Robert Hodges' talk about &lt;i&gt;Tungsten&lt;/i&gt;.  I had only heard of it in general terms, so it was interesting to get more specific details.  Robert's talk covered the basic architecture of Tungsten, as well as how their various adapters between multiple types of databases were used to ensure that the SQL that was executed on heterogeneous clusters would account for differences in datatype representation, encoding, DDL, specific query syntax, etc; for instance when executing a &lt;tt&gt;CREATE TABLE&lt;/tt&gt; statement, MySQL's &lt;tt&gt;AUTO_INCREMENT&lt;/tt&gt; fields would be converted to PostgreSQL's equivalent &lt;tt&gt;SERIAL&lt;/tt&gt; type.  There was lots of good discussion after the presentation, and I spoke with Robert after the talk about different design/architecture choices that they made with Tungsten and we discussed differences between that and Bucardo.&lt;/p&gt;

&lt;p&gt;At lunchtime I got to meet David Fetter's wife and baby (who looks &lt;a href="http://david.endpoint.com/fetter-baby.jpg"&gt;just&lt;/a&gt; like him!), then gave an updated version of my &lt;i&gt;Bucardo: More than just Multimaster&lt;/i&gt; talk.  Attendance was good, around 30-35, and the audience asked plenty of questions.&lt;/p&gt;

&lt;p&gt;After my talk, I attended one about database optimization.  This is always an interesting topic for me, so I'm glad to hear other's insights on this subject.&lt;/p&gt;

&lt;p&gt;This was all followed up by mandatory socializing.&lt;/p&gt;

&lt;h3&gt;Friday&lt;/h3&gt;

&lt;p&gt;I found the talk about &lt;i&gt;Translattice&lt;/i&gt; to be very interesting, as it highlighted specific problem domains for distributed, redundant, multi-write database clusters for more fault-tolerant applications.  It struck me as utilizing some of the same ideas as Cassandra or other decentralized distributed datastores, but doing so in a way that is transparent to the use of PostgreSQL.  What I found particularly interesting about this system was the use of data access/usage patterns, explicit policy, and locality to specify both the costing algorithm for accessing data as well as distributing knowledge about just where each copy of each piece of data exists.  The talk, while an introduction to the system, did not skimp on the details and the presenter was happy to answer my many specific questions.&lt;/p&gt;

&lt;p&gt;The remaining talks were fairly light-hearted.  I went to one called &lt;i&gt;Redis: Data Bacon&lt;/i&gt; for the title alone.  While I still don't understand why bacon, I walked away with an appreciation of the problem domain Redis addresses and how it could be used in specific cases.  The final talk I attended was about &lt;i&gt;Schemaverse&lt;/i&gt;, a project which implements a game entirely in SQL.  Each player has their own database user created that they can then use from either the web interface or even via just a regular &lt;tt&gt;psql&lt;/tt&gt; connection.  I can't speak for the game itself other than the overview given in the talk, but creative use/hacking of the game was explicitly encouraged, and seems like an interesting approach for testing things which may not often be stressed enough in (at least my) regular use of PostgreSQL, such as intra-database security/permissions, huge numbers of users, etc.  (It didn't surprise me that this game had been a hit at DEFCON.)&lt;/p&gt;

&lt;p&gt;This was followed by the closing session, and final goodbyes, etc.  Oh, and (need I say) mandatory socializing.&lt;/p&gt;

&lt;h3&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;I always enjoy going to PostgreSQL events, and continue to be impressed with the community that surrounds PostgreSQL.  Thanks to everyone who attended, and a special thanks to Josh Drake for the work he put into it.  Hope to see ya next time!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-3728351360508092223?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/3728351360508092223/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=3728351360508092223' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3728351360508092223'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3728351360508092223'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/10/pg-west-2011-re-cap.html' title='PG West 2011 Re-cap'/><author><name>David Christensen</name><uri>http://www.blogger.com/profile/14237702125131095813</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_eLhk5Eevkf8/SJtDBU4bnkI/AAAAAAAAAAU/1Gc6lO-4Egc/s1600-R/Big_superD.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-4733902751296920797</id><published>2011-10-05T06:45:00.001-04:00</published><updated>2011-10-07T01:30:43.304-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><category scheme='http://www.blogger.com/atom/ns#' term='audit'/><title type='text'>Viewing schema changes over time with check_postgres</title><content type='html'>&lt;div style="float:right; margin: 0 0 15px 10px;"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-mZhTquhcFos/TopTldyB83I/AAAAAAAAAKM/Vaq2UBuhYt8/s1600/multiple_elephants.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 308px;" src="http://3.bp.blogspot.com/-mZhTquhcFos/TopTldyB83I/AAAAAAAAAKM/Vaq2UBuhYt8/s320/multiple_elephants.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5659427784859186034" /&gt;&lt;/a&gt;&lt;br /&gt;Image by Flickr user &lt;a href="http://www.flickr.com/photos/edenpictures/"&gt;edenpictures&lt;/a&gt;&lt;/div&gt;&lt;style&gt;&lt;!--
span{font-family:sans-serif;}span.c{font-family:monospace;color:blue;}
--&gt;&lt;/style&gt;&lt;p&gt;Version 2.18.0 of &lt;a href="http://bucardo.org/wiki/Check_postgres"&gt;check_postgres&lt;/a&gt;, a monitoring tool for PostgreSQL, has just been released. This new version has quite a large number of changes: see the &lt;a href="https://mail.endcrypt.com/pipermail/check_postgres-announce/2011-October/000027.html"&gt;announcement&lt;/a&gt; for the full list. One of the major features is the overhaul of the &lt;a href="http://bucardo.org/check_postgres/check_postgres.pl.html#same_schema"&gt;same_schema&lt;/a&gt; action. This allows you to compare the structure of one database to another and get a report of all the differences check_postgres finds. Note that "schema" here means the database structure, not the object you get from a "CREATE SCHEMA" command. Further, remember the same_schema action does not compare the actual data, just its structure.&lt;/p&gt;

&lt;p&gt;Unlike most check_postgres actions, which deal with the current state of a single database, same_schema can compare databases to each other, as well as audit things by finding changes over time. In addition to having the entire system overhauled, same_schema now allows comparing as many databases you want to each other. The arguments have been simplified, in that a comma-separated list is all that is needed for multiple entries. For example:&lt;/p&gt;

&lt;pre&gt;&lt;span class="c"&gt;
./check_postgres.pl --action=same_schema \
  --dbname=prod,qa,dev --dbuser=alice,bob,charlie
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The above command will connect to three databases, as three different users, and compare their schemas (i.e. structures). Note that we don't need to specify a warning or critical value: we consider this an 'OK' Nagios check if the schemas match, otherwise it is 'CRITICAL'. Each database gets assigned a number for ease of reporting, and the output looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;span class="c"&gt;
POSTGRES_SAME_SCHEMA CRITICAL: (databases:prod,qa,dev)
  Databases were different. Items not matched: 1 | time=0.54s 
DB 1: port=5432 dbname=prod user=alice
DB 1: PG version: 9.1.1
DB 1: Total objects: 312
DB 2: port=5432 dbname=qa user=bob
DB 2: PG version: 9.1.1
DB 2: Total objects: 312
DB 3: port=5432 dbname=dev user=charlie
DB 3: PG version: 9.1.1
DB 3: Total objects: 313
Language "plpgsql" does not exist on all databases:
  Exists on:  3
  Missing on: 1, 2
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;The second large change was a simplification of the filtering options. Everything is now controlled by the &lt;strong&gt;--filter&lt;/strong&gt; argument, and basically you can tell it what things to ignore. For example:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
./check_postgres.pl --action=same_schema \
  --dbname=A,B --filter=nolanguage,nosequence
&lt;/pre&gt;&lt;/span&gt;&lt;p&gt;The above command will compare the schemas on databases A and B, but will ignore any difference in which languages are installed, and ignore any differences in the sequences used by the databases. Most objects can be filtered out in a similar way. There are also a few other useful options for the --filter argument:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;noposition: Ignore what order columns are in&lt;/li&gt;&lt;li&gt;noperms: Do not worry about any permissions on database objects&lt;/li&gt;&lt;li&gt;nofuncbody: Do not check function source&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The final and most exciting large change is the ability to compare a database to itself, over time. In other words, you can see exactly what changed during a certain time period. We have a client using that now to send a daily report on all schema changes made in the last 24 hours, for all the databases in their system. This is a very nice thing for a DBA to receive: not only is there a nice audit trail in your email, you can answer questions such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Was this a known change, or did someone make it without letting anyone else know?&lt;/li&gt;&lt;li&gt;Did somebody fat-finger and drop an index by mistake?&lt;/li&gt;&lt;li&gt;Were the changes applied to database X also applied to database Y and Z?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To enable time-based checks, simply provide a single database to check. The first time it is run, same_schema simply gathers all the schema information and stores it on disk. The next time it is run, it detects the file, reads it in as database "2", and compares it to the current database (number "1"). The &lt;strong&gt;--replace&lt;/strong&gt; argument will rewrite the file with the current data when it is done. So the cronjob for the aforementioned client is as simple as:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
10 0 * * * ~/bin/check_postgres.pl --action=same_schema \
  --host=bar --dbname=abc --quiet --replace
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The &lt;strong&gt;--quiet&lt;/strong&gt; argument ensures that no output is given if everything is 'OK'. If everything is not okay (i.e. if differences are found), cron gets a bunch of input sent to it and duly mails it out. Thus, a few minutes after 10AM each day, a report is sent if anything has changed in the last day. Here's a slightly redacted version of this morning's report, which shows that a schema named "stat_backup" was dropped at some point in the last 24 hours (which was a known operation):&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
POSTGRES_SAME_SCHEMA CRITICAL: DB "abc" (host:bar)
  Databases were different. Items not matched: 1 | time=516.56s
DB 1: port=5432 host=bar dbname=abc user=postgres
DB 1: PG version: 8.3.16
DB 1: Total objects: 11863
DB 2: File=check_postgres.audit.port.5432.host.bar.db.abc
DB 2: Creation date: Sun Oct  2 10:06:12 2011  CP version: 2.18.0
DB 2: port=5432 host=bar dbname=abc user=postgres
DB 2: PG version: 8.3.16
DB 2: Total objects: 11864
Schema "stat_backup" does not exist on all databases:
  Exists on:  2
  Missing on: 1
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;As you can see, the first part is a standard Nagios-looking output, followed by a header explaining how we defined database "1" and "2" (the former a direct database call, and the latter a frozen version of the same.)&lt;/p&gt;&lt;p&gt;Sometimes you want to store more than one version at a time: for example, if you want both a daily and a weekly view. To enable this, use the &lt;strong&gt;--suffix&lt;/strong&gt; argument to create different instances of the saved file. For example:&lt;pre&gt;&lt;span class="c"&gt;
10 0 * * * ~/bin/check_postgres.pl --action=same_schema \
  --host=bar --dbname=abc --quiet --replace --suffix=daily
10 0 * * Fri ~/bin/check_postgres.pl --action=same_schema \
  --host=bar --dbname=abc --quiet --replace --suffix=weekly
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The above command would end up recreating this file every morning at 10:&lt;strong&gt;check_postgres.audit.port.5432.host.bar.db.abc.daily&lt;/strong&gt; and this file each Friday at 10: &lt;strong&gt;check_postgres.audit.port.5432.host.bar.db.abc.weekly&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Thanks to all the people that made 2.18.0 happen (see the &lt;a href="https://mail.endcrypt.com/pipermail/check_postgres-announce/2011-October/000027.html"&gt;release notes&lt;/a&gt; for the list). There are still some rough edges to the same_schema action: for example, the output could be a little more user-friendly, and not all database objects are checked yet (e.g. no custom aggregates or operator classes). Development is ongoing; patches and other contributions are always welcome. In particular, we need more translators. We have French covered, but would like to include more languages. The code can be checked out at:&lt;/p&gt;

&lt;pre&gt;&lt;span class="c"&gt;
git clone git://bucardo.org/check_postgres.git
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;There is also a github mirror if you so prefer:&lt;/p&gt;

&lt;pre&gt;&lt;span class="c"&gt;
&lt;a href="https://github.com/bucardo/check_postgres"&gt;https://github.com/bucardo/check_postgres&lt;/a&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;You can also &lt;a href="http://bucardo.org/bugzilla"&gt;file a bug&lt;/a&gt; (or feature request), or join one of the mailing lists: &lt;a href="https://mail.endcrypt.com/mailman/listinfo/check_postgres"&gt;general&lt;/a&gt;, &lt;a href="https://mail.endcrypt.com/mailman/listinfo/check_postgres-announce"&gt;announce&lt;/a&gt;, and &lt;a href="https://mail.endcrypt.com/mailman/listinfo/check_postgres-commit"&gt;commit&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-4733902751296920797?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/4733902751296920797/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=4733902751296920797' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/4733902751296920797'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/4733902751296920797'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/10/viewing-schema-changes-over-time-with.html' title='Viewing schema changes over time with check_postgres'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-mZhTquhcFos/TopTldyB83I/AAAAAAAAAKM/Vaq2UBuhYt8/s72-c/multiple_elephants.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-6803831473360136103</id><published>2011-09-28T12:10:00.002-04:00</published><updated>2011-09-28T12:18:38.551-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bucardo'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>PostgreSQL Serializable and Repeatable Read Switcheroo</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-zXEQqm11bwo/ToKOEa6qnQI/AAAAAAAAAJ4/YWOCwyplWX4/s1600/goat_listening.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 240px; height: 160px;" src="http://4.bp.blogspot.com/-zXEQqm11bwo/ToKOEa6qnQI/AAAAAAAAAJ4/YWOCwyplWX4/s400/goat_listening.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5657240288526769410" /&gt;&lt;/a&gt;&lt;style&gt;&lt;!--
span{font-family:sans-serif;}span.p{color:red;}span.t{color:green;}span.o{font-family:monospace;color:blue;}th{text-align:center}
--&gt;&lt;/style&gt;&lt;p&gt;PostgreSQL allows for different transaction isolation levels to be specified. Because &lt;a href="http://bucardo.ord/wiki/Bucardo"&gt;Bucardo&lt;/a&gt; needs a consistent snapshot of each database involved in replication to perform its work, the first thing that the Bucardo daemon does when connecting to a remote &lt;a href="http://postgres.org/"&gt;PostgreSQL&lt;/a&gt; database is:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The 'READ WRITE' bit sets us in read/write mode, just in case the entire database has been set to read only (a quick and easy way to make your slave databases non-writeable!). It also sets the transaction isolation level to 'SERIALIZABLE'. At least, it used to. Now Bucardo uses 'REPEATABLE READ' like this:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ WRITE;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Why the change? In version 9.1 of PostgreSQL the concept of &lt;a href="http://wiki.postgresql.org/wiki/SSI"&gt;SSI (Serializable Snapshot Isolation)&lt;/a&gt; was introduced. How it actually works is a little complicated (follow the link for more detail), but before 9.1 PostgreSQL was only *sort of* doing serialized transactions when you asked for serializable mode. What it was really doing was repeatable read and not trying to really serialize the transactions. In 9.1, PostgreSQL is doing *true* &lt;a href="http://www.postgresql.org/docs/9.1/static/transaction-iso.html#XACT-SERIALIZABLE"&gt;serializable transactions&lt;/a&gt;. It also adds a new distinct 'internal' transaction mode, &lt;a href="http://www.postgresql.org/docs/9.1/static/transaction-iso.html#XACT-REPEATABLE-READ"&gt;'repeatable read'&lt;/a&gt;, which does exactly what the old 'serializable' used to do. Finally, if you issue a 'repeatable read' on a pre-9.1 database, it silently upgrades it to the old 'serializable' mode.&lt;/p&gt;&lt;p&gt;So in summary, if your application was using 'SERIALIZABLE' before, you can now replace that with 'REPEATABLE READ' and get the exact same behavior as before, regardless of the version. Of course, if you want *true* serializable transactions, use SERIALIZABLE. It will continue to mean the same as 'REPEATABLE READ' in pre-9.1 databases, and provide true serializability in 9.1 and beyond. (I haven't determined yet if Bucardo is going to use this new level, as it comes with a little bit of overhead)&lt;/p&gt;&lt;p&gt;Since this can be a little confusing, here's a handy chart showing how version 9.1 changed the meaning of SERIALIZABLE, and added a new 'internal' isolation level:&lt;/p&gt;&lt;table border="2" cellpadding="7"&gt;&lt;tr style="background-color: #00aaee"&gt;&lt;th colspan="4"&gt;Postgres version 9.0 and earlier&lt;/th&gt;&lt;th colspan="4"&gt;Postgres version 9.1 and later&lt;/th&gt;&lt;/tr&gt;&lt;tr style="background-color: #00bbbb"&gt;&lt;th&gt;Requested isolation level&lt;/th&gt;&lt;th&gt;&amp;rarr;&lt;/th&gt;&lt;th&gt;Actual internal isolation level&lt;/th&gt;&lt;th colspan="2"&gt;Version comparison&lt;/th&gt;&lt;th&gt;Actual internal isolation level&lt;/th&gt;&lt;th&gt;&amp;larr;&lt;/th&gt;&lt;th&gt;Requested isolation level&lt;/th&gt;&lt;/tr&gt;&lt;tr style="background-color: #ffcccc"&gt;&lt;th&gt;READ UNCOMMITTED&lt;/th&gt;&lt;td&gt;&amp;#x2198;&lt;/td&gt;&lt;th rowspan="2"&gt;Read committed&lt;/th&gt;&lt;th rowspan="2" colspan="2"&gt;Exact same&lt;/th&gt;&lt;th rowspan="2"&gt;Read committed&lt;/th&gt;&lt;td&gt;&amp;#x2199;&lt;/td&gt;&lt;th&gt;READ UNCOMMITTED&lt;/th&gt;&lt;/tr&gt;&lt;tr style="background-color: #ffcccc"&gt;&lt;th&gt;READ COMMITTED&lt;/th&gt;&lt;td&gt;&amp;#x2197;&lt;/td&gt;&lt;td&gt;&amp;#x2196;&lt;/td&gt;&lt;th&gt;READ COMMITTED&lt;/th&gt;&lt;/tr&gt;&lt;tr style="background-color: #ffee99"&gt;&lt;th&gt;REPEATABLE READ&lt;/th&gt;&lt;td&gt;&amp;#x2198;&lt;/td&gt;&lt;th rowspan="2"&gt;Serializable&lt;/th&gt;&lt;th rowspan="2"colspan="2"&gt;Functionally identical&lt;/th&gt;&lt;th rowspan="2"&gt;Repeatable read&lt;/th&gt;&lt;td rowspan="2"&gt;&amp;larr;&lt;/td&gt;&lt;th rowspan="2"&gt;REPEATABLE READ&lt;/th&gt;&lt;/tr&gt;&lt;tr style="background-color: #ffee99"&gt;&lt;th&gt;SERIALIZABLE&lt;/th&gt;&lt;td&gt;&amp;#x2197;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="background-color: #aaffaa"&gt;&lt;th colspan="3"&gt;&amp;nbsp;&lt;/th&gt;&lt;th colspan="2"&gt;9.1 only!&lt;/th&gt;&lt;th&gt;Serializable (true)&lt;/th&gt;&lt;td&gt;&amp;larr;&lt;/td&gt;&lt;th&gt;SERIALIZABLE&lt;/th&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Congratulations and thanks to Kevin Grittner and Dan Ports for making true serializability a reality!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-6803831473360136103?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/6803831473360136103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=6803831473360136103' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6803831473360136103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6803831473360136103'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/09/postgresql-allows-for-different.html' title='PostgreSQL Serializable and Repeatable Read Switcheroo'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-zXEQqm11bwo/ToKOEa6qnQI/AAAAAAAAAJ4/YWOCwyplWX4/s72-c/goat_listening.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-5947779313143955868</id><published>2011-09-22T00:14:00.001-04:00</published><updated>2011-09-22T00:15:55.216-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conference'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>Another Post-Postgres Open Post</title><content type='html'>&lt;p&gt;Well, that was fun!  I've always found attending conferences to be an invigorating experience.  The talks are generally very informative, it's always nice to put a face to names seen online in the community, and between the "hall track", lunches, and after-session social activities it's difficult to not find engaging discussions.&lt;/p&gt;&lt;p&gt;My favorite presentations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://postgresopen.org/2011/schedule/presentations/90/"&gt;Scaling servers with Skytools&lt;/a&gt; -- seeing what it takes to balance several high-velocity nodes was intriguing.&lt;/li&gt;&lt;li&gt;&lt;a href="http://postgresopen.org/2011/schedule/presentations/22/"&gt;Mission &lt;span style="text-decoration: line-through;"&gt;Im&lt;/span&gt;possible&lt;/a&gt; -- lots of good arguments for why Postgres can be an equivalent, nay, better replacement for an enterprise database.&lt;/li&gt;&lt;li&gt;&lt;a href="http://postgresopen.org/2011/schedule/presentations/65/"&gt;The PostgreSQL replication protocol&lt;/a&gt; -- even if I never intend to write something that'll interact with it directly, knowing how something like the new streaming replication works under the hood goes a long way to keeping it running at a higher level.&lt;/li&gt;&lt;li&gt;&lt;a href="http://postgresopen.org/2011/schedule/presentations/61/"&gt;True Serializable Transactions Are Here!&lt;/a&gt; -- I'll admit I haven't had a chance to fully check out the changes to Serializable, so getting to hear some of the reasoning and stepping through some of the use cases was quite helpful.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;But what of my talks?  &lt;a href="http://joshwilliams.name/talks/monitoring/"&gt;Monitoring&lt;/a&gt; went well -- it seemed to get the message out.  There was a lot of "gee, I have Postgres, and Nagios, but they're not talkin'.  Now they can!"  So hopefully, with a little more visibility into how the database is standing, the tools can boost confidence within business environments that aren't as sure about Postgres and help keep existing installations in place.  I think the &lt;a href="http://bucardo.org/slides/b5_multi_master/"&gt;Bucardo presentation&lt;/a&gt; had me a bit more animated for some reason.  That one also led to some interesting questions from the audience, and a couple challenges for the Bucardo project.&lt;/p&gt;&lt;p&gt;All in all, great work everyone!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-5947779313143955868?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/5947779313143955868/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=5947779313143955868' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5947779313143955868'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5947779313143955868'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/09/another-post-postgres-open-post.html' title='Another Post-Postgres Open Post'/><author><name>Josh Williams</name><uri>http://www.blogger.com/profile/10667170385197485182</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_TkzEDzlZHMg/S9Cz1qScO5I/AAAAAAAAAAM/_Ukoe431U5w/s1600-R/williams.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-6816653320659774046</id><published>2011-09-20T12:19:00.005-04:00</published><updated>2011-09-20T12:26:03.838-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>Headed out to PgWest next week</title><content type='html'>&lt;p&gt;I'm gearing up to go out to San Jose to attend and speak at the PG West PostgreSQL conference in sunny San Jose.  (Does anyone have directions...?)&lt;/p&gt;

&lt;p&gt;I'm excited to again meet and mingle with more PostgreSQL experts and enthusiasts and look forward to the various talks, technical discussions, and social opportunities.  My talk will be on &lt;a href="http://bucardo.org/"&gt;Bucardo&lt;/a&gt; and many uses for it as a general tool.  It'll also cover additional changes coming down the pipe in Bucardo 5.&lt;/p&gt;

&lt;p&gt;I look forward to seeing everyone!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-6816653320659774046?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/6816653320659774046/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=6816653320659774046' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6816653320659774046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6816653320659774046'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/09/headed-out-to-pgwest-next-week.html' title='Headed out to PgWest next week'/><author><name>David Christensen</name><uri>http://www.blogger.com/profile/14237702125131095813</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_eLhk5Eevkf8/SJtDBU4bnkI/AAAAAAAAAAU/1Gc6lO-4Egc/s1600-R/Big_superD.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-6589471534815304828</id><published>2011-09-13T17:20:00.003-04:00</published><updated>2011-09-13T17:22:50.620-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conference'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>Bucardo, 9.1, and you!</title><content type='html'>&lt;p&gt;A little bit of bad news for Bucardo fans, Greg Sabino Mullane won't be making Postgres Open due to scheduling conflicts.  But not to worry, I'll be giving the &lt;a href="http://postgresopen.org/2011/schedule/presentations/55/"&gt;"Postgres masters, other slaves"&lt;/a&gt; talk in the meantime in his place.&lt;/p&gt;&lt;p&gt;In looking over the slides, one thing that catches my eye is how quickly Bucardo is adopting PostgreSQL 9.1 features.  Specifically, Unlogged Tables will be very useful in boosting performance where Bucardo stages information about changed rows for multi-database updates.  I also wonder if the enhanced Serializable Snapshot Isolation would be helpful in some situations.  Innovation encouraging more innovation, gotta love open source!&lt;/p&gt;&lt;p&gt;If I hadn't said it before, thanks to everyone that made Postgres 9.1 possible.  Some of the other enhancements are just as exciting.  For instance, I'm eager to see some creative uses for Writable CTE's.  And it'll be very interesting to see what additional Foreign Data Wrappers pop up over time.&lt;/p&gt;&lt;p&gt;Now, back to packing...&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-6589471534815304828?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/6589471534815304828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=6589471534815304828' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6589471534815304828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6589471534815304828'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/09/bucardo-91-and-you.html' title='Bucardo, 9.1, and you!'/><author><name>Josh Williams</name><uri>http://www.blogger.com/profile/10667170385197485182</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_TkzEDzlZHMg/S9Cz1qScO5I/AAAAAAAAAAM/_Ukoe431U5w/s1600-R/williams.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-4950275016134172606</id><published>2011-09-07T23:04:00.001-04:00</published><updated>2011-09-07T23:12:26.921-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conference'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>Postgres Open: One week to go!</title><content type='html'>&lt;p&gt;Wow, time flies, &lt;a href="http://postgresopen.org/2011/"&gt;Postgres Open&lt;/a&gt; is almost upon us!&lt;/p&gt;&lt;p&gt;I'll be there giving a talk Thursday morning on &lt;a href="http://postgresopen.org/2011/schedule/presentations/60/"&gt;monitoring tools and techniques&lt;/a&gt;, and possibly helping with the &lt;a href="http://postgresopen.org/2011/schedule/presentations/55/"&gt;Bucardo 5 replication&lt;/a&gt; session Friday afternoon.  Sadly I'll need need to catch a flight shortly after that, so there won't be much time to explore Chicago around everything going on.  But at least it'll be nice to get out to a conference again!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-4950275016134172606?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/4950275016134172606/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=4950275016134172606' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/4950275016134172606'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/4950275016134172606'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/09/postgres-open-one-week-to-go.html' title='Postgres Open: One week to go!'/><author><name>Josh Williams</name><uri>http://www.blogger.com/profile/10667170385197485182</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_TkzEDzlZHMg/S9Cz1qScO5I/AAAAAAAAAAM/_Ukoe431U5w/s1600-R/williams.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-5751224674320891649</id><published>2011-09-05T05:00:00.006-04:00</published><updated>2011-09-07T17:34:40.721-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bucardo'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Bucardo PostgreSQL replication to other tables with customname</title><content type='html'>&lt;div style="float:right; margin: 0 0 20px 20px"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-02BXpEZUAZw/TmQ2Ul6ieHI/AAAAAAAAAJg/Sf-vrCGRm0A/s1600/bwgoat.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 214px;" src="http://3.bp.blogspot.com/-02BXpEZUAZw/TmQ2Ul6ieHI/AAAAAAAAAJg/Sf-vrCGRm0A/s320/bwgoat.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5648699560032368754" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;small&gt;Image by Flickr user &lt;a href="http://www.flickr.com/photos/soggydan/"&gt;Soggydan&lt;/a&gt;
&lt;/small&gt;&lt;/div&gt;&lt;style&gt;&lt;!-- span{font-family:sans-serif;}span.p{color:red;}span.t{color:green;}span.o{font-family:monospace;color:blue;} --&gt;&lt;/style&gt;&lt;p&gt;(Don't miss the Bucardo5 talk at &lt;a href="http://postgresopen.org/2011/home/"&gt;Postgres Open&lt;/a&gt; in Chicago)&lt;/p&gt;&lt;p&gt;Work on the next major version of Bucardo is wrapping up (version 5 is now in beta), and two new features have been added to this major version. The first, called &lt;strong&gt;customname&lt;/strong&gt;, allows you to replicate to a table with a different name. This has been a feature people have been asking for a long time, and even allows you to replicate between differently named Postgres schemas. The second option, called &lt;strong&gt;customcols&lt;/strong&gt;, allows you replicate to different columns on the target: not only a subset, but different column names (and types), as well as other neat tricks.&lt;/p&gt;&lt;p&gt;The "customname" options allows changing of the table name for one or more targets. Bucardo replicates tables from the source databases to the target databases, and all tables must have the same name and schema everywhere. With the customname feature, you can change the target table names, either globally, per database, or per sync.&lt;/p&gt;&lt;p&gt;We'll go through a full example here, using a stock 64-bit RedHat 6.1 EC2 box (ami-5e837b37). I find EC2 a great testing platform - not only can you try different operating systems and architectures, but (as my own personal box is very customized) it is great to start afresh from a stock configuration.&lt;/p&gt;&lt;p&gt;First, let's turn off SELinux, install the &lt;a href="http://fedoraproject.org/wiki/EPEL"&gt;EPEL rpm&lt;/a&gt;, update the box, and install a few needed packages.&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;#
#
#
#
#
#&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;echo 0 &amp;gt; /selinux/enforce
wget http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-5.noarch.rpm        
rpm -ivh epel-release-6-5.noarch.rpm
yum update
yum install emacs-nox perl-DBIx-Safe perl-DBD-Pg git postgresql-plperl
cpan boolean&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;The yum update takes a while to run, but I always feel better when things are up to date. Next, we will create a new database cluster, create the /var/run/bucardo directory that Bucardo uses to store its PIDs, adjust the ultraconservative stock pg_hba.conf file, and start Postgres up:&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;#
#
#
#
#&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;service postgresql initdb
mkdir /var/run/bucardo
chown postgres.postgres /var/run/bucardo
emacs /var/lib/pgsql/data/pg_hba.conf                                        
service postgresql start&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;For the pg_hba.conf configuration file, because we want to be able to connect to the database as the bucardo user without actually logging into that account, we will allow access using the 'md5' (password) method instead of 'ident'. But we don't want to bother creating a password for the postgres user, we will still allow those connections via ident. The relevant lines in the pg_hba.conf will end up like this:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
# TYPE   DATABASE   USER       METHOD
local    all        postgres   ident                          
local    all        all        md5                          
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;At this point, we (as the postgres user) download and install Bucardo itself:&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;#
$
$
$
$
$
$&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;su - postgres
git clone git://bucardo.org/bucardo.git
cd bucardo
perl Makefile.PL
make
sudo make install                                      
bucardo install&lt;/span&gt;# (enter 'p' and keep the default values)&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;We are now ready to start testing out the new customname feature. First we will need some data to replicate! For this demo we are going to use one of the handy sample datasets from &lt;a href="http://pgfoundry.org/projects/dbsamples/"&gt;the dbsamples project&lt;/a&gt;. The one we will use has a few small tables with information about towns in France. Note that the tarball does not (sadly) contain a top-level directory, so we have to create one ourselves. We will then create three identical databases holding the data from that file.&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;$
$
$
$
$
$
$
$
$&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;wget http://pgfoundry.org/frs/download.php/935/french-towns-communes-francaises-1.0.tar.gz                
mkdir frenchtowns
cd frenchtowns
tar xvfz ../french-towns-communes-francaises-1.0.tar.gz
psql -c 'create database french1'
psql french1 -q -f french-towns-communes-francaises.sql
psql -c 'create database french2 template french1'
psql -c 'create database french3 template french1'
psql -c 'create database french4 template french1'&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Bucardo is installed but does not know what to do yet, so we will teach Bucardo about each of the databases, and add in all the tables, grouping then into a herd in the process. Finally, we create a sync in which french1 and french2 are both source (master) databases, and french3 and french4 will be target (slave) databases.&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;$
$
$
$
$
$&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;bucardo add db f1 db=french1
bucardo add db f2 db=french2
bucardo add db f3 db=french3
bucardo add db f4 db=french4
bucardo add all tables herd=fherd
bucardo add sync wildstar herd=fherd dbs=f1=source,f2=source,f3=target,f4=target&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Before starting it up, I usually raise the debug level, as it gives a much clearer picture of what is going on in the logs. It does make the logs a lot more crowded, so it is not recommended for production use:&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;$
&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;echo log_level=DEBUG &amp;gt;&amp;gt; ~/.bucardorc&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Next, we start Bucardo up and make sure everything is working as it should. Scanning the log.bucardo file that is generated is a great way to do this:&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;$
$
$&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;bucardo start
sleep 3
tail log.bucardo&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;If all goes well, you should see something very similar to this in the last lines of your log.bucardo file:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
(972) [Sat Sep  3 16:18:54 2011] KID Total time for sync "wildstar" (0 rows): 0.05 seconds
(966) [Sat Sep  3 16:18:55 2011] CTL Got NOTICE ctl_syncdone_wildstar from 973 (line 1624)
(966) [Sat Sep  3 16:18:55 2011] CTL Kid 973 has reported that sync wildstar is done
(966) [Sat Sep  3 16:18:55 2011] CTL Sending NOTIFY "syncdone_wildstar" (line 1709)
(954) [Sat Sep  3 16:18:55 2011] MCP Got NOTICE syncdone_wildstar from 967 (line 749)
(954) [Sat Sep  3 16:18:55 2011] MCP Sync wildstar has finished
(954) [Sat Sep  3 16:18:55 2011] MCP Sending NOTIFY "syncdone_wildstar" (line 812)
(954) [Sat Sep  3 16:18:56 2011] MCP Got NOTICE syncdone_wildstar from 957 (Bucardo DB) (line 749)&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;From the above, we see that a KID finished running the sync we created, without finding any changed rows to replicate. Then there is some chatter between the different Bucardo processes. Now to test out the customname feature. We'll rename one of the tables, tell Bucardo about the change, reload the sync, and verify that all is still being replicated.&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;$
$
$&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;psql french3 -c 'ALTER TABLE regions RENAME TO tesla'
bucardo add customname regions tesla db=f3
bucardo reload wildstar&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;$

$

$

$
 &lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;psql french3 -c 'truncate table tesla cascade'
&lt;span class="o"&gt;TRUNCATE&lt;/span&gt;
psql french3 -t -c 'select count(*) from tesla'
&lt;span class="o"&gt;0&lt;/span&gt;
psql french1 -c 'update regions set name=name'
&lt;span class="o"&gt;UPDATE 26&lt;/span&gt;
psql french3 -t -c 'select count(*) from tesla'
&lt;span class="o"&gt;26&lt;/span&gt;&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;In the above, the update on the regions table inthe french1 database calls a trigger that notifies Bucardo that some rows have changed; Bucardo then has a KID copy the rows from the source databases french1 to the other source database french2, as well as the targets french3 and french4. The final internal DELETE and COPY that it performs is done on database french3 to the tesla table rather than the regions table.&lt;/p&gt;&lt;p&gt;The customname feature cannot be used to change the tables in a source database, as they must all be the same (for obvious reasons). We can, however, specify that a different &lt;em&gt;schema&lt;/em&gt; be used for a target, as well as a different table. This only applies to Postgres targets, as other database types (e.g. MySQL) do not use schemas. Let's see that in action:&lt;/p&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;$
$
$
$
$&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;psql french4 -c 'create schema banana'
psql french4 -c 'alter table regions set schema banana'
psql french4 -c 'truncate table banana.regions cascade'
bucardo add customname regions banana.regions db=f4
bucardo reload wildstar&lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;table border="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span class="p"&gt;&lt;pre&gt;$

$

$
 &lt;/pre&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span class="t"&gt;&lt;pre&gt;psql french4 -t -c 'select count(*) from banana.regions'
&lt;span class="o"&gt;0&lt;/span&gt;
psql french2 -c 'update regions set name=name'
&lt;span class="o"&gt;UPDATE 26&lt;/span&gt;
psql french4 -t -c 'select count(*) from banana.regions'
&lt;span class="o"&gt;26&lt;/pre&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;As before, the update on a source causes the changes to propagate to the other source database, as well as both targets. Note that the ALTER TABLE also mutated the associated sequence for the table, so there will be warnings in Bucardo's logs about the DEFAULT values for the primary keys in the regions' tables being different. Since this post is getting long, I will save the discussion of customcols for another day.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-5751224674320891649?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/5751224674320891649/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=5751224674320891649' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5751224674320891649'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5751224674320891649'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/09/bucardo-postgresql-replication-to-other.html' title='Bucardo PostgreSQL replication to other tables with customname'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-02BXpEZUAZw/TmQ2Ul6ieHI/AAAAAAAAAJg/Sf-vrCGRm0A/s72-c/bwgoat.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-6444088607538728985</id><published>2011-08-19T10:45:00.001-04:00</published><updated>2011-08-19T10:45:01.035-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='monitoring'/><category scheme='http://www.blogger.com/atom/ns#' term='analytics'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>PostgreSQL log analysis / PGSI</title><content type='html'>&lt;style&gt;span{font-family:sans-serif;}span.p{color:red;}span.t{color:blue;}span.c{font-family:monospace;color:blue;}&lt;/style&gt;&lt;div style="float:right; margin:0 0 10px 10px;&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-4wvmEmkEmfw/Tk3Vb-XyhoI/AAAAAAAAAIQ/d_4FToeBT7c/s1600/elephant_rearview_m.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 221px;" src="http://1.bp.blogspot.com/-4wvmEmkEmfw/Tk3Vb-XyhoI/AAAAAAAAAIQ/d_4FToeBT7c/s320/elephant_rearview_m.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5642400584741062274" /&gt;&lt;/a&gt;&lt;br clear="all"/&gt;&lt;small&gt;&lt;center&gt;Image by &lt;a href="http://www.flickr.com/photos/exfordy/"&gt;"exfordy" on Flickr&lt;/a&gt;&lt;/span&gt;&lt;/center&gt;&lt;/small&gt;&lt;/div&gt;&lt;p&gt;End Point recently started working with a new client (a startup in &lt;a href="http://en.wikipedia.org/wiki/Stealth_mode"&gt;stealth mode&lt;/a&gt;, cannot name names, etc.) who is using &lt;a href="http://postgres.org"&gt;PostgreSQL&lt;/a&gt; because of the great success some of the people starting the company have had with Postgres in previous companies. One of the things we recommend to our clients is a regular look at the database to see where the bottlenecks are. A good way to do this is by analyzing the logs. The two main tools for doing so are &lt;a href="http://bucardo.org/wiki/PGSI"&gt;PGSI&lt;/a&gt; (Postgres System Impact) and &lt;a href="http://pgfouine.projects.postgresql.org/"&gt;pgfouine&lt;/a&gt;. We prefer PGSI for a few reasons: the output is better, it considers more factors, and it does not require you to munge &lt;a href="http://www.postgresql.org/docs/9.0/static/runtime-config-logging.html#GUC-LOG-LINE-PREFIX"&gt;your log_line_prefix&lt;/a&gt; setting quite as badly.&lt;/p&gt;&lt;p&gt;Both programs work basically the same: given a large number of log lines from Postgres, normalize the queries, see how long they took, and produce some pretty output.If you only want to look at the longest queries, it's usually enough to set your log_min_duration_statement to something sane (such as 200), and then run a daily &lt;a href="http://bucardo.org/wiki/Tail_n_mail"&gt;tail_n_mail&lt;/a&gt; job against it. This is what we are doing with this client, and it sends a daily report that looks like this:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
Date: Mon Aug 29 11:22:33 2011 UTC
Host: acme-postgres-1
Minimum duration: 2000 ms
Matches from /var/log/pg_log/postgres-2011-08-29.log: 7

[1] (from line 227)
2011-08-29 08:36:50 UTC postgres@maindb [25198]
LOG: duration: 276945.482 ms statement: COPY public.sales 
(id, name, region, item, quantity) TO stdout;

[2] (from line 729)
2011-08-29 21:29:18 UTC tony@quadrant [17176]
LOG: duration: 8229.237 ms execute dbdpg_p29855_1: SELECT 
id, singer, track FROM album JOIN artist ON artist.id = 
album.singer WHERE id &lt; 1000 AND track &lt;&gt; 1
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;However, the PGSI program was born of the need to look at &lt;em&gt;&lt;strong&gt;all&lt;/strong&gt;&lt;/em&gt; the queries in the database, not just the slowest-running ones; the cumulative effect of many short queries can have much more of an impact on the server than a smaller number of long-running queries. Thus, PGSI looks not only at how long a query takes to run, but how many times it has run in a certain period, as well as how often it runs. All of this information is put together to give a score to each normalized query, known as the "system impact". Like the costs on a Postgres explain plan, this is a unit-less number and of little importance in and of itself - the important thing is to compare it to the other queries to see the relative impact. We also have that report emailed out, it looks similar to this (this is a text version of the HTML produced):&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
Log file: /var/log/pg_log/postgres-2011-08-29.log

 * SELECT (24)
 * UPDATE (1)

Query System Impact : SELECT

 Log activity from 2011-08-29 11:00:01 to 2011-08-29 11:15:01

   +----------------------------------+
   |   System Impact: | 0.15          |
   |   Mean Duration: | 1230.95 ms    |
   | Median Duration: | 1224.70 ms    |
   |     Total Count: | 411           |
   |   Mean Interval: | 4195 seconds  |
   |  Std. Deviation: | 126.01 ms     |
   +---------------------------------+

 SELECT *
  FROM albums
  WHERE track &lt;&gt; ? AND artist = ?
  ORDER BY artist, track

&lt;/pre&gt;&lt;/span&gt;
&lt;p&gt;At this point you may be wondering how we get all the queries into the log. This is done by setting log_min_duration_statement to 0. However, most (but not all!) clients do not want full logging 24 hours a day, as this creates some very large log files. So the solution we use is to analyze a slice of the day, only. It depends on the client, but we try for about 15 minutes during a busy time of day. Thus, the sequence of events is:&lt;/p&gt;&lt;p&gt;&lt;ol&gt;&lt;li&gt;Turn on "full logging" by dropping log_min_duration_statement to zero&lt;/li&gt;&lt;li&gt;Some time later, set log_min_duration_statement back to what it was (e.g. 200)&lt;/li&gt;&lt;li&gt;Extract the logs from the time it was set to zero to when it was flipped back.&lt;/li&gt;&lt;li&gt;Run PGSI against the log subsection we pulled out&lt;/li&gt;&lt;li&gt;Mail the results out&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;p&gt;All of this is run by cron. The first problem is how to update the postgresql.conf file and have Postgres re-read it, all automatically. As &lt;a href="http://blog.endpoint.com/2011/08/changing-postgresqlconf-from-script.html"&gt;covered previously&lt;/a&gt;, we use the &lt;a href="https://github.com/bucardo/modify_postgres_config"&gt;modify_postgres.pl&lt;/a&gt; script for this.&lt;/p&gt;&lt;p&gt;The exact incantation looks like this:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
0 11 * * * perl bin/modify_postgres_conf --quiet \
  --pgconf /etc/postgresql/9.0/main/postgresql.conf \
  --change log_min_duration_statement=0
15 11 * * * perl bin/modify_postgres_conf --quiet \
  --pgconf /etc/postgresql/9.0/main/postgresql.conf \
  --change log_min_duration_statement=200 --no-comment
## The above are both one line each, but split for readability here
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;This changes log_min_duration_statement to 0 at 11AM, and then back to 200 15 minutes later. We use the --quiet argument as this is run from cron so we don't want any output from modify_postgres_conf on success. We do want a comment when we flip it to 0, as this is the temporary state and we want people viewing the postgresql.conf file at that time to realize it (or someone just doing a "git diff"). We don't want a comment when we flip it back, as the timestamp in the comment would cause git to think the file had changed.&lt;/p&gt;&lt;p&gt;Now for the tricky bit: extracting out just the section of logs that we want and sending it to PGSI. Here's the recipe I came up with for this client:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
16 11 * * * tac `ls -1rt /var/log/pg_log/postgres*log \
  | tail -1` \
  | sed -n '/statement" changed to "200"/,/statement" changed to "0"/ p' \
  | tac \
  | bin/pgsi.pl --quiet &gt; tmp/pgsi.html &amp;&amp; bin/send_pgsi.pl
## Again, the above is all one line
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;What does this do? First, it finds the latest file in the /var/log/pg_log directory that starts with 'postgres' and ends with 'log'. Then it uses the &lt;strong&gt;tac&lt;/strong&gt; program to spool the file backwards, one line at a time ('tac' is the opposite of 'cat'). Then it pipes &lt;em&gt;that&lt;/em&gt; output to the &lt;strong&gt;sed&lt;/strong&gt; program, which prints out all lines starting with the one where we changed the log_min_duration_statement to 200, and ending with the one where we changed it to 0 (the reverse of what we actually did, as we are reading it backwards). Finally, we use tac again to put the lines back in the correct order, pipe the output to &lt;strong&gt;pgsi&lt;/strong&gt;, write the output to a temporary file, and then call a quick Perl script named &lt;strong&gt;send_pgsi.pl&lt;/strong&gt; which mails the temporary HTML file to some interested parties.&lt;/p&gt;&lt;p&gt;Why do we use tac? Because we want to read the file backwards, so as to make sure we get the correct slice of log files as delimited by the log_min_duration_statement changes. If we simply started at the beginning of the file, we might encounter other similar changes that were made earlier and not by us.&lt;/p&gt;&lt;p&gt;All of this is not foolproof, of course, but it does not have to be, as it is very easy to run manually is something (for example the sed recipe) goes wrong, as the log file will still be there. Yes, it's also possible to grab the ranges in other ways (such as perl), but I find sed the quickest and easiest. As tempting as it was to write Yet Another Perl Script to extract the lines, sometimes a few chained Unix programs can do the job quite nicely.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-6444088607538728985?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/6444088607538728985/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=6444088607538728985' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6444088607538728985'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/6444088607538728985'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/08/postgresql-log-analysis-pgsi.html' title='PostgreSQL log analysis / PGSI'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-4wvmEmkEmfw/Tk3Vb-XyhoI/AAAAAAAAAIQ/d_4FToeBT7c/s72-c/elephant_rearview_m.jpg' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-4712308836071838423</id><published>2011-08-10T17:00:00.000-04:00</published><updated>2011-08-10T17:00:02.419-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tools'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Changing postgresql.conf from a script</title><content type='html'>&lt;style&gt;span{font-family:sans-serif;}span.p{color:red;}span.t{color:green;}span.c{font-family:monospace;color:blue;}&lt;/style&gt;&lt;div style="float:right; margin:0 0 10px 10px;&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-htnFkm8LBBw/TkLHdC0MeaI/AAAAAAAAAHY/GoSvUtR1xmk/s1600/elephant_play.jpg"&gt;&lt;img cursor:pointer; cursor:hand;width: 320px; height: 207px;" src="http://1.bp.blogspot.com/-htnFkm8LBBw/TkLHdC0MeaI/AAAAAAAAAHY/GoSvUtR1xmk/s320/elephant_play.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5639288985207208354" /&gt;&lt;/a&gt;&lt;br clear="all"/&gt;&lt;small&gt;&lt;center&gt;Image by &lt;a href="http://www.flickr.com/photos/thebusybrain/"&gt;"TheBusyBrain" on Flickr&lt;/a&gt;&lt;/span&gt;&lt;/center&gt;&lt;/small&gt;&lt;/div&gt;&lt;p&gt;The modify_postgres_conf script for Postgres allows you to change your postgresql.conf file from the command line, via a cron job, or any time when you want to automate the process.&lt;/p&gt;&lt;p&gt;Postgres runs as a background daemon. The configuration parameters it runs with are stored in a file named postgresql.conf. To change the behavior of Postgres, one must usually edit this file, and then tell Postgres that you have made the changes. Sometimes all that is needed is to 'HUP' or reload Postgres. Most changes fall into this category. Other changes require a full restart of Postgres, which entails disconnecting all current clients.&lt;/p&gt;&lt;p&gt;Thus, to make a change, one must edit the file, find the item to change (the file consists of "name = value" lines), change it, then send a signal to the main Postgres process so it picks up the change. Finally, you should then connect to Postgres to make sure it is still running and has accepted the latest change.&lt;/p&gt;&lt;p&gt;Doing this automatically (such as via a cron script) is very difficult. One method, if you are doing something simple like toggling between two known configuration files, is to simply store copies of both files and replace them, like this example cronjob:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
30 10 * * * cp -f conf/postgresql.conf.1 /etc/postgresql.conf; /etc/init.d/postgresql reload
50 10 * * * cp -f conf/postgresql.conf.2 /etc/postgresql.conf; /etc/init.d/postgresql reload
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The major problem with that approach, as I quickly learned when I tried it, is that despite nobody making changes to the postgresql.conf file in *years*, a few days after I put the above change in place, someone decided to edit postgresql.conf. At 10:30AM the next day, their changes were blown away. A better way is to simply write a program to make the change for you. Thus, the modify_postgres_conf.pl script.&lt;/p&gt;&lt;p&gt;The basic usage is to tell the script where the conf file is, and list what changes you want to make. Here's an example that will change the random_page_cost to &lt;b&gt;2&lt;/b&gt; on a Debian system:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
./modify_postgres_conf.pl --pgconf /etc/postgresql/9.0/main/postgresql.conf --change random_page_cost=2
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Here is exactly what the script does for the above statement:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;For each item to be changed, we:&lt;ul&gt;&lt;li&gt;Ask the database what the current value is (and die if that parameter does not exist)&lt;/li&gt;&lt;li&gt;If the current and new value are the same, do nothing&lt;/li&gt;&lt;li&gt;Otherwise, open (and flock) the configuration file and change the parameter&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;If no changes were made, exit&lt;/li&gt; &lt;li&gt;Otherwise, close the configuration file&lt;/li&gt; &lt;li&gt;Figure out the Postgres PID and send it a HUP signal&lt;/li&gt;&lt;li&gt;Reconnect to the database and confirm each change has taken effect&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;By default, it adds a comment after the changed value as well, to help in tracking down who made the change. A diff of the postgresql.conf file after running the example above produces:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
diff -r1.1 postgresql.conf
499c499
&lt; random_page_cost = 4
---
&gt; random_page_cost = 2 ## changed by modify_postgres_conf.pl on Wed Aug 10 13:31:34 2011
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The addition of the comment can be stopped by added a --no-comment argument. If the script runs successfully, it also returns two items of information: the size and name of the current Postgres log file. This is useful so you can know exactly where in the log this change took place. Note that this only works for items that are already explicitly set in your configuration file. However, &lt;a href="http://blog.endpoint.com/2010/09/postgres-configuration-best-practices.html"&gt;as discussed before&lt;/a&gt;, you should already have all the items that you may possibly change explicitly listed out at the bottom of the file already. Whitespace is preserved as well, for those (like me) who like to keep things lined up neatly inside the file (see examples in the link above).&lt;/p&gt;&lt;p&gt;Here are some more examples of the script in action:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
&lt;span class="p"&gt;$&lt;/span&gt; ./modify_postgres_conf.pl --pgconf /etc/postgresql/9.0/main/postgresql.conf --change random_page_cost=2
&lt;span class="t"&gt;114991 /var/log/postgres/postgres-2011-08-10.log&lt;/span&gt;

&lt;span class="p"&gt;$&lt;/span&gt; ./modify_postgres_conf.pl --pgconf /etc/postgresql/9.0/main/postgresql.conf --change random_page_cost=2
&lt;span class="t"&gt;No change made: value of "random_page_cost" is already 2&lt;/span&gt;

&lt;span class="p"&gt;$&lt;/span&gt; ./modify_postgres_conf.pl --pgconf /etc/postgresql/9.0/main/postgresql.conf \
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; --change random_page_cost=2 \
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; --change log_statement=ddl \
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; --change log_min_duration_statement=100

&lt;span class="t"&gt;No change made: value of "random_page_cost" is already 2
118459 /var/log/postgres/postgres-2011-08-10.log&lt;/span&gt;

&lt;span class="p"&gt;$&lt;/span&gt; ./modify_postgres_conf.pl --pgconf /etc/postgresql/9.0/main/postgresql.conf \
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; --change default_statitics_target=200 --no-comment
&lt;span class="t"&gt;There is no Postgres variable named "default_statitics_target"!&lt;/span&gt;

&lt;span class="p"&gt;$&lt;/span&gt; ./modify_postgres_conf.pl --pgconf /etc/postgresql/9.0/main/postgresql.conf \
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; --change default_statistics_target=200 --no-comment
&lt;span class="t"&gt;123396 /var/log/postgres/postgres-2011-08-10.log&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Note that we make no attempt to automatically check changes in to version control: as you will see in an upcoming blog post on a real-life use case, such a checkin is usually not wanted, as we are making temporary changes.&lt;/p&gt;&lt;p&gt;This is a fairly simple Perl script, but I thought I would put it out there in the hopes of helping others out (and preventing the reinventing of wheels). Of course, if you find a bug or want to write a patch for it, those are welcome additions at any time! The code can be &lt;a href="https://github.com/bucardo/modify_postgres_config"&gt;found on github&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
git clone git://git@github.com:bucardo/modify_postgres_config.git
&lt;/span&gt;&lt;/pre&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-4712308836071838423?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/4712308836071838423/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=4712308836071838423' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/4712308836071838423'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/4712308836071838423'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/08/changing-postgresqlconf-from-script.html' title='Changing postgresql.conf from a script'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-htnFkm8LBBw/TkLHdC0MeaI/AAAAAAAAAHY/GoSvUtR1xmk/s72-c/elephant_play.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-3133705151446832271</id><published>2011-08-02T13:00:00.003-04:00</published><updated>2011-08-03T10:47:02.494-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open-source'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>Debian Postgres readline psql problem and the solutions</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-ptd7ztX60E0/TjgnEa9v8lI/AAAAAAAAAGQ/95aD5r4oepg/s1600/psql_readline.gif"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 208px;" src="http://3.bp.blogspot.com/-ptd7ztX60E0/TjgnEa9v8lI/AAAAAAAAAGQ/95aD5r4oepg/s320/psql_readline.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5636297890565452370" /&gt;&lt;/a&gt;
&lt;p&gt;There was a bit of &lt;a href="http://petereisentraut.blogspot.com/2011/02/squeeze-postgresql-broken.html"&gt;a controversy&lt;/a&gt; back in February as Debian decided to replace libreadline with libedit, which affected a number of apps, the most important of which for Postgres people is the psql utility. They did this because psql links to both OpenSSL and readline, and although psql is compatible with both, they are not compatible with each other!&lt;/p&gt;
&lt;p&gt;By compatible, I mean that the licenses they use (OpenSSL and readline) are not, in one strict interpretation, allowed to be used together. Debian attempts to live by the letter and spirit of the law as close as possible, and thus determined that they could not bundle both together. Interestingly, Red Hat does still ship psql using OpenSSL and readline; apparently their lawyers reached a different conclusion. Or perhaps they, as a business, are being more pragmatic than strictly legal, as it's very unlikely there would be any consequence for violating the licenses in this way.&lt;/p&gt;
&lt;p&gt;While libreadline (the library for &lt;a href="http://en.wikipedia.org/wiki/GNU_readline"&gt;GNU readline&lt;/a&gt;) is a feature rich, standard, mature, and widely used library, &lt;a href="http://www.cs.utah.edu/~bigler/code/libedit.html"&gt;libedit&lt;/a&gt; (sadly) is not as developed and has some important bugs and shortcomings (including no home page, apparently, and no Wikipedia page!). This resulted in frustration for many Debian users, who found that their command-line history commands in psql no longer worked, and worse, psql no longer supported non-ASCII input! Since I came across this problem recently on a client machine, I thought I would lay out the current solutions.&lt;/p&gt;

&lt;p&gt;The first and easiest solution is to simply upgrade. Debian has made a "workaround" by forcing psql to use the readline library when it is invoked.&lt;/p&gt;

&lt;p&gt;The next best solution, for those rare cases when you cannot upgrade, is to apply Debian's solution yourself by patching the 'pg_wrapper' program that Debian uses. In order to support running different versions of Postgres on the same box in a sane and standard fashion, Debian uses some wrapper scripts around some of the Postgres command-line utilities such as psql. Thus, the psql command in /usr/bin/psql is actually a symlink to the shell script pg_wrapper, which parses some arguments and then calls the &lt;strong&gt;actual&lt;/strong&gt; psql binary, which is no longer in the default path. So, to apply the Debian fix, just patch your pg_wrapper file like so:&lt;/p&gt;

&lt;pre&gt;&lt;span class="c"&gt;
*** pg_wrapper  2011/07/18 03:46:49     1.1
--- pg_wrapper  2011/07/18 03:48:23
***************
*** 94,100 ****
  }
  
  error 'Invalid PostgreSQL cluster version' unless -d "/usr/lib/postgresql/$version";
! my $cmd = get_program_path (((split '/', $0)[-1]), $version);
  error 'pg_wrapper: invalid command name' unless $cmd;
  unshift @ARGV, $cmd;
  exec @ARGV;
--- 94,110 ----
  }
  
  error 'Invalid PostgreSQL cluster version' unless -d "/usr/lib/postgresql/$version";
! my $cmdname = (split '/', $0)[-1];
! my $cmd = get_program_path ($cmdname, $version);
! 
! # libreadline is a lot better than libedit, so prefer that                                                                  
! if ($cmdname eq 'psql') {
!     my @readlines = sort(&lt;/lib/libreadline.so.*&gt;);
!     if (@readlines) {
!       $ENV{'LD_PRELOAD'} = ($ENV{'LD_PRELOAD'} or '') . ':' . $readlines[-1];
!     }
! }
! 
  error 'pg_wrapper: invalid command name' unless $cmd;
  unshift @ARGV, $cmd;
  exec @ARGV;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;As you can see, what Debian has done is set the LD_PRELOAD environment variable to point to the libreadline shared object, which means that when psql is started, it uses the libreadline library instead of libedit. This is great news for Debian users. I'm unconvinced of how "legal" this is per Debian's standards, but then I'm in the camp that think they are interpreting all the licensing around this in the wrong way, and should have just left libreadline alone.&lt;/p&gt;

&lt;p&gt;The second best solution, after patching pg_wrapper, is to simply define LD_PRELOAD yourself, either globally or per user.&lt;/p&gt;

&lt;p&gt;Another solution is to use the 'rlwrap' program, which is a wrapper around some arbitrary program (in this case, psql) which routes the user input through readline. So a quick alias would be:&lt;/p&gt;

&lt;pre&gt;&lt;span class="c"&gt;
alias p='rlwrap psql --no-readline'
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;(Yes, we could also use -n, but it's an alias and thus we don't have to type it out each time, so it's better to be more verbose). The rlwrap solution is a quick hack, and I do not recommend it, as it still leaves out many psql features, such as autocompletion and ctrl-c support.&lt;/p&gt;

&lt;p&gt;All of this is not strictly Debian's fault. If you read &lt;a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=608442"&gt;the various Debian bug reports&lt;/a&gt; as well as some of the &lt;a href="http://postgresql.1045698.n5.nabble.com/Debian-readline-libedit-breakage-td3380317.html"&gt;Postgres mailing list threads&lt;/a&gt; about this topic, you will find there is plenty of finger pointing going around. It seems to me the least guilty party here is readline itself, whose only fault is that it is GPL and not a better license ;). Debian should take a little blame, both for being too strict in what is obviously a very uncharted legal licensing mess, and for making this change so quickly without any announcement and apparently without realizing how many things would break. The worst offender appears to be OpenSSL, which apparently is being stubborn about changing its license to allow linking with the GPL readline. I'll throw a little bit of blame towards libedit as well, merely for its inability to keep up with 20th century ideas like Unicode (because whose database doesn't need more 麟?).&lt;/p&gt;

&lt;p&gt;The current Debian "solution" has stilled the waters a little bit, but we (Postgres) really need a long-term solution. Or solutions, as the case may be. As with &lt;a href="http://blog.endpoint.com/2011/05/postgres-bug-tracking-help-wanted.html"&gt;my previous post&lt;/a&gt;, the big question there is "who shall put the bell on the cat"? I'd like to see Debian itself fund some work into improving libedit, since they are strongly encouraging use of it over libreadline. That's solution one: improve libedit such that it becomes a decent readline replacement. This is nice because as great as libreadline is, it's one of the only pieces of Postgres that used the GPL, and it would be nice to get rid of it for that reason alone (the other big one is PostGIS).&lt;/p&gt;

&lt;p&gt;Another solution is to replace &lt;a href="http://www.openssl.org/"&gt;OpenSSL&lt;/a&gt;, since they apparently are never going to change their license, despite it being in everyone's best interest. &lt;a href="http://www.gnu.org/software/gnutls/"&gt;GnuTLS&lt;/a&gt; is an oft-mentioned replacement, which seems to be production ready, unlike libedit. The problem here is that psql has a lot of "openssl-isms" in the code. However, that is something that can be accomplished by the Postgres community.&lt;/p&gt;&lt;p&gt;Another option is to get readline to make an exception so it can play nicely with OpenSSL. Not only is this unlikely to happen, I think it's a band-aid and I'd rather see the above two actions happen instead.&lt;/p&gt;

&lt;p&gt;So, in summary, there are really two ways out of this mess: fix up libedit (hello Debian community) and allow Postgres support for GnuTLS (or other non-OpenSSL system for that matter) (hello Postgres community).&lt;/p&gt;

&lt;p&gt;For those wanting to dig into this some more, Greg Smith's excellent summation in &lt;a href="http://postgresql.1045698.n5.nabble.com/Debian-readline-libedit-breakage-td3380317.html"&gt;this thread&lt;/a&gt; is a great read.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-3133705151446832271?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/3133705151446832271/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=3133705151446832271' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3133705151446832271'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3133705151446832271'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/08/debian-postgres-readline-psql-problem.html' title='Debian Postgres readline psql problem and the solutions'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-ptd7ztX60E0/TjgnEa9v8lI/AAAAAAAAAGQ/95aD5r4oepg/s72-c/psql_readline.gif' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-5682256470188158081</id><published>2011-07-14T19:39:00.006-04:00</published><updated>2011-07-18T12:56:16.756-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tools'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Announcing pg_blockinfo!</title><content type='html'>&lt;p&gt;I'm pleased to announce the initial release of &lt;a href="http://github.com/machack666/pg_blockinfo/"&gt;pg_blockinfo&lt;/a&gt;. It is a tool to examine your PostgreSQL heap data files, written in Perl.&lt;/p&gt;

&lt;p&gt;Similar in purpose to &lt;tt&gt;pg_filedump&lt;/tt&gt;, it is used to display (and soon validate) buffer-page-level information for PostgreSQL page/heap files.&lt;/p&gt;

&lt;p&gt;&lt;tt&gt;pg_blockinfo&lt;/tt&gt; aims to work in a portable and non-destructive way, using read-only "mmap", sys-level IO functions, and "unpack" in order to minimize any Perl overhead.&lt;/p&gt;

&lt;p&gt;What we buy for the compromise of writing this in Perl instead of C is two-fold:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;b&gt;portability/low impact&lt;/b&gt; — &lt;tt&gt;pg_blockinfo&lt;/tt&gt; has no other dependencies than Perl and several core Perl modules and will work in environments where you can't or won't easily install other packages or compile based on specific headers.&lt;/li&gt;

&lt;li&gt;&lt;b&gt;expressibility&lt;/b&gt; — while not currently supported in full, one of &lt;tt&gt;pg_blockinfo&lt;/tt&gt;'s future goals is to allow you to specify criteria for display of both page-level and tuple-level info.  It will allow you to define arbitrary Perl expressions to filter the objects you're looking at (i.e., pages, tuples, etc; think "grep" but on a tuple level).  It will support a DSL for querying based off of the named fields as well as allow you to supply arbitrary Perl for scanning for any criteria.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Requirements&lt;/h2&gt;

&lt;p&gt;We require a perl version with PerlIO ":mmap" support, which basically means any perl &amp;gt;= 5.8.  We do not require any non-core perl modules; currently we only use Data::Dumper and Getopt::Long for debugging and option parsing respectively, the former only when requested.&lt;/p&gt;


&lt;h2&gt;Getting pg_blockinfo&lt;/h2&gt;

&lt;p&gt;The canonical git repo for development for pg_blockinfo is located at github:&lt;/p&gt;

&lt;pre&gt;
&lt;a href="http://github.com/machack666/pg_blockinfo/"&gt;http://github.com/machack666/pg_blockinfo/&lt;/a&gt;
&lt;/pre&gt;

&lt;p&gt;For the development repo, simply run:&lt;/p&gt;

&lt;pre&gt;
$ git clone git://github.com/machack666/pg_blockinfo.git
&lt;/pre&gt;

&lt;p&gt;Or you can just grab the current script directly here:&lt;/p&gt;

&lt;pre&gt;
&lt;a href="https://raw.github.com/machack666/pg_blockinfo/master/pg_blockinfo"&gt;https://raw.github.com/machack666/pg_blockinfo/master/pg_blockinfo&lt;/a&gt;
&lt;/pre&gt;

&lt;h2&gt;Using &lt;tt&gt;pg_blockinfo&lt;/tt&gt;&lt;/h2&gt;

&lt;p&gt;To get help including available options, canonical and alternate/abbreviated names of recognized fields, range syntax:&lt;/p&gt;

&lt;pre&gt;
$ pg_blockinfo -h
&lt;/pre&gt;

&lt;p&gt;To dump all fields for all page headers for all pages in a relation:&lt;/p&gt;

&lt;pre&gt;
$ pg_blockinfo /path/to/relfile
&lt;/pre&gt;

&lt;p&gt;To include only specific fields in the output you can specify multiple &lt;tt&gt;-f&lt;/tt&gt; options and/or include multiple options per &lt;tt&gt;-f&lt;/tt&gt; argument by comma delimiting.  Field specifiers are processed in order, so only the final logical set will be included.&lt;/p&gt;

&lt;p&gt;"&lt;tt&gt;all&lt;/tt&gt;" is a special shorthand type which will expand to all known columns.  &lt;tt&gt;pg_blockinfo&lt;/tt&gt; may support other shorthand groups in the future.  When no fields are provided explicitly, "&lt;tt&gt;all&lt;/tt&gt;" is implicitly assumed.&lt;/p&gt;

&lt;p&gt;There are both positive and negative field inclusions.  An example of a positive inclusion is:&lt;/p&gt;

&lt;pre&gt;
$ pg_blockinfo /path/to/relfile -f prune_xid,tli
&lt;/pre&gt;

&lt;p&gt;This will display only the indicated fields in question for all blocks in relfile.  To include all fields *except* certain ones, prefix their name with a '&lt;tt&gt;-&lt;/tt&gt;' sign:&lt;/p&gt;

&lt;pre&gt;
$ pg_blockinfo -f -pagesize_version /path/to/relfile
&lt;/pre&gt;

&lt;p&gt;This will display all page header fields in all blocks with the exception of the &lt;tt&gt;pagesize_version&lt;/tt&gt; header field.&lt;/p&gt;

&lt;p&gt;One consequence of the way these field display options are designed (particularly going forward with additional field/tuple display options) that you could define a "view" of the column data using a shell alias, then add/remove columns/criteria by passing additional -f options to it:&lt;/p&gt;

&lt;pre&gt;
# using this as a shorthand to display just those fields
$ alias lsn='pg_blockinfo -f lsn_seq,lsn_off,tli'
$ lsn -f -tli /path/to/foo                          # remove fields from the display
$ lsn -f prune_xid /path/to/foo                     # or add to the list as well
&lt;/pre&gt;

&lt;p&gt;Similar functionality is available for selecting the specific blocks available using the range option (&lt;tt&gt;-r&lt;/tt&gt; or &lt;tt&gt;-b&lt;/tt&gt;), which lets you specify a range of blocks to look at instead of the entire file.&lt;/p&gt;

&lt;pre&gt;
$ pg_blockinfo -r 2-49 /path/to/relfile
$ pg_blockinfo -r -100 /path/to/relfile
$ pg_blockinfo -r 2,4,120-140,0xFF-0x1100 /path/to/relfile
&lt;/pre&gt;

&lt;p&gt;Range options can be provided multiple times, each with one or more comma-delimited block-range specifications.  Blocks are numbered from 0, can be provided in decimal or hexadecimal (when prefixed via &lt;tt&gt;0x&lt;/tt&gt;), and can appear singly or in a range (unbounded or unbounded) when separated by a hyphen.&lt;/p&gt;


&lt;h2&gt;Planned future features/TODO&lt;/h2&gt;

&lt;p&gt;In no particular order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dump tuples/tuple headers.&lt;/li&gt;
&lt;li&gt;better output/interpretation of bitflags.&lt;/li&gt;
&lt;li&gt;support alternate structures to allow detection/specification of different target versions of the page/tuple headers.&lt;/li&gt;
&lt;li&gt;allow querying/filtering pages/tuples.&lt;/li&gt;
&lt;li&gt;validation/sanity checking of various pages.&lt;/li&gt;
&lt;li&gt;actual extraction of ranges in the heap file.&lt;/li&gt;
&lt;li&gt;extract/dump tuples by raw ctid.&lt;/li&gt;
&lt;li&gt;allow arbitrary expressions to define powerful filtering options when querying/displaying information about the tuples/data files.&lt;/li&gt;
&lt;li&gt;detections of invalid toast tuple pointers/corrupted lz_compressed data (will require connection to theactive system catalog).&lt;/li&gt;
&lt;li&gt;detect relfile type?&lt;/li&gt;
&lt;li&gt;mvcc queries against tuples at a given arbitrarily-constructed snapshot&lt;/li&gt;
&lt;li&gt;detect xids that are invalid (i.e. map to non-existent pages in the pg_clog directory).&lt;/li&gt;
&lt;li&gt;better/shorter name?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I look forward to any feedback, patches, or other improvements/interest.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-5682256470188158081?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/5682256470188158081/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=5682256470188158081' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5682256470188158081'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5682256470188158081'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/07/announcing-pgblockinfo.html' title='Announcing pg_blockinfo!'/><author><name>David Christensen</name><uri>http://www.blogger.com/profile/14237702125131095813</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_eLhk5Eevkf8/SJtDBU4bnkI/AAAAAAAAAAU/1Gc6lO-4Egc/s1600-R/Big_superD.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-3171140769024025931</id><published>2011-06-20T10:55:00.002-04:00</published><updated>2012-02-02T16:29:12.523-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open-source'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><category scheme='http://www.blogger.com/atom/ns#' term='dbdpg'/><title type='text'>DBD::Pg UTF-8 for PostgreSQL server_encoding</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-SYtmRa2MiiY/Tf9dv7Tm-MI/AAAAAAAAAEY/7yusFveN3Vw/s1600/Tower_of_babel.gif"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 167px; height: 276px;" src="http://3.bp.blogspot.com/-SYtmRa2MiiY/Tf9dv7Tm-MI/AAAAAAAAAEY/7yusFveN3Vw/s320/Tower_of_babel.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5620313937936840898" /&gt;&lt;/a&gt;
&lt;style&gt;span.c{font-family:sans-serif;color:blue}&lt;/style&gt;
&lt;p&gt;We are preparing to make a major version bump in DBD::Pg, the Perl interface for &lt;a href="http://postgres.org"&gt;PostgreSQL&lt;/a&gt;, from the 2.x series to 3.x. This is due to a reworking of how we handle UTF-8. The change is not going to be backwards compatible, but will probably not affect many people. If you are using the pg_enable_utf8 flag, however, you definitely need to read on for the details.&lt;/p&gt;&lt;p&gt;The short version is that DBD::Pg is going return all strings from the Postgres server with the Perl utf8 flag on. The sole exception will be databases in which the server_encoding is SQL_ASCII, in which case the flag will never be turned on.&lt;/p&gt;&lt;p&gt;For backwards compatibility and fine-tuning control, there is a new attribute called &lt;b&gt;pg_utf8_strings&lt;/b&gt; that can be set at connection time to override the decision above. For example, if you need your connection to return byte-soup, non-utf8-marked strings, despite coming from a UTF-8 Postgres database, you can say:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
  my $dsn = 'dbi:Pg:dbname=foobar';
  my $dbh = DBI-&gt;connect($dsn, $dbuser, $dbpass,
    { AutoCommit =&gt; 0,
      RaiseError =&gt; 0,
      PrintError =&gt; 0,
      pg_utf8_strings =&gt; 0,
    }
  );
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Similarly, you can set pg_utf8_strings to 1 and it will force settings returned strings as utf8, even if the backend is SQL_ASCII. You should not be using SQL_ASCII of course, and certainly not forcing the strings returned from it to UTF-8. :)&lt;/p&gt;&lt;p&gt;All Perl variables (be they strings or otherwise) are actually Perl objects, with some internal attributes defined on them. One of those is the utf8 flag, which can be flipped on to indicate that the string should be treated as possibly containing multi-byte characters, or it can be left off, to indicate the string should always be treated on a byte-by-byte basis. This will affect things like the Perl &lt;b&gt;length&lt;/b&gt; function, and the Perl &lt;b&gt;\w&lt;/b&gt; regex flag. This is completely unrelated to the Perl pragma &lt;b&gt;use utf8&lt;/b&gt;, which DBD::Pg has nothing at all to do with. Have I mentioned that UTF-8, and UTF-8 in Perl in particular, can be quite confusing?&lt;/p&gt;&lt;p&gt;There are a few exceptions as to what things DBD::Pg will mark as utf8. Integers and other numbers will not, boolean values will not, and no bytea data will ever have the flag set. When in doubt, assume that it is set.&lt;/p&gt;&lt;p&gt;The old attribute, &lt;b&gt;pg_enable_utf8&lt;/b&gt;, will be deprecated, and have no effect. We thought about re-using that but it seemed clearer and cleaner to simply create a new variable (pg_utf8_strings), as the behavior has significantly changed.&lt;/p&gt;&lt;p&gt;A beta version of DBD::Pg (2.99.9_1) with these changes has been uploaded to CPAN for anyone to experiment with. Right now, none of this is set in stone, but we did want to get a working version out there to start the discussion and see how it interacts with applications that were making use of the
pg_enable_utf8 flag. You can web search for "dbdpg" and look for the "Latest Dev. Release", or jump straight to &lt;a href="http://search.cpan.org/~turnstep/DBD-Pg-2.99.9_1/"&gt;the page for DBD::Pg 2.99.9_1&lt;/a&gt;. The trailing underscore is a CPAN convention that indicates this is a development version only, and thus will not replace the latest production version (2.18.1 as of this writing).&lt;/p&gt;&lt;p&gt;As a reminder, DBD::Pg has &lt;a href="http://blog.endpoint.com/2011/06/dbdpg-moves-to-git.html"&gt;switched to using git&lt;/a&gt;, so you can follow along with the development
with:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
git clone git://bucardo.org/dbdpg.git
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;There is also a commits mailing list you can join to receive notifications of commits as they are pushed to the main repo. To sign up, send an email to &lt;b&gt;dbd-pg-changes-subscribe@perl.org&lt;/b&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-3171140769024025931?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/3171140769024025931/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=3171140769024025931' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3171140769024025931'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3171140769024025931'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/06/dbdpg-utf-8-for-postgresql.html' title='DBD::Pg UTF-8 for PostgreSQL server_encoding'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-SYtmRa2MiiY/Tf9dv7Tm-MI/AAAAAAAAAEY/7yusFveN3Vw/s72-c/Tower_of_babel.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-5654164216256352257</id><published>2011-06-14T17:07:00.001-04:00</published><updated>2011-06-14T17:10:03.387-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='dbdpg'/><title type='text'>DBD::Pg moves to git!</title><content type='html'>&lt;p&gt;Just a note to everyone that development the official &lt;tt&gt;DBD::Pg&lt;/tt&gt; DBI driver for PostgreSQL source code repository has moved from its old home in SVN to a git repository.  All development has now moved to this repo.&lt;/p&gt;

&lt;p&gt;We have imported the SVN revision history, so it's just a matter of pointing your git clients to:&lt;/p&gt;

&lt;pre&gt;
$ git clone git://bucardo.org/dbdpg.git
&lt;/pre&gt;

&lt;p&gt;For those who prefer, there is a github mirror:&lt;/p&gt;

&lt;pre&gt;
$ git clone git://github.com/bucardo/dbdpg.git
&lt;/pre&gt;

&lt;p&gt;Git is available via many package managers or by following the download links at &lt;a href="http://git-scm.com/download"&gt;http://git-scm.com/download&lt;/a&gt; for your platform.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-5654164216256352257?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/5654164216256352257/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=5654164216256352257' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5654164216256352257'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5654164216256352257'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/06/dbdpg-moves-to-git.html' title='DBD::Pg moves to git!'/><author><name>David Christensen</name><uri>http://www.blogger.com/profile/14237702125131095813</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_eLhk5Eevkf8/SJtDBU4bnkI/AAAAAAAAAAU/1Gc6lO-4Egc/s1600-R/Big_superD.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-3456007119546090758</id><published>2011-06-12T22:25:00.001-04:00</published><updated>2011-06-13T18:27:11.119-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bucardo'/><category scheme='http://www.blogger.com/atom/ns#' term='open-source'/><category scheme='http://www.blogger.com/atom/ns#' term='data-warehouse'/><category scheme='http://www.blogger.com/atom/ns#' term='nosql'/><category scheme='http://www.blogger.com/atom/ns#' term='mongodb'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>MongoDB replication from Postgres using Bucardo</title><content type='html'>&lt;style&gt;span{font-family:sans-serif;}span.p{color:red;}span.t{color:green;}span.o{font-family:monospace;color:blue;}&lt;/style&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-pLuDquV3eSA/TfOWSlbBruI/AAAAAAAAAEQ/1rv9uoGbTxs/s1600/bucardo_mongo.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 200px; height: 128px;" src="http://1.bp.blogspot.com/-pLuDquV3eSA/TfOWSlbBruI/AAAAAAAAAEQ/1rv9uoGbTxs/s200/bucardo_mongo.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5616998406288420578" /&gt;&lt;/a&gt;
&lt;p&gt;One of the features of the upcoming version of &lt;a href="http://bucardo.org/wiki/Bucardo"&gt;Bucardo&lt;/a&gt; (a replication system for the PostgreSQL RDBMS) is the ability to replicate data to things other than &lt;a href="http://postgres.org"&gt;PostgreSQL&lt;/a&gt; databases. One of those new targets is &lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt;, a non-relational 'document-based' database. (to be clear, we can only use MongoDB as a target, not as a source)&lt;/p&gt;&lt;p&gt;To see this in action, let's setup a quick example, modified from the &lt;a href="http://blog.endpoint.com/2011/06/bucardo-multi-master-for-postgresql.html"&gt;earlier blog post on running Bucardo 5&lt;/a&gt;. We will create a Bucardo instance that replicates from two Postgres master databases to a Postgres database target and a MongoDB instance target. We will start by setting up the prerequisites:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
sudo aptitude install postgresql-server \
perl-DBIx-Safe \
perl-DBD-Pg \
postgresql-contrib
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Getting Postgres up and running is left as an exercise to the reader. If you have problems, the friendly folks at #postgresql on irc.freenode.net will be able to help you out.&lt;/p&gt;&lt;p&gt;Now for the MongoDB parts. First, we need the server itself. Your distro may have it already available, in which case it's as simple as:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
aptitude install mongodb
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;For more installation information, follow the links from the &lt;a href="http://www.mongodb.org/display/DOCS/Quickstart"&gt;MongoDB Quickstart page&lt;/a&gt;. For my test box, I ended up installing from source by following the directions at the &lt;a href="http://www.mongodb.org/display/DOCS/Building+for+Linux"&gt;Building for Linux page&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Once MongoDB is installed, we will need to start it up. First, create a place for MongoDB to store its data, and then launch the mongodb process:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; mkdir /tmp/mongodata
&lt;span class="p"&gt;$ &lt;/span&gt; mongod --dbpath=/tmp/mongodata --fork --logpath=/tmp/mongo.log
&lt;span class="o"&gt;all output going to: /tmp/mongo.log
forked process: 428&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;You can perform a quick test that it is working by invoking the command-line shell for MongoDB (named "mongo" of course) Use &lt;strong&gt;quit()&lt;/strong&gt; to exit:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; mongo
&lt;span class="o"&gt;MongoDB shell version: 1.8.1
Fri Jun 10 12:45:00
connecting to: test&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt; &lt;/span&gt;quit()
&lt;span class="p"&gt;$ &lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The other piece we need is a Perl driver so that Bucardo (which is written in Perl) can talk to the MongoDB server. Luckily, there is an excellent one available on CPAN named 'MongoDB'. We started the MongoDB server before doing this step because the driver we will install needs a running MongoDB instance to pass all of its tests. The module has very good documentation available on &lt;a href="http://search.cpan.org/dist/MongoDB/"&gt;its CPAN page&lt;/a&gt;. Installation may be as easy as:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; sudo cpan MongoDB
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;If that did not work for you (case matters!), there are more detailed directions on the &lt;a href="http://www.mongodb.org/display/DOCS/Perl+Language+Center"&gt;Perl Language Center&lt;/a&gt; page.&lt;/p&gt;&lt;p&gt;Our next step is to grab the latest Bucardo, install it, and create a new Bucardo instance. See the &lt;a href="http://blog.endpoint.com/2011/06/bucardo-multi-master-for-postgresql.html"&gt;previous blog post&lt;/a&gt; for more details about each step.&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;git clone git://bucardo.org/bucardo.git
&lt;span class="o"&gt;Initialized empty Git repository...&lt;/span&gt;

&lt;span class="p"&gt;$ &lt;/span&gt;cd bucardo
&lt;span class="p"&gt;$ &lt;/span&gt;perl Makefile.PL
&lt;span class="o"&gt;Checking if your kit is complete...
Looks good
Writing Makefile for Bucardo&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;make
&lt;span class="o"&gt;cp bucardo.schema blib/share/bucardo.schema
cp Bucardo.pm blib/lib/Bucardo.pm
cp bucardo blib/script/bucardo
/usr/bin/perl -MExtUtils::MY -e 'MY-&amp;gt;fixin(shift)' -- blib/script/bucardo
Manifying blib/man1/bucardo.1pm
Manifying blib/man3/Bucardo.3pm&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;sudo make install
&lt;span class="o"&gt;Installing /usr/local/lib/perl5/site_perl/5.10.0/Bucardo.pm
Installing /usr/local/share/bucardo/bucardo.schema
Installing /usr/local/bin/bucardo
Installing /usr/local/share/man/man1/bucardo.1pm
Installing /usr/local/share/man/man3/Bucardo.3pm
Appending installation info to /usr/lib/perl5/5.10.0/i386-linux-thread-multi/perllocal.pod&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;sudo mkdir /var/run/bucardo
&lt;span class="p"&gt;$ &lt;/span&gt;sudo chown $USER /var/run/bucardo
&lt;span class="p"&gt;$ &lt;/span&gt;bucardo install
&lt;span class="o"&gt;This will install the bucardo database into an existing Postgres cluster.
...
Installation is now complete.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Now we create some test databases and populate with pgbench:&lt;p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;psql -c 'create database btest1'
&lt;span class="o"&gt;CREATE DATABASE&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;pgbench -i btest1
&lt;span class="o"&gt;NOTICE:  table "pgbench_branches" does not exist, skipping
...
creating tables...
10000 tuples done.
20000 tuples done.
...
100000 tuples done.&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;psql -c 'create database btest2 template btest1'
&lt;span class="o"&gt;CREATE DATABASE&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;psql -c 'create database btest3 template btest1'
&lt;span class="o"&gt;CREATE DATABASE&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;psql btest3 -c 'truncate table pgbench_accounts'
&lt;span class="o"&gt;TRUNCATE TABLE&lt;/span&gt;

&lt;span class="p"&gt;$ &lt;/span&gt;bucardo add db t1 dbname=btest1
&lt;span class="o"&gt;Added database "t1"&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;bucardo add db t2 dbname=btest2
&lt;span class="o"&gt;Added database "t2"&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;bucardo add db t3 dbname=btest3
&lt;span class="o"&gt;Added database "t3"&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt;bucardo list dbs
&lt;span class="o"&gt;Database: t1  Status: active  Conn: psql -p 5432 -U bucardo -d btest1
Database: t2  Status: active  Conn: psql -p 5432 -U bucardo -d btest2
Database: t3  Status: active  Conn: psql -p 5432 -U bucardo -d btest3&lt;/span&gt;

&lt;span class="p"&gt;$ &lt;/span&gt;bucardo add tables pgbench_accounts pgbench_branches pgbench_tellers herd=therd
&lt;span class="o"&gt;Created herd "therd"
Added table "public.pgbench_accounts"
Added table "public.pgbench_branches"
Added table "public.pgbench_tellers"&lt;/span&gt;

&lt;span class="p"&gt;$ &lt;/span&gt;bucardo list tables
&lt;span class="o"&gt;Table: public.pgbench_accounts  DB: t1  PK: aid (int4)
Table: public.pgbench_branches  DB: t1  PK: bid (int4)
Table: public.pgbench_tellers   DB: t1  PK: tid (int4)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The next step is to add in our MongoDB instance. The syntax is the same as the "add db" above, but we also tell it the type of database, as it is not the default of "postgres". We will also assign an arbitrary database name, "btest1", the same as the others. Everything else (such as the port and host) is default, so all we need to say is:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; bucardo add db m1 dbname=btest1 type=mongo
&lt;span class="o"&gt;Added database "m1"&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; bucardo list dbs
&lt;span class="o"&gt;Database: m1  Type: mongo     Status: active  
Database: t1  Type: postgres  Status: active  Conn: psql -p 5432 -U bucardo -d btest1
Database: t2  Type: postgres  Status: active  Conn: psql -p 5432 -U bucardo -d btest2
Database: t3  Type: postgres  Status: active  Conn: psql -p 5432 -U bucardo -d btest3&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Next we group our databases together and assign them roles:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; bucardo add dbgroup tgroup  t1:source  t2:source  t3:target  m1:target
&lt;span class="o"&gt;Created database group "tgroup"
Added database "t1" to group "tgroup" as source
Added database "t2" to group "tgroup" as source
Added database "t3" to group "tgroup" as target
Added database "m1" to group "tgroup" as target&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Note that "target" is the default action, so we could shorten that to:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; bucardo add dbgroup tgroup t1:source  t2  t3  m1
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;However, I think it is best to be explicit, even if it does (incorrectly) hint that m1 could be anything *other* than a target. :)&lt;/p&gt;&lt;p&gt;We are almost ready to go. The final step is to create a sync (a basic replication event in Bucardo), then we can start up Bucardo, put some test data into the master databases, and 'kick' the sync:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; bucardo add sync mongotest  herd=therd  dbs=tgroup  ping=false
&lt;span class="o"&gt;Added sync "mongotest"&lt;/span&gt;

&lt;span class="p"&gt;$ &lt;/span&gt; bucardo start
&lt;span class="o"&gt;Checking for existing processes
Starting Bucardo&lt;/span&gt;

&lt;span class="p"&gt;$ &lt;/span&gt; pgbench -t 10000 btest1
&lt;span class="o"&gt;starting vacuum...end.
transaction type: TPC-B (sort of)
number of transactions actually processed: 10000/10000
...
tps = 503.300595 (excluding connections establishing)&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; pgbench -t 10000 btest2
&lt;span class="o"&gt;number of transactions actually processed: 10000/10000
...
tps = 408.059368 (excluding connections establishing)&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; bucardo kick mongotest
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;We'll give it a few seconds to replicate those changes (it took 18 seconds on my test box), and then check the output of bucardo status:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; bucardo status
&lt;span class="o"&gt;PID of Bucardo MCP: 3317
 Name        State    Last good    Time    Last I/D/C    Last bad    Time  
===========+========+============+=======+=============+===========+=======
 mongotest | Good   | 21:57:47   | 11s   | 6/36234/898 | none      |&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Looks good, but what about the data in MongoDB? Let's get some counts from the Postgres masters and slave, and then look at the data inside MongoDB with the mongo command-line client:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; psql btest1 -c 'SELECT count(*) FROM pgbench_accounts'
&lt;span class="o"&gt;100000&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; psql btest2 -c 'SELECT count(*) FROM pgbench_accounts'
&lt;span class="o"&gt;100000&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; psql btest3 -c 'SELECT count(*) FROM pgbench_accounts'
&lt;span class="o"&gt;18106&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; psql btest1 -qc 'SELECT min(abalance),max(abalance) FROM pgbench_accounts'
&lt;span class="o"&gt;-12071 | 13010&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; psql btest2 -qc 'SELECT min(abalance),max(abalance) FROM pgbench_accounts'
&lt;span class="o"&gt;-12071 | 13010&lt;/span&gt;
&lt;span class="p"&gt;$ &lt;/span&gt; psql btest3 -qc 'SELECT min(abalance),max(abalance) FROM pgbench_accounts'
&lt;span class="o"&gt;-12071 | 13010&lt;/span&gt;

&lt;span class="p"&gt;$ &lt;/span&gt; mongo btest1
&lt;span class="o"&gt;MongoDB shell version: 1.8.1
Fri Jun 10 12:46:00
connecting to: btest1&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt; &lt;/span&gt;show collections
&lt;span class="o"&gt;bucardo_status
pgbench_accounts
pgbench_branches
pgbench_tellers
system.indexes&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt; &lt;/span&gt; db.pgbench_accounts.count()
&lt;span class="o"&gt;18106&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt; &lt;/span&gt; db.pgbench_accounts.find().sort({abalance:1}).limit(1).next()
&lt;span class="o"&gt;{
  "_id" : ObjectId("4df39bcb8795839660001de5"),
  "abalance" : -12071,
  "aid" : 84733,
  "bid" : 1,
  "filler" : "               "
}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt; &lt;/span&gt;db.pgbench_accounts.find().sort({abalance:-1}).limit(1).next()
&lt;span class="o"&gt;{
  "_id" : ObjectId("4df39bd08795839660002fb0"),
  "abalance" : 13010,
  "aid" : 45500,
  "bid" : 1,
  "filler" : "               "
}&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Why the difference in counts? We only started replicating after we populated the Postgres tables on the master databases with 100,000 rows, so the eighteen thousand is the number of rows that was changed during the subsequent pgbench run. (Note that pgbench uses randomness, so your numbers will be different than the above). In the future Bucardo will support the &lt;a href="http://bucardo.org/wiki/Onetimecopy"&gt;"onetimecopy"&lt;/a&gt; feature for MongoDB, but until then we can fully populate the pgbench_accounts collection simply by "touching' all the records on one of the masters:&lt;/p&gt;&lt;pre&gt;&lt;span class="o"&gt;
&lt;span class="p"&gt;$&lt;/span&gt; psql btest1 -c 'UPDATE pgbench_accounts SET aid=aid'
&lt;span class="o"&gt;UPDATE 100000&lt;/span&gt;
&lt;span class="p"&gt;$&lt;/span&gt; bucardo kick mongotest
&lt;span class="o"&gt;Kicked sync mongotest&lt;/span&gt;
&lt;span class="p"&gt;$&lt;/span&gt; echo 'db.pgbench_accounts.count()' | mongo btest1
&lt;span class="o"&gt;MongoDB shell version: 1.8.1
Fri Jun 10 12:47:00
connecting to: btest1
&amp;gt; 100000
&amp;gt; bye
&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;A nice feature of MongoDB is its autovivification ability (aka dynamic schemas), which means unlike Postgres you do not have to create your tables first, but can simply ask MongoDB to do an insert, and it will create the table (or, in mongospeak, the collection) automatically for you.&lt;/p&gt;&lt;p&gt;Because MongoDB has no concept of transactions, and because Bucardo does not update, but does deletes plus inserts (for reasons I'll not get into today), there is one more trick Bucardo does when replicating to a MongoDB instance. A collection named 'bucardo_status' is created and updated at the start and the end of a sync (a replication event). Thus, your application can pause if it sees this table has a 'started' value, and wait until it sees 'complete' or 'failed'. Not foolproof by any means, but better than nothing :) You should, of course, carefully consider the way your app and Bucardo will coordinate things.&lt;/p&gt;&lt;p&gt;Feedback from Postgres or MongoDB folk is much appreciated: there are probably some rough edges, but as you can see from above, the basics are there are working. Feel free to email the &lt;a href="https://mail.endcrypt.com/mailman/listinfo/bucardo-general"&gt;bucardo-general mailing list&lt;/a&gt; or make a feature request / bug report on the &lt;a href="http://bucardo.org/bugzilla/"&gt;Bucardo Bugzilla page&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-3456007119546090758?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/3456007119546090758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=3456007119546090758' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3456007119546090758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3456007119546090758'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/06/mongodb-replication-from-postgres-using.html' title='MongoDB replication from Postgres using Bucardo'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-pLuDquV3eSA/TfOWSlbBruI/AAAAAAAAAEQ/1rv9uoGbTxs/s72-c/bucardo_mongo.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-375977212280601594</id><published>2011-06-06T18:02:00.003-04:00</published><updated>2011-08-02T11:29:00.809-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='Bucardo'/><category scheme='http://www.blogger.com/atom/ns#' term='open-source'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Bucardo multi-master for PostgreSQL</title><content type='html'>&lt;a href="http://2.bp.blogspot.com/-3LqGj3WqVEs/Te05V14HcsI/AAAAAAAAAEI/bYzxY9X-IR0/s1600/bucardo_pic.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;/a&gt;&lt;div style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 194px; height: 200px;"&gt;&lt;a href="http://2.bp.blogspot.com/-3LqGj3WqVEs/Te05V14HcsI/AAAAAAAAAEI/bYzxY9X-IR0/s1600/bucardo_pic.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://2.bp.blogspot.com/-3LqGj3WqVEs/Te05V14HcsI/AAAAAAAAAEI/bYzxY9X-IR0/s200/bucardo_pic.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5615207357803360962" /&gt;&lt;/a&gt;The original Bucardo&lt;/div&gt;&lt;style&gt;span{font-family:sans-serif;}span.t{color:green;}span.o{font-family:monospace;color:blue;}&lt;/style&gt;&lt;p&gt;The next version of &lt;a href="http://bucardo.org/wiki/Bucardo"&gt;Bucardo&lt;/a&gt;, a replication system for &lt;a href="http://postgres.org/"&gt;Postgres&lt;/a&gt;, is almost complete. The scope of the changes required a major version bump, so this Bucardo will start at version 5.0.0. Much of the innards was rewritten, with the following goals:&lt;/p&gt; &lt;h3&gt;Multi-master support&lt;/h3&gt;&lt;p&gt;Where "multi" means "as many as you want"! There are no more pushdelta (master to slaves) or swap (master to master) syncs: there is simply one sync where you tell it which databases to use, and what role they play. See examples below.&lt;/p&gt;&lt;h3&gt;Ease of use&lt;/h3&gt;&lt;p&gt;The &lt;strong&gt;bucardo&lt;/strong&gt; program (previously known as 'bucardo_ctl') has been greatly improved, making all the administrative tasks such as adding tables, creating syncs, etc. much easier.&lt;/p&gt;&lt;h3&gt;Performance&lt;/h3&gt;&lt;p&gt;Much of the underlying architecture was improved, and sometimes rewritten, to make things go much faster. Most striking is the difference between the old multi-master "swap syncs" and the new method, which has been described as "orders of magnitudes" faster by early testers. We use async database calls whenever possible, and no longer have the bottleneck of a single large bucardo_delta table.&lt;/p&gt;&lt;h3&gt;Improved logging&lt;/h3&gt;&lt;p&gt;Not only are more details provided, there is now the ability to control how verbose the logs are. Just set the log_level parameter to terse, normal, verbose, or debug. Those who had busy systems, which was the equivalent of a 'debug' firehose, will really appreciate this.&lt;/p&gt;&lt;h3&gt;Different targets&lt;/h3&gt;&lt;p&gt;Who says your slave (target) databases need to be Postgres? In addition to the ability to write text SQL files (for say, shipping to a different system), you can have Bucardo push to other systems as well. Stay tuned for more details on this. (Update: there is a &lt;a href="http://blog.endpoint.com/2011/06/mongodb-replication-from-postgres-using.html"&gt;blog post about using MongoDB as a target&lt;/a&gt;)&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;This new version is not quite at beta yet, but you can try out a demo of multi-master on Postgres quie easily. Let's see if we can do it in ten steps.&lt;/p&gt;&lt;h4&gt;I. Download all prerequisites&lt;/h4&gt;&lt;p&gt;To run Bucardo, you will need a Postgres database (obviously), the DBIx::Safe module, the DBI and DBD::Pg modules, and (for the purposes of this demo) the pgbench utility. Systems vary, but on aptitude-based systems, one can grab all of the above like this:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
aptitude install postgresql-server \
perl-DBIx-Safe \
perl-DBD-Pg \
postgresql-contrib
&lt;/span&gt;&lt;/pre&gt;&lt;h4&gt;II. Grab the latest Bucardo&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
git clone git://bucardo.org/bucardo.git
&lt;/span&gt;&lt;/pre&gt;&lt;h4&gt;III. Install the program&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
cd bucardo
perl Makefile.PL
make
sudo make install
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;You can ignore any errors that come up about ExtUtils::MakeMaker not being recent.&lt;/p&gt;&lt;h4&gt;IV. Setup an instance of Bucardo&lt;/h4&gt;&lt;p&gt;This step assumes there is a running Postgres available to connect to.&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
sudo mkdir /var/run/bucardo
sudo chown $USER /var/run/bucardo
bucardo install
&lt;/span&gt;&lt;/pre&gt;&lt;h4&gt;V. Use the pgbench program to create some test tables&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
psql -c 'CREATE DATABASE btest1'
pgbench -i btest1
psql -c 'CREATE DATABASE btest2 TEMPLATE btest1'
psql -c 'CREATE DATABASE btest3 TEMPLATE btest1'
psql -c 'CREATE DATABASE btest4 TEMPLATE btest1'
psql -c 'CREATE DATABASE btest5 TEMPLATE btest1'
&lt;/span&gt;&lt;/pre&gt;&lt;h4&gt;VI. Tell Bucardo about the databases and tables you are going to use&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo add db t1 dbname=btest1
bucardo add db t2 dbname=btest2
bucardo add db t3 dbname=btest3
bucardo add db t4 dbname=btest4
bucardo add db t5 dbname=btest5
bucardo list dbs

bucardo add table pgbench_accounts pgbench_branches pgbench_tellers herd=therd
bucardo list tables
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;A herd is simply a logical grouping of tables. We did not add the other pgbench table, pgbench_history, because it has no primary key or unique index.&lt;/p&gt;&lt;h4&gt;VII. Group the databases together and set their roles&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo add dbgroup tgroup t1:source t2:source t3:source t4:source t5:target
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;We've grouped all five databases together, and made four of them masters (aka source), and one of them a slave (aka target). You can any combination of master and slaves you want, as long as there is at least one master.&lt;/p&gt;&lt;h4&gt;VII. Create the Bucardo sync&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;bucardo add sync foobar herd=therd dbs=tgroup ping=false
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Here we simply create a new sync, which is a controllable replication event, telling it which tables we want to replicate, and which databases we are going to use. We also set ping to false, which means that we will not create triggers to automatically fire off replication on any changes, but will do it manually. In a real world scenario, you generally do want those triggers, or want to set Bucardo to check periodically.&lt;/p&gt;&lt;h4&gt;VIII. Start up Bucardo&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo start
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;If all went well, you should see some information in the log.bucardo file in the current directory.&lt;/p&gt;&lt;h4&gt;IX. Make a bunch of changes on all the source databases.&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
pgbench -t 10000 btest1
pgbench -t 10000 btest2
pgbench -t 10000 btest3
pgbench -t 10000 btest4
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Here, we've told pgbench to run ten thousand transactions against each of the first four databases. Triggers on these tables have captured the changes.&lt;/p&gt;&lt;h4&gt;X. Kick off the sync and watch the fun.&lt;/h4&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo kick foobar
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;You can now tail the log.bucardo file to see the fun, or simply run:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo status
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;...to see what it is doing, and the final counts when we are done. Don't forget to stop Bucardo when you are done testing:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo stop
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The output of bucardo status, after the sync has completed, should look like this:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo status
&lt;/span&gt;&lt;span class="o"&gt;
Name     State    Last good    Time    Last I/D/C           Last bad    Time
========+========+============+=======+====================+===========+=======
foobar | Good   | 17:58:37   | 3m2s  | 131836/131836/4785 | none      |
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Here we see that this syncs has never failed ("Last bad"), the time of day of the last good run, how long ago it was from right now (3 minutes and 2 seconds), as well as details of the last successful run. Last I/D/C stands for number of inserts, deletes, and collisions across all databases for this syncs. This is just an overview of all syncs at a high level, but we can also give status an argument of a sync name to see more details like so:&lt;/p&gt;&lt;pre&gt;&lt;span class="t"&gt;
bucardo status foobar
&lt;/span&gt;&lt;span class="o"&gt;
Last good                       : Jun 02, 2011 17:57:47 (time to run: 42s)
Rows deleted/inserted/conflicts : 131,836 / 131,836 / 4,785
Sync name                       : foobar
Current state                   : Good
Source herd/database            : therd / t1
Tables in sync                  : 3
Status                          : active
Check time                      : none
Overdue time                    : 00:00:00
Expired time                    : 00:00:00
Stayalive/Kidsalive             : yes / yes
Rebuild index                   : 0
Ping                            : no
Onetimecopy                     : 0
Post-copy analyze               : Yes
Last error:                     :
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;This gives us a little more information about the sync itself, as well as another important metric, how long the sync itself took to run, in this case, 42 seconds. That particular metric might make its way back to the overall "status" view above. Try things out and &lt;a href="http://bucardo.org/bugzilla/"&gt;help us find bugs&lt;/a&gt; and improve Bucardo!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-375977212280601594?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/375977212280601594/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=375977212280601594' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/375977212280601594'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/375977212280601594'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/06/bucardo-multi-master-for-postgresql.html' title='Bucardo multi-master for PostgreSQL'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-3LqGj3WqVEs/Te05V14HcsI/AAAAAAAAAEI/bYzxY9X-IR0/s72-c/bucardo_pic.jpg' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-582534517388227535</id><published>2011-05-31T18:08:00.001-04:00</published><updated>2011-05-31T18:08:44.937-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><title type='text'>Saving time with generate_series()</title><content type='html'>&lt;p&gt;I was giving a presentation once on various SQL constructs, and, borrowing
an analogy I'd seen elsewhere, described PostgreSQL's generate_series()
function as something you might use in places where, in some other language,
you'd use a FOR loop. One attendee asked, "So, why would you ever want a FOR
loop in a SQL query?" A fair question, and one that I answered using examples
later in the presentation. Another such example showed up recently on a
client's system where the &lt;a
href="http://en.wikipedia.org/wiki/Object-Relational_Mapping"&gt;ORM&lt;/a&gt; was
trying to be helpful, and chose a really bad query to do it.&lt;/p&gt;

&lt;p&gt;The application in question was trying to display a list of records, and
allow the user to search through them, modify them, filter them, etc. Since
the ORM knew users might filter on a date-based field, it wanted to present
a list of years containing valid records. So it did this:

&lt;pre&gt;
SELECT DISTINCT DATE_TRUNC('year', some_date_field) FROM some_table;
&lt;/pre&gt;

&lt;p&gt;In fairness to the ORM, this query wouldn't be so bad if &lt;i&gt;some_table&lt;/i&gt;
only had a few hundred or thousand rows. But in our case it has several tens
of millions. This query results in a sequential scan of each of those records,
in order to build a list of, as it turns out, about fifty total years. There
must be a better way...&lt;/p&gt;

&lt;p&gt;The better way we chose turns out to be, in essence, this: find the years of
the maximum and minimum date values in the date field, construct a list of all
years between the minimum and maximum, inclusive, and see which ones exist in
the table. This date field is indexed, so finding its maximum and minimum is
very fast:&lt;/p&gt;

&lt;pre&gt;
SELECT
    DATE_TRUNC('year', MIN(some_date_field)) AS mymin,
    DATE_TRUNC('year', MAX(some_date_field)) AS mymax
FROM some_table
&lt;/pre&gt;

&lt;p&gt;Here's where the FOR loop idea comes in, though it's probably better
described as an "iterator" rather than a FOR loop specifically: for each year
between &lt;i&gt;mymin&lt;/i&gt; and &lt;i&gt;mymax&lt;/i&gt; inclusive, I want a database row. The
analogy may not hold terribly well, but the technique is very useful, because
it will create a list of all the possible years I might be interested in, and
it will do it with just two scans of the &lt;i&gt;some_date_field&lt;/i&gt; index, rather
than a sequential scan of millions of rows.&lt;/p&gt;

&lt;pre&gt;
SELECT
    generate_series(mymin::INTEGER, mymax::INTEGER) AS yearnum
FROM (
    SELECT
        DATE_TRUNC('year', MIN(some_date_field)) AS mymin,
        DATE_TRUNC('year', MAX(some_date_field)) AS mymax
    FROM some_table
) minmax_tbl
&lt;/pre&gt;

&lt;p&gt;Now I simply have to convert these values to years, and see which ones
exist in the underlying table:&lt;/p&gt;

&lt;pre&gt;
SELECT
    yearbegin::timestamptz
FROM
    (
        SELECT
            yearnum * INTERVAL '1 year' + '0000-01-01'::date AS yearbegin
        FROM (
            SELECT
                generate_series(mymin::INTEGER, mymax::INTEGER) AS yearnum
            FROM (
                SELECT
                    DATE_TRUNC('year', MIN(some_date_field)) AS mymin,
                    DATE_TRUNC('year', MAX(some_date_field)) AS mymax
                FROM some_table
        ) yearnum_tbl
    ) beginend_tbl
WHERE
    EXISTS (
        SELECT 1 FROM some_table
        WHERE
            some_date_field BETWEEN yearbegin AND yearbegin + INTERVAL '1 year'
    )
ORDER BY yearbegin ASC
;
&lt;/pre&gt;

&lt;p&gt;As expected, this probes the &lt;i&gt;some_date_field&lt;/i&gt; index twice, to get the
maximum and minimum date values, and then once for each year between those
values. Because of some strangely-dated data in there, that means nearly
10,000 index probes, but that's still much faster than scanning the entire
table.&lt;p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-582534517388227535?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/582534517388227535/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=582534517388227535' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/582534517388227535'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/582534517388227535'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/05/saving-time-with-generateseries.html' title='Saving time with generate_series()'/><author><name>Joshua Tolley</name><uri>http://www.blogger.com/profile/08481531515300677240</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-8918052291876845100</id><published>2011-05-29T00:11:00.005-04:00</published><updated>2011-05-29T00:19:02.776-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open-source'/><category scheme='http://www.blogger.com/atom/ns#' term='community'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Postgres Bug Tracking - Help Wanted!</title><content type='html'>&lt;p&gt;Once again there is talk in the Postgres community about adopting the use of a bug tracker. The latest &lt;a href="http://postgresql.1045698.n5.nabble.com/How-can-I-check-the-treatment-of-bug-fixes-td4431752.html"&gt;thread, on pgsql-hackers&lt;/a&gt;, was started by someone asking about the status of their patch. Or rather, asking an even better meta-question about how one finds out the status of a PostgreSQL bug report or patch. Sadly, the answer is that there is no standard way, other than sending emails until someone replies one way or another. The current process works something like this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Someone finds a bug&lt;/li&gt;&lt;li&gt;They send an email to pgsql-bugs@postgresql.org &lt;strong&gt;OR&lt;/strong&gt; they use the web form, which grabs a sequential number and mails the report to pgsql-bugs@postgresql.org. Nothing else is done/stored, it just sends the email.&lt;/li&gt;&lt;li&gt;Someone replies about the bug &lt;strong&gt;OR&lt;/strong&gt; nobody replies about the bug.&lt;/li&gt;&lt;li&gt;After a fix is found, which may involve some emails on other mailing lists, someone replies that the bug is fixed on the original thread. Maybe.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;As you can see, there is some room for improvement there. Some of the most major and glaring holes in the current system:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;No way to search previous / existing bugs&lt;/li&gt;&lt;li&gt;No way to tell the status of a bug&lt;/li&gt;&lt;li&gt;No way to categorize and group bugs (per version, per platform, per component, per severity, etc.)&lt;/li&gt;&lt;li&gt;No way to know who is working on a bug&lt;/li&gt;&lt;li&gt;No way to prevent things from slipping through the cracks&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Luckily, the above problems have been solved for many many years now but a wide variety of bug tracking software. There have traditionally been three problems to getting a bug tracker working for the Postgres
project:&lt;/p&gt;&lt;h3&gt;Inertia&lt;/h3&gt;&lt;p&gt;The current system is, in a very literal sense, "good enough", so it's hard to impose the inevitable short-term pain of a new system when there always seem to be more pressing matters to attend to.&lt;/p&gt;&lt;h3&gt;Doesn't Make Julienne Fries&lt;/h3&gt;&lt;p&gt;Everyone wants a different set of features, and getting all the hackers involved to agree on even a simple subset of desired features is pretty difficult. This is sort of similar to the crusade by myself and others to get git as the replacement version control system; there were some strong voices for competing systems (e.g. mercurial).&lt;/p&gt;&lt;h3&gt;Who Will Put the Bell on the Cat?&lt;/h3&gt;&lt;p&gt;Everyone talks about the problem, and there have even been some attempts over the years to implement some sort of system, but the problem remains that setting up such a system, getting it smoothly integrated into the project's work flow, and then maintaining said system is a non-trivial task. Especially when you can't be assured of buy-in from some of the major players.&lt;/p&gt;&lt;p&gt;I'm hopeful that the recent thread indicates a slight shift of late in global acceptance of the need for a bug tracking system. The question is, which one, and who is going to take the time to write something? I'm really hoping
someone who has been lurking in the background will step up and help create something wonderful (okay, we can start with 'decent' :) Perhaps even someone with experience setting up bug tracking systems. Certainly Postgres must be one of the last major open source projects without a bug tracker; there is plenty of hard-won experience out there to be learned from. It would also be ideal if the person or persons was *not* a Postgres hacker of any sort, as taking the time to build and maintain this system would definitely take time away from their other hacking tasks. On the other hand, one could argue that a bug tracker is a vital piece of project infrastructure that is potentially as important as any other work that goes on. I certainly think so.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-8918052291876845100?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/8918052291876845100/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=8918052291876845100' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/8918052291876845100'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/8918052291876845100'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/05/postgres-bug-tracking-help-wanted.html' title='Postgres Bug Tracking - Help Wanted!'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-3264537966551469150</id><published>2011-05-19T14:37:00.012-04:00</published><updated>2011-05-19T15:47:35.473-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Only Try This At Home</title><content type='html'>&lt;div style="float:right; margin:0 0 10px 10px;cursor:pointer;width: 250px;"&gt;&lt;img src="http://joshwilliams.name/PLPython/run_server.jpg" border="0" alt="Taken by Josh 6 years to the day before the release of 9.1 beta 1" /&gt;&lt;br /&gt;&lt;small&gt;Taken by Josh 6 years to the day before the release of 9.1 beta 1&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;For the record, 9.1 is gearing up to be an awesome release.  I was tinkering and testing &lt;a href="http://www.postgresql.org/about/news.1313"&gt;PostgreSQL 9.1 Beta 1&lt;/a&gt; (... You are beta testing, too, right?) ... and some of the new &lt;a href="http://developer.postgresql.org/pgdocs/postgres/plpython.html"&gt;PL/Python&lt;/a&gt; features caught my eye.  These are minor among all the really cool high profile features, to be sure.  But it made me think back to a little bit of experimental code written some time ago, and how these couple language additions could make a big difference.&lt;/p&gt;&lt;p&gt;For one reason or another I'd just hit the top level &lt;a href="http://www.postgresql.org/"&gt;postgresql.org&lt;/a&gt; website, and suddenly realized just how many Postgres databases it took to put together what I was seeing on the screen.  Not only does it power the content database that generated the page, of course, but even the lookup of the .org went through &lt;a href="http://www.postgresql.org/about/press/presskit90"&gt;Afilias&lt;/a&gt; and their Postgres-backed domain service.  It's a pity the DBMS couldn't act as the middle layer between those.&lt;/p&gt;&lt;p&gt;Or could it?&lt;/p&gt;&lt;script src="https://gist.github.com/981161.js?file=run_server.pl.py"&gt;&lt;/script&gt;&lt;p&gt;That's a shortened form of it just for demonstration purposes (the original one had things like a table browser) ... but it works.  For example, on this test 9.1 install, hit http://localhost:8000/public/webtest and the following table appears:&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;generate_series&lt;/th&gt;&lt;th&gt;lh&lt;/th&gt;&lt;th&gt;rnd&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;0.548577250913&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1.70926172473&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1.24841631576&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;(etc)&lt;/td&gt;&lt;td&gt;...&lt;/td&gt;&lt;td&gt;...&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Note the use of two specific 9.1 features, though.  The plpy object contains nice query building helper utilities like quote_ident that you may be familiar with in other languages.  But this also makes use of subtransactions, which helps recover from db errors.  That's important here, as something like a typo in a table name will generate an error from Postgres and without that in place the database will end the transaction and ignore any subsequent commands the function tries to run.&lt;/p&gt;&lt;p&gt;But with that in place, the page shows the 404 error, and picks up where it left off with subsequent requests:&lt;/p&gt;&lt;pre&gt;Error code 404.

Message: Table not found.&lt;/pre&gt;&lt;p&gt;By the way, if it's not clear by now don't take this anywhere near a production database, if not any other reason that a transaction will be held open as long as that function runs.  That will hold back all the nice maintenance stuff that keeps things running efficiently.  Still, I think it helps show off what just a handful of lines of code can do in a powerful language like PL/Python.  I'm sure with the right module PL/PerlU could do something very similar.  But even more I think it shows how Postgres is growing and innovating by leaps and bounds, seemingly every day!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-3264537966551469150?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/3264537966551469150/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=3264537966551469150' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3264537966551469150'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3264537966551469150'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/05/only-try-this-at-home.html' title='Only Try This At Home'/><author><name>Josh Williams</name><uri>http://www.blogger.com/profile/10667170385197485182</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_TkzEDzlZHMg/S9Cz1qScO5I/AAAAAAAAAAM/_Ukoe431U5w/s1600-R/williams.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-5158864626720944405</id><published>2011-05-13T16:59:00.000-04:00</published><updated>2011-05-13T17:14:04.032-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open-source'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='dbdpg'/><title type='text'>DBD::Pg and the libpq COPY bug</title><content type='html'>&lt;style&gt;span.c{font-family:sans-serif;color:blue}&lt;/style&gt;&lt;div style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 240px; height: 191px;"&gt;&lt;img src="http://1.bp.blogspot.com/-mt0jO0wxU4Y/Tc2ccft0MZI/AAAAAAAAAD0/y-vCZVq-w64/s320/copyjam.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5606309124510069138" /&gt;&lt;small&gt;(image by &lt;a href="http://www.flickr.com/photos/kvh/"&gt;kvanhorn&lt;/a&gt;)&lt;/small&gt;&lt;/div&gt;&lt;br clear="all"&gt;&lt;p&gt;Version 2.18.1 of &lt;a href="http://search.cpan.org/search?query=DBD%3A%3APg"&gt;DBD::Pg&lt;/a&gt;, the Perl driver for Postgres, was just released. This was to fix &lt;a href="https://rt.cpan.org/Ticket/Display.html?id=68041"&gt;a serious bug&lt;/a&gt; in which we were not properly clearing things out after &lt;a href="http://www.postgresql.org/docs/current/interactive/sql-copy.html"&gt;performing a COPY&lt;/a&gt;. The only time the bug manifested, however, is if &lt;a href="http://search.cpan.org/~turnstep/DBD-Pg-2.18.1/Pg.pm#Asynchronous_Queries"&gt;an asynchronous query&lt;/a&gt; was done immediately after a COPY finished. I discovered this while working on the &lt;a href="https://mail.endcrypt.com/pipermail/bucardo-general/2011-May/001000.html"&gt;new version of Bucardo&lt;/a&gt;. The failing code section was this (simplified):&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
## Prepare the source
my $srccmd = "COPY (SELECT * FROM $S.$T WHERE $pkcols IN ($pkvals)) TO STDOUT";
$fromdbh-&gt;do($srccmd);

## Prepare each target
for my $t (@$todb) {
    my $tgtcmd = "COPY $S.$T FROM STDIN";
    $t-&gt;{dbh}-&gt;do($tgtcmd);
}

## Pull a row from the source, and push it to each target
while ($fromdbh-&gt;pg_getcopydata($buffer) &gt;= 0) {
    for my $t (@$todb) {
        $t-&gt;{dbh}-&gt;pg_putcopydata($buffer);
    }
}

## Tell each target we are done with COPYing
for my $t (@$todb) {
    $t-&gt;{dbh}-&gt;pg_putcopyend();
}

## Later on, run an asynchronous command on the source database
$sth{track}{$dbname}{$g} = $fromdbh-&gt;prepare($SQL, {pg_async =&gt; PG_ASYNC});
$sth{track}{$dbname}{$g}-&gt;execute();
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;This gave the error "&lt;strong&gt;another command is already in progress&lt;/strong&gt;". This error did not come from Postgres or DBD::Pg, but from &lt;strong&gt;libpq&lt;/strong&gt;, the underlying C library which DBD::Pg uses to talk to the database. Strangely enough, taking out the async part and running the exact same command produced no errors.&lt;/p&gt;&lt;p&gt;After tracking back through the libpq code, it turns out that DBD::Pg was only calling PQresult a single time after the copy ended. I can see why this was done: the &lt;a href="http://www.postgresql.org/docs/current/static/libpq-copy.html"&gt;docs for PQputCopyEnd&lt;/a&gt; state: "&lt;em&gt;After successfully calling PQputCopyEnd, call PQgetResult to obtain the final result status of the COPY command. One can wait for this result to be available in the usual way. Then return to normal operation.&lt;/em&gt;" What's not explicitly stated is that you need call PQgetResult again, and keep calling it, until it returns null, to "clear out the message queue". In this case, PQresult pulled back a 'c' message from Postgres, via the frontend/backend protocol, indicating that the copy command was complete. However, what it really needed was to call PQresult two more times, once to get back a 'C' (indicating the COPY statement was complete), and a 'Z' (indicating the backend was ready for a new query). Technically, there was nothing stopping libpq from sending a fresh query except that its own internal flag, conn-&gt;asyncStatus, is not reset on a simple end of copy, but only when 'Z' is encountered. Thus, DBD::Pg 2.18.1 now calls PQresult until it returns null.&lt;/p&gt;&lt;p&gt;If your application is encountering this bug and you cannot upgrade to 2.18.1 yet, the solution is simple: perform a non-asynchronous query between the end of the copy and the start of the asynchronous query. It can be any query at all, so the above code could be cured with:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
...
## Tell each target we are done with COPYing
for my $t (@$todb) {
    $t-&gt;{dbh}-&gt;pg_putcopyend();
    $t-&gt;{dbh}-&gt;do('SELECT 123');
}

## Later on, run an asynchronous command on the source database
$fromdbh-&gt;do('SELECT 123');
$sth{track}{$dbname}{$g} = $fromdbh-&gt;prepare($SQL, {pg_async =&gt; PG_ASYNC});
$sth{track}{$dbname}{$g}-&gt;execute();
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Why does the non-asynchronous command work? Doesn't it check the conn-&gt;asyncStatus as well? The secret is that PQexecstart has this bit of code in it:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
    /*
     * Silently discard any prior query result that application didn't eat.
     * This is probably poor design, but it's here for backward compatibility.
     */
    while ((result = PQgetResult(conn)) != NULL)
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Wow, that code looks familiar! So it turns out that the only reason this was not spotted earlier is that non-asynchronous commands (e.g. those using PQexec) were silently clearing out the message queue, kind of as a little favor from libpq to the driver. The async function, PQsendQuery, is not as nice, so it does the correct thing and fails right away with the error seen above (via PQsendQueryStart).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-5158864626720944405?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/5158864626720944405/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=5158864626720944405' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5158864626720944405'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/5158864626720944405'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/05/dbdpg-and-libpq-copy-bug.html' title='DBD::Pg and the libpq COPY bug'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-mt0jO0wxU4Y/Tc2ccft0MZI/AAAAAAAAAD0/y-vCZVq-w64/s72-c/copyjam.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7997313029981170997.post-3682093219202713374</id><published>2011-05-03T18:00:00.001-04:00</published><updated>2011-05-03T19:27:57.523-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bucardo'/><category scheme='http://www.blogger.com/atom/ns#' term='postgres'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>NOTIFY vs Prepared Transactions in Postgres (the Bucardo solution)</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-zJeKCbGuWb8/TcB6tscAtKI/AAAAAAAAADs/7ZhnKG6qJZY/s1600/goat_listening.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 240px; height: 160px;" src="http://1.bp.blogspot.com/-zJeKCbGuWb8/TcB6tscAtKI/AAAAAAAAADs/7ZhnKG6qJZY/s320/goat_listening.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5602612861890245794" /&gt;&lt;/a&gt;&lt;style&gt;span{font-family:sans-serif;}span.c{color:red;}span.p{font-family:sans-serif;color:green;}span.o{font-family:sans-serif;color:blue;}&lt;/style&gt;&lt;p&gt;We recently had a client use Bucardo to migrate their app from Postgres 8.2 to Postgres 9.0 with no downtime (which went great). They also were using Bucardo to replicate from the new 9.0 mater to a bunch of 9.0 slaves. This ran into problems the moment the application started, as we started seeing these messages in the logs:&lt;/p&gt;&lt;pre&gt;&lt;span class='e'&gt;
ERROR:  cannot PREPARE a transaction that has 
executed LISTEN, UNLISTEN or NOTIFY
&lt;/pre&gt;&lt;/span&gt;&lt;p&gt;The problem is that the Postgres LISTEN/NOTIFY system cannot be used with prepared transactions. Bucardo uses a trigger on the source tables that issues a NOTIFY to let the main Bucardo daemon know that something has changed and needs to be replicated. However, their application was issuing a PREPARE TRANSACTION as an occasional part of its work. Thus, they would update the table, which would fire the trigger, which would send the NOTIFY. Then the application would issue the PREPARE TRANSACTION which produced the error given above. Bucardo is setup to deal with this situation; rather than using notify triggers, the Bucardo daemon can be set to look for any changes at a set interval. The steps to change Bucardo's behavior for a given sync is simply:&lt;/p&gt;&lt;pre&gt;&lt;span class='c'&gt;
$ bucardo_ctl update sync foobar ping=false checktime=15
$ bucardo_ctl validate foobar
$ bucardo_ctl reload foobar
&lt;/pre&gt;&lt;/span&gt;&lt;p&gt;The first command tells the sync not to use notify triggers (these are actually statement-level triggers that simply issue a &lt;strong&gt;NOTIFY bucardo_kick_sync_foobar&lt;/strong&gt;. It also sets a checktime of 15 seconds, which means that the Bucardo daemon will check for changes every 15 seconds - or as if the original notify trigger is firing every 15 seconds. The second command validates the sync but checking that all supporting tables, functions, triggers, etc. are installed and up to date. It also removes triggers that are no longer needed: in this case, the statement-level notify triggers for all tables in this sync. Finally, the third command simply tells the Bucardo daemon to stop the sync, load in the new changes, and restart it.&lt;/p&gt;&lt;p&gt;Another solution to the problem is to simply not use prepared transactions: very few applications actually need it, but I've noticed a few that use it anyway when they should not be. What exactly is a prepared transaction? It's the Postgres way of implementing two-part commit. Basically, this means that a transaction's state is stored away on disk, and can be committed or rolled back at a later time - even by a different session. This is handy if you need to ensure that, for example, you can atomically commit multiple database connections. By atomically, I mean that either they all commit or none of them do. This is done by doing work on each database, issuing a PREPARE TRANSACTION, and then, once all have been prepared, issuing the COMMIT TRANSACTION against each one.&lt;/p&gt;

&lt;p&gt;As an aside, prepared &lt;b&gt;transactions&lt;/b&gt; are often confused with prepared &lt;b&gt;statements&lt;/b&gt;. While the use of prepared statements is very common, use of prepared transactions is very rare. Prepared statements are simply a way of planning a query one time, then re-running it multiple times without having to run the query through the planner each time. Many interfaces, such as DBD::Pg, will do this for you automatically behind the scenes. Sometimes using prepared statements can &lt;a href="http://blog.endpoint.com/2009/08/debugging-prepared-statements.html"&gt;cause issues&lt;/a&gt;, but it is usually a win.&lt;/p&gt;

&lt;p&gt;As mentioned above, the use of 2PC (two-phase commit) is very rare, which is why the default for the &lt;strong&gt;max_prepared_transactions&lt;/strong&gt; variable &lt;a href="http://archives.postgresql.org/pgsql-hackers/2009-04/msg01159.php"&gt;was recently changed&lt;/a&gt; to &lt;b&gt;0&lt;/b&gt;, which effectively disallows the use of prepared transactions until you explicitly turning them on in your postgresql.conf file. This helps prevent people from accidentally issuing a PREPARE TRANSACTION and then leaving them around. This mistake is easy to do, for once you issue the command, everything goes back to normal and it's easy to forget about them. However, having them around is a bad thing, as they continue to hold locks, and can prevent vacuum from running.The check_postgres program even has a specific check for this situation:&lt;a href="http://bucardo.org/check_postgres/check_postgres.pl.html#prepared_txns"&gt;check_prepared_txns&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;What does two-part commit look like? There are only three basic commands: PREPARE TRANSACTION, COMMIT PREPARED, and ROLLBACK PREPARED. Each takes a name, which is an arbitrary string 200 characters or less. Usage is to start a transaction, do some work, and then issue a PREPARE TRANSACTION instead of a COMMIT. At this point, all the work you have done is gone from your session and stored on disk. You cannot get back into this transaction: you can only commit it or roll it back. See the &lt;a href="http://www.postgresql.org/docs/current/static/sql-prepare-transaction.html"&gt;docs on PREPARE TRANSACTION&lt;/a&gt; for the full details.&lt;/p&gt;
&lt;p&gt;Here's an example of two-part commit in action:&lt;/p&gt;&lt;pre&gt;&lt;span class="c"&gt;
&lt;span class="p"&gt;testdb=# &lt;/span&gt;BEGIN;
&lt;span class="o"&gt;BEGIN&lt;/span&gt;
&lt;span class="p"&gt;testdb=#* &lt;/span&gt; CREATE TABLE preptest(a int);
&lt;span class="o"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="p"&gt;testdb=#* &lt;/span&gt; INSERT INTO preptest VALUES (1),(2),(3);
&lt;span class="o"&gt;INSERT 0 3&lt;/span&gt;
&lt;span class="p"&gt;testdb=#* &lt;/span&gt; SELECT * FROM preptest;
&lt;span class="o"&gt; a 
---
 1
 2
 3
(3 rows)&lt;/span&gt;

&lt;span class="p"&gt;testdb=#* &lt;/span&gt; PREPARE TRANSACTION 'foobar';
&lt;span class="o"&gt;PREPARE TRANSACTION&lt;/span&gt;
&lt;span class="p"&gt;testdb=# &lt;/span&gt;SELECT * FROM preptest;
&lt;span class="o"&gt;ERROR:  relation "preptest" does not exist
LINE 1: SELECT * FROM preptest;
                      ^&lt;/span&gt;
&lt;span class="p"&gt;testdb=# &lt;/span&gt;COMMIT PREPARED 'foobar';
&lt;span class="o"&gt;COMMIT PREPARED&lt;/span&gt;
&lt;span class="p"&gt;testdb=# &lt;/span&gt;SELECT * FROM preptest;
&lt;span class="o"&gt; a 
---
 1
 2
 3
(3 rows)
&lt;/span&gt;
&lt;/pre&gt;&lt;/span&gt;&lt;p&gt;A contrived example, but you can see how easy it could be to issue 
a PREPARE TRANSACTION and not even realize that it actually sticks 
around forever!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7997313029981170997-3682093219202713374?l=blog.endpoint.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.endpoint.com/feeds/3682093219202713374/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7997313029981170997&amp;postID=3682093219202713374' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3682093219202713374'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7997313029981170997/posts/default/3682093219202713374'/><link rel='alternate' type='text/html' href='http://blog.endpoint.com/2011/05/notify-vs-prepared-transactions-in.html' title='NOTIFY vs Prepared Transactions in Postgres (the Bucardo solution)'/><author><name>Greg Sabino Mullane</name><uri>http://www.blogger.com/profile/01284672712225991979</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-zJeKCbGuWb8/TcB6tscAtKI/AAAAAAAAADs/7ZhnKG6qJZY/s72-c/goat_listening.jpg' height='72' width='72'/><thr:total>3</thr:total></entry></feed>
