<!-- $Id: ddlchanges.sgml,v 1.15 2005/04/25 18:27:25 cbbrowne Exp $ -->
<sect1 id="ddlchanges">
<title>Database Schema Changes (DDL)</title>

<indexterm>
 <primary> DDL changes </primary>
 <secondary> changing the database schema </secondary>
</indexterm>

<para>When changes are made to the database schema,
<emphasis>e.g.</emphasis> - adding fields to a table, it is necessary
for this to be handled rather carefully, otherwise different nodes may
get rather deranged because they disagree on how particular tables are
built.</para>

<para>If you pass the changes through &slony1; via <xref
linkend="stmtddlscript"> (slonik) /<xref
linkend="function.ddlscript-integer-text-integer"> (stored function),
this allows you to be certain that the changes take effect at the same
point in the transaction streams on all of the nodes.  That may not be
so important if you can take something of an outage to do schema
changes, but if you want to do upgrades that take place while
transactions are still winding their way through your systems, this is
necessary.  </para>

<para>It is also necessary to use <command>EXECUTE SCRIPT</command> if
you alter tables so as to change their schemas.  If you do not, then
you may run into the problems <link linkend="neededexecddl"> described
here </link> where triggers on modified tables do not take account of
the schema change.  This has the potential to corrupt data on
subscriber nodes.  </para>

<para>It's worth making a couple of comments on <quote>special
things</quote> about <xref linkend="stmtddlscript">:</para>

<itemizedlist>

<listitem><para>The script <emphasis>must not</emphasis> contain
transaction <command>BEGIN</command> or <command>END</command>
statements, as the script is already executed inside a transaction.
In &postgres; version 8, the introduction of nested transactions may
modify this requirement somewhat, but you must still remain aware that
the actions in the script are wrapped inside a
transaction.</para></listitem>

<listitem><para>If there is <emphasis>anything</emphasis> broken about
the script, or about how it executes on a particular node, this will
cause the <xref linkend="slon"> daemon for that node to panic and
crash. If you restart the node, it will, more likely than not, try to
<emphasis>repeat</emphasis> the DDL script, which will, almost
certainly, fail the second time just as it did the first time.  I have
found this scenario to lead to a need to go to the
<quote>master</quote> node to delete the event to stop it from
continuing to fail.</para></listitem>

<listitem><para> For <application>slon</application> to, at that
point, <quote>panic</quote> is probably the
<emphasis>correct</emphasis> answer, as it allows the DBA to head over
to the database node that is broken, and manually fix things before
cleaning out the defective event and restarting
<application>slon</application>.  You can be certain that the updates
made <emphasis>after</emphasis> the DDL change on the provider node
are queued up, waiting to head to the subscriber.  You don't run the
risk of there being updates made that depended on the DDL changes in
order to be correct.</para></listitem>

<listitem><para> When you run <xref linkend="stmtddlscript">, this
causes the <application>slonik</application> to request, <emphasis>for
each table in the specified set</emphasis>, an exclusive table
lock.</para>

<para> It starts by requesting the lock, and altering the table to
remove &slony1; triggers:

<screen>
BEGIN;
LOCK TABLE table_name;
SELECT _oxrsorg.altertablerestore(tab_id);--tab_id is _slony_schema.sl_table.tab_id
</screen></para>

<para> After the script executes, each table is
<quote>restored</quote> to add back either the trigger that collects
updates at the origin or that denies updates on subscribers:

<screen>
SELECT _oxrsorg.altertableforreplication(tab_id);--tab_id is _slony_schema.sl_table.tab_id
COMMIT;
</screen></para>

<para> On a system which is busily taking updates, it may be
troublesome to <quote>get in edgewise</quote> to actually successfully
engage all the required locks.  The locks may run into deadlocks.
This points to two ways to address this:

<itemizedlist>

<listitem><para> You may be able to <link linkend="definesets"> define
replication sets </link> that consist of smaller sets of tables so
that fewer locks need to be taken in order for the DDL script to make
it into place.</para>

<para> If a particular DDL script only affects one table, it should be
unnecessary to lock <emphasis>all</emphasis> application tables.</para></listitem>

<listitem><para> You may need to take a brief application outage in
order to ensure that your applications are not demanding locks that
will conflict with the ones you need to take in order to update the
database schema.</para></listitem>

</itemizedlist></para></listitem>

</itemizedlist>

<para>Unfortunately, this nonetheless implies that the use of the DDL
facility is somewhat fragile and fairly dangerous.  Making DDL changes
must not be done in a sloppy or cavalier manner.  If your applications
do not have fairly stable SQL schemas, then using &slony1; for
replication is likely to be fraught with trouble and frustration.  See
the section on <link linkend="locking"> locking issues </link> for
more discussion of related issues.</para>

<para>There is an article on how to manage &slony1; schema changes
here: <ulink url="http://www.varlena.com/varlena/GeneralBits/88.php">
Varlena General Bits</ulink></para>

<sect2><title> Changes that you might <emphasis>not</emphasis> want to
process using <command>EXECUTE SCRIPT</command></title>

<para> While it is <emphasis> vitally necessary </emphasis> to use
<command>EXECUTE SCRIPT</command> to propagate DDL modifications to
tables that are being replicated, there are several sorts of changes
that you might wish to handle some other way:

<itemizedlist>

<listitem><para> There are various sorts of objects that don't have
triggers that &slony1; <emphasis>doesn't</emphasis> replicate, such as
stored functions, and it is quite likely to cause you grief if you
propagate updates to them associated with a replication set where
<command>EXECUTE SCRIPT</command> will lock a whole lot of tables that
didn't really need to be locked.</para>

<para> If you are propagating a stored procedure that
<emphasis>isn't</emphasis> used all the time (such that you'd care if
it was briefly out of sync between nodes), then you could simply
submit it to each node using <application>psql</application>, making
no special use of &slony1;.</para>

<para> If it <emphasis>does</emphasis> matter that the object be
propagated at the same location in the transaction stream on all the
nodes, then you but no tables need to be locked, then you might create
a replication set that contains <emphasis>no</emphasis> tables,
subscribe all the appropriate nodes to it, and use <command>EXECUTE
SCRIPT</command>, specifying that <quote>empty set.</quote></para></listitem>

<listitem><para> You may want an extra index on some replicated
node(s) in order to improve performance there.</para>

<para> For instance, a table consisting of transactions may only need
indices related to referential integrity on the <quote>origin</quote>
node, and maximizing performance there dictates adding no more indices
than are absolutely needed.  But nothing prevents you from adding
additional indices to improve the performance of reports that run
against replicated nodes.</para>

<para> It would be unwise to add additional indices that
<emphasis>constrain</emphasis> things on replicated nodes, as if they
find problems, this leads to replication breaking down as the
subscriber(s) will be unable to apply changes coming from the origin
that violate the constraints.</para>

<para> But it's no big deal to add some performance-enhancing indices.
You should almost certainly <emphasis> not</emphasis> use
<command>EXECUTE SCRIPT</command> to add them; that leads to some
replication set locking and unlocking tables, and possibly failing to
apply the event due to some locks outstanding on objects and having to
retry a few times before it gets the change in.  If you instead apply
the index <quote>directly</quote> such as with
<application>psql</application>, you can determine the time at which
the table lock is introduced.  Adding an index to a table will require
an exclusive lock for the time it takes to build the index; that will
implicitly stop replication, while the index builds, but shouldn't
cause any particular problems.  If you add an index on a table that
takes 20 minutes to build, replication will block for 20 minutes, but
should catch up quickly enough once the index is
created.</para></listitem>

</itemizedlist></para></sect2>

<sect2><title> Testing DDL Changes </title>

<para> A method for testing DDL changes has been pointed out as a
likely <quote>best practice.</quote></para>

<para> You <emphasis>need</emphasis> to test DDL scripts in a
non-destructive manner.</para>

<para> The problem is that if nodes are, for whatever reason, at all
out of sync, replication is likely to fall over, and this takes place
at what is quite likely one of the most inconvenient times, namely the
moment when you wanted it to <emphasis> work. </emphasis></para>

<para> You may indeed check to see if schema scripts work well or
badly, by running them by hand, against each node, adding <command>
BEGIN; </command> at the beginning, and <command> ROLLBACK; </command>
at the end, so that the would-be changes roll back.</para>

<para> If this script works OK on all of the nodes, that suggests that
it should work fine everywhere if executed via Slonik.  If problems
are encountered on some nodes, that will hopefully allow you to fix
the state of affairs on those nodes so that the script
<emphasis>will</emphasis> run without error.

<warning> <para> If the SQL script contains a <command> COMMIT;
</command> somewhere before the <command> ROLLBACK; </command>, that
may allow changes to go in unexpectedly.  </para>
</warning></para>
</sect2>
</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode:sgml
sgml-omittag:nil
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
sgml-parent-document:"book.sgml"
sgml-exposed-tags:nil
sgml-local-catalogs:("/usr/lib/sgml/catalog")
sgml-local-ecat-files:nil
End:
-->
