Environment
My customer, in this case an ISP, has multiple dns servers was going to scale-out the DNS records database for multiple DNS servers.
The startup configuration was a master/slave, with DRBD failover MySQL cluster; the new configuration was accomplished with a MySQL master database and three servers as slaves, two for DNS servers queries and one for the public management interface; nothing of particularly complex.
The solution
The big work was rewriting the PHP application they was using for the DNS management; the objective was balancing SQL queries between the databases sending SELECTs on slaves and INSERT/UPDATE statements to the master. To accomplish the task i wrote a PHP Database class which redirect the queries to the right server before checking who is the master in case of failover.
I’m posting here the database class’s draft i wrote (without support for failover):
class Database
{
private $num_rows;
private $master_dbh;
private $slave_dbh;
private $stmt;
private $multislave = false;
public function Database($dbname){
$this->KMVC =& get_instance();
if(!(isset($config->databases[$dbname]) && is_array($config->databases[$dbname])))
throw new Exception('Database '.$dbname.' does not exists');
$dsn = $config->databases[$dbname]['driver'].":host=".$config->databases[$dbname]['host'].";dbname=".$config->databases[$dbname]['database'];
try{
$this->master_dbh = new PDO($dsn, $config->databases[$dbname]['user'], empty($config->databases[$dbname]['pass']) ? NULL : $config->databases[$dbname]['pass']);
//$this->master_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(Exception $e){
log("Error connecting: ". $e->getMessage().". Looking for slaves now");
}
if(isset($config->databases[$dbname]['slaves']) && count($config->databases[$dbname]['slaves']) > 1){
$this->multislave = true;
$rand = rand(0, count($config->databases[$dbname]['slaves']) - 1);
$dbname = $config->databases[$dbname]['slaves'][$rand];
$slave_dns = $config->databases[$dbname]['driver'].":host=".$config->databases[$dbname]['host'].";dbname=".$config->databases[$dbname]['database'];
$this->slave_dbh = new PDO($slave_dsn, $config->databases[$dbname]['user'], empty($config->databases[$dbname]['pass']) ? NULL : $config->databases[$dbname]['pass']);
}
}
public function query($sql, $data = false){
if($this->multislave == true){
if(preg_match('/^select /i', $sql) > 0)
$dbh =& $this->slave_dbh;
else
$dbh =& $this->master_dbh;
}else
$dbh =& $this->master_dbh;
if(!$this->stmt = $dbh->prepare($sql))
throw new Exception(implode($this->stmt->errorInfo()), $stmt->errorCode());
if($data != false || is_null($data)){
$i = 0;
if(is_array($data))
foreach($data as $row)
$this->stmt->bindValue(++$i, $row);
else
$this->stmt->bindValue(++$i, $data);
}
if(!$this->stmt->execute())
if($config->debug)
throw new Exception(implode(' - ', $this->stmt->errorInfo()), (int)$this->stmt->errorCode());
else
throw new Exception("Error in query", (int)$this->stmt->errorCode());
$this->num_rows = $this->stmt->rowCount();
return true;
}
} |
class Database
{
private $num_rows;
private $master_dbh;
private $slave_dbh;
private $stmt;
private $multislave = false;
public function Database($dbname){
$this->KMVC =& get_instance();
if(!(isset($config->databases[$dbname]) && is_array($config->databases[$dbname])))
throw new Exception('Database '.$dbname.' does not exists');
$dsn = $config->databases[$dbname]['driver'].":host=".$config->databases[$dbname]['host'].";dbname=".$config->databases[$dbname]['database'];
try{
$this->master_dbh = new PDO($dsn, $config->databases[$dbname]['user'], empty($config->databases[$dbname]['pass']) ? NULL : $config->databases[$dbname]['pass']);
//$this->master_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(Exception $e){
log("Error connecting: ". $e->getMessage().". Looking for slaves now");
}
if(isset($config->databases[$dbname]['slaves']) && count($config->databases[$dbname]['slaves']) > 1){
$this->multislave = true;
$rand = rand(0, count($config->databases[$dbname]['slaves']) - 1);
$dbname = $config->databases[$dbname]['slaves'][$rand];
$slave_dns = $config->databases[$dbname]['driver'].":host=".$config->databases[$dbname]['host'].";dbname=".$config->databases[$dbname]['database'];
$this->slave_dbh = new PDO($slave_dsn, $config->databases[$dbname]['user'], empty($config->databases[$dbname]['pass']) ? NULL : $config->databases[$dbname]['pass']);
}
}
public function query($sql, $data = false){
if($this->multislave == true){
if(preg_match('/^select /i', $sql) > 0)
$dbh =& $this->slave_dbh;
else
$dbh =& $this->master_dbh;
}else
$dbh =& $this->master_dbh;
if(!$this->stmt = $dbh->prepare($sql))
throw new Exception(implode($this->stmt->errorInfo()), $stmt->errorCode());
if($data != false || is_null($data)){
$i = 0;
if(is_array($data))
foreach($data as $row)
$this->stmt->bindValue(++$i, $row);
else
$this->stmt->bindValue(++$i, $data);
}
if(!$this->stmt->execute())
if($config->debug)
throw new Exception(implode(' - ', $this->stmt->errorInfo()), (int)$this->stmt->errorCode());
else
throw new Exception("Error in query", (int)$this->stmt->errorCode());
$this->num_rows = $this->stmt->rowCount();
return true;
}
}