<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PHP Archives - EngineYard</title>
	<atom:link href="https://www.engineyard.com/blog/category/php/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>PaaS Platform as a Service &#124; PaaS Solution</description>
	<lastBuildDate>Fri, 18 Mar 2022 15:19:48 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://www.engineyard.com/wp-content/uploads/2021/10/cropped-favicon-32x32.png</url>
	<title>PHP Archives - EngineYard</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Query Caching with PHP &#038; MySQLnd</title>
		<link>https://www.engineyard.com/blog/query-caching-with-php-mysqlnd/</link>
		
		<dc:creator><![CDATA[Engine Yard Team]]></dc:creator>
		<pubDate>Mon, 19 Jan 2015 00:00:00 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://www.engineyard.com/query-caching-with-php-mysqlnd/</guid>

					<description><![CDATA[<p>Note: This is part four in our Extending MySQL with PHP&#8217;s MySQLnd Series While MySQL has a query cache built in, it has several big problems: Pros Cons Easy to use Just enable it in the MySQL config Simplistic It has zero knowledge of your intentions Transparent No application-level changes are needed Easy to invalidate [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.engineyard.com/blog/query-caching-with-php-mysqlnd/">Query Caching with PHP &#038; MySQLnd</a> appeared first on <a rel="nofollow" href="https://www.engineyard.com">EngineYard</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img src='https://www.engineyard.com/wp-content/uploads/2022/02/part-4.webp' alt='part-4'><img src='https://www.engineyard.com/wp-content/uploads/2022/02/part-4.webp' alt='part-4' width='0'></p>
<div class='panel panel-info'>
<div class='panel-body'><strong>Note:</strong> This is part four in our <a href='https://www.engineyard.com/blog/topic/extending-mysql'>Extending MySQL with PHP&#8217;s MySQLnd Series</a></div>
</div>
<p><span id="more-2335"></span></p>
<p>While MySQL has a query cache built in, it has several big problems:</p>
<table>
<tbody>
<tr>
<th>Pros</th>
<th>Cons</th>
</tr>
<tr>
<td>Easy to use Just enable it in the MySQL config</td>
<td>Simplistic It has zero knowledge of your intentions</td>
</tr>
<tr>
<td>Transparent No application-level changes are needed</td>
<td>Easy to invalidate Any change to a table will invalidate all associated data, even if unnecessary (see: simplistic)</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>Single threaded because the query cache is single threaded it can actually harm performance</td>
</tr>
</tbody>
</table>
<p>We can solve these issues by using application-level caching, but how can we get the simplicity and ease of use of the query cache without the problems?</p>
<p>This is where the <em>mysqlnd_qc</em> plugin comes in.</p>
<h2 id='installation'>Installation</h2>
<p>The mysqlnd_qc plugin is the least stable of those we are exploring in this series, and requires you to use the alpha package for newer versions of PHP (at least 5.5+):</p>
<div class='highlighter-rouge'>
<pre class='highlight'><code>$ pecl install mysqlnd_qc-alpha
</code></pre>
</div>
<h3 id='storage-backends'>Storage Backends</h3>
<p>The plugin supports multiple backends for cache storage known as <em>storage handlers</em>. It includes two built-in storage handlers, and three that you must compile in:</p>
<ul>
<li>default: per-process in-memory storage (built-in)</li>
<li>user: user-defined custom storage backend (built-in)</li>
<li>memcache: use memcached for storage</li>
<li>sqlite: use sqlite for storage</li>
<li>apc: use APC for storage — requires both mysqlnd_qc and APC be compiled statically, and may not work with <a href='http://pecl.php.net/package/APCu'>apcu</a> (for PHP 5.5+)</li>
</ul>
<p>When you install with the PECL command, it will ask you if you wish to include APC, Memcache, or SQLite (in that order). Simply type in “yes” at the prompt to include them.</p>
<p>Of all the storage options, memcache is the one that I would recommend —&nbsp;or as we’ll explore later, you can build your own using the user handler.</p>
<p>To change the storage handler, you must call <code class='highlighter-rouge'>mysqlnd_qc_set_storage_handler()</code>, and pass in one of the storage handlers above. For example, to use memcache (or the MySQL innodb memcache interface):</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>mysqlnd_qc_set_storage_handler('memcache');
</code></pre>
</div>
<p>You can configure the memcache connection settings using the INI configuration options <code class='highlighter-rouge'>mysqlnd_qc.memc_server</code>, and <code class='highlighter-rouge'>mysqlnd_qc.memc_port</code>. This can be done either in your INI files, or using <code class='highlighter-rouge'>ini_set()</code>:</p>
<div class='language-ini highlighter-rouge'>
<pre class='highlight'><code><span class='py'>mysqlnd_qc.memc_server</span> <span class='p'>=</span> <span class='s'>'localhost'</span>
<span class='py'>mysqlnd_qc.memc_port</span> <span class='p'>=</span> <span class='s'>'11211'</span>
</code></pre>
</div>
<p>or</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>ini_set('mysqlnd_qc.memc_server', 'localhost');
ini_set('mysqlnd_qc.memc_port', '11211');
</code></pre>
</div>
<p>The default is localhost, port 11211. Unfortunately, you can only specify a single memcache server, not a list of them. If you want to spread it across multiple servers then you will need to create a custom user storage handler.</p>
<h2 id='basic-usage'>Basic Usage</h2>
<p>The mysqlnd_qc plugin allows you to transparently cache all <code class='highlighter-rouge'>SELECT</code> queries that do not include dynamic columns (e.g. <code class='highlighter-rouge'>NOW()</code>, or <code class='highlighter-rouge'>LAST_INSERT_ID()</code>). This is done by setting the INI option <code class='highlighter-rouge'>mysqlnd_qc.cache_by_default</code> to <code class='highlighter-rouge'>1</code>.</p>
<p>With this option enabled it acts very similar to the MySQL query cache, in that all possible SELECTs are cached, however, because it is part of the PHP process, it does not have the contention issues that the single threaded MySQL query cache has.</p>
<h3 id='cache-invalidation'>Cache Invalidation</h3>
<p>Out of the box, the cache is invalided using a simple time-based mechanism. Each cache item (query) is given what is known as a <em>TTL</em> (Time To Live) which defaults to 30 seconds.</p>
<p>You can change the TTL in two ways. First, by changing the INI setting <code class='highlighter-rouge'>mysqlnd_qc.ttl</code> to whatever you desire. The higher you can set this without negatively impacting your application the larger the benefit you will see from this plugin.</p>
<p>The second way is using a SQL hint. This is represented by the constant <code class='highlighter-rouge'>MYSQLND_QC_TTL_SWITCH</code>, as you can see in the example below:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>$sql = sprintf('/*%s%d*/SELECT * FROM table', MYSQLND_QC_TTL_SWITCH, 10);
</code></pre>
</div>
<p>This will be expanded to:</p>
<div class='language-sql highlighter-rouge'>
<pre class='highlight'><code><span class='cm'>/*qc_tt=10*/</span><span class='k'>SELECT</span> <span class='o'>*</span><span class='k'>FROM</span> <span class='k'>table</span>
</code></pre>
</div>
<p>Which will set the TTL to 10 seconds.</p>
<h4 id='opting-out-of-the-cache'>Opting Out of the Cache</h4>
<p>If you do choose to enable caching by default, you can opt-out on a query-by-query basis by using another SQL hint, <code class='highlighter-rouge'>MYSQLND_QC_DISABLE_SWITCH</code>:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>$sql = sprintf('/*%s*/SELECT * FROM table', MYSQLND_QC_DISABLE_SWITCH);
</code></pre>
</div>
<p>This will be expanded to:</p>
<div class='language-sql highlighter-rouge'>
<pre class='highlight'><code><span class='cm'>/*qc=off*/</span><span class='k'>SELECT</span> <span class='o'>*</span> <span class='k'>FROM</span> <span class='k'>table</span>
</code></pre>
</div>
<p>Which will turn the query cache off.</p>
<h2 id='conditional-caching'>Conditional Caching</h2>
<p>In addition to the basic options of caching everything or nothing, and using SQL hints to change this behavior, <code class='highlighter-rouge'>mysqlnd_qc</code> also supports the ability to automatically include queries made against a specific database and table.</p>
<p>This is done using the <code class='highlighter-rouge'>mysqlnd_qc_set_cache_condition()</code> function. This function accepts three arguments, the first of which is currently always <code class='highlighter-rouge'>MYSQLND_QC_CONDITION_META_SCHEMA_PATTERN</code>. The second is a pattern to match against, and the last is an optional TTL. If you do not provide a TTL, the value set by the <code class='highlighter-rouge'>mysqlnd_qc.ttl</code> INI option is used.</p>
<p>The pattern uses the same syntax as MySQL’s <code class='highlighter-rouge'>LIKE</code> operator: <code class='highlighter-rouge'>%</code> matches one or more characters, and <code class='highlighter-rouge'>_</code> matches a single character.</p>
<p>For example, we could automatically cache session data for 5 minutes:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>mysqlnd_qc_set_cache_condition(MYSQLND_QC_CONDITION_META_SCHEMA_PATTERN, 'myapp.session', 5*60);
</code></pre>
</div>
<p>Or cache all user data for 15 seconds:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>mysqlnd_qc_set_cache_condition(MYSQLND_QC_CONDITION_META_SCHEMA_PATTERN, 'myapp.user_%', 15);
</code></pre>
</div>
<h2 id='pattern-based-caching'>Pattern Based Caching</h2>
<p>The mysqlnd_qc plugin gets really interesting when you introduce the pattern based caching feature. This feature allows you to set a callback that will evaluate each query to determine if it can be cached, and optionally, for how long.</p>
<p>The callback should return one of three values:</p>
<ul>
<li>false: The query should not be cached</li>
<li>true: The query should be cached for the default TTL as set in the INI by <code class='highlighter-rouge'>mysqlnd_qc.ttl</code></li>
<li>An integer: The query should be cached for N seconds</li>
</ul>
<p>It should be noted that returning true, or an integer, does not <em>guarantee</em> that the plugin will be able to cache the result set due to other factors such as non-deterministic SQL.</p>
<p>We can use this feature to replicate the behavior above programmatically:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>function is_cacheable($sql)
{
    if (preg_match('/SELECT (.*?) FROM session (.*)/ism', $sql) === 1) {
        return 5*60;
    }

    if (preg_match('/SELECT (.*?) FROM user_(.*?) (.*)/ism', $sql) === 1) {
        return 15;
    }

    return false;
}
</code></pre>
</div>
<p>We then set the callback:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>mysqlnd_qc_set_is_select('is_cacheable');
</code></pre>
</div>
<p>This becomes much more powerful, however, once we start to introduce more application logic. For example, we might choose to never cache when editing, or for admin users. Authenticated users may get a short cache TTL while unauthenticated users might get a longer one.</p>
<div class='inline-interest-prompt'>
<div class='prompt ey'><a href='https://www.engineyard.com/trial' title='Run your next project on Engine Yard' onclick='dataLayer.push({ 'event': 'trackEvent', 'gtmCategory': 'Blog Trial Promotion', 'gtmAction': 'Click', 'gtmLabel': 'embedded-post' });'></p>
<div class='row'>
<div class='details col-sm-8'>Run your next project on Engine Yard</div>
<div class='action col-sm-4'>Start Free Trial <span class='ss-navigateright'></span></div>
</div>
<p></a></div>
</div>
<h2 id='custom-cache-handlers'>Custom Cache Handlers</h2>
<p>The final feature provided by this plugin is the ability to write completely custom cache handlers. This allows you to create your own backend for managing cache storage.</p>
<p>For example, if you choose to use your framework’s default cache library – often this allows you to then easily switch out adapters to allow different storage backends, such as Cassandra or Riak.</p>
<p>To implement a user cache handler, you must define seven callback functions, or methods. While no interface is provided by PHP, it might look something like this:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>namespace MySQLnd;

interface CacheHandlerInterface {
	// Get a unique hash for the query, this is the cache-key
    public function get_hash($host_info, $port, $user, $db, $query);

	// Retrieve the data from the cache
    public function find_query_in_cache($key);

	// Called each time the data is pulled from the cache
    public function return_to_cache($key);

	// Add the query to the cache
    public function add_query_to_cache_if_not_exists($key, $data, $ttl, $run_time, $store_time, $row_count);

	// Called after the query executes to help maintain stats
    public function update_query_run_time_stats($key, $run_time, $store_time);

	// Get the stats
    public function get_stats($key = null);

	// Clear the cache completely
    public function clear_cache();
}
</code></pre>
</div>
<p>If we were to implement this using <code class='highlighter-rouge'>Zend\Cache</code> from Zend Framework 2, we would end up with something like this:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>namespace MyApp\Db\MySQLnd;

use MySQLnd\CacheHandlerInterface;
class CacheHandler implements CacheHandlerInterface
{

    protected $cache;

    public function __construct(\Zend\Cache\Storage\StorageInterface $cache)
    {
        $this-&gt;cache = $cache;
    }

    public function get_hash($host_info, $port, $user, $db, $query)
    {
        return md5(sprintf('%s%s%s%s%s', $host_info, $port, $user, $db, $query));
    }

    public function find_query_in_cache($key)
    {
        if ($this-&gt;cache-&gt;hasItem($key)) {
            return $this-&gt;cache-&gt;getItem($key);
        }
        return null;
    }

    public function return_to_cache($key)
    {
        $this-&gt;cache-&gt;touch($key);
    }

    public function add_query_to_cache_if_not_exists($key, $data, $ttl, $run_time, $store_time, $row_count)
    {
        $data = array(
            'data' =&gt; $data,
            'row_count' =&gt; $row_count,
            'valid_until' =&gt; time() + $ttl,
            'hits' =&gt; 0,
            'run_time' =&gt; $run_time,
            'store_time' =&gt; $store_time,
            'cached_run_times' =&gt; array(),
            'cached_store_times' =&gt; array(),
        );

        if ($this-&gt;cache-&gt;hasData($key)) {
            return null;
        }

        $this-&gt;cache-&gt;getOptions()-&gt;setTtl($ttl);
        $this-&gt;cache-&gt;setItem($key, $data);

        return true;
    }

    public function update_query_run_time_stats($key, $run_time, $store_time)
    {
        if ($this-&gt;cache-&gt;hasKey($key . '_stats')) {
            $stats = $this-&gt;cache-&gt;getKey($key);
        } else {
            $stats = [
                'hits' =&gt; 0,
                'run_times' =&gt; [],
                'store_times' =&gt; []
            ];
        }

        $stats['hits']++;
        $stats['run_times'][] = $run_time;
        $stats['store_times'][] = $store_time;

        $this-&gt;cache-&gt;setItem($key . '_stats', $stats);

    }

    public function get_stats($key = null)
    {
        if ($key !== null <span class='err'>&amp;&amp;</span> $this-&gt;cache-&gt;hasKey($key . '_stats')) {
            return $this-&gt;cache-&gt;getItem($key . '_stats');
        }

        return [];
    }

    public function clear_cache()
    {
        if ($this-&gt;cache instanceof \Zend\Cache\Storage\FlushableInterface) {
            $this-&gt;cache-&gt;flush();
            return true;
        }

        return false;
    }
}
</code></pre>
</div>
<p>We can then use this like so:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>use MyApp\Db\MySQLnd\CacheHandler;

// Get the cache service from the service locator
$cache = $serviceLocator-&gt;getServiceLocator()-&gt;get('cache');

// Create an instance of our CacheHandler, passing in the Cache instance
$ch = new CacheHandler($cache);

// Setup the user handler
\mysqlnd_qc_set_user_handlers([$ch, 'get_hash'], [$ch, 'find_query_in_cache'], [$ch, 'return_to_cache'], [$ch, 'add_query_to_cache_if_not_exists'], [$ch, 'update_query_run_time_stats'], [$ch, 'get_stats'], [$ch, 'clear_cache']);
</code></pre>
</div>
<p>Once you’ve registered your user handler it will use called to perform all caching actions automatically.</p>
<h2 id='conclusion'>Conclusion</h2>
<p>The cache handler allows you to solve a number of problems with the MySQL query cache, as well as providing an interface for easily, and transparently, implementing caching within your application. It provides the ability to automate query caching, and supportmultiple storage backends for storing your cache, including memcached, and custom user storage.</p>
<p>P.S. Have you tried any of the MySQLnd plugins yet? What’s your favorite? We’d love to hear about your experience with MySQLnd and it’s plugins.</p>
<div class='panel panel-info'>
<div class='panel-body'><strong>Note:</strong> This is part four in our <a href='https://www.engineyard.com/blog/collections/extending-mysql'>Extending MySQL with PHP&#8217;s MySQLnd Series</a></div>
</div>
<p>The post <a rel="nofollow" href="https://www.engineyard.com/blog/query-caching-with-php-mysqlnd/">Query Caching with PHP &#038; MySQLnd</a> appeared first on <a rel="nofollow" href="https://www.engineyard.com">EngineYard</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Easy Read/Write Splitting with PHP’s MySQLnd</title>
		<link>https://www.engineyard.com/blog/easy-read-write-splitting-with-phps-mysqlnd/</link>
		
		<dc:creator><![CDATA[Engine Yard Team]]></dc:creator>
		<pubDate>Wed, 10 Dec 2014 00:00:00 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://www.engineyard.com/easy-read-write-splitting-with-phps-mysqlnd/</guid>

					<description><![CDATA[<p>Note: This is part one in our Extending MySQL with PHP&#8217;s MySQLnd Series MySQL has always been the default go-to database server for pairing with PHP—it has been the go-to database since almost the inception of the language. Sure, some people use PostgreSQL, or SQL Server, or Oracle, but for the web workload, MySQL is [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.engineyard.com/blog/easy-read-write-splitting-with-phps-mysqlnd/">Easy Read/Write Splitting with PHP’s MySQLnd</a> appeared first on <a rel="nofollow" href="https://www.engineyard.com">EngineYard</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class='panel panel-info'>
<div class='panel-body'><strong>Note:</strong> This is part one in our <a href='https://www.engineyard.com/blog/topic/extending-mysql'>Extending MySQL with PHP&#8217;s MySQLnd Series</a></div>
</div>
<p><span id="more-2348"></span></p>
<p>MySQL has always been the default go-to database server for pairing with PHP—it has been the go-to database since almost the inception of the language. Sure, some people use PostgreSQL, or SQL Server, or Oracle, but for the web workload, MySQL is usually the relational database of choice.</p>
<p>This was due mostly in part because it was so easy to get going. <em>Libmysqlclient</em> was bundled with PHP itself until it was re-licensed under the GPL. This change meant it was no longer possible to bundle with PHP and it was removed.</p>
<p>This made the compilation process for PHP slightly more difficult, requiring that libmysqlclient be available on the host system.</p>
<p>Given the widespread nature of PHP, and the fact it was the most popular single language to use MySQL, this wasn’t ideal for Oracle (then Sun) and so they came to an agreement: they’d build the MySQL Native Driver—a PHP licensed contribution to PHP that would allow access to MySQL <em>without</em> libmysqlclient.</p>
<p>MySQL Native Drive (<em>mysqlnd</em>) was added in PHP 5.3 and has been the default since PHP 5.4 (though you can still compile against libmysqlclient). It brings extra features, better performance, and better memory usage than libmysqlclient.</p>
<p>From the MySQL manual:</p>
<blockquote>
<p>The mysqlnd library is using PHP internal C infrastructure for seamless integration into PHP. In addition, it is using PHP memory management, PHP Streams (I/O abstraction) and PHP string handling routines. The use of PHP memory management by mysqlnd allows, for example, memory savings by using read-only variables (copy on write) and makes mysqlnd apply to PHP memory limits.</p>
</blockquote>
<p>Additionally it has a plugin architecture, and a number of plugins are available.</p>
<h2 id='installation'>Installation</h2>
<p>To install, just compile one of the three MySQL extensions. In each instance, do not explicitly specify the path to the libmysqlclient library.</p>
<p>The three libraries are:</p>
<ul>
<li><em>ext/pdo_mysql</em> (since PHP 5.1)</li>
<li><em>ext/mysqli</em> (since PHP 5.0)</li>
<li><em>ext/mysql</em> (since PHP 2.0, <em>deprecated since PHP 5.6</em>)</li>
</ul>
<p><em>Note:</em> If you install ext/mysql or ext/mysqli, ext/pdo_mysql is enabled automatically.</p>
<p>You can select an extension by choosingone or more of the following configure flags:</p>
<ul>
<li><code class='highlighter-rouge'>--with-mysql</code></li>
<li><code class='highlighter-rouge'>--with-mysqli</code></li>
<li><code class='highlighter-rouge'>--with-pdo-mysql</code></li>
</ul>
<p>If you are using Debian, or Ubuntu, you can easily install the php5-mysqlnd package:</p>
<div class='language-sh highlighter-rouge'>
<pre class='highlight'><code><span class='gp'>$ </span>sudo apt-get install php5-mysqlnd
</code></pre>
</div>
<p>This will remove the libmysqlclient-based php5-mysql package, and includes all three extensions.</p>
<h2 id='mysql-native-driver-plugins'>MySQL Native Driver Plugins</h2>
<p>Aside from the performance benefits, the biggest benefit to mysqlnd are it’s plugins. These plugins are available via PECL, and can be installed easily using:</p>
<div class='language-sh highlighter-rouge'>
<pre class='highlight'><code><span class='gp'>$ </span>pecl install mysqlnd_&lt;name&gt;
</code></pre>
</div>
<p>The available (stable) plugins are:</p>
<ul>
<li><em>mysqlnd_memcache</em>: Transparently translate SQL to use the MySQL 5.6 memcache-protocol compatible NoSQL daemon</li>
<li><em>mysqlnd_ms</em>: Easily perform read/write splitting between master and slave (ms) servers, with simple load balancing</li>
<li><em>mysqlnd_qc</em>: Adds a simple query cache to PHP</li>
<li><em>mysqlnd_uh</em>: Allows writing mysqlnd plugins in PHP</li>
</ul>
<p>Because these plugins are for mysqlnd itself, they apply to all three extensions.</p>
<div class='inline-interest-prompt'>
<div class='prompt ey'><a href='https://www.engineyard.com/trial' title='Run your next PHP project on Engine Yard' onclick='dataLayer.push({ 'event': 'trackEvent', 'gtmCategory': 'Blog Trial Promotion', 'gtmAction': 'Click', 'gtmLabel': 'embedded-post' });'></p>
<div class='row'>
<div class='details col-sm-8'>Run your next PHP project on Engine Yard</div>
<div class='action col-sm-4'>Start Free Trial <span class='ss-navigateright'></span></div>
</div>
<p></a></div>
</div>
<h2 id='readwrite-splitting'>Read/Write Splitting</h2>
<p>The most useful plugin is mysqlnd_ms, or master/slave. This plugin allows you to transparently—albeit somewhat naively—split reads and writes between different servers.</p>
<h3 id='configuration'>Configuration</h3>
<p>Once you have installed using pecl, you need to configure both the <code class='highlighter-rouge'>php.ini</code>, and the mysqlnd_ms configuration file.</p>
<p>In <code class='highlighter-rouge'>php.ini</code> (or <code class='highlighter-rouge'>mysqlnd_ms.ini</code> on Debian-like systems):</p>
<div class='language-ini highlighter-rouge'>
<pre class='highlight'><code><span class='py'>extension</span><span class='p'>=</span><span class='s'>mysqlnd_ms.so</span>
<span class='py'>mysqlnd_ms.enable</span><span class='p'>=</span><span class='s'>1</span>
<span class='py'>mysqlnd_ms.config_file</span><span class='p'>=</span><span class='s'>/path/to/mysqlnd_ms.json</span>
</code></pre>
</div>
<p>Then you need to create the <code class='highlighter-rouge'>mysqlnd_ms.json</code> file. This file defines the master and slave servers, as well as the read/write splitting and load balancing strategies.</p>
<p>How you setup the configuration is going to depend on your replication topology.</p>
<p>The simplest configuration file includes one master, and one slave:</p>
<div class='language-javascript highlighter-rouge'>
<pre class='highlight'><code><span class='p'>{</span>
	<span class='s2'>'appname'</span><span class='err'>:</span> <span class='p'>{</span>
		<span class='s2'>'master'</span><span class='err'>:</span> <span class='p'>{</span>
			<span class='s2'>'master_0'</span><span class='err'>:</span> <span class='p'>{</span>
				<span class='s2'>'host'</span><span class='err'>:</span> <span class='s2'>'master.mysql.host'</span><span class='p'>,</span>
				<span class='s2'>'port'</span><span class='err'>:</span> <span class='s2'>'3306'</span><span class='p'>,</span>
				<span class='s2'>'user'</span><span class='err'>:</span> <span class='s2'>'dbuser'</span><span class='p'>,</span>
				<span class='s2'>'password'</span><span class='err'>:</span> <span class='s2'>'dbpassword'</span><span class='p'>,</span>
				<span class='s2'>'db'</span><span class='err'>:</span> <span class='s2'>'dbname'</span>
			<span class='p'>}</span>
		<span class='p'>},</span>
		<span class='s2'>'slave'</span><span class='err'>:</span> <span class='p'>{</span>
			<span class='s2'>'slave_0'</span><span class='err'>:</span> <span class='p'>{</span>
				<span class='s2'>'host'</span><span class='err'>:</span> <span class='s2'>'slave.mysql.host'</span><span class='p'>,</span>
				<span class='s2'>'port'</span><span class='err'>:</span> <span class='s2'>'3306'</span>
				<span class='s2'>'user'</span><span class='err'>:</span> <span class='s2'>'dbuser'</span><span class='p'>,</span>
				<span class='s2'>'password'</span><span class='err'>:</span> <span class='s2'>'dbpassword'</span><span class='p'>,</span>
				<span class='s2'>'db'</span><span class='err'>:</span> <span class='s2'>'dbname'</span>
			<span class='p'>},</span>
		<span class='p'>}</span>
	<span class='p'>}</span>
<span class='p'>}</span>
</code></pre>
</div>
<p>The only <em>required</em> setting is the host. All others are optional.</p>
<h3 id='load-balancing'>Load Balancing</h3>
<p>Additionally, mysqlnd_ms can do simple load balancing in one of several strategies:</p>
<ul>
<li>random—a random slave is picked for <em>each</em> read query</li>
<li>random once—a random slave is picked for the <em>first</em> read query and re-used for the remainder of the request</li>
<li>round robin—a new slave is picked for *each *read query, in the order they are defined</li>
<li>user—a user-specified callback determines which slave will be called for <em>each</em> query</li>
</ul>
<p>It is important to understand that unless you use the last strategy and maintain state yourself, every single request will execute the load balancing strategy in isolation. So, round-robin applies to each query within the same request, and it isn’t that one server is picked per request in sequential order as you might expect of an actual hardware or software load balancer.</p>
<p>With that in mind, I would recommend that youdo not use the load balancing aspect of this plugin, and instead use an actual load balancer for your slaves—such as haproxy—and simply point the configuration to the load balancer as the only slave.</p>
<h3 id='routing-queries'>Routing Queries</h3>
<p>By default mysqlnd_ms will transparently route all queries starting with <code class='highlighter-rouge'>SELECT</code> to the slave servers, and anything else to the master.</p>
<p>This is both good and bad. Being transparent, it means zero changes to your code. But it is also simplistic, and does not analyze the query to ensure it <em>is</em> actually a read-only query.</p>
<p>Not only will it <em>not</em> send a query that starts with <code class='highlighter-rouge'>(SELEC</code>T to the master, it <em>will</em> also send a write query using <code class='highlighter-rouge'>SELECT … INTO</code> to the <em>slave</em>, which could be a disaster.</p>
<p>Luckily, the plugin includes the ability to hint which server the query should be sent to. This is done by placing one of three SQL hint constants in the query:</p>
<ul>
<li><code class='highlighter-rouge'>MYSQLND_MS_MASTER_SWITCH</code>—Run the statement on the master</li>
<li><code class='highlighter-rouge'>MYSQLND_MS_SLAVE_SWITCH</code>—Run the statement on the slave</li>
<li><code class='highlighter-rouge'>MYSQLND_MS_LAST_USED_SWITCH</code>—Run the statement on whichever server was last used</li>
</ul>
<p>These three constants are simple placeholders for strings, <code class='highlighter-rouge'>ms=master</code>, <code class='highlighter-rouge'>ms=slave</code>, and <code class='highlighter-rouge'>ms=last_used</code> respectively. However these strings may change in the future and therefore the constants should be used.</p>
<p>To use a SQL hint, add a comment before the query with whichever one you wish to use. The easiest way to do this is to use sprintf() which will replace placeholders (in this case %s,the string placeholder) for the given arguments.</p>
<p>For example, to send a <code class='highlighter-rouge'>SELECT</code> to the master:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>$sql = sprintf('/*%s*/ SELECT * FROM table_name;', MYSQLND_MS_MASTER_SWITCH);
</code></pre>
</div>
<p>Or, to send a non-SELECT to a slave:</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>$sql = sprintf('/*%s*/ CREATE TEMPORARY TABLE `temp_table_name` SELECT * FROM table_name;', MYSQLND_MS_SLAVE_SWITCH);
</code></pre>
</div>
<p>The last hint will let you ensure that the same connection is used as for the previous query. This is particularly useful for ensuring that you switch to reading from the master after data has been modified but potentially not yet replicated, or when performing transactions that include both read and write statements.</p>
<div class='language-php highlighter-rouge'>
<pre class='highlight'><code>if ($request-&gt;isPost() <span class='err'>&amp;&amp;</span> $form-&gt;isValid()) {
	$user&gt;setValues($form-&gt;getValues());
	$user-&gt;save();
}

$sql = sprintf('/*%s*/ SELECT * FROM user_session WHERE user_id = :user_id', MYSQLND_LAST_USED_SWITCH);
</code></pre>
</div>
<p>This will use the master for the query if—and only if—the master was previously used. In this case the master is used if the user data is updated (with <code class='highlighter-rouge'>$user-&gt;save()</code>).</p>
<h2 id='conclusion'>Conclusion</h2>
<p>The mysqlnd_ms plugin is incredibly useful, especially when you want to move large legacy applications to using distributed read/write. While it’s not perfect, it should get you 80-90% of the way there for most applications without changing a single line of code.</p>
<p>In the <a href='https://www.engineyard.com/blog/advanced-read-write-splitting-with-phps-mysqlnd'>second installment</a> in this series, we’ll look at more advanced usage of the mysqlnd_ms plugin.</p>
<p>P.S. Have you been working with “vanilla” MySQL in your PHP app and want to try some of these methods? Share your experiences and thoughts.</p>
<div class='panel panel-info'>
<div class='panel-body'><strong>Note:</strong> This is part one in our <a href='https://www.engineyard.com/blog/topic/extending-mysql'>Extending MySQL with PHP&#8217;s MySQLnd Series</a></div>
</div>
<p>The post <a rel="nofollow" href="https://www.engineyard.com/blog/easy-read-write-splitting-with-phps-mysqlnd/">Easy Read/Write Splitting with PHP’s MySQLnd</a> appeared first on <a rel="nofollow" href="https://www.engineyard.com">EngineYard</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
