<?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>Dan Cryer - Leeds Web Developer &#187; scaling</title>
	<atom:link href="http://www.dancryer.com/tag/scaling/feed" rel="self" type="application/rss+xml" />
	<link>http://www.dancryer.com</link>
	<description>Dan Cryer - Leeds Web Developer</description>
	<lastBuildDate>Mon, 06 Sep 2010 22:05:42 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/><atom:link rel="hub" href="http://superfeedr.com/hubbub"/>		<item>
		<title>PHP UK Conference 2010</title>
		<link>http://www.dancryer.com/2010/02/php-uk-conference-2010</link>
		<comments>http://www.dancryer.com/2010/02/php-uk-conference-2010#comments</comments>
		<pubDate>Thu, 11 Feb 2010 19:13:44 +0000</pubDate>
		<dc:creator>Dan Cryer</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[conferences]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[scaling]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://www.dancryer.com/?p=465</guid>
		<description><![CDATA[I&#8217;ve been meaning to post about this for weeks now, but haven&#8217;t got around to it. The PHP UK Conference is coming up on the 26th of February in London and my boss, Remo Biagioni, is one of the speakers. Here&#8217;s some information about Remo and the talk from the official site: Remo&#8217;s Bio: Remo [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.dancryer.com/wp/wp-content/uploads/2010/02/php-logo.gif"><img class="alignright size-full wp-image-443" title="PHP Logo" src="http://www.dancryer.com/wp/wp-content/uploads/2010/02/php-logo.gif" alt="PHP Logo" width="200" height="160" /></a>I&#8217;ve been meaning to post about this for weeks now, but haven&#8217;t got around to it. The <a rel="nofollow" href="http://www.phpconference.co.uk/">PHP UK Conference</a> is coming up on the 26th of February in London and my boss, Remo Biagioni, is one of the speakers. Here&#8217;s some information about Remo and the talk from the official site:</p>
<p><strong>Remo&#8217;s Bio:</strong></p>
<blockquote><p>Remo is Head of Research &amp; Development at Stickyeyes, a search marketing agency based in Leeds. The R&amp;D team works on a mixture of client site builds, internal reporting tools and Market Defender a search marketing intelligence tool. His team works mainly with PHP, MySQL and Apache. Remo graduated with an MSc in pure mathematics having covered programming in Fortran, Pascal, Lisp &amp; C; he joined BT&#8217;s R&amp;D Labs at Martlesham Heath working on large scale systems (a dozen mainframes, a few hundred Unix servers&#8230;) before getting an MBA and leaving to set up his own web development business. Remo sold the business in 2008 and joined Stickyeyes. Remo is passionate about object-orientated coding and can talk for hours about the Liskov Subsitution Principle, test driven development and solid software engineering.</p></blockquote>
<p><strong>The Talk &#8211; Database Optimisation:</strong></p>
<blockquote><p>A real life example getting more throughput with fewer queries.</p>
<p>Over the last year we&#8217;ve grown a database from a few hundred megabytes to just over one terabyte. The database is reported on and populated by a network of servers using PHP. As the database has grown we&#8217;ve had to look again our initial assumptions and ways of working. One table has over 2billion rows; 2.5 million rows every day are added to another table. This talk will cover how we use explain, foreign keys, normalising data without sacrificing performance, queuing and using memcache. And, how we&#8217;ve made the system run faster now than it did with a much smaller database.</p></blockquote>
<p>Without going into too much detail, the talk basically centres around the work that myself, Wade and the rest of our team have been doing over the course of the last year and a half.</p>
<p>Much of the content will be familiar to those who read my blog. It&#8217;s about scaling MySQL from a couple of hundred megabyte data store in our office, to a two server set up holding over a terabyte of data, fed by tens of servers, all the whileincreasing the speed at which we could store and retrieve data. It&#8217;ll cover how we implemented beanstalkd and memcached to lessen database load, and I&#8217;d imagine it will also touch on our ongoing implementation of HAProxy load balancing.</p>
<p>If you&#8217;re attending the conference and this sounds interesting to you, make sure you get to the talk. Wade and I will be around as well, to answer any questions that might come up throughout the day. As always, you can also ask the questions here, and if the answer is interesting enough, it might make it into the talk (though no promises!)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dancryer.com/2010/02/php-uk-conference-2010/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL Load-Balanced Cluster Guide – Part 3</title>
		<link>http://www.dancryer.com/2010/01/load-balancing-mysql-with-ha-proxy</link>
		<comments>http://www.dancryer.com/2010/01/load-balancing-mysql-with-ha-proxy#comments</comments>
		<pubDate>Thu, 28 Jan 2010 17:14:00 +0000</pubDate>
		<dc:creator>Dan Cryer</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[PHP Conference 2010]]></category>
		<category><![CDATA[replication]]></category>
		<category><![CDATA[scaling]]></category>
		<category><![CDATA[servers]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://www.dancryer.com/?p=434</guid>
		<description><![CDATA[This article is the last in my MySQL Load-Balanced Cluster Guide series, following on from my earlier post today on setting up MySQL Monitoring on the cluster nodes, this article is a guide to setting up the load balancer with HAProxy, using the monitoring scripts we&#8217;ve created to check the node status. HAProxy is a [...]]]></description>
			<content:encoded><![CDATA[<p>This article is the last in my MySQL Load-Balanced Cluster Guide series, following on from my earlier post today on <a href="http://www.dancryer.com/2010/01/mysql-monitoring-with-xinetd">setting up MySQL Monitoring on the cluster nodes</a>, this article is a guide to setting up the load balancer with HAProxy, using the monitoring scripts we&#8217;ve created to check the node status.</p>
<p>HAProxy is a well known, powerful load balancer. Best known for use with HTTP servers, but it can work with any kind of TCP traffic, including MySQL. Upcoming versions will support native MySQL monitoring, so we&#8217;ll be able to retire the custom scripts we created in the previous step, when they&#8217;re available in apt-get.</p>
<p><strong>1. Install HAProxy using the following apt-get command: </strong>apt-get install haproxy</p>
<p><strong>2. Delete the file /etc/haproxy/haproxy.cfg and create a new one with the following contents:</strong></p>
<p><code>global<br />
log 127.0.0.1   local0<br />
maxconn 4096<br />
user haproxy<br />
group haproxy<br />
daemon</code></p>
<p><code> </code></p>
<p><code>defaults<br />
log     global<br />
mode    tcp<br />
option  tcplog<br />
option  dontlognull<br />
retries 3<br />
option redispatch<br />
maxconn 2000<br />
contimeout      4000<br />
clitimeout      50000<br />
srvtimeout      30000<br />
stats enable<br />
stats scope	.</code></p>
<p><code>frontend mysql_cluster<br />
bind {load-balancer-ip}:3306<br />
default_backend	mysql_cluster</code></p>
<p><code>backend mysql_cluster<br />
mode tcp<br />
balance roundrobin<br />
stats enable<br />
option tcpka<br />
option httpchk<br />
server {node-name} {ip-address}:3306 weight 1 check port 9200 inter 5s rise 2 fall 2<br />
server {node-name} {ip-address}:3306 weight 1 check port 9200 inter 5s rise 2 fall 2</p>
<p></code></p>
<p><code>listen stats {load-balancer-ip}:80<br />
mode http<br />
option httpclose<br />
balance roundrobin<br />
stats uri     /<br />
stats realm   Haproxy\ Statistics<br />
stats auth    {username}:{password}<br />
</code></p>
<p>Once you&#8217;ve modified this file with settings appropriate to your environment, you&#8217;ll have a ready to go load balancer for MySQL. It&#8217;ll open port 3306 on the load balancer and forward those connections using round-robin load balancing to your two cluster servers. If either server goes down, it&#8217;ll be removed from the pool and no connections will be sent to it until it comes back up.</p>
<p>Additionally, it&#8217;ll create a statistics interface similar to the following, which you can access by going to your load balancing server&#8217;s address in your browser.</p>
<p><a href="http://www.dancryer.com/wp/wp-content/uploads/2010/01/haproxy.png"><img class="aligncenter size-large wp-image-437" title="HA Proxy" src="http://www.dancryer.com/wp/wp-content/uploads/2010/01/haproxy-1024x566.png" alt="HA Proxy Web Interface" width="450" height="248" /></a></p>
<p>Each cluster node should be listed under the first &#8220;mysql_cluster&#8221; section. They should all appear in green, as they should all be live. If a server is down, it&#8217;ll appear red, as explained in the key at the top of the page. From this interface you can also see how long the load balancer has been up, how long each server has been up, how much downtime each have had, as well as the individual number of downtime incidents. As you can see in the image above, our &#8216;cluster-1&#8242; server has had 21 separate downtime incidents, totalling just over 9 minutes of downtime.</p>
<p>So what do you need to change in your haproxy.cfg file? Here&#8217;s a quick rundown:</p>
<ul>
<li>Firstly, you&#8217;ll need to change the line starting &#8216;bind&#8217; to use your load balancer&#8217;s public IP address.</li>
<li>Secondly, make the same change on the line starting &#8216;listen stats&#8217;. You may also choose to put this service on a different port than port 80.</li>
<li>Next, change the {username} and {password} section of the &#8216;stats auth&#8217; line to ones of your choosing. This provides password protection to your web interface.</li>
<li>Finally, for each of your MySQL cluster nodes, you&#8217;ll need an appropriate &#8216;server&#8217; line in the &#8216;mysql_cluster&#8217; backend group. The things you need to change are {node-name}, to the name of the cluster node you defined earlier (e.g. cluster-1, cluster-2) and {ip-address} to the cluster node&#8217;s internal IP address.</li>
</ul>
<p>You may also wish to change the interval at which the load balancer will poll the MySQL servers, by changing &#8216;inter 5&#8242; to a different number. For more information on HAProxy configuration, their detailed &#8211; if a little unfriendly &#8211; documentation is available <a rel="nofollow" href="http://haproxy.1wt.eu/download/1.3/doc/configuration.txt">here</a>.</p>
<p>The final step to get this running, is to restart haproxy: /etc/init.d/haproxy restart - You should now be able to access both your web interface on port 80 and your brand new MySQL cluster on port 3306!</p>
<p>If you&#8217;ve got any questions about these guides or setting up a similar cluster, please leave a comment below and I&#8217;ll do my best to answer it.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dancryer.com/2010/01/load-balancing-mysql-with-ha-proxy/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>MySQL Load-Balanced Cluster Guide – Part 2</title>
		<link>http://www.dancryer.com/2010/01/mysql-monitoring-with-xinetd</link>
		<comments>http://www.dancryer.com/2010/01/mysql-monitoring-with-xinetd#comments</comments>
		<pubDate>Thu, 28 Jan 2010 16:28:35 +0000</pubDate>
		<dc:creator>Dan Cryer</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[PHP Conference 2010]]></category>
		<category><![CDATA[replication]]></category>
		<category><![CDATA[scaling]]></category>
		<category><![CDATA[servers]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://www.dancryer.com/?p=425</guid>
		<description><![CDATA[Following on from yesterday&#8217;s guide to setting up MySQL master-master replication, the first of today&#8217;s guides is how to set up a script to monitor the status of your MySQL cluster nodes, which we&#8217;ll use in the next guide to set up our proxy. On every one of your MySQL cluster nodes, follow the steps [...]]]></description>
			<content:encoded><![CDATA[<p>Following on from yesterday&#8217;s guide to <a href="/2010/01/mysql-circular-replication">setting up MySQL master-master replication</a>, the first of today&#8217;s guides is how to set up a script to monitor the status of your MySQL cluster nodes, which we&#8217;ll use in the next guide to set up our proxy.</p>
<p>On every one of your MySQL cluster nodes, follow the steps below to get it ready for status reporting:</p>
<p><strong>1. Run the following apt-get command: <em>apt-get install xinetd php5-cli php5-mysql</em></strong> &#8211; We need xinetd to host our status server, and php5 for the status checking script.</p>
<p><strong>2. Put the following code into a new file: /etc/xinetd.d/mysqlchk</strong> &#8211; This creates an xinetd &#8220;server&#8221; on port 9200, using the /opt/mysql-status.php file to generate the response. Our proxy will connect to this server to check the status of the node.</p>
<p><code>#<br />
# /etc/xinetd.d/mysqlchk<br />
#<br />
service mysqlchk<br />
{<br />
flags           = REUSE<br />
socket_type     = stream<br />
protocol        = tcp<br />
port            = 9200<br />
wait            = no<br />
user            = mysql<br />
server          = /opt/mysql-status.php<br />
log_on_failure  += USERID<br />
disable         = no<br />
}</code></p>
<p><strong>3. Add the following line at the bottom of /etc/services</strong> &#8211; This registers your newly created xinetd service with the system to allow it to function.<br />
<code>mysqlchk	9200/tcp	# MySQL Monitoring</code></p>
<p><strong>4. Create the PHP file /opt/mysql-status.php with the code below.</strong><br />
This script tries to connect to your MySQL server, and if it succeeds, checks the &#8216;Seconds_Behind_Master&#8217; value of the slave status to make sure it is not too far behind it&#8217;s master. If either of these checks fail, it will return a 503 error to the client, telling it that the server is down.<br />
<code>#!/usr/bin/php<br />
&lt; ?php<br />
/**<br />
*	MySQL Replication Monitor<br />
*/</code></p>
<p><code>// Connection details:<br />
$_host     = 'localhost';<br />
$_username = '{username}';<br />
$_password = '{password}';<br />
$_timeout  = 60; // Number of seconds behind master this node can fall before being marked as "down".</p>
<p>try<br />
{<br />
$pdo = new PDO('mysql:host='.$_host, $_username, $_password);<br />
$pdo-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);</p>
<p>$stmt   = $pdo-&gt;query('SHOW SLAVE STATUS');<br />
$status = $stmt-&gt;fetch(PDO::FETCH_ASSOC);</p>
<p>if($status == false)<br />
{<br />
serverIsDown('Querying slave status failed.');<br />
}</p>
<p>if(intval($status['Seconds_Behind_Master']) &gt; $_timeout)<br />
{<br />
serverIsDown('Slave is behind.');<br />
}<br />
}<br />
catch(Exception $ex)<br />
{<br />
if(!is_null($pdo))<br />
unset($pdo);</p>
<p>serverIsDown('Failed to connect to MySQL.');<br />
}</p>
<p>unset($pdo);</p>
<p>serverIsUp();</p>
<p>function serverIsDown($message)<br />
{<br />
$output = 'Server is currently down. Message:' . $message;<br />
print "HTTP/1.1 503 Service Unavailable\r\n";<br />
print "Date: " . date('r') . "\r\n";<br />
print "Server: MySQL Monitor/1.0\r\n";<br />
print "Connection: close\r\n";<br />
print "Content-Type: text/plain\r\n";<br />
print "Content-Length: " . strlen($output) . "\r\n";<br />
print "\r\n";<br />
print $output;<br />
die;<br />
}</p>
<p></code></p>
<p><code>function serverIsUp()<br />
{<br />
$output = 'Server is up. All is okay.';<br />
print "HTTP/1.1 200 OK\r\n";<br />
print "Date: " . date('r') . "\r\n";<br />
print "Server: MySQL Monitor/1.0\r\n";<br />
print "Connection: close\r\n";<br />
print "Content-Type: text/plain\r\n";<br />
print "Content-Length: " . strlen($output) . "\r\n";<br />
print "\r\n";<br />
print $output;<br />
die;<br />
}</code></p>
<p><strong>5. chown -fv mysql:mysql /opt/mysql-status.php</strong> &#8211; Give ownership of the status checking script to mysql.<br />
<strong>6. chmod -fv +x /opt/mysql-status.php</strong> &#8211; Give execute permissions on the script.<br />
<strong>7. /etc/init.d/xinetd restart</strong> &#8211; Restart xinetd, to make all of the changes we&#8217;ve just made live.</p>
<p>Having completed the steps above, you will now have a script that checks the current status of your MySQL server, responding to connections on port 9200 of your server. In the next article, I&#8217;ll describe how to set up your load balancing server with HAProxy, and utilise the server we&#8217;ve just created to check which nodes are live at any given time.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dancryer.com/2010/01/mysql-monitoring-with-xinetd/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>MySQL Load-Balanced Cluster Guide &#8211; Part 1</title>
		<link>http://www.dancryer.com/2010/01/mysql-circular-replication</link>
		<comments>http://www.dancryer.com/2010/01/mysql-circular-replication#comments</comments>
		<pubDate>Wed, 27 Jan 2010 17:35:21 +0000</pubDate>
		<dc:creator>Dan Cryer</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[PHP Conference 2010]]></category>
		<category><![CDATA[replication]]></category>
		<category><![CDATA[scaling]]></category>
		<category><![CDATA[servers]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://www.dancryer.com/?p=409</guid>
		<description><![CDATA[I&#8217;m currently working on setting up a load-balanced many-master MySQL cluster at work, without using MySQL Cluster as it&#8217;s incompatible with our needs. Finding good guides on how to do this has proven incredibly difficult, so I thought I would document the process here. This first post is going to cover setting up the servers [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m currently working on setting up a load-balanced many-master MySQL cluster at work, without using MySQL Cluster as it&#8217;s incompatible with our needs. Finding good guides on how to do this has proven incredibly difficult, so I thought I would document the process here.</p>
<p>This first post is going to cover setting up the servers themselves and configuring MySQL replication.</p>
<p><strong>1. Set up the following three servers:<br />
</strong></p>
<ul>
<li><strong>Cluster Load Balancer</strong> &#8211; One Public IP, One Internal IP &#8211; Ubuntu Jaunty Basic installation</li>
<li><strong>Cluster MySQL 1</strong> &#8211; One Public IP, One Internal IP &#8211; Ubuntu Jaunty Basic + MySQL Server 5.x</li>
<li><strong>Cluster MySQL 2</strong> &#8211; One Public IP, One Internal IP &#8211; Ubuntu Jaunty Basic + MySQL Server 5.x</li>
</ul>
<p>We&#8217;re using <a rel="nofollow" href="http://manage.aff.biz/z/146/CD2815/">VPS.NET</a>. If you&#8217;re going to do the same, use the &#8216;Ubuntu 8.04 LTS 64-bit Basic Installation&#8217; template for the Load Balancer and &#8216;Ubuntu 8.04 x64 MySQL&#8217; for the MySQL nodes. You&#8217;ll then need to do the following to upgrade to Jaunty:</p>
<ul>
<li>Edit the file &#8216;<em>/etc/update-manager/release-upgrades</em>&#8216; and change the line &#8216;<em>Prompt=lts</em>&#8216; to &#8216;<em>Prompt=normal</em>&#8216;.</li>
<li>Run the command &#8216;<em>do-release-upgrade</em>&#8216;. Let it go through the entire process of installing Intrepid, then reboot.</li>
<li>Repeat the step above again to install of Jaunty.</li>
</ul>
<p><strong>2. On the two MySQL servers, edit /etc/mysql/my.cnf and comment out the line &#8216;<em>bind-address 127.0.0.1</em>&#8216; &#8211; the servers need to allow remote TCP/IP connections.<br />
</strong><br />
<strong>3. On each server, add the following to the /etc/mysql/my.cnf file:<br />
</strong><br />
<code>server-id = 1                         # Increment this number by one for each server, so 1 for cluster-1, 2 for cluster-2<br />
auto_increment_increment = 2          # Set to the number of nodes you have (or are likely to have)<br />
auto_increment_offset = 1             # Set to the same as the server-id<br />
replicate-same-server-id = 0          # To ensure the slave thread doesn't try to write updates that this node has produced.<br />
log-bin                               # Turn on binary logging (neccessary for replication)<br />
log-slave-updates                     # Neccessary for chain or circular replication<br />
relay-log                             # As above<br />
relay-log-index                       # As above</code></p>
<p><strong>Note:</strong> On our servers, we&#8217;ve used an <em>auto_increment_increment </em>of 4, to allow us to add two more nodes to our cluster at a later date, without hitting auto-increment problems.</p>
<p><strong>4. Restart MySQL on both servers.<br />
</strong><br />
<strong>5. On Cluster 1, log into the MySQL Console and run the following queries:<br />
</strong><br />
<code>STOP SLAVE;<br />
CHANGE master TO master_host="cluster-2.yourdomain.com", master_user="{username}", master_password="{password}", master_log_file="mysqld-bin.000001", master_log_pos=98;<br />
START SLAVE;</code></p>
<p><strong>Note:</strong> To verify the master_log_pos value, execute the query: &#8220;SHOW MASTER STATUS;&#8221; and check the Position column. Also, you may wish to use your internal IP addresses for the master_host option.</p>
<p><strong>6. On Cluster 2, log into the MySQL Console and run the following queries:<br />
</strong><br />
<code>STOP SLAVE;<br />
CHANGE master TO master_host="cluster-1.yourdomain.com", master_user="{username}", master_password="{password}", master_log_file="mysqld-bin.000001", master_log_pos=98;<br />
START SLAVE;</code></p>
<p><strong>Note:</strong> If on either server you get an error message at this point, verify that:</p>
<ul>
<li>a) the master_log_file name in the query above matches the ones your servers are generating</li>
<li>b) your username and password for the other server are correct</li>
<li>c) that your servers can communicate with each other using the host names you specified.</li>
<li>Additionally, you may wish to remove AppArmor, as that can interfere with MySQL replication and cause all kinds of miscellaneous problems. To do so, run &#8216;<em>apt-get remove apparmor</em>&#8216; followed by &#8216;<em>apt-get purge apparmor</em>&#8216;.</li>
</ul>
<p>You should now have master to master replication set up and running across your two MySQL servers. To test, create a database on cluster-1, then create a table within that database and insert some data into it. Next, log on to cluster-2 and you should be able to see that newly created table, complete with data.</p>
<p>If you want to ensure that it works both ways, insert some data on cluster-2 and you should see it on cluster-1. You will also want to test that your auto-increment settings are working correctly.</p>
<p>If you&#8217;ve got any questions about this article, or have any problems setting up the replication, please leave a comment below and I&#8217;ll do my best to help. The next article in this guide will cover setting up a proxy on the load balancer and hopefully, implementing failover.</p>
<ul>
<li>Next Step: <a href="http://www.dancryer.com/2010/01/mysql-monitoring-with-xinetd">Setting up MySQL Monitoring on the cluster nodes</a>.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.dancryer.com/2010/01/mysql-circular-replication/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP/MySQL Scaling Followup</title>
		<link>http://www.dancryer.com/2009/09/php-mysql-scaling-followup</link>
		<comments>http://www.dancryer.com/2009/09/php-mysql-scaling-followup#comments</comments>
		<pubDate>Mon, 14 Sep 2009 20:16:48 +0000</pubDate>
		<dc:creator>Dan Cryer</dc:creator>
				<category><![CDATA[Other Things]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[PHP Conference 2010]]></category>
		<category><![CDATA[scaling]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://www.dancryer.com/?p=141</guid>
		<description><![CDATA[I posted last week about our work on scaling our crawler application and it&#8217;s corresponding MySQL database, but left it with very little conclusion, apart from that Memcached is good. I wanted to follow up with some of the changes we made on Thursday and Friday. Having run the code with the changes we made [...]]]></description>
			<content:encoded><![CDATA[<p>I posted last week about our work on <a href="http://www.dancryer.com/2009/09/when-scaling-for-speed-slows-you-down">scaling our crawler application</a> and it&#8217;s corresponding MySQL database, but left it with very little conclusion, apart from that <a href="http://www.dancryer.com/2009/09/memcached">Memcached is good</a>. I wanted to follow up with some of the changes we made on Thursday and Friday.</p>
<p>Having run the code with the changes we made previously for a day or so, in order to give it a fair trial, it was obvious it wasn&#8217;t going to work. We were still doing an average of 5,000 pages an hour, way below the original code and even further below the targets we&#8217;d set ourselves.</p>
<p>The first change we made was to move the queue table back to InnoDB from MEMORY. The memory engine wasn&#8217;t providing the benefits we&#8217;d hoped, and it was using far more RAM than it was worth. It was also locking, a lot. I have to admit, it was probably unwise to assume that we could expect memory tables to perform any better, when working with over a gigabyte of data. Next, we turned our attention to what had become an obvious bottleneck: statement based binary logging. We&#8217;d made this choice early on, based on reasons unbeknownst to me. After much research and a little panic, we flipped the switch and turned our crawlers back on, seeing an immediate and significant performance improvement.</p>
<p>We&#8217;d finally done it, we were now running an average of 25,000 pages an hour, peaking at 45,000. We&#8217;re still working on it, of course, as we&#8217;d like to hit 100,000 pages an hour, but we&#8217;re very happy to have finally gotten past the bottlenecks.</p>
<p>As an additional interesting tidbit, I re-ran the graphs for our Memcache utilisation. This time over the period 12:00pm Thursday to 12:00pm Monday. As you can see, the hits to misses ratio is starting to level out over time, averaging around 70% hits, 30% misses. Here&#8217;s the charts:</p>
<p><img class="alignnone size-full wp-image-146" title="Memcache usage in real numbers under load" src="http://www.dancryer.com/wp/wp-content/uploads/2009/09/memcacheusagereal.png" alt="Memcache usage in real numbers under load" width="386" height="233" /></p>
<p><img class="alignnone size-full wp-image-144" title="Memcached usage under load" src="http://www.dancryer.com/wp/wp-content/uploads/2009/09/memcachedusage.png" alt="Memcached usage under load" width="386" height="233" /></p>
<p>As usual, please let me know if you&#8217;ve got any questions or comments, I (and I&#8217;m sure the others on my team) would be happy to help if I can.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dancryer.com/2009/09/php-mysql-scaling-followup/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL Replication Issues</title>
		<link>http://www.dancryer.com/2009/09/mysql-replication-issues</link>
		<comments>http://www.dancryer.com/2009/09/mysql-replication-issues#comments</comments>
		<pubDate>Mon, 14 Sep 2009 19:46:04 +0000</pubDate>
		<dc:creator>Dan Cryer</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[PHP Conference 2010]]></category>
		<category><![CDATA[replication]]></category>
		<category><![CDATA[scaling]]></category>

		<guid isPermaLink="false">http://www.dancryer.com/?p=139</guid>
		<description><![CDATA[In our ongoing, joyous adventures with scaling MySQL, we hit another stumbling block this morning. We arrived to find out that one of our two MySQL slaves had stopped replicating at 6AM on Saturday morning. This meant that our reporting systems were all running on two day old data, even though we&#8217;d been diligently collecting [...]]]></description>
			<content:encoded><![CDATA[<div>In our ongoing, joyous adventures with scaling MySQL, we hit another stumbling block this morning. We arrived to find out that one of our two MySQL slaves had stopped replicating at 6AM on Saturday morning. This meant that our reporting systems were all running on two day old data, even though we&#8217;d been diligently collecting and storing data all weekend.</div>
<div>Our setup is reasonably standard:</div>
<ul>
<li>One master &#8211; All writes go to this server.</li>
<li>Two slaves:
<ul>
<li>Slave One runs in our office, is a somewhat less powerful machine than the master, but usually keeps up. This slave&#8217;s job is to do a daily backup, as such, it can be up to six hours behind, as the backup takes that long.</li>
<li>Slave Two runs in the same rack as the master at our data centre, this box is of equal spec to the master and is usually 0-2 seconds behind. This slave&#8217;s job is to serve our reporting systems.</li>
</ul>
</li>
<li>Row-based replication.</li>
</ul>
<p>When we arrived, we were hit with Slave Two throwing errors such as: &#8220;Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event&#8217;s master log&#8221;. Slave One was, and has remained, fine. Remo, Ben, and I spent quite some time Googling for a solution, but that turned up absolutely nothing. There were a few suggestions, such as setting &#8220;slave_exec_mode&#8221; to &#8220;IDEMPOTENT&#8221;. This is merely silent error suppression for such errors, and seems to be suggested for environments using master to master replication and/or cluster-based replication.  We were very uncomfortable with using this, as it meant that our slaves could slowly slip completely out of sync on all tables, and we&#8217;d never know about it.</p>
<p>Luckily for us, the affected table is one that we never need to read from on the slaves. It&#8217;s a queue table, which we update on the master, and then need to immediately read those rows from, so we do that on the master too. Our solution became clear later in the afternoon. As MySQL allows you to use different storage engines on your slaves to the master, we went the dirty route. The queue tables on the slaves are now powered by the <a rel="nofollow" href="http://dev.mysql.com/doc/refman/5.1/en/blackhole-storage-engine.html">BlackHole storage engine</a>.</p>
<p>Getting the slave back up and running was a whole task in itself, however, as thousands upon thousands of queries had failed in the time this problem had been occuring. The commonly accepted solution is to do the following:</p>
<blockquote><p>STOP SLAVE;</p>
<p>SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;</p>
<p>START SLAVE;</p>
<p>SHOW SLAVE STATUS\G;</p></blockquote>
<p>However, manually running this thousands of times was going to become tedius, so one of the team wrote a script to do it for us. It&#8217;ll automatically check that it&#8217;s only bypassing errors on a specific table (look out for &#8220;database.table&#8221; in the script), and bypass them all. It&#8217;s not perfect, but it seems to work pretty well. Here&#8217;s the code: <a rel="nofollow" href="http://pastie.org/private/mcxnw1oienrlfswciix4w">http://pastie.org/private/mcxnw1oienrlfswciix4w</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.dancryer.com/2009/09/mysql-replication-issues/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>When scaling for speed slows you down&#8230;</title>
		<link>http://www.dancryer.com/2009/09/when-scaling-for-speed-slows-you-down</link>
		<comments>http://www.dancryer.com/2009/09/when-scaling-for-speed-slows-you-down#comments</comments>
		<pubDate>Fri, 04 Sep 2009 20:05:24 +0000</pubDate>
		<dc:creator>Dan Cryer</dc:creator>
				<category><![CDATA[Search Engine Optimisation]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[PHP Conference 2010]]></category>
		<category><![CDATA[scaling]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://www.dancryer.com/?p=113</guid>
		<description><![CDATA[At work, the past few days, I&#8217;ve been working on the scalability of one of our systems and, hopefully, we&#8217;re almost closing on the finishing line. For some context, the system I&#8217;m talking about is effectively a web crawler. It (all too slowly) works through a queue of URLs, downloading the pages and parsing them [...]]]></description>
			<content:encoded><![CDATA[<p>At work, the past few days, I&#8217;ve been working on the scalability of one of our systems and, hopefully, we&#8217;re almost closing on the finishing line. For some context, the system I&#8217;m talking about is effectively a web crawler. It (all too slowly) works through a queue of URLs, downloading the pages and parsing them for various information, including backlinks. As it inserts data into a MySQL database, primarily made up of innodb tables. </p>
<p>This all started on Wednesday, we&#8217;d been looking at the queue and decided that it was far too large. We&#8217;d hit 12.5M pages waiting to be checked, and were processing about 200,000 a day, tops. The initial reaction, as this is a distributed system, was to scale it horizontally. We rented six more <a href="http://manage.aff.biz/z/146/CD2815/" rel="nofollow">VPSs</a> (to add to the existing 5) and planned to run them at capacity until the queue was empty. After setting all the boxes up, we left them over night, in hope of completing 750,000 or more pages. Boy, were we wrong. On Thursday morning, we came in to discover it was running at a peak of 5,000 pages an hour (144,000 a day). We&#8217;d slowed it down, and not only that, we&#8217;d slowed everything else down too.</p>
<p>This is when the optimisation work started. We looked at the queries we were running, and noticed that an obvious bottleneck appeared to be the queue itself. Each VPS ran 15 instances of our application at any given time, and each one did a query for 250 pages to check when it was starting up, updated them as in progress, and deleting each from the queue on completion. The first change we made was to change the indexing on the table, which merely served to slow us down further. The second, more interesting change, was to change the entire table from innodb to the memory/heap table engine. This meant changing both max_heap_table_size and tmp_table_size to over 2.5GB to accomodate the 1GB table at the same time as allowing temporary tables to be created. Getting this change replicated onto our two slaves was a delight. </p>
<p>The memory table &#8220;solution&#8221; worked well after turning all of our VPSs back on, for about 10 minutes. At which point, they were up to capacity and the queue table started locking, a lot. As did all of the other tables, inexplicably. In addition to inducing panic, this highlighted to us that another table was starting to cause problems, our anchor text table. This is responsible for storing only the words that are used to link from one site to another. This table was seeing anything up to 2 million inserts an hour, and the same number of selects. After several failed attempts to remedy this, we finally settled on selecting all of the anchor texts we&#8217;d just collected, any that came back from the database were then dropped from our insert, thus only inserting those that were not present. Those inserts were done with batches of 10 per query. This cut the number of queries down significantly. Whilst this has improved the locking situation, it has, unfortunately, led to hanging selects. These selects are using potentially gigantic &#8216;IN(one, two, three)&#8217; conditions, and are taking anything from 0.01 to 120 seconds to complete.  Completely unpredictably.</p>
<p>Next priority was fixing the table locking on our queue. We&#8217;ve done this by marking all deletes from the queue as LOW_PRIORITY. In doing that, we&#8217;re allowing MySQL to queue those deletes for when no other clients are accessing the table, preventing unnecessary locking. </p>
<p>At the time of this writing, 113 instances of our application are running without significant conflicts on either of the tables we&#8217;ve optimised. However, we&#8217;re still processing just 5,000 pages an hour. I&#8217;m sure I&#8217;ll be working on this problem again on Monday, and will update here with anything we do discover. </p>
<p>So to summarise this somewhat rambley and unhelpful post a little, don&#8217;t assume that because you&#8217;ve built your system to run across multiple servers, that it can infact run across multiple servers. Ours scaled up to 4, and then the scaling started working against us. If you found it interesting, have any MySQL questions, want to offer advice, or are doing similar work yourself&#8230; drop me a comment below. I&#8217;d love to know that people are reading this!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dancryer.com/2009/09/when-scaling-for-speed-slows-you-down/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
