MDL-73713 dml: moodle_read_slave_trait default latency to 1 second

In practice it is as if the feature was turned off most of the time.
However, some long processes may benefit from this very safe value
in case admins missed to configure it.
This commit is contained in:
Srdjan 2022-02-02 14:36:52 +10:00
parent 343384d921
commit d8baf29573
3 changed files with 40 additions and 34 deletions

View File

@ -105,8 +105,9 @@ $CFG->dboptions = array(
'latency' => 0.5, // Set read-only slave sync latency in seconds.
// When 'latency' seconds have lapsed after an update to a table
// it is deemed safe to use readonly slave for reading from the table.
// It is optional. If omitted once written to a table it will always
// use master handle for reading.
// It is optional, defaults to 1 second. If you want once written to a table
// to always use master handle for reading set it to something ridiculosly big,
// eg 10.
// Lower values increase the performance, but setting it too low means
// missing the master-slave sync.
'exclude_tables' => [ // Tables to exclude from read-only slave feature.

View File

@ -43,8 +43,8 @@ defined('MOODLE_INTERNAL') || die();
* - It supports multiple 'instance' entries, in case one is not accessible,
* but only one (first connectable) instance is used.
* - 'latency' option: master -> slave sync latency in seconds (will probably
* be a fraction of a second). If specified, a table being written to is
* deemed fully synced and suitable for slave read.
* be a fraction of a second). A table being written to is deemed fully synced
* after that period and suitable for slave read. Defaults to 1 sec.
* - 'exclude_tables' option: a list of tables that never go to the slave for
* querying. The feature is meant to be used in emergency only, so the
* readonly feature can still be used in case there is a rogue query that
@ -54,8 +54,8 @@ defined('MOODLE_INTERNAL') || die();
*
* Choice of the database handle is based on following:
* - SQL_QUERY_INSERT, UPDATE and STRUCTURE record table from the query
* in the $written array and microtime() the event if the 'latency' option
* is set. For those queries master write handle is used.
* in the $written array and microtime() the event. For those queries master
* write handle is used.
* - SQL_QUERY_AUX queries will always use the master write handle because they
* are used for transactionstart/end, locking etc. In that respect, query_start() and
* query_end() *must not* be used during the connection phase.
@ -63,11 +63,9 @@ defined('MOODLE_INTERNAL') || die();
* -- any of the tables involved is a temp table
* -- any of the tables involved is listed in the 'exclude_tables' option
* -- any of the tables involved is in the $written array:
* * If the 'latency' option is set then the microtime() is compared to
* the write microrime, and if more then latency time has passed the slave
* handle is used.
* * Otherwise (not enough time passed or 'latency' option not set)
* we choose the master write handle
* * current microtime() is compared to the write microrime, and if more than
* latency time has passed the slave handle is used
* * otherwise (not enough time passed) we choose the master write handle
* If none of the above conditions are met the slave instance is used.
*
* A 'latency' example:
@ -92,7 +90,7 @@ trait moodle_read_slave_trait {
private $wantreadslave = false;
private $readsslave = 0;
private $slavelatency = 0;
private $slavelatency = 1;
private $written = []; // Track tables being written to.
private $readexclude = []; // Tables to exclude from using dbhreadonly.
@ -324,31 +322,27 @@ trait moodle_read_slave_trait {
}
if (isset($this->written[$tablename])) {
if ($this->slavelatency) {
$now = $now ?: microtime(true);
// Paranoid check.
if ($this->written[$tablename] === true) {
debugging(
"$tablename last written set to true outside transaction - should not happen!",
DEBUG_DEVELOPER
);
$this->written[$tablename] = $now;
return false;
}
if ($now - $this->written[$tablename] < $this->slavelatency) {
return false;
}
unset($this->written[$tablename]);
} else {
$now = $now ?: microtime(true);
// Paranoid check.
if ($this->written[$tablename] === true) {
debugging(
"$tablename last written set to true outside transaction - should not happen!",
DEBUG_DEVELOPER
);
$this->written[$tablename] = $now;
return false;
}
if ($now - $this->written[$tablename] < $this->slavelatency) {
return false;
}
unset($this->written[$tablename]);
}
}
return true;
case SQL_QUERY_INSERT:
case SQL_QUERY_UPDATE:
$now = $this->slavelatency && !$this->transactions ? microtime(true) : true;
$now = $this->transactions ? true : microtime(true);
foreach ($this->table_names($sql) as $tablename) {
$this->written[$tablename] = $now;
}
@ -372,7 +366,7 @@ trait moodle_read_slave_trait {
* @throws dml_transaction_exception Creates and throws transaction related exceptions.
*/
public function commit_delegated_transaction(moodle_transaction $transaction) {
if ($this->slavelatency && $this->written) {
if ($this->written) {
// Adjust the written time.
$now = microtime(true);
foreach ($this->written as $tablename => $when) {

View File

@ -44,6 +44,8 @@ class dml_read_slave_test extends \base_testcase {
/** @var float */
static private $dbreadonlylatency = 0.8;
/** @var float */
static private $defaultlatency = 1;
/**
* Instantiates a test database interface object.
@ -227,18 +229,27 @@ class dml_read_slave_test extends \base_testcase {
$this->assertEquals('test_rw::test:test', $handle);
$this->assertEquals(0, $DB->perf_get_reads_slave());
sleep(1);
$handle = $DB->get_records('table');
$this->assertEquals('test_rw::test:test', $handle);
$this->assertEquals(0, $DB->perf_get_reads_slave());
$handle = $DB->get_records('table2');
$handle = $DB->get_records_sql("SELECT * FROM {table2} JOIN {table}");
$this->assertEquals('test_rw::test:test', $handle);
$this->assertEquals(0, $DB->perf_get_reads_slave());
sleep(1);
$handle = $DB->get_records('table');
$this->assert_readonly_handle($handle);
$this->assertEquals(1, $DB->perf_get_reads_slave());
$handle = $DB->get_records('table2');
$this->assert_readonly_handle($handle);
$this->assertEquals(2, $DB->perf_get_reads_slave());
$handle = $DB->get_records_sql("SELECT * FROM {table2} JOIN {table}");
$this->assertEquals('test_rw::test:test', $handle);
$this->assertEquals(1, $DB->perf_get_reads_slave());
$this->assert_readonly_handle($handle);
$this->assertEquals(3, $DB->perf_get_reads_slave());
}
/**