Показаны сообщения с ярлыком PostgreSQL. Показать все сообщения
Показаны сообщения с ярлыком PostgreSQL. Показать все сообщения

пятница, 6 января 2023 г.

Creating a simple foreign data wrapper for PostgreSQL

Today I'd like to describe how to create own minimal usable foreign data wrapper (FDW) for PostgreSQL.

Common information about FDW interfaces

I suppose you know C and have basic PostgreSQL DBA experience. Foreign data wrapper (FDW) is a mean of accesssing another data sources from PostgreSQL DBMS. In this article I'll walk you through writing minimal usable FDW (mufdw). Going through mufdw code, I'll show you how you can use PostgreSQL FDW API (its description you can find in the official documentation).

To access another data source you define FDW by specifying its name, validator function and handler function like this

CREATE FOREIGN DATA WRAPPER mufdw
  HANDLER mufdw_handler
  VALIDATOR mufdw_validator;
Both handler and validator are SQL functions. A validator is called when user mapping, foreign server or foreign table is created or modified to check if option is valid for the object. A handler just returns a struct, containing methods defining FDW (FdwRoutine). Most FDW will do foreign data wrapper creation behind the scenes when you create extension, so for administrator this could look like
CREATE EXTENSION mufdw;
Next you should define a foreign server, which basically specifies a remote server host in one or another way. For example, you can define host name, port and database name in foreign server options. Our sample mufdw allows to query another table in the same database. So server definition doesn't define any options and you can create server simply by
CREATE SERVER loopback FOREIGN DATA WRAPPER mufdw;
Now we have to define user mapping - how local user should access remote data source. Usually user mapping definition includes authentication information - like remote user name and password. As our minimal usable fdw will just wrap local tables and use the same user for scanning them (via SPI interface), our user mapping will be formal:
CREATE USER MAPPING FOR PUBLIC SERVER loopback;
Let's create a plain table which will be used as a source for our foreign table.
CREATE TABLE players(id int primary key, nick text, score int);
INSERT INTO players SELECT i, 'nick_'||i, i*10 from generate_series(1,10) i;
Foreign table is actually an object which behaves mostly like normal table, but provides access to remote data. In foreign table definition we provide options, necessary to identify this data. In our case it's local table and schema name.
CREATE FOREIGN TABLE f_players (id int, nick text, score int) server loopback options (table_name 'players', schema_name 'public');
Now we can query data from our foreign table.
SELECT * FROM players ;
 id |  nick   | score 
----+---------+-------
  1 | nick_1  |    10
  2 | nick_2  |    20
  3 | nick_3  |    30
  4 | nick_4  |    40
  5 | nick_5  |    50
  6 | nick_6  |    60
  7 | nick_7  |    70
  8 | nick_8  |    80
  9 | nick_9  |    90
 10 | nick_10 |   100
(10 rows)

What happens when you query a foreign table? PostgreSQL planner firstly looks if a table used in query is a foreign table and looks for its foreign server FdwRoutine. Pointer to FdwRoutine is recorded in RelOptInfo structure, which is used in planner to represent relation. This work is done in get_relation_info() function. Firstly planner access our fdw methods when looks for relation size in set_rel_size() (which is called for each base relation in the begining of planning (make_one_rel()). set_foreign_size() calls GetForeignRelSize() to find out relation size after applying restriction clauses. When planner generate possible paths to access a base relation in set_rel_pathlist(), it calls GetForeignPaths() function from RelOptInfo->fdwroutine to generate foreign path. Access paths are used by planner to find out possible strategy to access relation. Usually for foreign tables there will be no another paths besides foreign path. For join relation there could be several paths - foreign join path and several local join paths (for example, using nestloop or hash join method). If foreign path is the best access path for a particular relation, GetForeignPlan() function from its fdwroutine will be called to generate ForeignScan plan in create_foreignscan_plan(). Executor executes ForeignScan plan. When it initializes foreign scan state, BeginForeignScan() is called. Later tuples are fetched as needed by executor in IterateForeignScan(). If it's necessary to restart foreign scan from begining, ReScanForeignScan() is called. When relation scan is ended, EndForeignScan() is called.

Mufdw implementation

Let's look at how the minimal set of functions needed by read-only FDW is implemented in mufdw.

The basic logic behind mufdw is simple. For each foreign table we provide table name and table schema as foreign table options. When user queries remote table we open SPI cursor and fetch data from local table, identified by these schema and name. Query text is saved in foreign scan private field during planning. In BeginForeignScan() we create cursor for later use. IterateForeignScan() fetches one tuple from it and saves in executor state node scan slot. Here node is a basic "class"-like hierarchical structures used in PostgreSQL source code.

GetForeignRelSize() function

void GetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid)

This methods obtains relation size estimates for a foreign table. Here root is the planner information about the query, baserel - information about the table and foreigntableid is the Oid of the foreign table. In this function we have to fill in baserel->tuples and baserel->rows. baserel->fdw_private can be initialized and used for private fdw purposes.

In mufdw it is implemented as mufdwGetForeignRelSize(). mufdwGetForeignRelSize() basically allocates memory for relation fdw_private structure, searches in foreign table options for "table_schema" and "table_name" and saves them for further use. It also makes minimal effort to estimate relation size, but in fact it gives default estimations, as we don't gather any statistic for mufdw foreign table.

GetForeignPaths() function

void GetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid)

This function should create foreign path and add it to baserel->pathlist. It's recommended to use create_foreignscan_path to build the ForeignPath. Arguments are the same.

In mufdw it is implemented as mufdwGetForeignPaths(). It creates basic foreign scan path and adds it to the relation path list. Cost is estimated as a sequential scan. These estimates are not accurate in any way.

GetForeignPlan() function

ForeignScan *GetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan)

This function is called in the end of query planning and should create a ForeignScan plan node. It is passed foreign path, generated by GetForeignPaths(), GetForeignJoinPaths() or GetForeignUpperPaths(). We also get the target list, which should be emitted by plan node, restriction clauses to be enforced and outer subplan of the ForeignScan.

In mufdw it is implemented as mufdwGetForeignPlan(). We put all scan clauses in the plan node qual list for recheck as mufdw doesn't perform "remote" filtering. Function gets scan clauses as list of RestrinctInfo nodes, but should use expression list for plan quals. extract_actual_clauses() is used to perform this transformation. Also we construct simple "SELECT *" query to extract data from plain table. fdw_private foreignscan field should be a list of nodes, so that copyObject() could copy them. So we wrap our C string, representing query, into String node and create fdw_private as single-member list.

BeginForeignScan() function

void BeginForeignScan(ForeignScanState *node, int eflags)

This function is called to begin foreign scan execution. It can, for example, establish connection to DBMS. Note that function will also be called when running explain for a query (in that case (eflags & EXEC_FLAG_EXPLAIN_ONLY) is true). When called from explain, function should avoid doing any externally visible actions.

In mufdw it is implemented as mufdwBeginForeignScan(). Here we create cursor for the query, constructed during plan creation and initialize internal scan state with its name. Our local open_new_cursor() function opens cursor for query using SPI and returns name of corresponding portal. The issue here is that SPI context is transient, we can't use memory, allocated in this context, in other parts of program, so should copy it to our old context.

IterateForeignScan function

TupleTableSlot *IterateForeignScan(ForeignScanState *node)

This function is used to fetch one tuple and return it in ForeignScanState's tuple slot.

In mufdw it is implemented as mufdwIterateForeignScan(). Firstly we clear tuple slot, search for our SPI cursor and fetch one tuple from it. If there's no data, it will be enough. If there is some data, we deform SPI tuple to slot's array of values and nulls and call ExecStoreVirtualTuple() to store it. The only issue is that it should be materialized, as SPI tuple will be released when SPI context is destroyed.

ReScanForeignScan function

void ReScanForeignScan(ForeignScanState *node)

This function is used to restart scan from the begining.

In mufdw it is implemented as mufdwReScanForeignScan(). It just closes old cursor and opens new one for our query, stored in foreign scan internal state.

EndForeignScan function

void EndForeignScan(ForeignScanState *node)

This function should just end scan and release resources.

In mufdw it is an empty function. We don't explicitly release resources as they will be released when transaction ends.

fdw_handler function

fdw_handler is just a SQL function which should return pointer to FdwRoutine structure, which contains references to FDW functions, which we've just described.

In mufdw it's implemeted as mufdw_handler(). Only functions, necessary for qeurying foreign tables, are implemented.

fdw_validator function

fdw_validator checks options, used during CREATE or ALTER statements for foreign data wrapeprs, foreign servers, user mappings and foreign tables. It gets list of options as an array of text as the first argument and OID representing the type of object the options are associated with. OID can be ForeignDataWrapperRelationId, ForeignServerRelationId, UserMappingRelationId, or ForeignTableRelationId.

In mufdw it's implemented as mufdw_validator. It allows only "table_name" and "schema_name" options for foreign tables and doesn't check options for other objects.

SQL code

We need a bit of SQL code to glue this all together. We should create sql-level functions for validator and handler and create foreign data wrapper. For mufdw it's done in its extension script.

Afterword

We looked at how you can create a simple read-only foreign data wrapper in C, analyzing mufdw sample foreign data wrapper. I hope, this was useful, or at least interesting.

среда, 21 ноября 2012 г.

PostgreSQL quals in rewritten query plans

I've just examined PostgreSQL query debug output from the next query :
SELECT * from low_paid_emps_r;
where low_paid_emps_r is defined as
# SELECT definition from pg_views  where viewname like '%emps%';
                           definition     
-----------------------------------------------------
SELECT employees.employee_id, employees.first_name, employees.last_name, employees.email, employees.phone_number, employees.hire_date, employees.job_id, employees.salary, employees.commission_pct, employees.manager_id, employees.department_id FROM employees WHERE (employees.salary < (5000)::numeric);
In particular, I wished to find where is my where expression. It looks like the following.
   :quals 
               {OPEXPR 
               :opno 1754 
               :opfuncid 0 
               :opresulttype 16 
               :opretset false 
               :opcollid 0 
               :inputcollid 0 
               :args (
                  {VAR 
                  :varno 3 
                  :varattno 8 
                  :vartype 1700 
                  :vartypmod 524294 
                  :varcollid 0 
                  :varlevelsup 0 
                  :varnoold 3 
                  :varoattno 8 
                  :location -1
                  }
                  {FUNCEXPR 
                  :funcid 1740 
                  :funcresulttype 1700 
                  :funcretset false 
                  :funcformat 2 
                  :funccollid 0 
                  :inputcollid 0 
                  :args (
                     {CONST 
                     :consttype 23 
                     :consttypmod -1 
                     :constcollid 0 
                     :constlen 4 
                     :constbyval true 
                     :constisnull false 
                     :location -1 
                     :constvalue 4 [ -120 19 0 0 ]
                     }
                  )
                  :location -1
                  }
               )
               :location -1
               }
So, we have here <:
# SELECT oprname from pg_operator where oid=1754;
 oprname 
---------
<
(1 row)
The first argument is 8th attribute of our relation (salary). The other piece is ::numeric conversion:
# select proname from pg_proc where oid=1740;
 proname 
---------
 numeric
(1 row)
Ok, we reconstructed salary < numeric (Const) part. Let's find our constant.
# select typname from pg_type where oid=23;
 typname 
---------
 int4
(1 row)
It's int4. And constvalue as we can see from source code is a Datum. It seems form postgres.h that int4 is int32 and represented as:
#define DatumGetInt32(X) ((int32) GET_4_BYTES(X))
Now we can reconstruct out integer from ":constvalue 4 [ -120 19 0 0 ]" as 19*256+(256-120)=5000... Not evident; I possibly couldn't do this analysis not knowing original statement...

вторник, 3 апреля 2012 г.

PostgreSQL mostly online REINDEX

What do you do, when you want to do REINDEX online? Build new index concurrently, drop old one and rename new. Only you can't do it for indexes supporting primary keys and other constraints without dropping constraint. So, in any case, you have to REINDEX them. And let everyone to be waiting...
And if you have several hundred indexes, it is not very convenient to do it manually. Also note that CREATE INDEX CONCURRENTLY can't be EXECUTE'd from plpgsql function. I wish we had concurrent reindex in PostgreSQL or at least autonomous transactions to deal with the latter problem... But, we must work with available functionality...
So, the decision for me was to write PL/pgSQL script which will generate necessary SQL commands and feed them to psql.
The script itself is the following:

DO
$$
DECLARE
ind record;
str text;
str_drop text;
str_rename text;
BEGIN
FOR ind IN (SELECT i.oid,i.relname FROM
pg_class r,pg_class i , pg_index ir, pg_namespace ns
WHERE ir.indexrelid=i.oid AND ir.indrelid=r.oid AND ns.oid=r.relnamespace
AND ns.nspname='public' AND NOT ir.indisprimary \
AND i.oid NOT IN
(SELECT conindid FROM pg_constraint)) LOOP
str:=replace(
pg_get_indexdef(ind.oid),
'INDEX '||ind.relname|| ' ON ',
'INDEX CONCURRENTLY '||ind.relname|| '_X999 ON ');
str_drop:='DROP INDEX ' || ind.relname;
str_rename:='ALTER INDEX ' || ind.relname||'_X999 RENAME TO ' ||ind.relname ;
RAISE NOTICE '%', str;
RAISE NOTICE '%', str_drop;
RAISE NOTICE '%', str_rename;
END LOOP;
FOR ind IN (SELECT i.oid,i.relname FROM
pg_class r,pg_class i, pg_index ir, pg_namespace ns
WHERE ir.indexrelid=i.oid AND ir.indrelid=r.oid AND ns.oid=r.relnamespace
AND ns.nspname='public' AND (ir.indisprimary
OR i.oid IN (SELECT conindid FROM pg_constraint)) LOOP
str:='REINDEX INDEX ' || ind.relname;
raise notice '%', str;
end loop;
END;
$$ LANGUAGE PLPGSQL;


Now with something like this:

$ psql -d DBNAME -f generate_reindex.plpgsql" 2>&1| awk -F 'NOTICE:' '{ if (NF==2) {print $2 ";" } ; }' | psql -d DBNAME

we can mostly concurrently rebuild all indexes in database DBNAME. And last thing to remember is to add
\set ON_ERROR_STOP
to your ~/.psqlrc file just to be safe...

четверг, 15 марта 2012 г.

Remember TRUNCATE peculiarities

Just remember the following about TRUNCATE:
1) It is DDL operation and so is not transactional in Oracle (issuing TRUNCATE will make an implicit commit)
2) It is not MVCC-safe in PostgreSQL.

What does it mean?


alp=> create table t199 as select * from pg_opclass ;
SELECT 117
alp=> BEGIN ;
BEGIN
alp=> TRUNCATE t199;
TRUNCATE TABLE
alp=> ROLLBACK ;
ROLLBACK ^
alp=> SELECT count(*) from t199;
count
-------
117
(1 row)

So far, everything looks good: TRUNCATE was rolled back. Transactional DDL is a powerful PostgreSQL feature. But let's start parallel transaction. In first session I'll issue

alp=> \set PROMPT1 'T1> '

and in second:

alp=> \set PROMPT1 'T1> '


So, let's fun begin.
 
T1> BEGIN;
BEGIN
T1> SET transaction_isolation TO serializable;
SET
T1> UPDATE employees SET email=email;
UPDATE 103

T2> BEGIN ;
BEGIN
T2> TRUNCATE t199;
TRUNCATE TABLE
T2> COMMIT ;
COMMIT

T1> SELECT count(*) from t199;
count
-------
0
(1 row)

Oops... What has happened? Let's repeat...


T2> INSERT INTO t199 SELECT * from pg_opclass;
INSERT 0 117

T1> BEGIN;
BEGIN
T1> SET transaction_isolation TO serializable;
SET
T1> UPDATE employees SET email=email;
UPDATE 103

T2> BEGIN ;
BEGIN
T2> DELETE FROM t199;
DELETE 117
T2> COMMIT ;
COMMIT

T1> SELECT count(*) from t199;
count
-------
117
(1 row)


Do you see the difference? T2's DELETE didn't modified T1 snapshot... But TRUNCATE did. And what about "UPDATE employees"? It is just necessary to get real XID and snapshot for transaction.
So, the main idea: be attentive with "TRUNCATE". It is not just "fast DELETE", it has some not-so-evident consequences.

понедельник, 27 февраля 2012 г.

Minor PostgreSQL updates and different unpleasant surprises

Today I've updated two our DBMS servers to PostgreSQL 9.0.7. It was a minor update, everything went smoothly: I just updated ports and let pormaster to do its job...
But I got an interesting consequence - something was fixed and our server began to write error log in Russian. Oh, shit! It wasn't pleasant. Have you ever tried to find something about particular error using its localized description?
I've checked my environment: LC_* variables were not set, pgsql's user class was not "russian", nothing has changed. However, I got a nice log with localized reports... One more trouble - they were messed up in some way (don't know, whose fault was this - OS, PostgreSQL) and hardly readable. I really don't understand, why OS or any service should ever print any errors or logs in native language, this practice leads to difficulties in their interpretation.
In any way, it turned out that in postgresql.conf lc_messages were set to "ru_RU.UTF-8", maybe since initdb run, but before update this GUC didn't have such influence. Something was fixed, and behavior changed. So, keep in mind, even minor updates may lead to different surprises...

среда, 19 октября 2011 г.

PostgreSQL: server closed connection

Today I got a ton of messages

DBI connect('dbname=xxx;host=yyy','username',...) failed: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request. at....


from our network statistic collector.
I spent some time trying to understand what had happened (besides yesterdays power fail). I looked at PostgreSQL server debug log (it honestly told that client just closed connection). There were no traces of dying postgres backend. I rolled back some minor configuration changes which I made yesterday... And later it downed upon me: it was just one old server which was supposed to have its network interface shut down waked after power failure... It was seen in dmesg:

arp: x.y.z.w moved from AA:AA:AA:AA:AA:AA to BB:BB:BB:BB:BB:BB on em0
arp: x.y.z.w moved from BB:BB:BB:BB:BB:BB to AA:AA:AA:AA:AA:AA on em0


Really, power cycling is neccessary sometimes to ascertain that all your services are configured properly :)

среда, 21 сентября 2011 г.

PostgreSQL 9.1 create extension and database upgrade

Yesterday I was eager to try some new 9.1 features, in particular to experiment with exclude constraints (I've read they were improved since 9.0). So, I decided to upgrade my test 9.0 installation. It worth mentioning that it was 8.4 installation, previously upgraded to 9.0.
Everything went as expected, even my C functions were successfully recompiled without any warnings.
So, I decided to try extensions. I had hstore module installed in my db, so I issued "create extension" and received the following error:


db=# CREATE EXTENSION hstore WITH SCHEMA public FROM unpackaged;
ERROR: function hstore_recv(internal) does not exist


I looked at hstore definition in /usr/local/share/postgresql/extension/hstore--1.0.sql, copied definition of this function, created it, but received error saying that some other function doesn't exist.
So I run full script to create unpackaged extension:

# sed -e 's|MODULE_PATHNAME|$libdir/hstore|' < /usr/local/share/postgresql/extension/hstore--1.0.sql | su pgsql -c 'psql -d db '

After that it was possible to create extension:

db=# CREATE EXTENSION hstore WITH SCHEMA public FROM unpackaged;


It seems that the reason for this behavior is that I didn't upgrade hstore module after upgrading database from 8.4 to 9.0...

воскресенье, 18 сентября 2011 г.

Extracting domain name from uri with SQL

I don't know if it is effective, but this ugly SQL (in PostgreSQL dialect) extracts domain name part from uri (i.e. translates protocol://something/page into "something"):

SELECT uri, substring (uri from position('//' in uri)+2 for ( char_length(uri) - char_length(substring (uri from position ('/' in substring ( uri from position('//' in uri)+2 )) ) ))) from proxy_data

It seems, it would look better with regexp:
 
select uri, substring ( substring (uri from '//[[:alnum:]\._\-]+') from 3) from proxy_data

However, in later case we should list all possible symbols in domain name in our regexp.

среда, 15 июня 2011 г.

вторник, 19 апреля 2011 г.

суббота, 9 апреля 2011 г.

Shit happens (serious bug in PostgreSQL pg_upgrade)

I've just found out the following info: due to bug in pg_upgrade all users of pg_upgrade MAY LOOSE DATA.. Fix and details are available here.

вторник, 15 марта 2011 г.

PostrgreSQL pgarch_ArchiverCopyLoop

Today I had an interesting experiment while tuning our new PostgreSQL server. I powered it off during operation. PostgreSQL restored after reboot and was rather happy after restoring from log files in pg_xlog, but later it began to swear:

Mar 15 17:36:01 pgsql pgsql[1579]: [10-2] db , client , user LOCATION: pgarch_ArchiverCopyLoop, pgarch.c:466
Mar 15 17:37:03 pgsql pgsql[1579]: [14-1] db , client , user WARNING: 01000: transaction log file "00000001000000170000007E" could not be archived: too many failures

It seems that during power failure one log was archiving and as result file "00000001000000170000007E.bz2" existed in archive. So, PostgreSQL refused to overwrite it (test -f returned not 0). So, I moved that file to other directory and postgresql created new archive copy of this wal file. I compared md5 sums of original log file in pg_xlog and archived one, they matched and I deleted old copy. It seems that bzip2 process was interrupted during power failure. It's a good expirience: it confirms that my archive_command

/bin/test ! -f /path/to/archived/logs/%f.bz2 && /bin/cat %p | /usr/bin/bzip2 -c > /path/to/archived/logs/%f.bz2'
is right.

суббота, 22 января 2011 г.

PostgreSQL talk on NYC MySQL Group event at Oracle's New York headquarters

This talk by EnterpriseDB's CEO is just a general introduction discussing the place of PostgreSQL in modern world. It wasn't a technical talk. However, a lot of interesting question were concerened like PostgreSQL development model, necessity of RAC open source alternative, scaling on commodity servers and so on.

пятница, 14 января 2011 г.

repmgr FreeBSD port

I've just managed to create initial prototype of repmgr port for FreeBSD. Port compiles, but repmgrd dumps core for unknown reason... Need to investigate. Shell archive of the port is provided.

Updated: repmgrd works fine with included patch.


# This is a shell archive. Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file". Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
# databases/postgresql-repmgr
# databases/postgresql-repmgr/Makefile
# databases/postgresql-repmgr/pkg-descr
# databases/postgresql-repmgr/distinfo
# databases/postgresql-repmgr/pkg-plist
# databases/postgresql-repmgr/files
# databases/postgresql-repmgr/files/patch-repmgrd.c
#
echo c - databases/postgresql-repmgr
mkdir -p databases/postgresql-repmgr > /dev/null 2>&1
echo x - databases/postgresql-repmgr/Makefile
sed 's/^X//' >databases/postgresql-repmgr/Makefile << '48fb6f3f1b95d8369d928a104f7bd6a3'
X# New ports collection makefile for: repmgr
X# Date created: January 15, 2011
X
XPORTNAME= postgresql-repmgr
XPORTVERSION= 1.0.0
XPORTREVISION= 0
XCATEGORIES= databases
XMASTER_SITES= http://projects.2ndquadrant.it/sites/default/files/
X#MASTER_SITE_SUBDIR= sites/default/files/
XDISTNAME= repmgr-${PORTVERSION}
X
XMAINTAINER= alp@sfedu.ru
XCOMMENT= PostgreSQL repmgr
X
XBUILD_DEPENDS+= postgresql-server>=9.0:${PORTSDIR}/databases/postgresql90-server
XRUN_DEPENDS:= ${BUILD_DEPNDS}
XRUN_DEPENDS+= rsync:${PORTSDIR}/net/rsync
X
XUSE_GMAKE= yes
XMAKE_ENV= USE_PGXS=1
X
XPORTDOCS= README.*
X
Xdo-install:
X ${INSTALL_PROGRAM} ${WRKSRC}/repmgr ${PREFIX}/bin
X ${INSTALL_PROGRAM} ${WRKSRC}/repmgrd ${PREFIX}/sbin
X.if !defined(NOPORTDOCS)
X @${MKDIR} ${DOCSDIR}
X ${INSTALL_DATA} ${WRKSRC}/README.rst ${DOCSDIR}
X.endif
X.if !defined(NOPORTDATA)
X @${MKDIR} ${DATADIR}
X ${INSTALL_DATA} ${WRKSRC}/repmgr.conf ${DATADIR}
X ${INSTALL_DATA} ${WRKSRC}/repmgr.sql ${DATADIR}
X.endif
X
X.include <bsd.port.mk>
48fb6f3f1b95d8369d928a104f7bd6a3
echo x - databases/postgresql-repmgr/pkg-descr
sed 's/^X//' >databases/postgresql-repmgr/pkg-descr << '4003202a12e35bb4d428938652326a06'
XPostgreSQL replication manager (repmgr)
X
XWWW: http://projects.2ndquadrant.com/repmgr
4003202a12e35bb4d428938652326a06
echo x - databases/postgresql-repmgr/distinfo
sed 's/^X//' >databases/postgresql-repmgr/distinfo << 'e1ae01bffabbb92bb754fc0eea6621aa'
XMD5 (repmgr-1.0.0.tar.gz) = e9a90058ff2ad81de20fd8dc7b8743f0
XSHA256 (repmgr-1.0.0.tar.gz) = b0345a9a32c3f3bab542c8d2ef72881f6f45b6c0d703f481998beb5755a1d79b
XSIZE (repmgr-1.0.0.tar.gz) = 35940
e1ae01bffabbb92bb754fc0eea6621aa
echo x - databases/postgresql-repmgr/pkg-plist
sed 's/^X//' >databases/postgresql-repmgr/pkg-plist << '1ca1316c7087064a99a71f8064202df0'
Xbin/repmgr
Xsbin/repmgrd
X%%DATADIR%%/repmgr.conf
X%%DATADIR%%/repmgr.sql
1ca1316c7087064a99a71f8064202df0
echo c - databases/postgresql-repmgr/files
mkdir -p databases/postgresql-repmgr/files > /dev/null 2>&1
echo x - databases/postgresql-repmgr/files/patch-repmgrd.c
sed 's/^X//' >databases/postgresql-repmgr/files/patch-repmgrd.c << '39954438281f6f4b810cfb628d4308b3'
X--- repmgrd.c 2010-12-06 05:09:48.000000000 +0300
X+++ repmgrd.c 2011-01-16 12:34:49.345998423 +0300
X@@ -173,7 +173,12 @@
X }
X
X /* close the connection to the database and cleanup */
X- CloseConnections();
X+ if (PQisBusy(primaryConn) == 1)
X+ CancelQuery();
X+ if (myLocalConn != NULL)
X+ PQfinish(myLocalConn);
X+ if (primaryConn != NULL && myLocalMode != PRIMARY_MODE)
X+ PQfinish(primaryConn);
X
X return 0;
X }
39954438281f6f4b810cfb628d4308b3
exit


вторник, 11 января 2011 г.

repmgr or manage your PostgreSQL cluster easily

I've looked through Greg Smith's post about repmgr and feel quite interested. As I understood, this utility significantly simplifies PostgreSQL cluster management. I'd like to test it this week. There are a lot of questions. Will this repmgr compile on FreeBSD? How will be fail of master processed? What will happen if master goes down, e.g. due to network failure, and then is reborn while old slave is a new master? What will happen if nodes are divided in several groups? We'll see. At least, first three questions are critical...

One more thing which I'd like to see in PostgreSQL is a transparent support for HA configurations from client library. The following scenario worked in Oracle RAC. We had two "IN A" DNS records for "oracle.our.domain" pointing to our RAC nodes, and applications transparently switched from one node to another on node's failure. More supported behavior was achieved by specifying both nodes in ADDRESS_LIST and using proper FAILOVER_MODE.

понедельник, 27 декабря 2010 г.

PostgreSQL 9.0 High perfomance

I've just finished G.Smith's "PostgreSQL 9.0 High performance". It's just an excellent book for any DBA. It provides detail information on PostgreSQL-related tuning (from server hardware and OS settings to query tuning and DBMS monitoring). We are going to upgrade our DBMS server next year, so I'll have an opportunity to apply this recommendations in practice. The only disadvantage is that the book is Linux-oriented, and we have FreeBSD DBMS server. However, basic settings for our OS are also covered in this book. Now I wonder: should I use ZFS for PostgreSQL on FreeBSD? I think at least I should make some benchmarks...

воскресенье, 5 декабря 2010 г.

XID wraparround and postgresql single user mode

I've just found on pgsql-general mailing list interesting detail. This case supports the idea that manuals should be read from cover to cover. Of course, I've never bothered with this :)
The case was the following: DBMS refused to start with message:

2010-12-01 08:36:42 EST LOG: database system was shut down at
2010-11-29 17:38:17 EST
2010-12-01 08:36:42 EST FATAL: invalid cache id: 19
2010-12-01 08:36:43 EST FATAL: the database system is starting up
2010-12-01 08:36:43 EST LOG: startup process (PID 640) exited with exit
code 1
2010-12-01 08:36:43 EST LOG: aborting startup due to startup process
failure

The previous messages in the log were

2010-11-29 12:39:17 EST ERROR: database is not accepting commands to
avoid wraparound data loss in database "fps_data"
2010-11-29 12:39:17 EST HINT: Stop the postmaster and use a standalone
backend to vacuum that database.


I'm glad, that it was not mine DBMS. As we see from log, postgresql shutted down itself to avoid XID wrapparound. It is covered in detail in the manual. The main idea is that every row in table is marked with a XID of last transaction, which modified it. So, when row is not modified for long, vacuum (or autovacuum) will increase its XID, and it will not wrap (to compare XIDs PostgreSQL using modulo-2^31 arithmetic, so that "for every normal XID, there are two billion XIDs that are "older" and two billion that are "newer"; another way to say it is that the normal XID space is circular with no endpoint" (c) PostgreSQL 9.0 Documentation).
In this situation, server had seen that we were close to maximum 2 billion difference between current XID and oldest one in DB and shutted down. So, now we have to run postgresql in single user mode and do vacuum on affected database. Something like that:

# su - pgsql -c '/usr/local/bin/postgres --single -D /pgdata dbname'
PostgreSQL stand-alone backend 9.0.0
backend> vacuum;
backend> ^D
# /usr/local/etc/rc.d/postgresql start

пятница, 3 декабря 2010 г.

DBI-Link - why is it so buggy?

I've spent several hours trying to make dbi-link work - I had errors related to table names and errors related to dbi_link.shadow_trigger_func(). However, I couldn't just get up - I was asked to suggest a sollution for MySQL <=> PostgreSQL replication and this module could help me to do the job...
So, I've found the following post , which helped me a lot, so I modified dbi_link.shadow_trigger_func to look the following way:

CREATE OR REPLACE FUNCTION dbi_link.shadow_trigger_func()
RETURNS TRIGGER
LANGUAGE plperlu
AS $$
require 5.8.3;
######################################################
# #
# Immediately reject anything that is not an INSERT. #
# #
######################################################
if ($_TD->{event} ne 'INSERT') {
return "SKIP";
}
spi_exec_query('SELECT dbi_link.dbi_link_init()');
my $data_source_id = shift;
die "In shadow_trigger_function, data_source_id must be an integer"
unless ($data_source_id =~ /^\d+$/);
my $query = "SELECT dbi_link.cache_connection( $data_source_id )";
warn "In shadow_trigger_function, calling\n $query" if $_SHARED{debug};
warn "In shadow_trigger_function, the trigger payload is\n". Dump(\$_TD) if $_SHARED{debug};
my $rv = spi_exec_query($query);
my $remote_schema = $_SHARED{get_connection_info}->({
data_source_id => $data_source_id
})->{remote_schema};
my $table = $_TD->{relname};
warn "Raw table name is $table" if $_SHARED{debug};
warn "In trigger on $table, action is $_TD->{new}{iud_action}" if $_SHARED{debug};
$table =~ s{
\A # Beginning of string.
(.*) # Actual table name.
_shadow # Strip off shadow.
\z # End of string.
}
{$1}sx;
$table = $remote_schema . "." . $table if defined $remote_schema;
warn "Cooked table name is $table" if $_SHARED{debug};
my $iud = {
I => \&do_insert,
U => \&do_update,
D => \&do_delete,
};
if ($iud->{ $_TD->{new}{iud_action} }) {
$iud->{ $_TD->{new}{iud_action} }->({
payload => $_TD->{new},
tbl => $table,
source_id => $data_source_id
});
}
else {
die "Trigger event was $_TD->{new}{iud_action}<, but should have been one of I, U or D!"
}
return 'SKIP';
sub do_insert {
my ($params) = @_;
die "In do_insert, must pass a payload!"
unless (defined $params->{payload});
die "In do_insert, payload must be a hash reference!"
unless (ref $params->{payload} eq 'HASH');
my (@keys, @values);
foreach my $key (sort keys %{ $params->{payload} } ) {
next unless $key =~ /^.?new_(.*)/;
my $real_key = $1;
push @keys, $real_key;
push @values, $_SHARED{dbh}{ $params->{source_id} }->quote(
$params->{payload}{$key}
);
}
my $sql = <<SQL;
INSERT INTO $params->{tbl} (
@{[
join(
",\n ",
@keys
)
]}
)
VALUES (
@{[
join(
",\n ",
@values
)
]}
)
SQL
warn "SQL is\n$sql" if $_SHARED{debug};
$_SHARED{dbh}{ $params->{source_id} }->do($sql);
}
sub do_update {
my ($params) = @_;
die "In do_update, must pass a payload!"
unless (defined $params->{payload});
die "In do_update, payload must be a hash reference!"
unless (ref $params->{payload} eq 'HASH');
my $sql = <<SQL;
UPDATE $params->{tbl}
SET
@{[ make_pairs({
payload => $params->{payload},
which => 'new',
joiner => ",\n ",
transform_null => 'false',
source_id => "$params->{source_id}"
}) ]}
WHERE
@{[ make_pairs({
payload => $params->{payload},
which => 'old',
joiner => "\nAND ",
transform_null => 'true',
source_id => "$params->{source_id}"
}) ]}
SQL
warn "SQL is\n$sql" if $_SHARED{debug};
$_SHARED{dbh}{ $params->{source_id} }->do($sql);
}
sub do_delete {
my ($params) = @_;
die "In do_delete, must pass a payload!"
unless (defined $params->{payload});
die "In do_delete, payload must be a hash reference!"
unless (ref $params->{payload} eq 'HASH');
my $sql = <<SQL;
DELETE FROM $params->{tbl}
WHERE
@{[ make_pairs({
payload => $params->{payload},
which => 'old',
joiner => "\nAND ",
transform_null => 'true',
source_id => "$params->{source_id}"
}) ]}
SQL
warn "SQL is\n$sql" if $_SHARED{debug};
$_SHARED{dbh}{ $params->{source_id} }->do($sql);
}
sub make_pairs {
my ($params) = @_;
die "In make_pairs, must pass a payload!"
unless (defined $params->{payload});
die "In make_pairs, payload must be a hash reference!"
unless (ref $params->{payload} eq 'HASH');
warn "In make_pairs, parameters are:\n". Dump($params) if $_SHARED{debug};
my @pairs;
foreach my $key (
keys %{ $params->{payload} }
) {
next unless $key =~ m/^(.?)$params->{which}_(.*)/;
my $left = "$1$2";
warn "In make_pairs, raw key is $key, cooked key is $left" if $_SHARED{debug};
warn "In make_pairs, pairs are @pairs" if $_SHARED{debug};
$bl=!defined $params->{payload}{$key};
$bl1=defined $params->{transform_null};
if (
defined $params->{transform_null} && # In a WHERE clause,
!defined $params->{payload}{$key} # turn undef into IS NULL
) {
push (@pairs, "$left");
}
else {
warn "o, fuck";
warn "params->source_id is $params->{source_id}";
push @pairs, "$left = " . $_SHARED{dbh}{ $params->{source_id} }->quote(
$params->{payload}{$key}
);
}
}
my $ret =
join (
$params->{joiner},
@pairs,
);
warn "In make_pairs, the pairs are:\n". Dump(\@pairs) if $_SHARED{debug};
return $ret;
}
$$;



And this worked! Now I just can create after each row triggers on local PostgreSQL table and promote data to MySQL. I like DBI-Link, however, if I were to use it in production I would examine its code attentively...

среда, 25 августа 2010 г.

SQL is Turing complete

I've found one interesting thing: with recursive CTE and windowing functions SQL is a Turing complete language. The proof maybe found here.

пятница, 13 августа 2010 г.

Why I'm going to read Oracle course in university... on PostgreSQL

I used to like Oracle. They gave us one of the best DBMS. They made a lot of work on promoting Linux. They used a lot of open source software in their products and promoted Java...
But their greed is unnatural. It is more than Microsoft's greed.
1) Their DBMS costs too much. When our university tried to prolong their support, they said taht we had to pay taxes... because we hadn't done this earlier.
2) They don't allow to use their products in education for free. Even MS Academy program is free for our University.
3) They have done everything to kill Sun OpenSolaris. Damn them!
4) They prohibited to use Sun Solaris for free.
5) And now they are suing Google for using Java technologies in Android.
IMHO, this company is crazy and ill on immediate profit. It doesn't matter to them that their steps only makes potential customers nervous. What is next? Will they sue Apache or SpringSource for developing Java-related products or FreeBSD for using ZFS?
No, thanks. I will not promote their products at our campus.
FreeBSD and PostgreSQL is a real base for DBMS server, not Solaris and one overpriced DBMS...