dmllib: introducing the rcache_* functions for record cache handling

Abstracted Sam's initial work with $record_cache into a series of functions
that abstract things _just enough_ that we can use an internal
in-memory-array implementation or something that is shared across
processes, like memcached or the turckmmcache/eaccelerator caches.

Also
- added hit/miss stats tracking and reporting
- removed max entries limiting as it was buggy - var names mismatches
  and not counting unset()s
This commit is contained in:
martinlanghoff 2006-12-27 22:39:32 +00:00
parent adb61bc203
commit 6c2f585f85
2 changed files with 160 additions and 54 deletions

View File

@ -39,8 +39,11 @@
$empty_rs_cache = array(); // Keeps copies of the recordsets used in one invocation
$metadata_cache = array(); // Keeps copies of the MetaColumns() for each table used in one invocations
$record_cache = array(); // Keeps copies of all simple get_record results from one invocation
$record_cache_size = 0; // Count of get_record results stored in this invocation
$rcache = new StdClass; // Cache simple get_record results
$rcache->data = array();
$rcache->hits = 0;
$rcache->misses = 0;
/// FUNCTIONS FOR DATABASE HANDLING ////////////////////////////////
@ -379,15 +382,16 @@ function count_records_sql($sql) {
*/
function get_record($table, $field1, $value1, $field2='', $value2='', $field3='', $value3='', $fields='*') {
global $CFG, $record_cache, $record_cache_count;
global $CFG;
// Check to see whether this record is eligible for caching (fields=*, only condition is id)
$docache = false;
if (!empty($CFG->enablerecordcache) && $field1=='id' && !$field2 && !$field3 && $fields=='*') {
$docache = true;
// If it's in the cache, return it
if (!empty($record_cache[$table][$value1])) {
return $record_cache[$table][$value1];
$cached = rcache_get($table, $value1);
if (!empty($cached)) {
return $cached;
}
}
@ -395,13 +399,10 @@ function get_record($table, $field1, $value1, $field2='', $value2='', $field3=''
$record = get_record_sql('SELECT '.$fields.' FROM '. $CFG->prefix . $table .' '. $select);
// If we're caching records, store this one (supposing we got something - we don't cache failures)
if ($record && $docache && $record_cache_count<$CFG->enablerecordcache) {
$record_cache[$table][$value1] = $record;
// We only cache records up to a limit. This is to prevent memory usage becoming
// unreasonably high for pages which do things like, load every student record in
// a course, or some such.
$record_cache_count++;
// If we're caching records, store this one
// (supposing we got something - we don't cache failures)
if ($record && $docache) {
rcache_set($table, $value1, $record);
}
return $record;
@ -1036,26 +1037,19 @@ function get_fieldset_sql($sql) {
*/
function set_field($table, $newfield, $newvalue, $field1, $value1, $field2='', $value2='', $field3='', $value3='') {
global $CFG, $record_cache;
global $CFG;
// Clear record_cache based on the parameters passed (individual record or whole table)
// Clear record_cache based on the parameters passed
// (individual record or whole table)
if (!empty($CFG->enablerecordcache)) {
if ($field1 == 'id') {
if (isset($record_cache[$table][$value1])) {
unset($record_cache[$table][$value1]);
}
rcache_unset($table, $value1);
} else if ($field2 == 'id') {
if (isset($record_cache[$table][$value2])) {
unset($record_cache[$table][$value2]);
}
rcache_unset($table, $value1);
} else if ($field3 == 'id') {
if (isset($record_cache[$table][$value3])) {
unset($record_cache[$table][$value3]);
}
rcache_unset($table, $value1);
} else {
if (isset($record_cache[$table])) {
unset($record_cache[$table]);
}
rcache_unset_table($table);
}
}
@ -1078,7 +1072,7 @@ function set_field($table, $newfield, $newvalue, $field1, $value1, $field2='', $
*/
function set_field_select($table, $newfield, $newvalue, $select, $localcall = false) {
global $db, $CFG, $record_cache;
global $db, $CFG;
if (defined('MDL_PERFDB')) { global $PERF ; $PERF->dbqueries++; };
@ -1087,11 +1081,10 @@ function set_field_select($table, $newfield, $newvalue, $select, $localcall = fa
$select = 'WHERE ' . $select;
}
// Clear record_cache based on the parameters passed (individual record or whole table)
// Clear record_cache based on the parameters passed
// (individual record or whole table)
if (!empty($CFG->enablerecordcache)) {
if (isset($record_cache[$table])) {
unset($record_cache[$table]);
}
rcache_unset_table($table);
}
}
@ -1147,26 +1140,19 @@ function set_field_select($table, $newfield, $newvalue, $select, $localcall = fa
*/
function delete_records($table, $field1='', $value1='', $field2='', $value2='', $field3='', $value3='') {
global $db, $CFG, $record_cache;
global $db, $CFG;
// Clear record_cache based on the parameters passed (individual record or whole table)
// Clear record_cache based on the parameters passed
// (individual record or whole table)
if (!empty($CFG->enablerecordcache)) {
if ($field1 == 'id') {
if (isset($record_cache[$table][$value1])) {
unset($record_cache[$table][$value1]);
}
rcache_unset($table, $value1);
} else if ($field2 == 'id') {
if (isset($record_cache[$table][$value2])) {
unset($record_cache[$table][$value2]);
}
rcache_unset($table, $value2);
} else if ($field3 == 'id') {
if (isset($record_cache[$table][$value3])) {
unset($record_cache[$table][$value3]);
}
rcache_unset($table, $value3);
} else {
if (isset($record_cache[$table])) {
unset($record_cache[$table]);
}
rcache_unset_table($table);
}
}
@ -1189,11 +1175,11 @@ function delete_records($table, $field1='', $value1='', $field2='', $value2='',
*/
function delete_records_select($table, $select='') {
global $CFG, $db, $record_cache;
global $CFG, $db;
// Clear record_cache (whole table)
if (!empty($CFG->enablerecordcache) && isset($record_cache[$table])) {
unset($record_cache[$table]);
if (!empty($CFG->enablerecordcache)) {
rcache_unset_table($table);
}
if (defined('MDL_PERFDB')) { global $PERF ; $PERF->dbqueries++; };
@ -1401,15 +1387,15 @@ function insert_record($table, $dataobject, $returnid=true, $primarykey='id') {
*/
function update_record($table, $dataobject) {
global $db, $CFG, $record_cache;
global $db, $CFG;
if (! isset($dataobject->id) ) {
return false;
}
// Remove this record from record cache since it will change
if (!empty($CFG->enablerecordcache) && isset($record_cache[$table][$dataobject->id])) {
unset($record_cache[$table][$dataobject->id]);
if (!empty($CFG->enablerecordcache)) {
rcache_unset($table, $dataobject->id);
}
/// Temporary hack as part of phasing out all access to obsolete user tables XXX
@ -2079,4 +2065,117 @@ function db_update_lobs ($table, $sqlcondition, &$clobs, &$blobs) {
return $status;
}
function rcache_set($table, $id, $rec) {
global $CFG, $rcache;
$rcache->data[$table][$id] = $rec;
return true;
}
function rcache_unset($table, $id) {
global $CFG, $rcache;
if (isset($rcache->data[$table][$id])) {
unset($rcache->data[$table][$id]);
}
return true;
}
/**
* Get cached record if available. ONLY use if you
* are trying to get the cached record and will NOT
* fetch it yourself if not cached.
*
* Use rcache_getforfill() if you are going to fetch
* the record if not cached...
*
* This function is private and must not be used outside dmllib at all
*
* @param $table string
* @param $id integer
* @return mixed object-like record on cache hit, false otherwise
*/
function rcache_get($table, $id) {
global $CFG, $rcache;
if (isset($rcache->data[$table][$id])) {
$rcache->hits++;
return $rcache->data[$table][$id];
} else {
$rcache->misses++;
return false;
}
}
/**
* In the simple case, this function will
* get cached record if available. If the record
* is not available, it will try to get an exclusive
* lock that announces that this process will fetch
* the record and populate the cache.
*
* If we fail to get the lock -- this means another
* process is going to fetch the rec and fill the cache
* so we wait (block) for a few microseconds while we wait for
* the cache to be filled or the lock to timeout.
*
* If you get a false from this call, you _must_ fetch the
* rec from DB and populate the cache ASAP or indicate that
* you won't by calling rcache_releaseforfill().
*
* This technique forces serialisation and so helps deal
* with thundering herd scenarios where a lot of clients
* ask the for the same idempotent (and costly) operation.
* The implementation is based on suggestions in this message
* http://marc.theaimsgroup.com/?l=git&m=116562052506776&w=2
*
* This function is private and must not be used outside dmllib at all
*
* @param $table string
* @param $id integer
* @return mixed object-like record on cache hit, false otherwise
*/
function rcache_getforfill($table, $id) {
global $CFG, $rcache;
return rcache_get($table, $id);
}
/**
* Release the exclusive lock obtained by
* rcache_getforfill(). See rcache_getforfill()
* for more details.
*
* This function is private and must not be used outside dmllib at all
*
* @param $table string
* @param $id integer
* @return bool
*/
function rcache_releaseforfill($table, $id) {
global $CFG, $rcache;
return true;
}
/**
* Remove or invalidate all rcache entries related to
* a table. Not all caching mechanisms cluster entries
* by table so in those cases we use alternative strategies.
*
* This function is private and must not be used outside dmllib at all
*
* @param $table string the table to invalidate records for
* @return bool
*/
function rcache_unset_table ($table) {
global $CFG, $rcache;
if (isset($rcache->data[$table])) {
unset($rcache->data[$table]);
}
return true;
}
?>

View File

@ -6466,7 +6466,7 @@ function array_is_nested($array) {
***
**/
function get_performance_info() {
global $CFG, $PERF;
global $CFG, $PERF, $rcache;
$info = array();
$info['html'] = ''; // holds userfriendly HTML representation
@ -6529,10 +6529,17 @@ function get_performance_info() {
if (!empty($server_load)) {
$info['serverload'] = $server_load;
$info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
$info['txt'] .= 'serverload: '.$info['serverload'];
$info['txt'] .= "serverload: {$info['serverload']} ";
}
if (isset($rcache->hits) && isset($rcache->misses)) {
$info['rcachehits'] = $rcache->hits;
$info['rcachemisses'] = $rcache->misses;
$info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
($rcache->hits / $rcache->misses) . "({$rcache->hits}/{$rcache->misses})</span> ";
$info['txt'] .= 'rcache: '. ($rcache->hits / $rcache->misses) .
"({$rcache->hits}/{$rcache->misses}) ";
}
$info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
return $info;
}