Files
j.foucher 68cea1eb4d V2 : optimisation API (cache, index, vue materialisee)
- BDD (migrations 1.6.0 + 1.7.0, idempotentes) :
  * 4 nouveaux index composites sur reactevents, triggerevents, sessions
  * vue sessiondebriefs reecrite (sous-requetes correlees -> JOINs)
    puis materialisee dans table session_debriefs_cache via procedure
    stockee rebuild_debrief_cache, recalculee uniquement aux changements
  * fix bug double JOIN participates : TargetRole = "IA" pour les hits
    NPC/objets (au lieu d'un role aleatoire de participant)
- API PHP :
  * connexion PDO en singleton + ATTR_PERSISTENT
  * cache disque par session (cache/sess_<id>/) pour debrief, get, userhistory
    avec invalidation ciblee sur events et lifecycle
  * pagination opt-in (limit/offset) sur lists/sessions_for_user
  * helpers ensure_/rebuild_/invalidate_session_debrief_cache_db
  * debug/benchmark.php pour mesurer la latence des 6 endpoints cles
- Endpoints HTTP : signatures inchangees, compat Unreal preservee
- Gain mesure : -58% a -83% sur les 5 endpoints cles

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:41:52 +02:00

110 lines
3.9 KiB
PHP

<?php
// Cache disque pour resultats lourds et immuables (debrief, userhistory, get session).
// Chaque session a son propre sous-dossier cache/sess_<id>/ : invalider une session
// se reduit a supprimer le dossier, sans risque de match partiel sur d'autres ids.
// Toutes les fonctions sont silencieuses en cas d'echec (best-effort, jamais bloquant).
if (!defined('PROSERVE_CACHE_DIR'))
define('PROSERVE_CACHE_DIR', __DIR__ . '/../cache');
function cache_session_dir($sessionId)
{
return PROSERVE_CACHE_DIR . '/sess_' . intval($sessionId);
}
function cache_path($sessionId, $key)
{
$safe = preg_replace('/[^a-zA-Z0-9_-]/', '_', $key);
return cache_session_dir($sessionId) . '/' . $safe . '.json';
}
function cache_get($sessionId, $key)
{
if (intval($sessionId) <= 0) return null;
$path = cache_path($sessionId, $key);
if (!file_exists($path)) return null;
$data = @file_get_contents($path);
if ($data === false || $data === '') return null;
return $data;
}
function cache_put($sessionId, $key, $jsonString)
{
if (intval($sessionId) <= 0) return;
$dir = cache_session_dir($sessionId);
if (!is_dir($dir)) @mkdir($dir, 0755, true);
@file_put_contents(cache_path($sessionId, $key), $jsonString, LOCK_EX);
}
function cache_invalidate_session($sessionId)
{
$sid = intval($sessionId);
if ($sid <= 0) return;
$dir = cache_session_dir($sid);
if (!is_dir($dir)) return;
$files = glob($dir . '/*');
if (is_array($files)) {
foreach ($files as $f) @unlink($f);
}
@rmdir($dir);
}
// =============================================================================
// DB-level cache for the materialized sessiondebriefs view (table session_debriefs_cache)
// Populated by stored procedure rebuild_debrief_cache(p_session_id) created in migration 1.7.0.
// =============================================================================
// Marque le cache DB d'une session comme stale (delete des lignes).
// Appele depuis les endpoints d'evenements (storetriggerevent, storereactevent)
// pour amortir le cout : la reconstruction se fait paresseusement au prochain
// read via ensure_session_debriefs_cached_db, ou explicitement via rebuild...
function invalidate_session_debrief_cache_db($db, $sessionId)
{
$sid = intval($sessionId);
if ($sid <= 0 || $db === null) return;
try {
$stmt = $db->prepare("DELETE FROM session_debriefs_cache WHERE SessionId = ?");
$stmt->execute([$sid]);
} catch (Exception $e) {
// best-effort, ne jamais bloquer la requete metier
}
}
// Reconstruit immediatement le cache DB d'une session (DELETE + INSERT via la proc).
// Appele depuis session/stop, session/userleave, session/updateobjectives.
function rebuild_session_debrief_cache_db($db, $sessionId)
{
$sid = intval($sessionId);
if ($sid <= 0 || $db === null) return;
try {
$stmt = $db->prepare("CALL rebuild_debrief_cache(?)");
$stmt->execute([$sid]);
$stmt->closeCursor();
} catch (Exception $e) {
// best-effort
}
}
// Garantit que le cache DB contient au moins une ligne pour la session.
// Appele avant tout SELECT sur sessiondebriefs cote stats_object.php.
// Si le cache a ete invalide par un evenement et n'a pas encore ete reconstruit,
// cette fonction le reconstruit a la demande.
function ensure_session_debriefs_cached_db($db, $sessionId)
{
$sid = intval($sessionId);
if ($sid <= 0 || $db === null) return;
try {
$stmt = $db->prepare("SELECT 1 FROM session_debriefs_cache WHERE SessionId = ? LIMIT 1");
$stmt->execute([$sid]);
$exists = $stmt->fetchColumn();
$stmt->closeCursor();
if ($exists === false) {
rebuild_session_debrief_cache_db($db, $sid);
}
} catch (Exception $e) {
// si la table n'existe pas (migration 1.7.0 pas encore appliquee),
// on ne fait rien : le code legacy continue de marcher via la vue
}
}
?>