Monday, June 27, 2011

Pressflow 6 with mysql Replication

Pressflow6 should support database replication according to the pressflow wiki. but when I configured master and slave, pressflow instance always pick master. If master was down, site couldn't load and site redirected to the site-offline error page.

I did some modifications in database.inc, database.mysqli.inc and settings.php in order to work when master mysql server was down.

Scenario

  1. If mysql master is inactive, connect to Slave,
  2. if slave is inactive but Master is active, then connect to Master
  3. if Master and Slave both are inactive, Redirect user to Offline page


Modifications can be found in red text.


database.inc

  1. Below change will modify the db_connect call to Master instance by passing 2nd parameter ("master") and by assigning $db_slave_conns[$name] values to $active_db.
  2. Validate before slave connection create. If master is active we are not calling Slave by adding $db_conns[$name]->connect_errno > 0 

function db_set_active($name = 'default') {
  global $db_url, $db_slave_url, $db_type, $active_db, $active_slave_db;
  static $db_conns, $db_slave_conns, $active_name = FALSE;


  if (empty($db_url)) {
    include_once 'includes/install.inc';
    install_goto('install.php');
  }


  if (!isset($db_conns[$name])) {
    // Initiate a new connection, using the named DB URL specified.
    if (is_array($db_url)) {
      $connect_url = array_key_exists($name, $db_url) ? $db_url[$name] : $db_url['default'];
      if (is_array($db_slave_url[$name])) {
        $slave_index = mt_rand(0, count($db_slave_url[$name]) - 1);
        $slave_connect_url = $db_slave_url[$name][$slave_index];
      }
      else {
        $slave_connect_url = $db_slave_url[$name];        
      }
    }
    else {
      $connect_url = $db_url;
      if (is_array($db_slave_url)) {
        $slave_index = mt_rand(0, count($db_slave_url) - 1);
        $slave_connect_url = $db_slave_url[$slave_index];
      }
      else {
        $slave_connect_url = $db_slave_url;        
      }
    }


    $db_type = substr($connect_url, 0, strpos($connect_url, '://'));
    $handler = "./includes/database.$db_type.inc";


    if (is_file($handler)) {
      include_once $handler;
    }
    else {
      _db_error_page("The database type '". $db_type ."' is unsupported. Please use either 'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
    }


    $db_conns[$name] = db_connect($connect_url,'master');
    if ($db_conns[$name]->connect_errno > 0 && !empty($slave_connect_url)) {
      $db_slave_conns[$name] = db_connect($slave_connect_url); 
    }
  }


  $previous_name = $active_name;
  // Set the active connection.
  $active_name = $name;
  $active_db = $db_conns[$name];
  if (isset($db_slave_conns[$name])) {
    $active_slave_db = $db_slave_conns[$name];
    $active_db = $db_slave_conns[$name];    
  }
  else {
    unset($active_slave_db);
  }


  return $previous_name;
}




database.mysqli.inc
  1. db_connect function accept a 2nd parameter. default value will be slave
  2. wrapped _db_error_page(mysqli_connect_error()) with slave server validation. this will stop error page redirection if master server is down.
  3. wrapped with else block to void calling mysqli_query with uninitiated mysql resource. 

function db_connect($url,$server='slave') {
  // Check if MySQLi support is present in PHP
  if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) {
    _db_error_page('Unable to use the MySQLi database because the MySQLi extension for PHP is not installed. Check your php.ini to see how you can enable it.');
  }


  $url = parse_url($url);


  // Decode url-encoded information in the db connection string
  $url['user'] = urldecode($url['user']);
  // Test if database url has a password.
  $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
  $url['host'] = urldecode($url['host']);
  $url['path'] = urldecode($url['path']);
  if (!isset($url['port'])) {
    $url['port'] = NULL;
  }


  $connection = mysqli_init();
  @mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS);


  if (mysqli_connect_errno() > 0) {
    if($server == 'slave'){
  _db_error_page(mysqli_connect_error());
    }
  }else {


 // Force UTF-8.
 mysqli_query($connection, 'SET NAMES "utf8"');
  }
  return $connection;
}




settings.php



$db_url = array();
$db_url['default'] = 'mysqli://user:password@master-host/database';


$db_slave_url = array();
$db_slave_url['default'] = 'mysqli://user:password@slave-host/database';

2 comments :

nickg76571 said...

Hey, I was wondering about the load on the master/slave server are they distributed evenly for all the requests or ONLY when the master goes down slave gets activated.

I just have regular drupal for now. I am little skeptical about installing pressflow. In the future i believe my website would grow and want to handle the needs of any load. So do you think pressflow will be able to do that. In a way do they really split reads & writes ?

Unknown said...

I just came across from this article and I read it. It is too good and worth reading. And now I would like to say that thanks for sharing with me such nice article. Deciding upon a content management system is highly important long-term I advocate Drupal. Because Seeing that the leading nonprofits use Durpal, I picked Drupal For hosting I run Pantheon with features like automated back-up retention.. What Content Management System do you like?

Drupal for Education