mirror of
https://github.com/sigmasternchen/php-doc-en
synced 2025-03-27 14:28:56 +00:00

git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@317722 c90b9560-bf6c-de11-be94-00142212c4b1
1877 lines
66 KiB
XML
Executable file
1877 lines
66 KiB
XML
Executable file
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
|
|
<chapter xml:id="mysqlnd-ms.setup" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
&reftitle.setup;
|
|
|
|
<section xml:id="mysqlnd-ms.requirements">
|
|
&reftitle.required;
|
|
<para>
|
|
<literal>PHP 5.3.6</literal> or newer.
|
|
</para>
|
|
<para>
|
|
The <literal>mysqlnd_ms</literal> replication and load balancing
|
|
plugin supports all PHP applications and all available PHP MySQL extensions
|
|
(<link linkend="ref.mysqli">mysqli</link>,
|
|
<link linkend="ref.mysql">mysql</link>,
|
|
<link linkend="ref.pdo-mysql">PDO_MYSQL</link>).
|
|
The PHP MySQL extension must be configured to use
|
|
<link linkend="book.mysqlnd">mysqlnd</link> in order to be able
|
|
to use the <literal>mysqlnd_ms</literal> plugin for
|
|
<link linkend="book.mysqlnd">mysqlnd</link>.
|
|
</para>
|
|
</section>
|
|
|
|
&reference.mysqlnd-ms.configure;
|
|
&reference.mysqlnd-ms.ini;
|
|
|
|
<section xml:id="mysqlnd-ms.plugin-ini-json">
|
|
<title xmlns="http://docbook.org/ns/docbook">Plugin configuration file (>=1.1.x)</title>
|
|
<note>
|
|
<para>
|
|
The below description applies to PECL/mysqlnd_ms >= 1.1.0-beta.
|
|
It is not valid for prior versions.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
The plugin is using its own configuration file. The configuration file
|
|
holds information on the MySQL replication master server,
|
|
the MySQL replication slave servers, the server pick (load balancing) policy,
|
|
the failover strategy and the use of lazy connections.
|
|
</para>
|
|
<para>
|
|
The PHP configuration directive
|
|
<link linkend="ini.mysqlnd-ms.ini-file"><literal>mysqlnd_ms.ini_file</literal></link>
|
|
is used to set the plugins configuration file.
|
|
</para>
|
|
<para>
|
|
As of mysqlnd 1.1.0-beta JavaScript Object Notation (<literal>JSON</literal>)
|
|
format is used for the plugins configuration file.
|
|
<literal>JSON</literal> structures are fast to parse by the plugin,
|
|
thanks PHPs JSON support. The <literal>JSON</literal> format makes it easy
|
|
to define hierarchical
|
|
data structures. Hierachical data structures are used, for example,
|
|
to allow definition of chained filters. Expressing hierarchical data structures
|
|
with the standard <literal>php.ini</literal> format is much more inconvenient.
|
|
</para>
|
|
<para>
|
|
If you are unfamiliar with the <literal>JSON</literal> you may want to convert
|
|
the configuration file into a PHP hash, edit the hash and convert it back
|
|
to <literal>JSON</literal> format.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Converting a PHP hash into JSON format</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$config = array(
|
|
"myapp" => array(
|
|
"master" => array(
|
|
"master_0" => array(
|
|
"host" => "localhost",
|
|
"socket" => "/tmp/mysql.sock",
|
|
),
|
|
),
|
|
"slave" => array(),
|
|
),
|
|
);
|
|
|
|
file_put_contents("mysqlnd_ms.ini", json_encode($config, JSON_PRETTY_PRINT));
|
|
printf("mysqlnd_ms.ini file created...\n");
|
|
printf("Dumping file contents...\n");
|
|
printf("%s\n", str_repeat("-", 80));
|
|
echo file_get_contents("mysqlnd_ms.ini");
|
|
printf("\n%s\n", str_repeat("-", 80));
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
mysqlnd_ms.ini file created...
|
|
Dumping file contents...
|
|
--------------------------------------------------------------------------------
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": [
|
|
|
|
]
|
|
}
|
|
}
|
|
--------------------------------------------------------------------------------
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
A plugin configuration file consists of one or more sections. Sections
|
|
are represented by the top-level object properties of the
|
|
object encoded in the <literal>JSON</literal> file. Sections could also
|
|
be called configuration names.
|
|
</para>
|
|
<para>
|
|
Applications reference sections by their name. Applications use section names
|
|
as the host (server) parameter to the various connect methods of the
|
|
<link linkend="ref.mysqli">mysqli</link>,
|
|
<link linkend="ref.mysql">mysql</link> and
|
|
<link linkend="ref.pdo-mysql">PDO_MYSQL</link> extensions. Upon connect
|
|
the <link linkend="book.mysqlnd">mysqlnd</link> plugin compares the hostname
|
|
with all section names from the plugin configuration file. If hostname and
|
|
section name match, the plugin will load the sections settings.
|
|
</para>
|
|
<para xml:id="mysqlnd-ms.plugin-ini-json.using_section">
|
|
<example>
|
|
<title>Using section names example</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.27"
|
|
},
|
|
"slave_1": {
|
|
"host": "192.168.2.27",
|
|
"port": 3306
|
|
}
|
|
}
|
|
},
|
|
"localhost": {
|
|
"master": [
|
|
{
|
|
"host": "localhost",
|
|
"socket": "\/path\/to\/mysql.sock"
|
|
}
|
|
],
|
|
"slave": [
|
|
{
|
|
"host": "192.168.3.24",
|
|
"port": "3305"
|
|
},
|
|
{
|
|
"host": "192.168.3.65",
|
|
"port": "3309"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
/* All of the following connections will be load balanced */
|
|
$mysqli = new mysqli("myapp", "username", "password", "database");
|
|
$pdo = new PDO('mysql:host=myapp;dbname=database', 'username', 'password');
|
|
$mysql = mysql_connect("myapp", "username", "password");
|
|
|
|
$mysqli = new mysqli("localhost", "username", "password", "database");
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Section names are strings. It is valid to use a section name such as
|
|
<literal>192.168.2.1</literal>, <literal>127.0.0.1</literal> or
|
|
<literal>localhost</literal>. If, for example, an application
|
|
connects to <literal>localhost</literal> and a plugin
|
|
configuration section <literal>localhost</literal> exists, the
|
|
semantics of the connect operation are changed. The application will
|
|
no longer only use the MySQL server running on the host
|
|
<literal>localhost</literal> but the plugin will start to load balance
|
|
MySQL queries following the rules from the <literal>localhost</literal>
|
|
configuration section. This way you can load balance queries from
|
|
an application without changing the applications source code.
|
|
Please keep in mind, that such a configuration may not contribute
|
|
to overall readability of your applications source code. Using section names
|
|
that can be mixed up with host names should be seen as a last resort.
|
|
</para>
|
|
<para xml:id="mysqlnd-ms.plugin-ini-json.server_list_syntax">
|
|
Each configuration section contains at least a list of master servers
|
|
and a list of slave servers. The master list is configured with the keyword
|
|
<literal>master</literal>, the slave list is configured with the <literal>slave</literal>
|
|
Failing to provide a slave list will result in an error of type
|
|
<literal>E_ERROR</literal> (fatal error). Although you are not allowed
|
|
to omit the slave list, it may be empty.
|
|
</para>
|
|
<para>
|
|
The master and slave server lists can be optionally indexed by symbolic
|
|
names for the servers they describe. If optional indexing entries
|
|
by symbolic names is not needed, use an array of server descriptions for
|
|
the slave or master that is to be described.
|
|
<example>
|
|
<title>List of anonymous slaves</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
"slave": [
|
|
{
|
|
"host": "192.168.3.24",
|
|
"port": "3305"
|
|
},
|
|
{
|
|
"host": "192.168.3.65",
|
|
"port": "3309"
|
|
}
|
|
]
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
An anonymous server list is encoded by the <literal>JSON array</literal> type.
|
|
You can optionally use symbolic names for indexing the slave or master servers
|
|
of a server list. In the latter case you have to use the
|
|
<literal>JSON object</literal> type.
|
|
<example>
|
|
<title>Master list using symbolic names</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
It is recommended to index the server lists with symbolic server names.
|
|
The alias names will be shown in error messages.
|
|
</para>
|
|
<para>
|
|
The order of servers is preserved and taken into account by mysqlnd_ms.
|
|
If, for example, you configure round robin load balancing strategy, the
|
|
first <literal>SELECT</literal> statement will be executed on the
|
|
slave that appears first in the slave server list.
|
|
</para>
|
|
<para>
|
|
A configured server can be described with the <literal>host</literal>,
|
|
<literal>port</literal>, <literal>socket</literal>, <literal>db</literal>,
|
|
<literal>user</literal>, <literal>password</literal> and <literal>connect_flags</literal>.
|
|
It is mandatory to set the database server host using the <literal>host</literal>
|
|
keyword. All other settings are optional.
|
|
<example>
|
|
<title>Keywords to configure a server</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "db_server_host",
|
|
"port": "db_server_port",
|
|
"socket": "db_server_socket",
|
|
"db": "database_resp_schema",
|
|
"user": "user",
|
|
"password": "password",
|
|
"connect_flags": 0
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "db_server_host",
|
|
"port": "db_server_port",
|
|
"socket": "db_server_socket"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
If a setting is omitted, the plugin will use the value provided by the user
|
|
API call used to open a connection. Please, see the
|
|
<link linkend="mysqlnd-ms.plugin-ini-json.using_section">using section names example</link> above.
|
|
</para>
|
|
<para>
|
|
The configuration file format has been changed in version 1.1.0-beta to allow for
|
|
chained filters. Filters are responsible for filtering the configured list of
|
|
servers to identify a server for execution of a given statement.
|
|
Filters are configured with the <literal>filter</literal> keyword. Filters
|
|
are executed by mysqlnd_ms in the order of their appearance.
|
|
Defining filters is optional. A configuration section in the plugins
|
|
configuration file does not need to have a <literal>filters</literal> entry.
|
|
</para>
|
|
<para>
|
|
Filters replace the
|
|
<link linkend="ini.mysqlnd-ms-plugin-config.pick"><literal>pick[]</literal></link>
|
|
setting from prior versions. The new <literal>random</literal> and
|
|
<literal>roundrobin</literal> provide the same functionality.
|
|
<example>
|
|
<title>New <literal>roundrobin</literal> filter, old functionality</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
},
|
|
"slave_1": {
|
|
"host": "192.168.78.137",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"filters": {
|
|
"roundrobin": [
|
|
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
The function
|
|
<link linkend="function.mysqlnd-ms-set-user-pick-server">
|
|
<function>mysqlnd_ms_set_user_pick_server</function></link>
|
|
has been removed. Setting a callback is now done with the <literal>user</literal>
|
|
filter. Some filters accept parameters. The <literal>user</literal> filter
|
|
requires and accepts a mandatory <literal>callback</literal> parameter
|
|
to set the callback previously set through the function <function>mysqlnd_ms_set_user_pick_server</function>.
|
|
<example>
|
|
<title>The <literal>user</literal> filter replaces <function>mysqlnd_ms_set_user_pick_server</function></title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
"filters": {
|
|
"user": {
|
|
"callback": "pick_server"
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Here is a short explanation of the configuration directives that can be used.
|
|
</para>
|
|
<para>
|
|
<variablelist>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.master">
|
|
<term>
|
|
<parameter>master</parameter>
|
|
<type>array or object</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
List of MySQL replication master servers. The list of either
|
|
of the <literal>JSON type array</literal> to declare an anonymous list
|
|
of servers or of the <literal>JSON type object</literal>. Please,
|
|
see <link linkend="mysqlnd-ms.plugin-ini-json.server_list_syntax">above</link>
|
|
for examples.
|
|
</para>
|
|
<para>
|
|
Setting at least one master server is mandatory. The plugin will issue an
|
|
error of type <literal>E_ERROR</literal> if the user has failed to
|
|
provide a master server list for a configuration section.
|
|
The fatal error may read
|
|
<literal>(mysqlnd_ms) Section [master] doesn't exist for host
|
|
[name_of_a_config_section] in %s on line %d</literal>.
|
|
</para>
|
|
<para>
|
|
A server is described with
|
|
the <literal>host</literal>, <literal>port</literal>,
|
|
<literal>socket</literal>, <literal>db</literal>,
|
|
<literal>user</literal>, <literal>password</literal> and
|
|
<literal>connect_flags</literal>. It is mandatory to
|
|
provide at a value for <literal>host</literal>. If any of the
|
|
other values is not given, it will be taken from the user
|
|
API connect call, please, see also:
|
|
<link linkend="mysqlnd-ms.plugin-ini-json.using_section">using section names example</link>.
|
|
</para>
|
|
<para xml:id="mysqlnd-ms.plugin-ini-json.server_config_keywords">
|
|
Table of server configuration keywords.
|
|
</para>
|
|
<informaltable>
|
|
<tgroup cols="3">
|
|
<colspec colwidth="10%"/>
|
|
<colspec colwidth="70%"/>
|
|
<colspec colwidth="20%"/>
|
|
<thead>
|
|
<row>
|
|
<entry>Keyword</entry>
|
|
<entry>Description</entry>
|
|
<entry>Version</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>
|
|
<literal>host</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
Database server host. This is a mandatory setting.
|
|
Failing to provide, will cause an error of type <literal>E_RECOVERABLE_ERROR</literal>
|
|
when the plugin tries to connect to the server. The error message may
|
|
read <literal>(mysqlnd_ms) Cannot find [host] in
|
|
[%s] section in config in %s on line %d</literal>.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>port</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
Database server TCP/IP port.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>socket</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
Database server Unix domain socket.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>db</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
Database (schemata).
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>user</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
MySQL database user.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>password</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
MySQL database user password.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>connect_flags</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
Connection flags.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</informaltable>
|
|
<para>
|
|
The plugin supports using only one master server. An experimental
|
|
setting exists to enable multi-master support. The details are
|
|
not documented. The setting is meant for development only.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.slave">
|
|
<term>
|
|
<parameter>slave</parameter>
|
|
<type>array or object</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
List of one or more MySQL replication slave servers. The syntax is
|
|
identical to setting master servers, please, see
|
|
<link linkend="ini.mysqlnd-ms-plugin-config-v2.master"><literal>master</literal></link>
|
|
above for details.
|
|
</para>
|
|
<para>
|
|
The plugin supports using one or more slave servers.
|
|
</para>
|
|
<para>
|
|
Setting a list of slave servers is mandatory. The plugin will report
|
|
an error of the type <literal>E_ERROR</literal> if <literal>slave</literal>
|
|
is not given for a configuration section. The fatal error message may read
|
|
<literal>(mysqlnd_ms) Section [slave] doesn't exist for host [%s] in %s on line %d</literal>.
|
|
Note, that it is valid to use an empty slave server list.
|
|
The error has been introduced to prevent accidently setting no slaves by
|
|
forgetting about the <literal>slave</literal> setting.
|
|
A master-only setup is still possible using an empty slave server list.
|
|
</para>
|
|
<para>
|
|
If an empty slave list is configured and an attempt is made to
|
|
execute a statement on a slave the plugin may emit a warning like
|
|
<literal>mysqlnd_ms) Couldn't find the appropriate slave connection.
|
|
0 slaves to choose from.</literal> upon statement execution.
|
|
It is possible that another warning follows such as
|
|
<literal>(mysqlnd_ms) No connection selected by the last filter</literal>.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.filters">
|
|
<term>
|
|
<parameter>filters</parameter>
|
|
<type>object</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
List of filters. A filter is responsible to filter the list of available
|
|
servers for executing a given statement. Filters can be chained.
|
|
The <literal>random</literal> and <literal>roundrobin</literal> filter
|
|
replace the
|
|
<link linkend="ini.mysqlnd-ms-plugin-config.pick"><literal>pick[]</literal></link>
|
|
directive used in prior version to select a load balancing policy.
|
|
The <literal>user</literal> filter replaces the
|
|
<link linkend="function.mysqlnd-ms-set-user-pick-server">
|
|
<function>mysqlnd_ms_set_user_pick_server</function></link> function.
|
|
</para>
|
|
<para>
|
|
Filters may accept parameters to refine their actions.
|
|
</para>
|
|
<para>
|
|
If no load balancing policy is set, the plugin will default to
|
|
<literal>random_once</literal>. The <literal>random_once</literal>
|
|
policy picks a random slave server when running the first read-only
|
|
statement. The slave server will be used for all read-only
|
|
statements until the PHP script execution ends. No load balancing
|
|
policy is set and thus, defaulting takes place,
|
|
if neither the <literal>random</literal> nor the
|
|
<literal>roundrobin</literal> are part of a configuration section.
|
|
</para>
|
|
<para>
|
|
If a filter chain is configured so that a filter which output no
|
|
more than once server is used as input for a filter which should be given
|
|
more than one server as input, the plugin may emit a warning upon
|
|
opening a connection. The warning may read: <literal>(mysqlnd_ms) Error while creating
|
|
filter '%s' . Non-multi filter '%s' already created.
|
|
Stopping in %s on line %d</literal>. Futhermore an error of
|
|
the error code <literal>2000</literal>, the sql state <literal>HY000</literal>
|
|
and an error message similar to the warning may be set on the connection
|
|
handle.
|
|
<example>
|
|
<title>Invalid filter sequence</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"filters": [
|
|
"roundrobin",
|
|
"random"
|
|
]
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$link = new mysqli("myapp", "root", "", "test");
|
|
printf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error());
|
|
$link->query("SELECT 1 FROM DUAL");
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
PHP Warning: mysqli::mysqli(): (HY000/2000): (mysqlnd_ms) Error while creating filter 'random' . Non-multi filter 'roundrobin' already created. Stopping in filter_warning.php on line 1
|
|
[2000] (mysqlnd_ms) Error while creating filter 'random' . Non-multi filter 'roundrobin' already created. Stopping
|
|
PHP Warning: mysqli::query(): Couldn't fetch mysqli in filter_warning.php on line 3
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.filter_random">
|
|
<term>
|
|
Filter: <parameter>random</parameter>
|
|
<type>object</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
The <literal>random</literal> filter features the random and random once
|
|
load balancing policies, set through the
|
|
<link linkend="ini.mysqlnd-ms-plugin-config.pick"><literal>pick[]</literal></link>
|
|
directive in older versions.
|
|
</para>
|
|
<para>
|
|
The random policy will pick a random server whenever
|
|
a read-only statement is to be executed. The random once strategy
|
|
picks a random slave server once and continues using the slave for the
|
|
rest of the PHP web request. Random once is a default,
|
|
if load balancing is not configured through a filter.
|
|
</para>
|
|
<para>
|
|
If the <literal>random</literal> filter is not given any arguments, it
|
|
stands for random load balancing policy.
|
|
<example>
|
|
<title>Random load balancing with <literal>random</literal> filter</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
},
|
|
"slave_1": {
|
|
"host": "192.168.78.137",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"filters": [
|
|
"random"
|
|
]
|
|
}
|
|
}
|
|
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
Optionally, the <literal>sticky</literal> argument can be passed to the
|
|
filter. If the parameter <literal>sticky</literal> is set to the string
|
|
<literal>1</literal>, the filter follows the random once
|
|
load balancing strategy.
|
|
<example>
|
|
<title>Random once load balancing with <literal>random</literal> filter</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"filters": {
|
|
"random": {
|
|
"sticky": "1"
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
Unknown arguments are ignored. No warning or error is given.
|
|
</para>
|
|
|
|
<para>
|
|
Expects one or more servers as input. Outputs one server.
|
|
A filter sequence such as
|
|
<literal>random</literal>, <literal>roundrobin</literal> may
|
|
cause a warning and an error message to be set on the connection
|
|
handle when executing a statement.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.filter_roundrobin">
|
|
<term>
|
|
Filter: <parameter>roundrobin</parameter>
|
|
<type>object</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
If using the <literal>roundrobin</literal> filter, the plugin
|
|
iterates over the list of configured slave servers to pick a server
|
|
for statement execution. If the plugin reaches the end of the list,
|
|
it wraps around to the beginning of the list and picks the first
|
|
configured slave server.
|
|
<example>
|
|
<title><literal>roundrobin</literal> filter</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"filters": [
|
|
"roundrobin"
|
|
]
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Expects one or more servers as input. Outputs one server.
|
|
A filter sequence such as
|
|
<literal>roundrobin</literal>, <literal>random</literal> may
|
|
cause a warning and an error message to be set on the connection
|
|
handle when executing a statement.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.filter_user">
|
|
<term>
|
|
Filter: <parameter>user</parameter>
|
|
<type>object</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
The <literal>user</literal> replaces <link linkend="function.mysqlnd-ms-set-user-pick-server">
|
|
<function>mysqlnd_ms_set_user_pick_server</function></link> function,
|
|
which was removed in 1.1.0-beta. The filter sets a callback for user-defined
|
|
read/write splitting and server selection.
|
|
</para>
|
|
<para>
|
|
The plugins built-in read/write query split mechanism decisions can be
|
|
overwritten in two ways. The easiest way is to prepend a query string
|
|
with the SQL hints <constant>MYSQLND_MS_MASTER_SWITCH</constant>,
|
|
<constant>MYSQLND_MS_SLAVE_SWITCH</constant> or
|
|
<constant>MYSQLND_MS_LAST_USED_SWITCH</constant>. Using SQL hints one can
|
|
control, for example, whether a query shall be send to the MySQL replication
|
|
master server or one of the slave servers. By help of SQL hints it is
|
|
not possible to pick a certain slave server for query execution.
|
|
</para>
|
|
<para>
|
|
Full control on server selection can be gained using a callback function.
|
|
Use of a callback is recommended to expert users only because the callback
|
|
has to cover all cases otherwise handled by the plugin.
|
|
</para>
|
|
<para>
|
|
The plugin will invoke the callback function for selecting a server from the
|
|
lists of configured master and slave servers. The callback function
|
|
inspects the query to run and picks a server for query execution by returning
|
|
the hosts URI, as found in the master and slave list.
|
|
</para>
|
|
<para>
|
|
If the lazy connections are enabled and the callback choses a slave server for
|
|
which no connection has been established so far and establishing the connection
|
|
to the slave fails, the plugin will return an error upon the next action
|
|
on the failed connection, for example, when running a query. It is the
|
|
responsibility of the application developer to handle the error. For example,
|
|
the application can re-run the query to trigger a new server selection and
|
|
callback invocation. If so, the callback must make sure to select
|
|
a different slave, or check slave availability, before returning to
|
|
the plugin to prevent an endless loop.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Setting a callback</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"filters": {
|
|
"user": {
|
|
"callback": "pick_server"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
The callback is supposed to return a host to run the query on.
|
|
The host URI is to be taken from the master and slave connection lists
|
|
passed to the callback function. If callback returns a value neither
|
|
found in the master nor in the slave connection lists the plugin
|
|
will emit an error of the type <literal>E_RECOVERABLE_ERROR</literal>
|
|
The error may read like
|
|
<literal> (mysqlnd_ms) User filter callback has returned an unknown server.
|
|
The server 'server that is not in master or slave list' can neither be found
|
|
in the master list nor in the slave list</literal>.
|
|
If the application catches the error to ignore it, follow up errors
|
|
may be set on the connection handle, for example,
|
|
<literal>(mysqlnd_ms) No connection selected by the last filter</literal> with
|
|
the error code <literal>2000</literal> and the sqlstate <literal>HY000</literal>.
|
|
Furthermore a warning may be emitted.
|
|
</para>
|
|
<para>
|
|
Referencing a non-existing function as a callback will result
|
|
in any error of the type <literal>E_RECOVERABLE_ERROR</literal> whenever
|
|
the plugin tries to callback function. The error message may reads like:
|
|
<literal>(mysqlnd_ms) Specified callback (pick_server) is
|
|
not a valid callback</literal>. If the application catches the error to
|
|
ignore it, follow up errors may be set on the connection handle, for example,
|
|
<literal>(mysqlnd_ms) Specified callback (pick_server) is
|
|
not a valid callback</literal> with the error code <literal>2000</literal>
|
|
and the sqlstate <literal>HY000</literal>. Furthermore a warning
|
|
may be emitted.
|
|
</para>
|
|
<para>
|
|
The following parameters are passed from the plugin to the callback.
|
|
</para>
|
|
<informaltable>
|
|
<tgroup cols="3">
|
|
<colspec colwidth="10%"/>
|
|
<colspec colwidth="70%"/>
|
|
<colspec colwidth="20%"/>
|
|
<thead>
|
|
<row>
|
|
<entry>Parameter</entry>
|
|
<entry>Description</entry>
|
|
<entry>Version</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>
|
|
<literal>connected_host</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
URI of the currently connected database server.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>query</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
Query string of the statement for which a server needs
|
|
to be picked.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>masters</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
List of master servers to choose from. Note, that the list of master
|
|
servers may not be identical to the list of configured master
|
|
servers if the filter is not the first in the filter chain.
|
|
Previously run filters may have reduced the master
|
|
list already.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>slaves</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
List of slave servers to choose from. Note, that the list of master
|
|
servers may not be identical to the list of configured master
|
|
servers if the filter is not the first in the filter chain.
|
|
Previously run filters may have reduced the master
|
|
list already.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>last_used_connection</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
URI of the server of the connection used to execute the previous
|
|
statement on.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
<row>
|
|
<entry>
|
|
<literal>in_transaction</literal>
|
|
</entry>
|
|
<entry>
|
|
<para>
|
|
Boolean flag indicating wheter the statement is
|
|
part of an open transaction. If autocommit mode is turned
|
|
off, this will be set to <literal>TRUE</literal>. Otherwise
|
|
it is set to <literal>FALSE</literal>.
|
|
</para>
|
|
<para>
|
|
Transaction detection is based on monitoring the
|
|
mysqlnd library call <literal>set_autocommit</literal>.
|
|
Monitoring is not possible beofre PHP 5.4.0. Please, see
|
|
<link linkend="mysqlnd-ms.pooling">connection pooling and switching</link>
|
|
concepts discussion for further details.
|
|
</para>
|
|
</entry>
|
|
<entry>Since 1.1.0.</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</informaltable>
|
|
<para>
|
|
<example>
|
|
<title>Using a callback</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.27",
|
|
"port": "3306"
|
|
},
|
|
"slave_1": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"filters": {
|
|
"user": {
|
|
"callback": "pick_server"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function pick_server($connected, $query, $masters, $slaves, $last_used_connection, $in_transaction)
|
|
{
|
|
static $slave_idx = 0;
|
|
static $num_slaves = NULL;
|
|
if (is_null($num_slaves))
|
|
$num_slaves = count($slaves);
|
|
|
|
/* default: fallback to the plugins build-in logic */
|
|
$ret = NULL;
|
|
|
|
printf("User has connected to '%s'...\n", $connected);
|
|
printf("... deciding where to run '%s'\n", $query);
|
|
|
|
$where = mysqlnd_ms_query_is_select($query);
|
|
switch ($where)
|
|
{
|
|
case MYSQLND_MS_QUERY_USE_MASTER:
|
|
printf("... using master\n");
|
|
$ret = $masters[0];
|
|
break;
|
|
case MYSQLND_MS_QUERY_USE_SLAVE:
|
|
/* SELECT or SQL hint for using slave */
|
|
if (stristr($query, "FROM table_on_slave_a_only"))
|
|
{
|
|
/* a table which is only on the first configured slave */
|
|
printf("... access to table available only on slave A detected\n");
|
|
$ret = $slaves[0];
|
|
}
|
|
else
|
|
{
|
|
/* round robin */
|
|
printf("... some read-only query for a slave\n");
|
|
$ret = $slaves[$slave_idx++ % $num_slaves];
|
|
}
|
|
break;
|
|
case MYSQLND_MS_QUERY_LAST_USED:
|
|
printf("... using last used server\n");
|
|
$ret = $last_used_connection;
|
|
break;
|
|
}
|
|
|
|
printf("... ret = '%s'\n", $ret);
|
|
return $ret;
|
|
}
|
|
|
|
$mysqli = new mysqli("myapp", "root", "", "test");
|
|
|
|
if (!($res = $mysqli->query("SELECT 1 FROM DUAL")))
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
else
|
|
$res->close();
|
|
|
|
if (!($res = $mysqli->query("SELECT 2 FROM DUAL")))
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
else
|
|
$res->close();
|
|
|
|
|
|
if (!($res = $mysqli->query("SELECT * FROM table_on_slave_a_only")))
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
else
|
|
$res->close();
|
|
|
|
$mysqli->close();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
User has connected to 'myapp'...
|
|
... deciding where to run 'SELECT 1 FROM DUAL'
|
|
... some read-only query for a slave
|
|
... ret = 'tcp://192.168.2.27:3306'
|
|
User has connected to 'myapp'...
|
|
... deciding where to run 'SELECT 2 FROM DUAL'
|
|
... some read-only query for a slave
|
|
... ret = 'tcp://192.168.78.136:3306'
|
|
User has connected to 'myapp'...
|
|
... deciding where to run 'SELECT * FROM table_on_slave_a_only'
|
|
... access to table available only on slave A detected
|
|
... ret = 'tcp://192.168.2.27:3306'
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.failover">
|
|
<term>
|
|
<parameter>failover</parameter>
|
|
<type>string</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
Failover policy. Supported policies:
|
|
<literal>disabled</literal> (default), <literal>master</literal>.
|
|
</para>
|
|
<para>
|
|
If no failover policy is set, the plugin will not do any
|
|
automatic failover (<literal>failover=disabled</literal>). Whenever
|
|
the plugin fails to connect a server it will emit a warning and
|
|
set the connections error code and message. Thereafter it is up to
|
|
the application to handle the error and, for example, resent the
|
|
last statement to trigger the selection of another server.
|
|
</para>
|
|
<para>
|
|
If using <literal>failover=master</literal> the plugin will implicitly
|
|
failover to a slave, if available. Please check the
|
|
concepts documentation to learn about potential
|
|
pitfalls and risks of using <literal>failover=master</literal>.
|
|
<example>
|
|
<title>Optional master failover when failing to connect to slave</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"failover": "master"
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Setting <literal>failover</literal> to any other value but
|
|
<literal>disabled</literal> or <literal>master</literal> will not
|
|
emit any warning or error.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.lazy_connections">
|
|
<term>
|
|
<parameter>lazy_connections</parameter>
|
|
<type>bool</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
Controls the use of lazy connections. Lazy connections
|
|
are connections which are not opened before the client sends the first
|
|
connection. Lazy connections are a default.
|
|
</para>
|
|
<para>
|
|
It is strongly recommended to use lazy connections.
|
|
Lazy connections help to keep the number of open connections low.
|
|
If you disable lazy connections and, for example, configure one MySQL
|
|
replication master server and two MySQL replication slaves, the
|
|
plugin will open three connections upon the first call to a
|
|
connect function although the application might use the master
|
|
connection only.
|
|
</para>
|
|
<para>
|
|
Lazy connections bare a risk if you make heavy use of actions
|
|
which change the state of a connection. The plugin does not dispatch
|
|
all state changing actions to all connections from the connection pool.
|
|
The few dispatched actions are applied to already opened connections
|
|
only. Lazy connections opened in the future are not affected.
|
|
Only some settings are "remembered" and applied when lazy
|
|
connections are opened.
|
|
<example>
|
|
<title>Disabling lazy connection</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"lazy_connections": 0
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.master_on_write">
|
|
<term>
|
|
<parameter>master_on_write</parameter>
|
|
<type>bool</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
If set, the plugin will use the master server only after the
|
|
first statement has been executed on the master. Applications
|
|
can still send statements to the slaves using SQL hints to
|
|
overrule the automatic decision.
|
|
</para>
|
|
<para>
|
|
The setting may help with replication lag. If an application runs
|
|
an <literal>INSERT</literal> the plugin will, by default, use the
|
|
master to execute all following statements, including
|
|
<literal>SELECT</literal> statements. This helps to avoid problems
|
|
with reads from slaves which have not replicated the
|
|
<literal>INSERT</literal> yet.
|
|
<example>
|
|
<title>Master on write for consistent reads</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"master_on_write": 1
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config-v2.trx_stickiness">
|
|
<term>
|
|
<parameter>trx_stickiness</parameter>
|
|
<type>string</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
Transaction stickiness policy. Supported policies:
|
|
<literal>disabled</literal> (default), <literal>master</literal>.
|
|
</para>
|
|
<para>
|
|
Experimental feature.
|
|
</para>
|
|
<para>
|
|
The setting requires 5.4.0 or newer. If used with PHP older than 5.4.0,
|
|
the plugin will emit a warning like
|
|
<literal>(mysqlnd_ms) trx_stickiness strategy is not supported before PHP 5.3.99</literal>.
|
|
</para>
|
|
<para>
|
|
If no transaction stickiness policy is set or,
|
|
if setting <literal>trx_stickiness=disabled</literal>,
|
|
the plugin is not transaction aware. Thus, the plugin may load balance
|
|
connections and switch connections in the middle of a transaction.
|
|
The plugin is not transaction safe. SQL hints must be used
|
|
avoid connection switches during a transaction.
|
|
</para>
|
|
<para>
|
|
As of PHP 5.4.0 the mysqlnd library allows the plugin to monitor
|
|
the <literal>autocommit</literal> mode set by calls to the
|
|
libraries <literal>set_autocommit()</literal> function.
|
|
If setting <literal>set_stickiness=master</literal> and
|
|
<literal>autocommit</literal> gets disabled by a PHP MySQL extension
|
|
invoking the <literal>mysqlnd</literal> library internal
|
|
function call <literal>set_autocommit()</literal>, the plugin is made
|
|
aware of the begin of a transaction. Then, the plugin stops load balancing
|
|
and directs all statements to the master server until
|
|
<literal>autocommit</literal> is enabled. Thus, no SQL hints are required.
|
|
</para>
|
|
<para>
|
|
An example of a PHP MySQL API function calling the <literal>mysqlnd</literal>
|
|
library internal function call <literal>set_autocommit()</literal> is
|
|
<function>mysqli_autocommit</function>.
|
|
</para>
|
|
<para>
|
|
Although setting <literal>ser_stickiness=master</literal>, the plugin
|
|
cannot be made aware of <literal>autocommit</literal> mode changes caused
|
|
by SQL statements such as <literal>SET AUTOCOMMIT=0</literal>.
|
|
<example>
|
|
<title>Using master to execute transactions</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.78.136",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"trx_stickiness": "master"
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.plugin-ini-v1">
|
|
<title xmlns="http://docbook.org/ns/docbook">Plugin configuration file (<= 1.0.x)</title>
|
|
<note>
|
|
<para>
|
|
The below description applies to PECL/mysqlnd_ms < 1.1.0-beta.
|
|
It is not valid for later versions.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
The plugin is using its own configuration file. The configuration file
|
|
holds information on the MySQL replication master server,
|
|
the MySQL replication slave servers, the server pick (load balancing) policy,
|
|
the failover strategy and the use of lazy connections.
|
|
</para>
|
|
<para>
|
|
The PHP configuration directive
|
|
<link linkend="ini.mysqlnd-ms.ini-file"><literal>mysqlnd_ms.ini_file</literal></link>
|
|
is used to set the plugins configuration file.
|
|
</para>
|
|
<para>
|
|
The configuration file mimics standard the <literal>php.ini</literal> format.
|
|
It consists of one or more sections. Every section defines its own unit
|
|
of settings. There is no global section for setting defaults.
|
|
</para>
|
|
<para>
|
|
Applications reference sections by their name. Applications use section names
|
|
as the host (server) parameter to the various connect methods of the
|
|
<link linkend="ref.mysqli">mysqli</link>,
|
|
<link linkend="ref.mysql">mysql</link> and
|
|
<link linkend="ref.pdo-mysql">PDO_MYSQL</link> extensions. Upon connect
|
|
the <link linkend="book.mysqlnd">mysqlnd</link> plugin compares the hostname
|
|
with all section names from the plugin configuration file. If hostname and
|
|
section name match, the plugin will load the sections settings.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Using section names example</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
[myapp]
|
|
master[] = localhost
|
|
slave[] = 192.168.2.27
|
|
slave[] = 192.168.2.28:3306
|
|
[localhost]
|
|
master[] = localhost:/tmp/mysql/mysql.sock
|
|
slave[] = 192.168.3.24:3305
|
|
slave[] = 192.168.3.65:3309
|
|
]]>
|
|
</programlisting>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
/* All of the following connections will be load balanced */
|
|
$mysqli = new mysqli("myapp", "username", "password", "database");
|
|
$pdo = new PDO('mysql:host=myapp;dbname=database', 'username', 'password');
|
|
$mysql = mysql_connect("myapp", "username", "password");
|
|
|
|
$mysqli = new mysqli("localhost", "username", "password", "database");
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Section names are strings. It is valid to use a section name such as
|
|
<literal>192.168.2.1</literal>, <literal>127.0.0.1</literal> or
|
|
<literal>localhost</literal>. If, for example, an application
|
|
connects to <literal>localhost</literal> and a plugin
|
|
configuration section <literal>[localhost]</literal> exists, the
|
|
semantics of the connect operation are changed. The application will
|
|
no longer only use the MySQL server running on the host
|
|
<literal>localhost</literal> but the plugin will start to load balance
|
|
MySQL queries following the rules from the <literal>[localhost]</literal>
|
|
configuration section. This way you can load balance queries from
|
|
an application without changing the applications source code.
|
|
</para>
|
|
<para>
|
|
The <literal>master[]</literal>, <literal>slave[]</literal>
|
|
and <literal>pick[]</literal> configuration directives use a list-like syntax.
|
|
Configuration directives supporting list-like syntax may appear multiple
|
|
times in a configuration section. The plugin maintains the order in
|
|
which entries appear when interpreting them. For example,
|
|
the below example shows two <literal>slave[]</literal> configuration
|
|
directives in the configuration section <literal>[myapp]</literal>.
|
|
If doing round-robin load balancing for read-only queries, the plugin
|
|
will send the first read-only query to the MySQL server
|
|
<literal>mysql_slave_1</literal> because it is the first in the list.
|
|
The second read-only query will be send to the MySQL server
|
|
<literal>mysql_slave_2</literal> because it is the second in the list.
|
|
Configuration directives supporting list-like syntax result are ordered
|
|
from top to bottom in accordance to their appearance within a configuration
|
|
section.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>List-like syntax</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
[myapp]
|
|
master[] = mysql_master_server
|
|
slave[] = mysql_slave_1
|
|
slave[] = mysql_slave_2
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Here is a short explanation of the configuration directives that can be used.
|
|
</para>
|
|
<para>
|
|
<variablelist>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config.master">
|
|
<term>
|
|
<parameter>master[]</parameter>
|
|
<type>string</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
URI of a MySQL replication master server. The URI follows the syntax
|
|
<literal>hostname[:port|unix_domain_socket]</literal>.
|
|
</para>
|
|
<para>
|
|
The plugin supports using only one master server.
|
|
</para>
|
|
<para>
|
|
Setting a master server is mandatory. The plugin will report a
|
|
warning upon connect if the user has failed to provide a master
|
|
server for a configuration section.
|
|
The warning may read
|
|
<literal>(mysqlnd_ms) Cannot find master section in config</literal>.
|
|
Furthermore the plugin may set an error code for the connection handle such as
|
|
<literal>HY000/2000 (CR_UNKNOWN_ERROR)</literal>. The corresponding error
|
|
message depends on your language settings.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config.slave">
|
|
<term>
|
|
<parameter>slave[]</parameter>
|
|
<type>string</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
URI of one or more MySQL replication slave servers. The URI follows the syntax
|
|
<literal>hostname[:port|unix_domain_socket]</literal>.
|
|
</para>
|
|
<para>
|
|
The plugin supports using one or more slave servers.
|
|
</para>
|
|
<para>
|
|
Setting a slave server is mandatory. The plugin will report a
|
|
warning upon connect if the user has failed to provide at least one slave
|
|
server for a configuration section. The warning may read
|
|
<literal>(mysqlnd_ms) Cannot find slaves section in config</literal>.
|
|
Furthermore the plugin may set an error code for the connection handle such as
|
|
<literal>HY000/2000 (CR_UNKNOWN_ERROR)</literal>. The corresponding error
|
|
message depends on your language settings.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config.pick">
|
|
<term>
|
|
<parameter>pick[]</parameter>
|
|
<type>string</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
Load balancing (server picking) policy. Supported policies:
|
|
<literal>random</literal>, <literal>random_once</literal> (default),
|
|
<literal>roundrobin</literal>, <literal>user</literal>.
|
|
</para>
|
|
<para>
|
|
If no load balancing policy is set, the plugin will default to
|
|
<literal>random_once</literal>. The <literal>random_once</literal>
|
|
policy picks a random slave server when running the first read-only
|
|
statement. The slave server will be used for all read-only
|
|
statements until the PHP script execution ends.
|
|
</para>
|
|
<para>
|
|
The <literal>random</literal> policy will pick a random server whenever
|
|
a read-only statement is to be executed.
|
|
</para>
|
|
<para>
|
|
If using
|
|
<literal>roundrobin</literal> the plugin iterates over the list of
|
|
configured slave servers to pick a server for statement execution.
|
|
If the plugin reaches the end of the list, it wraps around to the beginning
|
|
of the list and picks the first configured slave server.
|
|
</para>
|
|
<para>
|
|
Setting more than one load balancing policy for a configuration
|
|
section makes only sense in conjunction with <literal>user</literal>
|
|
and <function>mysqlnd_ms_set_user_pick_server</function>. If the
|
|
user defined callback fails to pick a server, the plugin falls
|
|
back to the second configured load balancing policy.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config.failover">
|
|
<term>
|
|
<parameter>failover</parameter>
|
|
<type>string</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
Failover policy. Supported policies:
|
|
<literal>disabled</literal> (default), <literal>master</literal>.
|
|
</para>
|
|
<para>
|
|
If no failover policy is set, the plugin will not do any
|
|
automatic failover (<literal>failover=disabled</literal>). Whenever
|
|
the plugin fails to connect a server it will emit a warning and
|
|
set the connections error code and message. Thereafter it is up to
|
|
the application to handle the error and, for example, resent the
|
|
last statement to trigger the selection of another server.
|
|
</para>
|
|
<para>
|
|
If using <literal>failover=master</literal> the plugin will implicitly
|
|
failover to a slave, if available. Please check the
|
|
concepts documentation to learn about potential
|
|
pitfalls and risks of using <literal>failover=master</literal>.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config.lazy_connections">
|
|
<term>
|
|
<parameter>lazy_connections</parameter>
|
|
<type>bool</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
Controls the use of lazy connections. Lazy connections
|
|
are connections which are not opened before the client sends the first
|
|
connection.
|
|
</para>
|
|
<para>
|
|
It is strongly recommended to use lazy connections.
|
|
Lazy connections help to keep the number of open connections low.
|
|
If you disable lazy connections and, for example, configure one MySQL
|
|
replication master server and two MySQL replication slaves, the
|
|
plugin will open three connections upon the first call to a
|
|
connect function although the application might use the master
|
|
connection only.
|
|
</para>
|
|
<para>
|
|
Lazy connections bare a risk if you make heavy use of actions
|
|
which change the state of a connection. The plugin does not dispatch
|
|
all state changing actions to all connections from the connection pool.
|
|
The few dispatched actions are applied to already opened connections
|
|
only. Lazy connections opened in the future are not affected.
|
|
If, for example, the connection character set is changed using a
|
|
PHP MySQL API call, the plugin will change the character set of all
|
|
currently opened connection. It will not remember the character set
|
|
change to apply it on lazy connections opened in the future. As a
|
|
result the internal connection pool would hold connections using
|
|
different character sets. This is not desired. Remember that character
|
|
sets are taken into account for escaping.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config.master_on_write">
|
|
<term>
|
|
<parameter>master_on_write</parameter>
|
|
<type>bool</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
If set, the plugin will use the master server only after the
|
|
first statement has been executed on the master. Applications
|
|
can still send statements to the slaves using SQL hints to
|
|
overrule the automatic decision.
|
|
</para>
|
|
<para>
|
|
The setting may help with replication lag. If an application runs
|
|
an <literal>INSERT</literal> the plugin will, by default, use the
|
|
master to execute all following statements, including
|
|
<literal>SELECT</literal> statements. This helps to avoid problems
|
|
with reads from slaves which have not replicated the
|
|
<literal>INSERT</literal> yet.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry xml:id="ini.mysqlnd-ms-plugin-config.trx_stickiness">
|
|
<term>
|
|
<parameter>trx_stickiness</parameter>
|
|
<type>string</type>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
Transaction stickiness policy. Supported policies:
|
|
<literal>disabled</literal> (default), <literal>master</literal>.
|
|
</para>
|
|
<para>
|
|
Experimental feature.
|
|
</para>
|
|
<para>
|
|
The setting requires 5.4.0 or newer. If used with PHP older than 5.4.0,
|
|
the plugin will emit a warning like
|
|
<literal>(mysqlnd_ms) trx_stickiness strategy is not supported before PHP 5.3.99</literal>.
|
|
</para>
|
|
<para>
|
|
If no transaction stickiness policy is set or,
|
|
if setting <literal>trx_stickiness=disabled</literal>,
|
|
the plugin is not transaction aware. Thus, the plugin may load balance
|
|
connections and switch connections in the middle of a transaction.
|
|
The plugin is not transaction safe. SQL hints must be used
|
|
avoid connection switches during a transaction.
|
|
</para>
|
|
<para>
|
|
As of PHP 5.4.0 the mysqlnd library allows the plugin to monitor
|
|
the <literal>autocommit</literal> mode set by calls to the
|
|
libraries <literal>trx_autocommit()</literal> function.
|
|
If setting <literal>trx_stickiness=master</literal> and
|
|
<literal>autocommit</literal> gets disabled by a PHP MySQL extension
|
|
invoking the <literal>mysqlnd</literal> library internal
|
|
function call <literal>trx_autocommit()</literal>, the plugin is made
|
|
aware of the begin of a transaction. Then, the plugin stops load balancing
|
|
and directs all statements to the master server until
|
|
<literal>autocommit</literal> is enabled. Thus, no SQL hints are required.
|
|
</para>
|
|
<para>
|
|
An example of a PHP MySQL API function calling the <literal>mysqlnd</literal>
|
|
library internal function call <literal>trx_autocommit()</literal> is
|
|
<function>mysqli_autocommit</function>.
|
|
</para>
|
|
<para>
|
|
Although setting <literal>trx_stickiness=master</literal>, the plugin
|
|
cannot be made aware of <literal>autocommit</literal> mode changes caused
|
|
by SQL statements such as <literal>SET AUTOCOMMIT=0</literal>.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.testing">
|
|
<title xmlns="http://docbook.org/ns/docbook">Testing</title>
|
|
<note>
|
|
<para>
|
|
The section applies to myslqnd_ms 1.1.0 or newer, not the 1.0 series.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
The PECL/mysqlnd_ms test suite is in the <filename>tests/</filename>
|
|
directory of the source distribution. The test suite consists of standard
|
|
phpt tests, which are described on the PHP Quality Assurance Teams website.
|
|
</para>
|
|
<para>
|
|
Before running the tests, edit <filename>tests/config.inc</filename> to
|
|
configure the MySQL servers to be used for testing. You will need at least
|
|
one MySQL server to run the tests. However, configuring two distinct MySQL
|
|
servers is better. If two distinct servers are configured, the tests can use
|
|
one of them as a logical master and the other one as a logical slave. Those
|
|
two servers do not need to be part of a MySQL replication cluster. Tests
|
|
are written in a way that they can be successfully executed without having
|
|
to setup a MySQL replication cluster. If two servers are configured they
|
|
must allow login to a test database using the same username and password on
|
|
both servers.
|
|
</para>
|
|
<para>
|
|
The most basic configuration is as follows.
|
|
<programlisting>
|
|
<![CDATA[
|
|
putenv("MYSQL_TEST_HOST=localhost");
|
|
putenv("MYSQL_TEST_PORT=3306");
|
|
putenv("MYSQL_TEST_USER=root");
|
|
putenv("MYSQL_TEST_PASSWD=");
|
|
putenv("MYSQL_TEST_DB=test");
|
|
putenv("MYSQL_TEST_ENGINE=MyISAM");
|
|
putenv("MYSQL_TEST_SOCKET=");
|
|
|
|
putenv("MYSQL_TEST_SKIP_CONNECT_FAILURE=1");
|
|
putenv("MYSQL_TEST_CONNECT_FLAGS=0");
|
|
putenv("MYSQL_TEST_EXPERIMENTAL=0");
|
|
|
|
putenv("MYSQL_TEST_MASTER_HOST=". getenv("MYSQL_TEST_HOST"));
|
|
putenv("MYSQL_TEST_SLAVE_HOST=". getenv("MYSQL_TEST_HOST"));
|
|
]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
<literal>MYSQL_TEST_HOST</literal>, <literal>MYSQL_TEST_PORT</literal> and
|
|
<literal>MYSQL_TEST_SOCKET</literal> define the hostname,
|
|
TCP/IP port and Unix domain socket of the default database server.
|
|
<literal>MYSQL_TEST_USER</literal> and <literal>MYSQL_TEST_PASSWD</literal>
|
|
contain the user and password needed to connect to the database/schema
|
|
configured with <literal>MYSQL_TEST_DB</literal>. If using two distinct
|
|
servers, a logical master and a logical slave server, both of them must
|
|
have the same database user configured to give access to the test database.
|
|
</para>
|
|
<para>
|
|
Some tests require availability of two distinct MySQL test servers. Those
|
|
tests will be skipped, if only one server is configured. To configure a logical
|
|
slave server that runs on a different host and/or listens to a different port
|
|
or socket than the default server, set <literal>MYSQL_TEST_SLAVE_HOST</literal>.
|
|
Using <literal>host</literal>, <literal>host:port</literal> or <literal>host:/path/to/socket</literal>
|
|
syntax for <literal>MYSQL_TEST_SLAVE_HOST</literal> you can set an alternate
|
|
host, host and port or host and socket for the logical slave host.
|
|
The same syntax can be used to set <literal>MYSQL_TEST_MASTER_HOST</literal>.
|
|
<programlisting>
|
|
<![CDATA[
|
|
putenv("MYSQL_TEST_SLAVE_HOST=192.168.78.136:3307"));
|
|
putenv("MYSQL_TEST_SLAVE_HOST=myserver_hostname:/path/to/socket"));
|
|
]]>
|
|
</programlisting>
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.debugging">
|
|
<title xmlns="http://docbook.org/ns/docbook">Debugging and Tracing</title>
|
|
<para>
|
|
The mysqlnd debug log can be used to debug and trace the actitivities of
|
|
PECL/mysqlnd_ms. As a mysqlnd PECL/mysqlnd_ms adds trace information to the
|
|
mysqlnd library debug file. Please, see the
|
|
<link linkend="mysqlnd.debug"><literal>mysqlnd.debug</literal></link>
|
|
PHP configuration directive documentation for a detailed description
|
|
on how to configure the debug log.
|
|
</para>
|
|
<para>
|
|
Configuration setting example to activate the debug log:
|
|
<programlisting>
|
|
<![CDATA[
|
|
mysqlnd.debug=d:t:x:O,/tmp/mysqlnd.trace
|
|
]]>
|
|
</programlisting>
|
|
<note>
|
|
<para>
|
|
This feature is only available with a debug build of PHP. Works
|
|
on Microsoft Windows if using a debug build of PHP and PHP was
|
|
built using Microsoft Visual C version 9 and above.
|
|
</para>
|
|
</note>
|
|
</para>
|
|
<para>
|
|
The debug log shows mysqlnd library and PECL/mysqlnd_ms plugin function calls,
|
|
similar to a trace log. Mysqlnd library calls are usually prefixed with
|
|
<literal>mysqlnd_</literal>. PECL/mysqlnd internal calls begin with
|
|
<literal>mysqlnd_ms</literal>.
|
|
</para>
|
|
<para>
|
|
Example excerpt from the debug log (connect):
|
|
<programlisting>
|
|
<![CDATA[
|
|
[...]
|
|
>mysqlnd_connect
|
|
| info : host=myapp user=root db=test port=3306 flags=131072
|
|
| >mysqlnd_ms::connect
|
|
| | >mysqlnd_ms_config_json_section_exists
|
|
| | | info : section=[myapp] len=[5]
|
|
| | | >mysqlnd_ms_config_json_sub_section_exists
|
|
| | | | info : section=[myapp] len=[5]
|
|
| | | | info : ret=1
|
|
| | | <mysqlnd_ms_config_json_sub_section_exists
|
|
| | | info : ret=1
|
|
| | <mysqlnd_ms_config_json_section_exists
|
|
[...]
|
|
]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
The debug log is not only useful for plugin developers but also to find the
|
|
cause of user errors. For example, if your application does not do proper
|
|
error handling and fails to record error messages, checking the debug
|
|
and trace log may help finding the cause. Use of the debug log
|
|
to debug application issues should be considered only if no other
|
|
option is available. Writing the debug log to disk is a slow
|
|
operation and may have negative impact on the application
|
|
performance.
|
|
</para>
|
|
<para>
|
|
Example excerpt from the debug log (connection failure):
|
|
<programlisting>
|
|
<![CDATA[
|
|
[...]
|
|
| | | | | | | info : adding error [Access denied for user 'root'@'localhost' (using password: YES)] to the list
|
|
| | | | | | | info : PACKET_FREE(0)
|
|
| | | | | | | info : PACKET_FREE(0x7f3ef6323f50)
|
|
| | | | | | | info : PACKET_FREE(0x7f3ef6324080)
|
|
| | | | | | <mysqlnd_auth_handshake
|
|
| | | | | | info : switch_to_auth_protocol=n/a
|
|
| | | | | | info : conn->error_info.error_no = 1045
|
|
| | | | | <mysqlnd_connect_run_authentication
|
|
| | | | | info : PACKET_FREE(0x7f3ef63236d8)
|
|
| | | | | >mysqlnd_conn::free_contents
|
|
| | | | | | >mysqlnd_net::free_contents
|
|
| | | | | | <mysqlnd_net::free_contents
|
|
| | | | | | info : Freeing memory of members
|
|
| | | | | | info : scheme=unix:///tmp/mysql.sock
|
|
| | | | | | >mysqlnd_error_list_pdtor
|
|
| | | | | | <mysqlnd_error_list_pdtor
|
|
| | | | | <mysqlnd_conn::free_contents
|
|
| | | | <mysqlnd_conn::connect
|
|
[...]
|
|
]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
The trace log can also be used to verify correct behaviour
|
|
of PECL/mysqlnd_ms itself, for example, to check which server has been
|
|
selected for query execution and why.
|
|
</para>
|
|
<para>
|
|
Example excerpt from the debug log (plugin decision):
|
|
<programlisting>
|
|
<![CDATA[
|
|
[...]
|
|
>mysqlnd_ms::query
|
|
| info : query=DROP TABLE IF EXISTS test
|
|
| >_mysqlnd_plugin_get_plugin_connection_data
|
|
| | info : plugin_id=5
|
|
| <_mysqlnd_plugin_get_plugin_connection_data
|
|
| >mysqlnd_ms_pick_server_ex
|
|
| | info : conn_data=0x7fb6a7d3e5a0 *conn_data=0x7fb6a7d410d0
|
|
| | >mysqlnd_ms_select_servers_all
|
|
| | <mysqlnd_ms_select_servers_all
|
|
| | >mysqlnd_ms_choose_connection_rr
|
|
| | | >mysqlnd_ms_query_is_select
|
|
[...]
|
|
| | | <mysqlnd_ms_query_is_select
|
|
[...]
|
|
| | | info : Init the master context
|
|
| | | info : list(0x7fb6a7d3f598) has 1
|
|
| | | info : Using master connection
|
|
| | | >mysqlnd_ms_advanced_connect
|
|
| | | | >mysqlnd_conn::connect
|
|
| | | | | info : host=localhost user=root db=test port=3306 flags=131072 persistent=0 state=0
|
|
]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
In this case the statement <literal>DROP TABLE IF EXISTS test</literal> has been
|
|
executed. Note that the statement string is shown in the log file. You may want
|
|
to take measures to restrict access to the log for security considerations.
|
|
</para>
|
|
<para>
|
|
The statement has been load balanced using round robin policy,
|
|
as you can easily guess from the functions name <literal>>mysqlnd_ms_choose_connection_rr</literal>.
|
|
It has been sent to a master server running on
|
|
<literal>host=localhost user=root db=test port=3306 flags=131072 persistent=0 state=0</literal>.
|
|
</para>
|
|
</section>
|
|
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|