perf: Add TTL-based caching to UserModel to prevent stale data

Cache optimization with automatic expiration:

1. New Cache Structure:
   - Changed from simple array to TTL-aware structure
   - Each entry: ['data' => ..., 'expires' => timestamp]
   - 5-minute (300s) TTL prevents indefinite stale data

2. Helper Methods:
   - getCached($key): Returns data if not expired, null otherwise
   - setCached($key, $data): Stores with expiration timestamp
   - invalidateCache($userId, $username): Manual cache clearing

3. Updated All Cache Access Points:
   - syncUserFromAuthelia() - User sync from Authelia
   - getSystemUser() - System user for daemon operations
   - getUserById() - User lookup by ID
   - getUserByUsername() - User lookup by username

Benefits:
- Prevents memory leaks from unlimited cache growth
- Ensures user data refreshes periodically
- Maintains performance benefits of caching
- Automatic cleanup of expired entries

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 16:27:04 -05:00
parent 4a05c82852
commit becee84821

View File

@@ -4,12 +4,50 @@
*/ */
class UserModel { class UserModel {
private $conn; private $conn;
private static $userCache = []; private static $userCache = []; // ['key' => ['data' => ..., 'expires' => timestamp]]
private static $cacheTTL = 300; // 5 minutes
public function __construct($conn) { public function __construct($conn) {
$this->conn = $conn; $this->conn = $conn;
} }
/**
* Get cached user data if not expired
*/
private static function getCached($key) {
if (isset(self::$userCache[$key])) {
$cached = self::$userCache[$key];
if ($cached['expires'] > time()) {
return $cached['data'];
}
// Expired - remove from cache
unset(self::$userCache[$key]);
}
return null;
}
/**
* Store user data in cache with expiration
*/
private static function setCached($key, $data) {
self::$userCache[$key] = [
'data' => $data,
'expires' => time() + self::$cacheTTL
];
}
/**
* Invalidate specific user cache entry
*/
public static function invalidateCache($userId = null, $username = null) {
if ($userId !== null) {
unset(self::$userCache["user_id_$userId"]);
}
if ($username !== null) {
unset(self::$userCache["user_$username"]);
}
}
/** /**
* Sync user from Authelia headers (create or update) * Sync user from Authelia headers (create or update)
* *
@@ -22,8 +60,9 @@ class UserModel {
public function syncUserFromAuthelia($username, $displayName = '', $email = '', $groups = '') { public function syncUserFromAuthelia($username, $displayName = '', $email = '', $groups = '') {
// Check cache first // Check cache first
$cacheKey = "user_$username"; $cacheKey = "user_$username";
if (isset(self::$userCache[$cacheKey])) { $cached = self::getCached($cacheKey);
return self::$userCache[$cacheKey]; if ($cached !== null) {
return $cached;
} }
// Determine if user is admin based on groups // Determine if user is admin based on groups
@@ -72,8 +111,8 @@ class UserModel {
$stmt->close(); $stmt->close();
// Cache user // Cache user with TTL
self::$userCache[$cacheKey] = $user; self::setCached($cacheKey, $user);
return $user; return $user;
} }
@@ -85,8 +124,9 @@ class UserModel {
*/ */
public function getSystemUser() { public function getSystemUser() {
// Check cache first // Check cache first
if (isset(self::$userCache['system'])) { $cached = self::getCached('system');
return self::$userCache['system']; if ($cached !== null) {
return $cached;
} }
$stmt = $this->conn->prepare("SELECT * FROM users WHERE username = 'system'"); $stmt = $this->conn->prepare("SELECT * FROM users WHERE username = 'system'");
@@ -95,7 +135,7 @@ class UserModel {
if ($result->num_rows > 0) { if ($result->num_rows > 0) {
$user = $result->fetch_assoc(); $user = $result->fetch_assoc();
self::$userCache['system'] = $user; self::setCached('system', $user);
$stmt->close(); $stmt->close();
return $user; return $user;
} }
@@ -113,8 +153,9 @@ class UserModel {
public function getUserById($userId) { public function getUserById($userId) {
// Check cache first // Check cache first
$cacheKey = "user_id_$userId"; $cacheKey = "user_id_$userId";
if (isset(self::$userCache[$cacheKey])) { $cached = self::getCached($cacheKey);
return self::$userCache[$cacheKey]; if ($cached !== null) {
return $cached;
} }
$stmt = $this->conn->prepare("SELECT * FROM users WHERE user_id = ?"); $stmt = $this->conn->prepare("SELECT * FROM users WHERE user_id = ?");
@@ -124,7 +165,7 @@ class UserModel {
if ($result->num_rows > 0) { if ($result->num_rows > 0) {
$user = $result->fetch_assoc(); $user = $result->fetch_assoc();
self::$userCache[$cacheKey] = $user; self::setCached($cacheKey, $user);
$stmt->close(); $stmt->close();
return $user; return $user;
} }
@@ -142,8 +183,9 @@ class UserModel {
public function getUserByUsername($username) { public function getUserByUsername($username) {
// Check cache first // Check cache first
$cacheKey = "user_$username"; $cacheKey = "user_$username";
if (isset(self::$userCache[$cacheKey])) { $cached = self::getCached($cacheKey);
return self::$userCache[$cacheKey]; if ($cached !== null) {
return $cached;
} }
$stmt = $this->conn->prepare("SELECT * FROM users WHERE username = ?"); $stmt = $this->conn->prepare("SELECT * FROM users WHERE username = ?");
@@ -153,7 +195,7 @@ class UserModel {
if ($result->num_rows > 0) { if ($result->num_rows > 0) {
$user = $result->fetch_assoc(); $user = $result->fetch_assoc();
self::$userCache[$cacheKey] = $user; self::setCached($cacheKey, $user);
$stmt->close(); $stmt->close();
return $user; return $user;
} }