Load Balancing Two MySQL Servers for PHP Applications

My “research” on clustering and replication was just timely. I had to find a fix for an overloaded server. I could’ve just rewritten the code but I wanted to try something new first. I found out that a MySQL cluster needs to have at least three servers to get full redundancy. Replication was my only choice because I only have two servers for this application and the queries that are producing the most load are select queries.

MySQL replication works by having a master server where all the inserts, updates and deletes (basically any writing done) and one or more slave servers that polls the master server to replicate the database. You can only issue select queries to the slave server. You can also have multiple master servers but it won’t be covered here. You can follow this article to setup replication.

I’ll be using Round Robin to balance the load since I’ll be load balancing for a separate portion only where the same queries are used. This will equally split the load to each server (…almost). To do this in PHP, I wrote a very simple script that opens a socket. Once a host connects, it tells which database server to connect to and immediately terminates the connection.

#!/usr/bin/php -q
<?php
// Bind and listen
socket_bind($stream, "127.0.0.1",3307);
socket_listen($stream, 100);

// Define DB login credentials in an array
// host|user|passwd
$hosts = array(
0 => array('db1.dbservers.net','someuser','somepass'),
1 => array('db2.dbservers.net','anotheruser','anotherpass'),
/***** if you add another host (just follow the drift) *****/ 
// 2 => array('db3.dbservers.net','yetanotheruser','yetanotherpass') 
);

// Loop forever
while(true)
{
	// Accept anyone
	$client = socket_accept($stream);
	$key = key($hosts);
	$reply = implode($hosts[$key], "|");

	// Move internal pointer to next host
	if(next($hosts) === FALSE)
		reset($hosts);

	// Push response then kill the connection
	socket_write($client, $reply);
	socket_close($client);
}
?>

This script should be called from the command line and run like a daemon. Then we modify how we connect to the database. We connect to the “daemon” and catch the login information.

<?php
// Connect to load balancer daemon
$fp = fsockopen("localhost", 3307);

// Fallback and use some host in case of failure
if(!$fp)
{
	$host = "localhost";
	$user = "someuser";
	$pass = "somepasswd";
}
else
{
	// Get DB login information
	$packet = fgets($fp);
	$account_details = explode("|", $packet);
	$host = $account_details[0];
	$user = $account_details[1];
	$pass = $account_details[2];
}

// Connect
$link = mysql_connect($host,$user,$pass);
if(!$link)
{
	die("Fatal Error: Can't connnect to database\n");
}
mysql_select_db("somedb",$link);
?>

The code above can be improved further to check if a host is still up, give weights on the server depending on its hardware and other bells and whistles.

The beauty of this is you can safely change to another algorithm like Weighted Round Robin or Job Informed and all of the code that has to be changed is in the daemon. You may learn more on other algorithms from the paper by Dennis Haney and Klaus S. Madsen.

I’m looking into venturing to a Job Informed algorithm once the whole application uses load balancing. Queries will have weights then some form of load estimation can be achieved. Query analysis is also a possibility (based on subqueries, query type, constraints ,etc).

  • I like your method, I'm gonna implement it on my server.
  • As the MySQL cluster management server does not use many resources, and the system would just sit there doing nothing, we can put our first load balancer on the same machine, which saves us one machine, so we end up with four machines.
  • If you want to make it more robust, you could add a couple of features to your daemon. For example, you should pull your config information from a file. Then you could implement a simple protocol. For example, the client could send "REQ" to request a DB handle. Or the client to send "RELOAD" to cause the daemon to reload it's config file.
  • Thanks for posting the code. It's really helpful although this was posted 2 years ago.
  • Chris
    For anyone trying this.. don't forget
    $stream = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

    Put that above the socket_bind...
  • fgh
    hfgh
  • _
  • I found your site on faves.com bookmarking site.. I like it ..gave it a fave for you..ill be checking back later
  • You're lucky I still have the PDF file stored somewhere. Here it is:
    http://rapidshare.com/files/88019396/03-11.pdf....
  • sam
    http://davh.dk/projects/mysql-load-balance/

    this link is not working.can you provide me any other link which has more information about algorithm.
  • The link I gave in my last comment is for clustering. But the one used in my post is replication.

    I also extended my daemon script that's running in production. I just wanted to give a basic idea of how it works.

    For performance, refer to the image below. The one in blue was when it wasn't rotating between servers. The one in red, when rotation was in effect.

    <img src="http://img458.imageshack.us/img458/4946/jppniccmysqlqueriesweekyi4.png" alt="QPS before/after"></img>
  • fred
    Hi, i wanna know how much about performance do you get with this trick?
  • Marcus
    There is a difference between MySQL Cluster and MySQL replication. What you seem to be describing is replication, in which case I don't think that the linked article will help you.

    It seems to me that your approach should work out just fine. Just don't try to get into master-master replication... it's a pain and it can only lead to tears. :)

    If you want to make it more robust, you could add a couple of features to your daemon. For example, you should pull your config information from a file. Then you could implement a simple protocol. For example, the client could send "REQ" to request a DB handle. Or the client to send "RELOAD" to cause the daemon to reload it's config file. This way you wouldn't have to stop the daemon...

    Then again, PHP probably isn't the best language to use for the daemon, since it will block on each request. If you have two requests coming in at the same time, the second one will have to wait for the first to get done. This might be an issue over time.
  • It's possible to have a cluster with two servers. You'll initially need three servers then you can turn off the third one once it's running (not recommended). See this article.
  • Andrew Blake
    "I found out that a MySQL cluster needs to have at least three servers to get full redundancy."

    I'm thinking of implementing cluster over two servers - could you tell me about the issues you found that make it require three machines ?

    Thanks

    Andy
blog comments powered by Disqus
Locations of visitors to this page
De La Salle Canlubang Top Sites top blogs Best blogs on the Web: all about WWW