Zend Framework Database Admin

If you’re looking for a simple tool that uses Zend Framework’s robust database classes (such as Zend_Db and Zend_Db_Table), you can check out zdbform. It’s a short yet effective library that let’s you perform simple administration tasks on your database, with minimal coding.

It’s not a full blown phpMyAdmin, but it’s a simple way to view, edit and add your tables rows in a web interface. Also, don’t expect it to scale, because I am sure this library was written to serve some quick table administration needs, and that it is not ready to handle large datasets. But, it is very convenient if you have a small database to administer.

I implemented it with the Zend MVC components, and following is a brief overview.

In the front controller, or front plugin, or any class that your controller subclasses:

$this->db = Zend_Db::factory('Pdo_Mysql', array(
				'host'     => DB_HOST,
				'username' => DB_USER,
				'password' => DB_PASS,
				'dbname'   => DB_NAME
		));
Zend_Db_Table_Abstract::setDefaultAdapter($this->db);

Then set your controller and view scripts as necessary. Let’s say you have two tables to admin, “clients” and “history”. First make sure they are declared as subclasses of Zend_Db_Table:

require_once "Zend/Db/Table/Abstract.php";

class Clients extends Zend_Db_Table_Abstract
{
    protected $_name = 'clients';
}

class History extends Zend_Db_Table_Abstract
{
    protected $_name = 'history';
}

Your controller would look like:

require_once "Zend/Controller/Action.php";

class AdminController extends Zend_Controller_Action
{

	public function init()
	{
		require_once 'zdbform/zdbform.class.php';
		require_once 'zdbform/zdbform_widgets.class.php';
		require_once 'zdbform/zdbform_validations.php';

		parent::init();

		$this->view->headLink()->appendStylesheet('/zdbform/zdbform.css');
		$this->_helper->viewRenderer('index');
	}

	public function indexAction()
	{
	}

	public function clientsAction()
	{
		$this->view->dbform = new Zdbform('Clients');
		$this->view->dbform->setWidget('description', 'textarea');
		$this->view->dbform->processForms();
	}

	public function historyAction()
	{
		$this->view->dbform = new Zdbform('History');
		$this->view->dbform->processForms();
	}

}

And the single view script you need is admin/index.phtml:

<?php
echo $this->headLink();

include_once "Zend/Filter/Word/CamelCaseToDash.php";
include_once "Zend/Filter/Word/CamelCaseToUnderscore.php";
$cctd = new Zend_Filter_Word_CamelCaseToDash();
$cctu = new Zend_Filter_Word_CamelCaseToUnderscore();
$classes = get_declared_classes();

foreach ($classes as $class)
{
	if (is_subclass_of($class,'Zend_Db_Table_Abstract'))
	{
	?>
		<a href="/admin/<?= strtolower($cctd->filter($class)) ?>"><?= strtolower($cctu->filter($class)) ?></a>&nbsp;&nbsp;
	<?php
	}
}

if ($this->dbform)
{
	?>
	<h1>Table: <?= $this->dbform->tableName ?></h1>
	<?php
	$this->dbform->showForms();
	$this->dbform->showTable();
}
?>

There were also a couple things that needed changing in the zdbform class itself:

  • Replace all PHP_SELF with REQUEST_URL. On the mvc case, PHP_SELF is empty or index.php, and we don’t want all the forms posted there, we want them to go back to /admin/clients or /admin/history
  • After this line
    $this->pk = $tableInfo['primary'];

    I had to add this:

    if (is_array($this->pk))
    	$this->pk = $this->pk[1];
  • zdbform->orderBy is treated as a single column, of you want multiple column sorting you have to hack a bit with getAllRows().

That’s it, point your browser to /admin and you’re good to go. In a very short time and with a little bit of code, you can get something similar to a stripped down version of phpMyAdmin, using the power of Zend Framework.

MMM (Mysql Master-Master Replication) on EC2

Maintaining a MySQL high availablity cluster is one of the first missions encountered when scaling web applications. Very quickly your application gets to the point where the one database machine you have is not enough to handle the load, and you need to make sure that when failure happens (and it always happens), your cluster is ready to failover gracefully.

Some basic MySQL replication paradigms

MySQL master-slave replication was one of the first architectures used for failovers. The rationale is that if a master fails. a slave can be promoted to master, and start handle the writes. For this you could use several combinations of ip tools and monitoring software, for example, iproute and nagios, or heartbeat and mon.

However, master-slave architecure for MySQL replication has several flaws, most notable are:

  • The need to manually take care of bringing the relegated master back to life, as a slave to the now-promoted master (this can be scripted, but usually contains many pitfalls when trying to automate as a script).
  • The possibility of failover during a crash, which can result in same transaction being committed both on the old master and the new master. Good luck then, when trying to bring back the master as a slave. You’ll most likely get some duplicate key failure because of auto increments on the last transaction when starting replication again, and then the whole database on the relegated master is useless.
  • The inablity to switch roles quickly. Say the master is on a better machine than the slave, and now there was a failover. How can you easily restore the situation the way it was before, with the master being on the better machine? Double the headache.

Along came master-master architecture, which in essence is an architecture which keeps two live masters at all times, with one being a hot standby for the other, and switching between them is painless. (Baron Schwartz has a very interesting post about why referring to master-master replication as a “hot” standby could be dangerous, but this is out of the scope of this post). One of the important things that lies in the bottom of this paradigm, is that every master works in its own scope in regards to auto-increment keys, thanks to the configuration settings auto_increment_increment and auto_increment_offset. For example, say you have two masters, call them db1 and db2, then db1 works on the odd auto-increments, and db2 on the even auto-increments. Thus the problem of duplication on auto increment keys is avoided.

Managing master-master replication with MMM

Master-master replication is easily managed by a great piece of perl code called MMM. Written initially by Alexey Kovyrin, and now maintained and being installed daily in production environments by percona, MMM is a management daemon for master-master clusters. It’s a convenient and reliable cluster management tool, which simplifies switching roles between machines in the cluster, takes care of their monitoring and health issues, and prevents you from doing stupid mistakes (like making an offline server an active master…).

And now comes the hard, EC2 part. Managing high availability MySQL clusters is always based on the ability to control machines ip addresses in the internal network. You set up a floating internal ip address for each of the masters, configure your router to handle these addresses, and you’re done. When the time comes to failover, the passive master sends ARP and takes over the active master’s ip address, and everything swtiches smoothly. It’s just that on EC2, the router part or any internal ip address can not be determined by you (I was told FlexiScale gives you the option to control ip addresses on the internal network, but I never got to testing it).

So how can we use MMM on EC2 for master-master replication?

One way is to try using EC2’s elastic ip feature. The problem with it, is that currently moving an elastic ip address from one instance to another, takes several minutes. Imagine a failover from active master to passive master in which you would have to wait several minutes for the application to respond again — not acceptable.

Another way is to use name resolving instead of ip addresses. This has major drawbacks, especially the non reliable nature of the DNS. But it seems to work if you can set up a name resolver that serves your application alone, and use the MMM ns_agent contribution I wrote. You can check out MMM source, and install the ns_agent module according to contrib/ns_agent/README.

I am happy to say that I currently have a testing cluster set up on EC2 using this feature, and up until now it worked as expected, with the exception of several false-positive master switching due to routing issues (ping failed). Any questions or comments on the issue are welcome, and you can also post to the devolpement group.