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:
236
ProserveAPI/htdocs/proserve/update_proserve_db 1.7.0.sql
Normal file
236
ProserveAPI/htdocs/proserve/update_proserve_db 1.7.0.sql
Normal file
@@ -0,0 +1,236 @@
|
||||
-- ============================================================
|
||||
-- Migration 1.7.0 - Materialisation de la vue sessiondebriefs
|
||||
-- ============================================================
|
||||
-- Cible : MariaDB 10.4+.
|
||||
-- Idempotent : peut etre rejoue sans erreur. Le cache est entierement
|
||||
-- reconstruit a chaque execution (TRUNCATE + backfill).
|
||||
--
|
||||
-- Changement principal : la vue sessiondebriefs etait recalculee a chaque
|
||||
-- requete (12 LEFT JOIN + GROUP BY + sous-requetes correlees). Elle est
|
||||
-- desormais materialisee dans une vraie table session_debriefs_cache,
|
||||
-- peuplee :
|
||||
-- - en bulk au moment de cette migration (backfill toutes sessions),
|
||||
-- - puis par une procedure stockee rebuild_debrief_cache(p_session_id)
|
||||
-- appelee depuis le code PHP a chaque modification de session
|
||||
-- (session/stop, session/userleave, session/updateobjectives,
|
||||
-- events/storetriggerevent, events/storereactevent).
|
||||
--
|
||||
-- La vue sessiondebriefs garde son nom et ses colonnes : le code consommateur
|
||||
-- (stats_object.php : getResultsForSession, getUserHistory) ne change pas.
|
||||
-- Elle devient un simple passe-plat sur la table cache.
|
||||
--
|
||||
-- Action 2 (fix partiel du double JOIN participates) : la jointure PH
|
||||
-- (qui sert a determiner TargetRoleId/TargetRole) est desormais contrainte
|
||||
-- sur PH.userId = R.hitUserId au lieu de matcher tous les participants.
|
||||
-- Effet :
|
||||
-- * Pour les hits user-on-user : meme resultat (correct par construction).
|
||||
-- * Pour les hits NPC/objets : TargetRoleId vaut 3 (IA) au lieu d'un
|
||||
-- role aleatoire d'un autre participant. C'est un fix de bug.
|
||||
-- La jointure PS reste inchangee (preserve le fallback ShooterId quand T est NULL).
|
||||
-- ============================================================
|
||||
|
||||
USE proserveapi;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 1) Table de cache (creation idempotente)
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `session_debriefs_cache` (
|
||||
`SessionId` int(11) NOT NULL,
|
||||
`SessionTypeId` int(11) DEFAULT NULL,
|
||||
`SessionType` varchar(255) DEFAULT NULL,
|
||||
`SessionName` varchar(255) DEFAULT NULL,
|
||||
`SessionDate` datetime DEFAULT NULL,
|
||||
`MapName` varchar(255) DEFAULT NULL,
|
||||
`ScenarioName` varchar(255) DEFAULT NULL,
|
||||
`SessionSuccessful` tinyint(1) DEFAULT NULL,
|
||||
`SessionDuration` float DEFAULT NULL,
|
||||
`TriggerTypeId` int(11) DEFAULT NULL,
|
||||
`TriggerType` varchar(255) DEFAULT NULL,
|
||||
`ShooterId` int(11) DEFAULT NULL,
|
||||
`ShooterName` varchar(255) DEFAULT NULL,
|
||||
`ShooterRoleId` int(11) DEFAULT NULL,
|
||||
`ShooterRole` varchar(255) DEFAULT NULL,
|
||||
`ShotIndex` int(11) DEFAULT NULL,
|
||||
`ReactId` int(11) DEFAULT NULL,
|
||||
`ReactModeId` int(11) DEFAULT NULL,
|
||||
`ReactMode` varchar(255) DEFAULT NULL,
|
||||
`ReactTypeId` int(11) DEFAULT NULL,
|
||||
`ReactType` varchar(255) DEFAULT NULL,
|
||||
`TargetUserId` int(11) DEFAULT NULL,
|
||||
`TargetUserName` varchar(255) DEFAULT NULL,
|
||||
`TargetRoleId` int(11) DEFAULT NULL,
|
||||
`TargetRole` varchar(255) DEFAULT NULL,
|
||||
`TargetName` varchar(255) DEFAULT NULL,
|
||||
`TargetBoneName` varchar(255) DEFAULT NULL,
|
||||
`TargetKilled` int(4) DEFAULT NULL,
|
||||
`HitLocationX` double DEFAULT NULL,
|
||||
`HitLocationY` double DEFAULT NULL,
|
||||
`HitLocationTag` varchar(255) DEFAULT NULL,
|
||||
`HitPrecision` double DEFAULT NULL,
|
||||
`HitTargetDistance` double DEFAULT NULL,
|
||||
`ReactionTime` double DEFAULT NULL,
|
||||
`TimeStamp` double DEFAULT NULL,
|
||||
`NbHit` bigint(21) DEFAULT NULL,
|
||||
`NbKilled` bigint(21) DEFAULT NULL,
|
||||
KEY `idx_sdc_session` (`SessionId`),
|
||||
KEY `idx_sdc_session_shooter` (`SessionId`, `ShooterId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 2) Procedure stockee pour reconstruire le cache d'une session
|
||||
-- ------------------------------------------------------------
|
||||
DROP PROCEDURE IF EXISTS rebuild_debrief_cache;
|
||||
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE rebuild_debrief_cache(IN p_session_id INT)
|
||||
BEGIN
|
||||
DELETE FROM session_debriefs_cache WHERE SessionId = p_session_id;
|
||||
|
||||
INSERT INTO session_debriefs_cache (
|
||||
SessionId, SessionTypeId, SessionType, SessionName, SessionDate,
|
||||
MapName, ScenarioName, SessionSuccessful, SessionDuration,
|
||||
TriggerTypeId, TriggerType, ShooterId, ShooterName,
|
||||
ShooterRoleId, ShooterRole, ShotIndex, ReactId, ReactModeId,
|
||||
ReactMode, ReactTypeId, ReactType, TargetUserId, TargetUserName,
|
||||
TargetRoleId, TargetRole, TargetName, TargetBoneName, TargetKilled,
|
||||
HitLocationX, HitLocationY, HitLocationTag, HitPrecision,
|
||||
HitTargetDistance, ReactionTime, TimeStamp, NbHit, NbKilled
|
||||
)
|
||||
SELECT
|
||||
S.id,
|
||||
S.sessionType,
|
||||
ST.displayName,
|
||||
S.sessionName,
|
||||
S.sessionDate,
|
||||
S.mapName,
|
||||
S.scenarioName,
|
||||
S.success,
|
||||
S.timeToFinish,
|
||||
COALESCE(T.type, -1),
|
||||
COALESCE(TT.displayName, ''),
|
||||
IFNULL(T.srcUserId, PS.userId),
|
||||
US.username,
|
||||
IFNULL(PS.role, 3),
|
||||
URS.displayName,
|
||||
COALESCE(T.indexCount, -1),
|
||||
COALESCE(R.id, -1),
|
||||
COALESCE(R.reactMode, 2),
|
||||
COALESCE(RM.displayName, ''),
|
||||
COALESCE(R.reactType, -1),
|
||||
COALESCE(RT.displayName, ''),
|
||||
COALESCE(R.hitUserId, -1),
|
||||
COALESCE(UH.username, ''),
|
||||
IFNULL(PH.role, 3),
|
||||
URT.displayName,
|
||||
COALESCE(R.hitTargetName, ''),
|
||||
COALESCE(R.hitBoneName, ''),
|
||||
COALESCE(R.targetKilled, 0),
|
||||
COALESCE(R.objectHitLocationX, 0),
|
||||
COALESCE(R.objectHitLocationY, 0),
|
||||
COALESCE(R.objectHitTagLocation, ''),
|
||||
COALESCE(R.hitPrecision, 0),
|
||||
COALESCE(R.distance, 0),
|
||||
COALESCE(R.reactTime, 0),
|
||||
COALESCE(R.timeStamp, 0),
|
||||
COUNT(DISTINCT R.id),
|
||||
COUNT(DISTINCT RK.srcEventIndex, RK.hitTargetName)
|
||||
FROM sessions S
|
||||
LEFT JOIN participates PS ON (S.id = PS.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)
|
||||
-- PH : Action 2 du fix - PH represente la participation de l'utilisateur cible
|
||||
-- (au lieu de "n'importe quel participant") pour que TargetRole soit coherent.
|
||||
LEFT JOIN participates PH ON (S.id = PH.sessionId AND PH.userId = R.hitUserId)
|
||||
LEFT JOIN userroles URS ON (URS.id = IFNULL(PS.role, 3))
|
||||
LEFT JOIN userroles URT ON (URT.id = IFNULL(PH.role, 3))
|
||||
WHERE S.id = p_session_id
|
||||
GROUP BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.hitTargetName, '')
|
||||
ORDER BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.id, -1);
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 3) Backfill complet : recalcule le cache pour toutes les sessions existantes
|
||||
-- ------------------------------------------------------------
|
||||
TRUNCATE TABLE session_debriefs_cache;
|
||||
|
||||
INSERT INTO session_debriefs_cache (
|
||||
SessionId, SessionTypeId, SessionType, SessionName, SessionDate,
|
||||
MapName, ScenarioName, SessionSuccessful, SessionDuration,
|
||||
TriggerTypeId, TriggerType, ShooterId, ShooterName,
|
||||
ShooterRoleId, ShooterRole, ShotIndex, ReactId, ReactModeId,
|
||||
ReactMode, ReactTypeId, ReactType, TargetUserId, TargetUserName,
|
||||
TargetRoleId, TargetRole, TargetName, TargetBoneName, TargetKilled,
|
||||
HitLocationX, HitLocationY, HitLocationTag, HitPrecision,
|
||||
HitTargetDistance, ReactionTime, TimeStamp, NbHit, NbKilled
|
||||
)
|
||||
SELECT
|
||||
S.id,
|
||||
S.sessionType,
|
||||
ST.displayName,
|
||||
S.sessionName,
|
||||
S.sessionDate,
|
||||
S.mapName,
|
||||
S.scenarioName,
|
||||
S.success,
|
||||
S.timeToFinish,
|
||||
COALESCE(T.type, -1),
|
||||
COALESCE(TT.displayName, ''),
|
||||
IFNULL(T.srcUserId, PS.userId),
|
||||
US.username,
|
||||
IFNULL(PS.role, 3),
|
||||
URS.displayName,
|
||||
COALESCE(T.indexCount, -1),
|
||||
COALESCE(R.id, -1),
|
||||
COALESCE(R.reactMode, 2),
|
||||
COALESCE(RM.displayName, ''),
|
||||
COALESCE(R.reactType, -1),
|
||||
COALESCE(RT.displayName, ''),
|
||||
COALESCE(R.hitUserId, -1),
|
||||
COALESCE(UH.username, ''),
|
||||
IFNULL(PH.role, 3),
|
||||
URT.displayName,
|
||||
COALESCE(R.hitTargetName, ''),
|
||||
COALESCE(R.hitBoneName, ''),
|
||||
COALESCE(R.targetKilled, 0),
|
||||
COALESCE(R.objectHitLocationX, 0),
|
||||
COALESCE(R.objectHitLocationY, 0),
|
||||
COALESCE(R.objectHitTagLocation, ''),
|
||||
COALESCE(R.hitPrecision, 0),
|
||||
COALESCE(R.distance, 0),
|
||||
COALESCE(R.reactTime, 0),
|
||||
COALESCE(R.timeStamp, 0),
|
||||
COUNT(DISTINCT R.id),
|
||||
COUNT(DISTINCT RK.srcEventIndex, RK.hitTargetName)
|
||||
FROM sessions S
|
||||
LEFT JOIN participates PS ON (S.id = PS.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 participates PH ON (S.id = PH.sessionId AND PH.userId = R.hitUserId)
|
||||
LEFT JOIN userroles URS ON (URS.id = IFNULL(PS.role, 3))
|
||||
LEFT JOIN userroles URT ON (URT.id = IFNULL(PH.role, 3))
|
||||
GROUP BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.hitTargetName, '')
|
||||
ORDER BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.id, -1);
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 4) La vue devient un passe-plat sur la table cache
|
||||
-- ------------------------------------------------------------
|
||||
CREATE OR REPLACE VIEW sessiondebriefs AS
|
||||
SELECT * FROM session_debriefs_cache;
|
||||
Reference in New Issue
Block a user