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

@@ -325,7 +325,61 @@ INSERT INTO `userstatus` (`id`, `displayName`, `description`) VALUES
--
DROP TABLE IF EXISTS `sessiondebriefs`;
CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `sessiondebriefs` AS SELECT `s`.`id` AS `SessionId`, `s`.`sessionType` AS `SessionTypeId`, `st`.`displayName` AS `SessionType`, `s`.`sessionName` AS `SessionName`, `s`.`sessionDate` AS `SessionDate`, `s`.`mapName` AS `MapName`, `s`.`scenarioName` AS `ScenarioName`, `s`.`success` AS `SessionSuccessful`, `s`.`timeToFinish` AS `SessionDuration`, coalesce(`t`.`type`,-1) AS `TriggerTypeId`, coalesce(`tt`.`displayName`,'') AS `TriggerType`, (select ifnull(`t`.`srcUserId`,`ps`.`userId`)) AS `ShooterId`, `us`.`username` AS `ShooterName`, (select ifnull(`ps`.`role`,3)) AS `ShooterRoleId`, (select `r`.`displayName` from `userroles` `r` where `r`.`id` = `ShooterRoleId`) AS `ShooterRole`, coalesce(`t`.`indexCount`,-1) AS `ShotIndex`, coalesce(`r`.`id`,-1) AS `ReactId`, coalesce(`r`.`reactMode`,2) AS `ReactModeId`, coalesce(`rm`.`displayName`,'') AS `ReactMode`, coalesce(`r`.`reactType`,-1) AS `ReactTypeId`, coalesce(`rt`.`displayName`,'') AS `ReactType`, coalesce(`r`.`hitUserId`,-1) AS `TargetUserId`, coalesce(`uh`.`username`,'') AS `TargetUserName`, (select ifnull(`ph`.`role`,3)) AS `TargetRoleId`, (select `r`.`displayName` from `userroles` `r` where `r`.`id` = `TargetRoleId`) AS `TargetRole`, coalesce(`r`.`hitTargetName`,'') AS `TargetName`, coalesce(`r`.`hitBoneName`,'') AS `TargetBoneName`, coalesce(`r`.`targetKilled`,0) AS `TargetKilled`, coalesce(`r`.`objectHitLocationX`,0) AS `HitLocationX`, coalesce(`r`.`objectHitLocationY`,0) AS `HitLocationY`, coalesce(`r`.`objectHitTagLocation`,'') AS `HitLocationTag`, coalesce(`r`.`hitPrecision`,0) AS `HitPrecision`, coalesce(`r`.`distance`,0) AS `HitTargetDistance`, coalesce(`r`.`reactTime`,0) AS `ReactionTime`, coalesce(`r`.`timeStamp`,0) AS `TimeStamp`, count(distinct `r`.`id`) AS `NbHit`, count(distinct `rk`.`srcEventIndex`,`rk`.`hitTargetName`) AS `NbKilled` FROM (((((((((((`sessions` `s` left join `participates` `ps` on(`s`.`id` = `ps`.`sessionId`)) left join `participates` `ph` on(`s`.`id` = `ph`.`sessionId`)) left join `triggerevents` `t` on(`s`.`id` = `t`.`sessionId`)) left join `triggereventtypes` `tt` on(`tt`.`id` = `t`.`type`)) left join `sessiontypes` `st` on(`st`.`id` = `s`.`sessionType`)) left join `reactevents` `r` on(`t`.`indexCount` = `r`.`srcEventIndex` and `t`.`sessionId` = `r`.`srcEventSessionId`)) left join `users` `uh` on(`uh`.`id` = `r`.`hitUserId`)) left join `users` `us` on(`us`.`id` = `t`.`srcUserId` or `us`.`id` = `ps`.`userId`)) left join `reacteventtypes` `rt` on(`rt`.`id` = `r`.`reactType`)) left join `reacteventmodes` `rm` on(`rm`.`id` = coalesce(`r`.`reactMode`,2))) left join `reactevents` `rk` on(`r`.`id` = `rk`.`id` and `rk`.`targetKilled` = 1)) GROUP BY `s`.`id`, (select ifnull(`t`.`srcUserId`,`ps`.`userId`)), `t`.`indexCount`, `r`.`hitTargetName` ORDER BY `s`.`id` ASC, (select ifnull(`t`.`srcUserId`,`ps`.`userId`)) ASC, `t`.`indexCount` ASC, `r`.`id` ASC ;
CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `sessiondebriefs` AS
SELECT
`s`.`id` AS `SessionId`,
`s`.`sessionType` AS `SessionTypeId`,
`st`.`displayName` AS `SessionType`,
`s`.`sessionName` AS `SessionName`,
`s`.`sessionDate` AS `SessionDate`,
`s`.`mapName` AS `MapName`,
`s`.`scenarioName` AS `ScenarioName`,
`s`.`success` AS `SessionSuccessful`,
`s`.`timeToFinish` AS `SessionDuration`,
COALESCE(`t`.`type`, -1) AS `TriggerTypeId`,
COALESCE(`tt`.`displayName`, '') AS `TriggerType`,
IFNULL(`t`.`srcUserId`, `ps`.`userId`) AS `ShooterId`,
`us`.`username` AS `ShooterName`,
IFNULL(`ps`.`role`, 3) AS `ShooterRoleId`,
`urs`.`displayName` AS `ShooterRole`,
COALESCE(`t`.`indexCount`, -1) AS `ShotIndex`,
COALESCE(`r`.`id`, -1) AS `ReactId`,
COALESCE(`r`.`reactMode`, 2) AS `ReactModeId`,
COALESCE(`rm`.`displayName`, '') AS `ReactMode`,
COALESCE(`r`.`reactType`, -1) AS `ReactTypeId`,
COALESCE(`rt`.`displayName`, '') AS `ReactType`,
COALESCE(`r`.`hitUserId`, -1) AS `TargetUserId`,
COALESCE(`uh`.`username`, '') AS `TargetUserName`,
IFNULL(`ph`.`role`, 3) AS `TargetRoleId`,
`urt`.`displayName` AS `TargetRole`,
COALESCE(`r`.`hitTargetName`, '') AS `TargetName`,
COALESCE(`r`.`hitBoneName`, '') AS `TargetBoneName`,
COALESCE(`r`.`targetKilled`, 0) AS `TargetKilled`,
COALESCE(`r`.`objectHitLocationX`, 0) AS `HitLocationX`,
COALESCE(`r`.`objectHitLocationY`, 0) AS `HitLocationY`,
COALESCE(`r`.`objectHitTagLocation`, '') AS `HitLocationTag`,
COALESCE(`r`.`hitPrecision`, 0) AS `HitPrecision`,
COALESCE(`r`.`distance`, 0) AS `HitTargetDistance`,
COALESCE(`r`.`reactTime`, 0) AS `ReactionTime`,
COALESCE(`r`.`timeStamp`, 0) AS `TimeStamp`,
COUNT(DISTINCT `r`.`id`) AS `NbHit`,
COUNT(DISTINCT `rk`.`srcEventIndex`, `rk`.`hitTargetName`) AS `NbKilled`
FROM `sessions` `s`
LEFT JOIN `participates` `ps` ON (`s`.`id` = `ps`.`sessionId`)
LEFT JOIN `participates` `ph` ON (`s`.`id` = `ph`.`sessionId`)
LEFT JOIN `triggerevents` `t` ON (`s`.`id` = `t`.`sessionId`)
LEFT JOIN `triggereventtypes` `tt` ON (`tt`.`id` = `t`.`type`)
LEFT JOIN `sessiontypes` `st` ON (`st`.`id` = `s`.`sessionType`)
LEFT JOIN `reactevents` `r` ON (`t`.`indexCount` = `r`.`srcEventIndex` AND `t`.`sessionId` = `r`.`srcEventSessionId`)
LEFT JOIN `users` `uh` ON (`uh`.`id` = `r`.`hitUserId`)
LEFT JOIN `users` `us` ON (`us`.`id` = `t`.`srcUserId` OR `us`.`id` = `ps`.`userId`)
LEFT JOIN `reacteventtypes` `rt` ON (`rt`.`id` = `r`.`reactType`)
LEFT JOIN `reacteventmodes` `rm` ON (`rm`.`id` = COALESCE(`r`.`reactMode`, 2))
LEFT JOIN `reactevents` `rk` ON (`r`.`id` = `rk`.`id` AND `rk`.`targetKilled` = 1)
LEFT JOIN `userroles` `urs` ON (`urs`.`id` = IFNULL(`ps`.`role`, 3))
LEFT JOIN `userroles` `urt` ON (`urt`.`id` = IFNULL(`ph`.`role`, 3))
GROUP BY `SessionId`, `ShooterId`, `ShotIndex`, `TargetName`
ORDER BY `SessionId`, `ShooterId`, `ShotIndex`, `ReactId`;
--
-- Index pour les tables déchargées