From 9fcba75de4b56d2bf2d6ca96a02f36a3ae254e21 Mon Sep 17 00:00:00 2001 From: Srdjan Date: Wed, 24 May 2023 16:59:51 +1000 Subject: [PATCH] MDL-78212 dml: Added MYSQLI_CLIENT_COMPRESS option for mysqli Config dboptions key 'clientcompress'. Decreases traffic between the application server and the database host. --- config-dist.php | 4 + lib/dml/mysqli_native_moodle_database.php | 7 +- .../mysqli_native_moodle_database_test.php | 109 ++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 lib/dml/tests/mysqli_native_moodle_database_test.php diff --git a/config-dist.php b/config-dist.php index 3d09fde245d..ede6566825f 100644 --- a/config-dist.php +++ b/config-dist.php @@ -91,6 +91,10 @@ $CFG->dboptions = array( // set to zero if you are using pg_bouncer in // 'transaction' mode (it is fine in 'session' // mode). + // 'clientcompress' => true // Use compression protocol to communicate with the database server. + // Decreases traffic from the database server. + // Not needed if the databse is on the same host. + // Currently supported only with mysqli driver. /* 'connecttimeout' => null, // Set connect timeout in seconds. Not all drivers support it. 'readonly' => [ // Set to read-only slave details, to get safe reads diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php index 61e979ca810..d2d71e4424e 100644 --- a/lib/dml/mysqli_native_moodle_database.php +++ b/lib/dml/mysqli_native_moodle_database.php @@ -573,11 +573,16 @@ class mysqli_native_moodle_database extends moodle_database { $this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->dboptions['connecttimeout']); } + $flags = 0; + if ($this->dboptions['clientcompress'] ?? false) { + $flags |= MYSQLI_CLIENT_COMPRESS; + } + $conn = null; $dberr = null; try { // real_connect() is doing things we don't expext. - $conn = @$this->mysqli->real_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket); + $conn = @$this->mysqli->real_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket, $flags); } catch (\Exception $e) { $dberr = "$e"; } diff --git a/lib/dml/tests/mysqli_native_moodle_database_test.php b/lib/dml/tests/mysqli_native_moodle_database_test.php new file mode 100644 index 00000000000..4d839bffb2f --- /dev/null +++ b/lib/dml/tests/mysqli_native_moodle_database_test.php @@ -0,0 +1,109 @@ +. + +namespace core; + +use ReflectionClass; +use mysqli; +use moodle_database, mysqli_native_moodle_database; +use moodle_exception; + +/** + * Test specific features of the MySql dml. + * + * @package core + * @category test + * @copyright 2023 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \mysqli_native_moodle_database + */ +class mysqli_native_moodle_database_test extends \advanced_testcase { + + /** + * Set up. + */ + public function setUp(): void { + global $DB; + parent::setUp(); + // Skip tests if not using Postgres. + if (!($DB instanceof mysqli_native_moodle_database)) { + $this->markTestSkipped('MySql-only test'); + } + } + + /** + * SSL connection helper. + * + * @param bool|null $compress + * @return mysqli + * @throws moodle_exception + */ + public function new_connection(?bool $compress = false): mysqli { + global $DB; + + // Open new connection. + $cfg = $DB->export_dbconfig(); + if (!isset($cfg->dboptions)) { + $cfg->dboptions = []; + } + + $cfg->dboptions['clientcompress'] = $compress; + + // Get a separate disposable db connection handle with guaranteed 'readonly' config. + $db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); + $db2->raw_connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); + + $reflector = new ReflectionClass($db2); + $rp = $reflector->getProperty('mysqli'); + $rp->setAccessible(true); + return $rp->getValue($db2); + } + + /** + * Test client compression helper. + * + * @param mysqli $mysqli + * @return array + */ + public function connection_status($mysqli): array { + $mysqli->query("SELECT * FROM INFORMATION_SCHEMA.TABLES"); + + $stats = []; + foreach ($mysqli->query('SHOW SESSION STATUS')->fetch_all(MYSQLI_ASSOC) as $r) { + $stats[$r['Variable_name']] = $r['Value']; + } + return $stats; + } + + /** + * Test client compression. + * + * @return void + */ + public function test_client_compression(): void { + $mysqli = $this->new_connection(); + $stats = $this->connection_status($mysqli); + $this->assertEquals('OFF', $stats['Compression']); + $sent = $stats['Bytes_sent']; + + $mysqlic = $this->new_connection(true); + $stats = $this->connection_status($mysqlic); + $this->assertEquals('ON', $stats['Compression']); + $sentc = $stats['Bytes_sent']; + + $this->assertLessThan($sent, $sentc); + } +}