Built in caching for primary key queries

Selects done against a primary key will automatically cache to Memcached (haven't tried it, but it should fail gracefully) indexed by the model name and the primary key ([NAMESPACE-]MODEL-PKEY). Any updates or deletes against the same primary key will purge the cache automatically. The major caveat here is the case of mass updates which would result in stale data. As it stands the data is being cached for a mere 5 minutes, so this multiple row update scenario would be short lived but ideally, I'll be pushing back the time to live on the cache and/or making it something that's configurable. If you have to do mass updates, you're probably doing them with a cronjob and should just be flushing all of the cache in that scenario (as it would be nearly impossible to detect the affected keys and purge them all).
This commit is contained in:
Josh Sherman 2012-10-23 23:30:04 -04:00
parent dc0d98906f
commit 99aa78b6fa
3 changed files with 110 additions and 84 deletions

View file

@ -24,7 +24,9 @@
* don't entirely remember specifics, but the reason for not using Memcached() * don't entirely remember specifics, but the reason for not using Memcached()
* was due to an unexplainable bug in the version in the repository for Ubuntu * was due to an unexplainable bug in the version in the repository for Ubuntu
* 10.04 LTS. Memcached() does support more of the memcached protocol and will * 10.04 LTS. Memcached() does support more of the memcached protocol and will
* eventually be what PICKLES uses. * eventually be what PICKLES uses. Keys are forced to be uppercase for
* consistencies sake as I've been burned by the case sensitivity due to typos
* in my code.
* *
* Requires php5-memcache * Requires php5-memcache
* *
@ -155,7 +157,7 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
return $this->connection->get($this->namespace . $key); return $this->connection->get(strtoupper($this->namespace . $key));
} }
return false; return false;
@ -177,9 +179,11 @@ class Cache extends Object
*/ */
public function set($key, $value, $expire = 300) public function set($key, $value, $expire = 300)
{ {
$key = strtoupper($key);
if ($this->open()) if ($this->open())
{ {
return $this->connection->set($this->namespace . $key, $value, 0, $expire); return $this->connection->set(strtoupper($this->namespace . $key), $value, 0, $expire);
} }
return false; return false;
@ -197,7 +201,7 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
return $this->connection->delete($this->namespace . $key); return $this->connection->delete(strtoupper($this->namespace . $key));
} }
return false; return false;
@ -216,7 +220,7 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
return $this->connection->increment($this->namespace . $key); return $this->connection->increment(strtoupper($this->namespace . $key));
} }
return false; return false;

View file

@ -26,6 +26,14 @@ class Model extends Object
{ {
// {{{ Properties // {{{ Properties
/**
* Model Name
*
* @access private
* @var string
*/
private $model = null;
/** /**
* Database Object * Database Object
* *
@ -76,14 +84,6 @@ class Model extends Object
*/ */
private $input_parameters = array(); private $input_parameters = array();
/**
* Datasource
*
* @access protected
* @var string
*/
protected $datasource;
/** /**
* Insert Priority * Insert Priority
* *
@ -298,13 +298,16 @@ class Model extends Object
// Runs the parent constructor so we have the config // Runs the parent constructor so we have the config
parent::__construct(); parent::__construct();
// Gets an instance of the cache and database // Gets an instance of the database and check which it is
// @todo Datasource has no way of being set $this->db = Database::getInstance();
$this->db = Database::getInstance($this->datasource != '' ? $this->datasource : null);
$this->caching = $this->db->cache;
$this->mysql = ($this->db->driver == 'pdo_mysql'); $this->mysql = ($this->db->driver == 'pdo_mysql');
$this->postgresql = ($this->db->driver == 'pdo_pgsql'); $this->postgresql = ($this->db->driver == 'pdo_pgsql');
// Sets up the cache object and grabs the class name to use in our cache keys
$this->cache = Cache::getInstance();
$this->model = get_class($this);
// Default column mapping
$columns = array( $columns = array(
'id' => 'id', 'id' => 'id',
'created_at' => 'created_at', 'created_at' => 'created_at',
@ -342,12 +345,7 @@ class Model extends Object
} }
} }
$this->db->columns = $columns; $this->columns = $columns;
if ($this->caching)
{
$this->cache = Cache::getInstance();
}
// Takes a snapshot of the [non-object] object properties // Takes a snapshot of the [non-object] object properties
foreach ($this as $variable => $value) foreach ($this as $variable => $value)
@ -409,6 +407,7 @@ class Model extends Object
} }
elseif (ctype_digit((string)$type_or_parameters)) elseif (ctype_digit((string)$type_or_parameters))
{ {
$cache_key = $this->model . '-' . $type_or_parameters;
$parameters = array($this->columns['id'] => $type_or_parameters); $parameters = array($this->columns['id'] => $type_or_parameters);
if ($this->columns['is_deleted']) if ($this->columns['is_deleted'])
@ -417,10 +416,10 @@ class Model extends Object
} }
$this->loadParameters($parameters); $this->loadParameters($parameters);
$cache_key = 'PICKLES-' . $this->datasource . '-' . $this->table . '-' . $type_or_parameters;
} }
elseif (ctype_digit((string)$parameters)) elseif (ctype_digit((string)$parameters))
{ {
$cache_key = $this->model . '-' . $parameters;
$parameters = array($this->columns['id'] => $parameters); $parameters = array($this->columns['id'] => $parameters);
if ($this->columns['is_deleted']) if ($this->columns['is_deleted'])
@ -462,7 +461,7 @@ class Model extends Object
if (isset($cache_key)) if (isset($cache_key))
{ {
//$cached = $this->cache->get($cache_key); $cached = $this->cache->get($cache_key);
} }
if (isset($cached) && $cached) if (isset($cached) && $cached)
@ -471,11 +470,14 @@ class Model extends Object
} }
else else
{ {
$this->records = $this->db->fetch(implode(' ', $this->sql), (count($this->input_parameters) == 0 ? null : $this->input_parameters)); $this->records = $this->db->fetch(
implode(' ', $this->sql),
(count($this->input_parameters) == 0 ? null : $this->input_parameters)
);
if (isset($cache_key)) if (isset($cache_key))
{ {
//$this->cache->set($cache_key, $this->records); $this->cache->set($cache_key, $this->records);
} }
} }
@ -600,7 +602,7 @@ class Model extends Object
{ {
$format = (stripos($columns, 'USE ') === false); $format = (stripos($columns, 'USE ') === false);
$this->sql[] = ($format == true ? 'USE INDEX (' : '') . $columns . ($format == true ? ')' : ''); $this->sql[] = ($format ? 'USE INDEX (' : '') . $columns . ($format ? ')' : '');
} }
} }
} }
@ -608,7 +610,7 @@ class Model extends Object
{ {
$format = (stripos($this->hints, 'USE ') === false); $format = (stripos($this->hints, 'USE ') === false);
$this->sql[] = ($format == true ? 'USE INDEX (' : '') . $this->hints . ($format == true ? ')' : ''); $this->sql[] = ($format ? 'USE INDEX (' : '') . $this->hints . ($format ? ')' : '');
} }
} }
@ -1063,7 +1065,7 @@ class Model extends Object
} }
else else
{ {
if ($update === true) if ($update == true)
{ {
$sql = 'UPDATE'; $sql = 'UPDATE';
} }
@ -1092,13 +1094,13 @@ class Model extends Object
$sql .= ' INTO'; $sql .= ' INTO';
} }
$sql .= ' ' . $this->table . ($update === true ? ' SET ' : ' '); $sql .= ' ' . $this->table . ($update ? ' SET ' : ' ');
} }
$input_parameters = null; $input_parameters = null;
// Limits the columns being updated // Limits the columns being updated
$record = ($update === true ? array_diff_assoc($this->record, isset($this->original[$this->index]) ? $this->original[$this->index] : array()) : $this->record); $record = ($update ? array_diff_assoc($this->record, isset($this->original[$this->index]) ? $this->original[$this->index] : array()) : $this->record);
// Makes sure there's something to INSERT or UPDATE // Makes sure there's something to INSERT or UPDATE
if (count($record) > 0) if (count($record) > 0)
@ -1110,7 +1112,7 @@ class Model extends Object
{ {
if ($column != $this->columns['id']) if ($column != $this->columns['id'])
{ {
if ($update === true) if ($update == true)
{ {
if ($input_parameters != null) if ($input_parameters != null)
{ {
@ -1129,7 +1131,7 @@ class Model extends Object
} }
// If it's an UPDATE tack on the ID // If it's an UPDATE tack on the ID
if ($update === true) if ($update == true)
{ {
if ($this->columns['updated_at'] != false) if ($this->columns['updated_at'] != false)
{ {
@ -1155,11 +1157,6 @@ class Model extends Object
$sql .= ' WHERE ' . $this->columns['id'] . ' = ?' . ($this->mysql ? ' LIMIT 1' : '') . ';'; $sql .= ' WHERE ' . $this->columns['id'] . ' = ?' . ($this->mysql ? ' LIMIT 1' : '') . ';';
$input_parameters[] = $this->record[$this->columns['id']]; $input_parameters[] = $this->record[$this->columns['id']];
if ($this->caching)
{
//$this->cache->delete('PICKLES-' . $this->datasource . '-' . $this->table . '-' . $this->record[$this->columns['id']]);
}
} }
else else
{ {
@ -1187,7 +1184,7 @@ class Model extends Object
} }
// Executes the query // Executes the query
if ($this->postgresql && $update === false) if ($this->postgresql && $update == false)
{ {
$results = $this->db->fetch($sql, $input_parameters); $results = $this->db->fetch($sql, $input_parameters);
@ -1195,7 +1192,15 @@ class Model extends Object
} }
else else
{ {
return $this->db->execute($sql, $input_parameters); $results = $this->db->execute($sql, $input_parameters);
// Clears the cache
if ($update)
{
$this->cache->delete($this->model . '-' . $this->record[$this->columns['id']]);
}
return $results;
} }
} }
} }
@ -1241,8 +1246,12 @@ class Model extends Object
} }
$input_parameters[] = $this->record[$this->columns['id']]; $input_parameters[] = $this->record[$this->columns['id']];
$results = $this->db->execute($sql, $input_parameters);
return $this->db->execute($sql, $input_parameters); // Clears the cache
$this->cache->delete($this->model . '-' . $this->record[$this->columns['id']]);
return $results;
} }
else else
{ {

97
jar.php
View file

@ -457,7 +457,9 @@ class Browser
* don't entirely remember specifics, but the reason for not using Memcached() * don't entirely remember specifics, but the reason for not using Memcached()
* was due to an unexplainable bug in the version in the repository for Ubuntu * was due to an unexplainable bug in the version in the repository for Ubuntu
* 10.04 LTS. Memcached() does support more of the memcached protocol and will * 10.04 LTS. Memcached() does support more of the memcached protocol and will
* eventually be what PICKLES uses. * eventually be what PICKLES uses. Keys are forced to be uppercase for
* consistencies sake as I've been burned by the case sensitivity due to typos
* in my code.
* *
* Requires php5-memcache * Requires php5-memcache
* *
@ -588,7 +590,7 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
return $this->connection->get($this->namespace . $key); return $this->connection->get(strtoupper($this->namespace . $key));
} }
return false; return false;
@ -610,9 +612,11 @@ class Cache extends Object
*/ */
public function set($key, $value, $expire = 300) public function set($key, $value, $expire = 300)
{ {
$key = strtoupper($key);
if ($this->open()) if ($this->open())
{ {
return $this->connection->set($this->namespace . $key, $value, 0, $expire); return $this->connection->set(strtoupper($this->namespace . $key), $value, 0, $expire);
} }
return false; return false;
@ -630,7 +634,7 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
return $this->connection->delete($this->namespace . $key); return $this->connection->delete(strtoupper($this->namespace . $key));
} }
return false; return false;
@ -649,7 +653,7 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
return $this->connection->increment($this->namespace . $key); return $this->connection->increment(strtoupper($this->namespace . $key));
} }
return false; return false;
@ -4036,6 +4040,14 @@ class Model extends Object
{ {
// {{{ Properties // {{{ Properties
/**
* Model Name
*
* @access private
* @var string
*/
private $model = null;
/** /**
* Database Object * Database Object
* *
@ -4086,14 +4098,6 @@ class Model extends Object
*/ */
private $input_parameters = array(); private $input_parameters = array();
/**
* Datasource
*
* @access protected
* @var string
*/
protected $datasource;
/** /**
* Insert Priority * Insert Priority
* *
@ -4308,13 +4312,16 @@ class Model extends Object
// Runs the parent constructor so we have the config // Runs the parent constructor so we have the config
parent::__construct(); parent::__construct();
// Gets an instance of the cache and database // Gets an instance of the database and check which it is
// @todo Datasource has no way of being set $this->db = Database::getInstance();
$this->db = Database::getInstance($this->datasource != '' ? $this->datasource : null);
$this->caching = $this->db->cache;
$this->mysql = ($this->db->driver == 'pdo_mysql'); $this->mysql = ($this->db->driver == 'pdo_mysql');
$this->postgresql = ($this->db->driver == 'pdo_pgsql'); $this->postgresql = ($this->db->driver == 'pdo_pgsql');
// Sets up the cache object and grabs the class name to use in our cache keys
$this->cache = Cache::getInstance();
$this->model = get_class($this);
// Default column mapping
$columns = array( $columns = array(
'id' => 'id', 'id' => 'id',
'created_at' => 'created_at', 'created_at' => 'created_at',
@ -4352,12 +4359,7 @@ class Model extends Object
} }
} }
$this->db->columns = $columns; $this->columns = $columns;
if ($this->caching)
{
$this->cache = Cache::getInstance();
}
// Takes a snapshot of the [non-object] object properties // Takes a snapshot of the [non-object] object properties
foreach ($this as $variable => $value) foreach ($this as $variable => $value)
@ -4419,6 +4421,7 @@ class Model extends Object
} }
elseif (ctype_digit((string)$type_or_parameters)) elseif (ctype_digit((string)$type_or_parameters))
{ {
$cache_key = $this->model . '-' . $type_or_parameters;
$parameters = array($this->columns['id'] => $type_or_parameters); $parameters = array($this->columns['id'] => $type_or_parameters);
if ($this->columns['is_deleted']) if ($this->columns['is_deleted'])
@ -4427,10 +4430,10 @@ class Model extends Object
} }
$this->loadParameters($parameters); $this->loadParameters($parameters);
$cache_key = 'PICKLES-' . $this->datasource . '-' . $this->table . '-' . $type_or_parameters;
} }
elseif (ctype_digit((string)$parameters)) elseif (ctype_digit((string)$parameters))
{ {
$cache_key = $this->model . '-' . $parameters;
$parameters = array($this->columns['id'] => $parameters); $parameters = array($this->columns['id'] => $parameters);
if ($this->columns['is_deleted']) if ($this->columns['is_deleted'])
@ -4472,7 +4475,7 @@ class Model extends Object
if (isset($cache_key)) if (isset($cache_key))
{ {
//$cached = $this->cache->get($cache_key); $cached = $this->cache->get($cache_key);
} }
if (isset($cached) && $cached) if (isset($cached) && $cached)
@ -4481,11 +4484,14 @@ class Model extends Object
} }
else else
{ {
$this->records = $this->db->fetch(implode(' ', $this->sql), (count($this->input_parameters) == 0 ? null : $this->input_parameters)); $this->records = $this->db->fetch(
implode(' ', $this->sql),
(count($this->input_parameters) == 0 ? null : $this->input_parameters)
);
if (isset($cache_key)) if (isset($cache_key))
{ {
//$this->cache->set($cache_key, $this->records); $this->cache->set($cache_key, $this->records);
} }
} }
@ -4610,7 +4616,7 @@ class Model extends Object
{ {
$format = (stripos($columns, 'USE ') === false); $format = (stripos($columns, 'USE ') === false);
$this->sql[] = ($format == true ? 'USE INDEX (' : '') . $columns . ($format == true ? ')' : ''); $this->sql[] = ($format ? 'USE INDEX (' : '') . $columns . ($format ? ')' : '');
} }
} }
} }
@ -4618,7 +4624,7 @@ class Model extends Object
{ {
$format = (stripos($this->hints, 'USE ') === false); $format = (stripos($this->hints, 'USE ') === false);
$this->sql[] = ($format == true ? 'USE INDEX (' : '') . $this->hints . ($format == true ? ')' : ''); $this->sql[] = ($format ? 'USE INDEX (' : '') . $this->hints . ($format ? ')' : '');
} }
} }
@ -5073,7 +5079,7 @@ class Model extends Object
} }
else else
{ {
if ($update === true) if ($update == true)
{ {
$sql = 'UPDATE'; $sql = 'UPDATE';
} }
@ -5102,13 +5108,13 @@ class Model extends Object
$sql .= ' INTO'; $sql .= ' INTO';
} }
$sql .= ' ' . $this->table . ($update === true ? ' SET ' : ' '); $sql .= ' ' . $this->table . ($update ? ' SET ' : ' ');
} }
$input_parameters = null; $input_parameters = null;
// Limits the columns being updated // Limits the columns being updated
$record = ($update === true ? array_diff_assoc($this->record, isset($this->original[$this->index]) ? $this->original[$this->index] : array()) : $this->record); $record = ($update ? array_diff_assoc($this->record, isset($this->original[$this->index]) ? $this->original[$this->index] : array()) : $this->record);
// Makes sure there's something to INSERT or UPDATE // Makes sure there's something to INSERT or UPDATE
if (count($record) > 0) if (count($record) > 0)
@ -5120,7 +5126,7 @@ class Model extends Object
{ {
if ($column != $this->columns['id']) if ($column != $this->columns['id'])
{ {
if ($update === true) if ($update == true)
{ {
if ($input_parameters != null) if ($input_parameters != null)
{ {
@ -5139,7 +5145,7 @@ class Model extends Object
} }
// If it's an UPDATE tack on the ID // If it's an UPDATE tack on the ID
if ($update === true) if ($update == true)
{ {
if ($this->columns['updated_at'] != false) if ($this->columns['updated_at'] != false)
{ {
@ -5165,11 +5171,6 @@ class Model extends Object
$sql .= ' WHERE ' . $this->columns['id'] . ' = ?' . ($this->mysql ? ' LIMIT 1' : '') . ';'; $sql .= ' WHERE ' . $this->columns['id'] . ' = ?' . ($this->mysql ? ' LIMIT 1' : '') . ';';
$input_parameters[] = $this->record[$this->columns['id']]; $input_parameters[] = $this->record[$this->columns['id']];
if ($this->caching)
{
//$this->cache->delete('PICKLES-' . $this->datasource . '-' . $this->table . '-' . $this->record[$this->columns['id']]);
}
} }
else else
{ {
@ -5197,7 +5198,7 @@ class Model extends Object
} }
// Executes the query // Executes the query
if ($this->postgresql && $update === false) if ($this->postgresql && $update == false)
{ {
$results = $this->db->fetch($sql, $input_parameters); $results = $this->db->fetch($sql, $input_parameters);
@ -5205,7 +5206,15 @@ class Model extends Object
} }
else else
{ {
return $this->db->execute($sql, $input_parameters); $results = $this->db->execute($sql, $input_parameters);
// Clears the cache
if ($update)
{
$this->cache->delete($this->model . '-' . $this->record[$this->columns['id']]);
}
return $results;
} }
} }
} }
@ -5251,8 +5260,12 @@ class Model extends Object
} }
$input_parameters[] = $this->record[$this->columns['id']]; $input_parameters[] = $this->record[$this->columns['id']];
$results = $this->db->execute($sql, $input_parameters);
return $this->db->execute($sql, $input_parameters); // Clears the cache
$this->cache->delete($this->model . '-' . $this->record[$this->columns['id']]);
return $results;
} }
else else
{ {