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>
This commit is contained in:
2026-05-04 16:41:52 +02:00
parent 97b980a05a
commit 68cea1eb4d
20 changed files with 757 additions and 45 deletions

View File

@@ -118,7 +118,8 @@ class StatsObject extends DBTableObject
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// get sessions of given type that given user has taken part in
public function getRawSessions ($userId=-1, $typeId=-1)
// $limit and $offset are optional : when $limit <= 0 the full list is returned (legacy behavior)
public function getRawSessions ($userId=-1, $typeId=-1, $limit=-1, $offset=0)
{
// Check if we are looking for a specific user and/or type of session
$userConstraint = $userId > 0 ? " AND P.userId=" . $userId : "";
@@ -126,11 +127,17 @@ class StatsObject extends DBTableObject
// Order results from newest to oldest session
$orderConstraint = " ORDER BY SD.id DESC";
//$query = "SELECT DISTINCT S.* FROM " . SESSIONS_TABLE_NAME . " S LEFT JOIN " . PARTICIPATES_TABLE_NAME . " P ON S.id = P.sessionId WHERE 1 " .
// Optional pagination : SQL-level LIMIT/OFFSET when caller provided a positive limit
$limitConstraint = "";
if (intval($limit) > 0) {
$limitConstraint = " LIMIT " . intval($limit) . " OFFSET " . max(0, intval($offset));
}
//$query = "SELECT DISTINCT S.* FROM " . SESSIONS_TABLE_NAME . " S LEFT JOIN " . PARTICIPATES_TABLE_NAME . " P ON S.id = P.sessionId WHERE 1 " .
$query = "SELECT DISTINCT SD.* FROM " . SESSIONS_TABLE_NAME . " SD, " . PARTICIPATES_TABLE_NAME . " P WHERE SD.id = P.sessionId " .
$this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $userConstraint . $typeConstraint . $orderConstraint;
$this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $userConstraint . $typeConstraint . $orderConstraint . $limitConstraint;
// prepare and execute query
$stmt = $this->conn->prepare($query);
$stmt->execute();
@@ -141,11 +148,11 @@ class StatsObject extends DBTableObject
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// get sessions that given user has taken part in
public function getSessionsForUser ($userId, $typeId=-1)
public function getSessionsForUser ($userId, $typeId=-1, $limit=-1, $offset=0)
{
// Check if we are looking for a specific user and/or session
$stmt = $this->getRawSessions($userId, $typeId);
$stmt = $this->getRawSessions($userId, $typeId, $limit, $offset);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
{
//$sess = new GameSession($this->conn);
@@ -469,11 +476,16 @@ class StatsObject extends DBTableObject
}
else
{
// Lazy rebuild du cache materialise pour cette session avant le SELECT FROM sessiondebriefs
if ($sessionId > 0 && function_exists('ensure_session_debriefs_cached_db')) {
ensure_session_debriefs_cached_db($this->conn, $sessionId);
}
// create SQL constraints for query
$sessionConstraint = $sessionId > 0 ? " AND SD.SessionId = " . $sessionId : "";
$userConstraint = $userId >= 0 ? " AND SD.ShooterId = " . $userId : "";
$userSubConstraint = $userId >= 0 ? " AND P.UserId = " . $userId : "";
// get raw results from query
$sessionStats = $this->getRawStatsForSession ($sessionId, $userId, -1);
@@ -692,9 +704,15 @@ class StatsObject extends DBTableObject
// debrief mission : get given users results in given session
public function getResultsForSession ($sessionId, $userId, $fromUserId=-1)
{
// Lazy rebuild du cache materialise (table session_debriefs_cache)
// si la session a ete invalidee par un evenement et pas encore reconstruite
if ($sessionId > 0 && function_exists('ensure_session_debriefs_cached_db')) {
ensure_session_debriefs_cached_db($this->conn, $sessionId);
}
$userConstraint = $userId > 0 ? " AND SD.ShooterId=" . $userId : "";
$sessionConstraint = $sessionId > 0 ? " AND SD.SessionId=" . $sessionId : "";
$query = "SELECT SD.* FROM " . SESSIONDEBRIEFS_VIEW_NAME . " SD WHERE 1 " . $this->getCalibrationSessionsConstraint() . $userConstraint . $sessionConstraint . " GROUP BY SD.SessionId, SD.ShooterId, SD.ShotIndex";
//$query = "SELECT SD.* FROM (" . SESSIONDEBRIEFS_VIEW_QUERY . ") AS SD WHERE 1 " . $userConstraint . $sessionConstraint . " GROUP BY SD.SessionId, SD.ShooterId, SD.ShotIndex";