Add the apcu caching ability for performance (#7276)

* add the apcu caching ability for performance
When the PHP extension APCu is loaded, the settings class and the auto_loader will cache their results across requests in RAM. For more information about the APCu extension visit the PHP page: https://www.php.net/apcu

* use global instead of default terminology
This commit is contained in:
frytimo 2025-02-25 20:21:41 -04:00 committed by GitHub
parent 305f585b17
commit d919a3cc1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 171 additions and 37 deletions

View File

@ -331,4 +331,3 @@
require_once "resources/footer.php";
?>

View File

@ -46,7 +46,7 @@ $search = $_REQUEST['search'] ?? '';
$domain_uuid = $_GET['id'] ?? null;
//reload default settings
require "resources/classes/domains.php";
settings::clear_cache();
$domain = new domains();
$domain->set();

View File

@ -307,6 +307,9 @@
}
}
//clear domain apcu cache due to changes
settings::clear_cache('domain');
//redirect the browser
if ($action == "update") {
message::add($text['message-update']);

View File

@ -298,6 +298,9 @@ if (!empty($_POST) && empty($_POST["persistformvar"])) {
}
}
//clear the user settings cache
settings::clear_cache('user');
//redirect the browser
if ($action == "update") {
message::add($text['message-update']);

View File

@ -27,10 +27,21 @@
class auto_loader {
const FILE = 'autoloader_cache.php';
const CACHE_KEY = 'autoloader_classes';
private $classes;
/**
* Tracks the APCu extension for caching to RAM drive across requests
* @var bool
*/
private $apcu_enabled;
public function __construct($project_path = '') {
//set if we can use RAM cache
$this->apcu_enabled = function_exists('apcu_enabled') && apcu_enabled();
//classes must be loaded before this object is registered
if (!$this->load_cache()) {
//cache miss so load them
@ -43,34 +54,49 @@ class auto_loader {
}
public function update_cache(string $file = ''): bool {
//guard against writing an empty file
if (!empty($this->classes)) {
return false;
}
//update RAM cache when available
if ($this->apcu_enabled) {
apcu_store(self::CACHE_KEY, $this->classes);
}
//ensure we have somewhere to put the file
if (empty($file)) {
$file = sys_get_temp_dir() . '/' . self::FILE;
}
//guard against writing an empty file
if (!empty($this->classes)) {
//export the classes array using PHP engine
$data = var_export($this->classes, true);
//export the classes array using PHP engine
$data = var_export($this->classes, true);
//put the array in a form that it can be loaded directly to an array
$result = file_put_contents($file, "<?php\n return " . $data . ";\n");
if ($result !== false) {
return true;
}
$error_array = error_get_last();
//send to syslog when debugging
if (!empty($_REQUEST['debug']) && $_REQUEST['debug'] == 'true') {
openlog("PHP", LOG_PID | LOG_PERROR, LOG_LOCAL0);
syslog(LOG_WARNING, "[php][auto_loader] " . $error_array['message']);
closelog();
}
//put the array in a form that it can be loaded directly to an array
$result = file_put_contents($file, "<?php\n return " . $data . ";\n");
if ($result !== false) {
return true;
}
$error_array = error_get_last();
//send to syslog when debugging
if (!empty($_REQUEST['debug']) && $_REQUEST['debug'] == 'true') {
openlog("PHP", LOG_PID | LOG_PERROR, LOG_LOCAL0);
syslog(LOG_WARNING, "[php][auto_loader] " . $error_array['message']);
closelog();
}
return false;
}
public function load_cache(string $file = ''): bool {
$this->classes = [];
//use apcu when available
if ($this->apcu_enabled && apcu_exists(self::CACHE_KEY)) {
$this->classes = apcu_fetch(self::CACHE_KEY, $success);
if ($success) return true;
}
//use a standard file
if (empty($file)) {
$file = sys_get_temp_dir() . '/'. self::FILE;
@ -81,6 +107,10 @@ class auto_loader {
}
//assign to an array
if (!empty($this->classes)) {
//cache edge case of first time using apcu cache
if ($this->apcu_enabled) {
apcu_store(self::CACHE_KEY, $this->classes);
}
return true;
}
return false;
@ -125,6 +155,11 @@ class auto_loader {
return true;
}
//Smarty has it's own autoloader so reject the request
if ($class_name === 'Smarty_Autoloader') {
return false;
}
//cache miss
if (!empty($_REQUEST['debug']) && $_REQUEST['debug'] == 'true') {
openlog("PHP", LOG_PID | LOG_PERROR, LOG_LOCAL0);
@ -186,4 +221,10 @@ class auto_loader {
}
return '';
}
public static function clear_cache() {
if (function_exists('apcu_enabled') && apcu_enabled()) {
apcu_delete(self::CACHE_KEY);
}
}
}

View File

@ -187,6 +187,17 @@ class cache {
closelog();
}
//check for apcu extension
if (function_exists('apcu_enabled') && apcu_enabled()) {
//flush everything
apcu_clear_cache();
}
//remove the autoloader file cache
if (file_exists(sys_get_temp_dir() . '/' . auto_loader::FILE)) {
@unlink(sys_get_temp_dir() . '/' . auto_loader::FILE);
}
//cache method memcache
if ($this->method === "memcache") {
// connect to event socket

View File

@ -53,6 +53,12 @@ class settings {
*/
private $database;
/**
* Tracks if the APCu extension is loaded for database queries
* @var bool
*/
private $apcu_enabled;
/**
* Create a settings object using key/value pairs in the $setting_array.
*
@ -63,6 +69,16 @@ class settings {
*/
public function __construct($setting_array = []) {
//try to use RAM cache by default
if (!isset($setting_array['allow_caching'])) {
$setting_array['allow_caching'] = true;
}
//track ram caching
if ($setting_array['allow_caching']) {
$this->apcu_enabled = function_exists('apcu_enabled') && apcu_enabled();
}
//open a database connection
if (empty($setting_array['database'])) {
$this->database = database::new();
@ -155,7 +171,7 @@ class settings {
* @param bool $enabled (optional) True or False. Default is True.
* @param string $description (optional) Description. Default is empty string.
*/
public function set(string $table_prefix, string $uuid, string $category, string $subcategory, string $type = 'text', string $value = "", bool $enabled = true, string $description = "") {
public function set(string $table_prefix, string $uuid, string $category, string $subcategory, string $value = "", string $type = 'text', bool $enabled = true, string $description = "") {
//set the table name
$table_name = $table_prefix.'_settings';
@ -207,6 +223,15 @@ class settings {
*/
private function default_settings() {
//set the key for global defaults
$key = 'settings_global_' . $this->category;
//if the apcu extension is loaded get the already parsed array
if ($this->apcu_enabled && apcu_exists($key)) {
$this->settings = apcu_fetch($key);
return;
}
//get the default settings
$sql = "select * from v_default_settings ";
$sql .= "where default_setting_enabled = 'true' ";
@ -247,7 +272,11 @@ class settings {
}
}
}
unset($sql, $result, $row);
//if the apcu extension is loaded store the result
if ($this->apcu_enabled) {
apcu_store($key, $this->settings);
}
}
/**
@ -255,13 +284,22 @@ class settings {
* @access private
*/
private function domain_settings() {
$sql = "select * from v_domain_settings ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "and domain_setting_enabled = 'true' ";
$parameters['domain_uuid'] = $this->domain_uuid;
$result = $this->database->select($sql, $parameters, 'all');
unset($sql, $parameters);
$key = 'settings_domain_'.$this->domain_uuid;
$result = '';
//if the apcu extension is loaded get the cached database result
if ($this->apcu_enabled && apcu_exists($key)) {
$result = apcu_fetch($key);
} else {
$sql = "select * from v_domain_settings ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "and domain_setting_enabled = 'true' ";
$parameters['domain_uuid'] = $this->domain_uuid;
$result = $this->database->select($sql, $parameters, 'all');
//if the apcu extension is loaded store the result
if ($this->apcu_enabled) {
apcu_store($key, $result);
}
}
if (!empty($result)) {
//domain setting array types override the default settings set as type array
foreach ($result as $row) {
@ -305,7 +343,6 @@ class settings {
}
}
unset($result, $row);
}
/**
@ -314,15 +351,25 @@ class settings {
* @depends $this->domain_uuid
*/
private function user_settings() {
$sql = "select * from v_user_settings ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "and user_uuid = :user_uuid ";
$sql .= " order by user_setting_order asc ";
$parameters['domain_uuid'] = $this->domain_uuid;
$parameters['user_uuid'] = $this->user_uuid;
$result = $this->database->select($sql, $parameters, 'all');
if (is_array($result)) {
$key = 'settings_user_'.$this->user_uuid;
$result = '';
//if the apcu extension is loaded get the cached database result
if ($this->apcu_enabled && apcu_exists($key)) {
$result = apcu_fetch($key);
} else {
$sql = "select * from v_user_settings ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "and user_uuid = :user_uuid ";
$sql .= " order by user_setting_order asc ";
$parameters['domain_uuid'] = $this->domain_uuid;
$parameters['user_uuid'] = $this->user_uuid;
$result = $this->database->select($sql, $parameters, 'all');
//if the apcu extension is loaded store the result
if ($this->apcu_enabled) {
apcu_store($key, $result);
}
}
if (!empty($result)) {
foreach ($result as $row) {
if ($row['user_setting_enabled'] == 'true') {
$name = $row['user_setting_name'];
@ -406,4 +453,34 @@ class settings {
}
}
}
/**
* Clears the settings cache
* @param string $type Empty clears all settings. Values can be global, domain, or user.
*/
public static function clear_cache(string $type = '') {
if (function_exists('apcu_enabled') && apcu_enabled()) {
switch ($type) {
case 'all':
case '':
default:
$type = "";
break;
case 'global':
case 'domain':
case 'user':
$type .= "_";
break;
}
$cache = apcu_cache_info(false);
if (!empty($cache['cache_list'])) {
foreach ($cache['cache_list'] as $entry) {
$key = $entry['info'];
if (str_starts_with($key, 'settings_' . $type)) {
apcu_delete($key);
}
}
}
}
}
}