As part of the Solar-1.0.0alpha2 release, we included an SQL adapter that lets you connect to master/slave MySQL installations. Read on for how to set it up using just the config file.


"Regular" MySQL Setup

With Solar, you connect to SQL databases using the Solar_Sql factory class, which returns a Solar_Sql_Adapter class for you.

Most developers only need to connect to a single MySQL server. For example, you could do this as a piece of standalone code:

<?php
// create an SQL connection object
$sql = Solar::factory('Solar_Sql', array(
    'adapter' => 'Solar_Sql_Adapter_Mysql',
    'host'    => 'localhost',
    'user'    => 'username',
    'pass'    => 'password',
    'name'    => 'dbname',
));

// query with placeholders
$stmt = "SELECT foo FROM bar WHERE baz = :baz";
$data = array(
    'baz' => 'dib';
);
$list = $sql->fetchAll($stmt, $data);

// insert a row
$table = 'bar';
$data  = array(
    'foo' => 'another foo',
    'baz' => 'another dib',
);
$sql->insert($table, $data);
?>

In that example you pass the configuration array directly to the factory call, which means the configuration will apply just to that one object.

Savvy Solar developers know that if you want to set a default configuration, you place it in your config file:

<?php
// tell Solar_Sql what adapter to use
$config['Solar_Sql'] = array(
    'adapter' => 'Solar_Sql_Adapter_Mysql',
);

// tell the adapter how to set itself up
$config['Solar_Sql_Adapter_Mysql'] = array(
    'host' => 'localhost',
    'user' => 'username',
    'pass' => 'password',
    'name' => 'dbname',
);
?>

Then whenever you call Solar::factory('Solar_Sql') without a configuration, it will use the defaults from the config file.

"Replicated" MySQL Connection

However, when you get into a situation where you need to scale up, you might need a replicated MySQL database setup. In such cases, there is one "master" server that handles reads and writes, and there are one or more "slave" servers that are read-only. This means you need to pick which server you're going to connect to based on the kind of statement you need to issue (SELECT, INSERT, UPDATE, etc).

The Solar_Sql_Adapter_MysqlReplicated adapter handles all the connections and switching-around for you, and you don't need change a single line of application code. All you need to do is modify your configuration file to tell it where your master and slave servers are.

<?php
// tell Solar_Sql to use the MysqlReplicated adapter
$config['Solar_Sql'] = array(
    'adapter' => 'Solar_Sql_Adapter_MysqlReplicated',
);

// point to one master server and two slave servers
$config['Solar_Sql_Adapter_MysqlReplicated'] = array(
    // master server; also acts as default values for
    // the slave servers
    'host' => '192.168.1.100',
    'port' => null,
    'user' => 'mainuser',
    'pass' => 'mainpass',
    'name' => 'dbname',

    // the slave servers. note that they use the master
    // server values as their defaults, so if the username,
    // password, and database values are the same on all
    // servers, you don't need to re-enter them here.
    'slaves' => array(
        0 => array(
            'host' => '192.168.1.101',
        ),
        1 => array(
            'host' => '192.168.1.102',
        ),
    ),
);
?>

The adapter now looks at the first few characters of every statement to see if it's a "read" or "write":

  • If the statement is a "read" (i.e. a SELECT) then the adapter randomly picks a slave server, lazy-connects to it, and issues the statement against that slave server. The adapter maintains this first connection, so once it's "on" a slave server, it stays there.

  • If the statement is a "write" (e.g. INSERT, UPDATE, DELETE) then the adapter creates a second internal connection pointing to the master, and then issues the statement against the master server. This connection is maintained separately from the slave connection.

Our example application code from above does not need to change at all:

<?php
// query with placeholders.
// uses the slave server automatically.
$stmt = "SELECT foo FROM bar WHERE baz = :baz";
$data = array(
    'baz' => 'dib';
);
$list = $sql->fetchAll($stmt, $data);

// insert a row.
// uses the master server automatically.
$table = 'bar';
$data  = array(
    'foo' => 'another foo',
    'baz' => 'another dib',
);
$sql->insert($table, $data);

// another query with placeholders.
// uses the slave server automatically.
$stmt = "SELECT dib FROM zim WHERE gir = :gir";
$data = array(
    'gir' => 'irk';
);
$list = $sql->fetchAll($stmt, $data);
?>

Questions? Comments? Please leave them below, or feel free to join the Solar mailing-list and IRC channel for more extensive discussion.

Comments

  1. avatar.php?gravatar_id=727fb10cb58c0bb157feb8bad6d0e58a&rating=PG&size=80&default=http%3A%2F%2Fsolarphp.com%2Fpublic%2FSolarWeb%2Fimages%2Fanon.png
    Thomas Hurst on Sat, 4 Oct 2008 (16:34)

    How does this deal with stale servers, critical queries which need to be run on the master for consistency, transactions, and so forth? Is it just naively sending SELECTS to a random slave and that's it?

    What if the user's just triggered an INSERT, and gets redirected back to show their changes, but the slave hasn't replicated that INSERT yet? It'll just mysteriously disappear.

  2. avatar.php?gravatar_id=7d76e2bb6f8c962a5628093c9f5bc6fb&rating=PG&size=80&default=http%3A%2F%2Fsolarphp.com%2Fpublic%2FSolarWeb%2Fimages%2Fanon.png
    Evert on Mon, 6 Oct 2008 (23:10)

    Was going to write a comment similar to thomas', but it got lengthy so ended up making a post out of it:

    http://www.rooftopsolutions.nl/article/204

  3. avatar.php?gravatar_id=081f2239965a1669621a6be20c2cbca2&rating=PG&size=80&default=http%3A%2F%2Fsolarphp.com%2Fpublic%2FSolarWeb%2Fimages%2Fanon.png
    Paul M. Jones on Tue, 7 Oct 2008 (08:08)

    Hi Thomas, Evert --

    You're both correct, there are times when you need to force the use of the master, not a slave (e.g. the INSERT-then-SELECT situation you both mention).

    When you start a transaction with MysqlReplicated, the adapter stays on the master until the transaction is complete. So with MysqlReplicated, working within a transaction is the way to allow for write-then-read issues. Is that solution perfect and beautiful always and forever? No, but it sure goes a long way. :-)

    The "stale server" issue has not been a problem yet, but it might become one later. I'm considering at least one additional idea to solve that in the adapter. If either of you have code suggestions for how to code that kind of thing, I'd be happy to hear them.

    Thanks for reading, guys.

  4. avatar.php?gravatar_id=972152e611296a66afb7e48334326cf8&rating=PG&size=80&default=http%3A%2F%2Fsolarphp.com%2Fpublic%2FSolarWeb%2Fimages%2Fanon.png
    Federico on Tue, 7 Oct 2008 (15:16)

    Hi Paul,

    I think what you are doing is great and I'm sure it's going to inspire other PHP projects, including the Zend Framework.

    I've been following Simon's discussion about multiple database support:

    http://groups.google.com/group/django-developers/browse_thread/thread/9f0353fe0682b73?pli=1

    Here are the requirements and problems they are trying to solve:

    http://code.djangoproject.com/wiki/MultipleDatabaseSupport

    (There are some interesting links at the bottom of that page)

    In PHP, the only replicated adapter I'm aware of is HyperDB, the one that Wordpress uses.

    http://codex.wordpress.org/HyperDB

  5. avatar.php?gravatar_id=081f2239965a1669621a6be20c2cbca2&rating=PG&size=80&default=http%3A%2F%2Fsolarphp.com%2Fpublic%2FSolarWeb%2Fimages%2Fanon.png
    Paul M. Jones on Tue, 7 Oct 2008 (15:40)

    Federico --

    Thank you. I hope it does help to inspire others, either to build new stuff (and release it) or publish their existing stuff. That'll help share the knowledge around, and everyone will be the better for it.

    Also, thanks for the links, I will read and incorporate what seems to make sense.

    Cheers!

  6. avatar.php?gravatar_id=fb948dd3ef05b7854c7b820cfa142318&rating=PG&size=80&default=http%3A%2F%2Fsolarphp.com%2Fpublic%2FSolarWeb%2Fimages%2Fanon.png
    Mirco on Mon, 22 Dec 2008 (05:27)

    Hi, this is really great and exactly what I searched, before deciding for a Framework.

    But a Possibility for sending Select-Queries to the Master is really missing.

    Greets!

Add A Comment
Your email address will not be disclosed.


Local