JeuWeb - Crée ton jeu par navigateur
Boucle UPDATE, RAND - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Boucle UPDATE, RAND (/showthread.php?tid=7787)

Pages : 1 2 3 4


Boucle UPDATE, RAND - MeTaLLiQuE - 28-03-2017

Bonsoir,

Je rencontre un soucis... Facile à développer mais la meilleure solution pour flinger mon serveur...

En gros, j'ai une table qui pourra contenir (sur le moyen terme) des millions de lignes... Chaque colonne contiendra en majorité des données de type INT (int, tinyint, ...)

Dans mon code PHP, je fais un calcul pour savoir si toutes les populations (Au nombre de 3 types différents) ont reçues leur nourriture (il y en a 10 différentes). Si toutes les populations ont reçues leur nourriture, le moral augmente individuellement de 20% s'il y a une population qui n'a pas reçue au moins une nourriture elle perd individuellement 20%

EXEMPLE :

Mes trois types de populations (et leur nombre) sont : 100 serfs, 50 artisans, 10 bourgeois
Les ressources différentes : blé, viande, pain, peau, etc....

Je vérifie donc pour chaque ressource si j'arrive à nourrir les 100 serfs, puis les 50 artisans puis les 10 bourgeois
Si j'arrive à tous les nourrires j'augmente individuellement de 20% leur moral
Si je n'arrive à nourrir que 80 serfs en blé = 20% à tous les serfs que j'aurais nouris
et les 20 serfs restant verront leur moral baissé de 20%

et ce, à chaque ressource et sur chaque type de la population.

(les populations qui se verront augmenter et baisser de 20% sont choisis aléatoirement, je ne vous parle pas du gouffre avec la fonction RAND...).

Je ne sais pas comment arriver à optimiser à sachant que ça ferait au total 100*10+50*10+10*10 = 1600 requêtes UPDATE pour un seul joueur dans cet exemple...

Je ne pense pas qu'il soit possible de faire comme les INSERT INTO en les regroupant tous et exécuter qu'une seule fois la requête...

Je vous mets un bout de code au caou si vous n'arrivez pas à comprendre ce que je veux dire...


<?php

// ARRAY contenant les types de population et le nom de leur bâtiment respectif
foreach($popList as $population => $habitation)
{
  // Requête pour sélectionner le nombre de consommation par nourriture selon le type de population
  $reqConso = $bdd->prepare("SELECT * FROM consommations_population WHERE type=?");
  $reqConso->execute(array($population));
  $donConso = $reqConso->fetch();

  // ARRAY contenant l'alimentation à consommer qui est débloquée selon un niveau du joueur
  foreach($consoPop as $consommation => $niveauRequis)
  {
    // Si le joueur a un niveau supérieur ou égal au niveau requis de la nourriture
    if($donUsers['evolution'] >= $niveauRequis)
    {
      // La consommation totale que doit consommer vos populations
      $consoTotale = $donPop[$population]*$donConso[$consommation];

      if($donRess[$consommation] >= $consoTotale)
      {
        /* Requête UPDATE pour rajouter 20% */
      } else {
        // Combien n'ont-il pas été nourris ?
        $notNourris = floor($donRess[$consommation]*$donPop[$population]/$consoTotale);

        /* requête UPDATE pour retirer 20% */
      }
  }
}
?>


Merci d'avance Smile


RE: Boucle UPDATE, RAND - Xenos - 28-03-2017

Citation :Dans mon code PHP, je fais un calcul pour savoir si toutes les populations (Au nombre de 3 types différents) ont reçues leur nourriture (il y en a 10 différentes).
Fais-le dans le MySQL, par procédure stockée. Ca dépote (perso, sur Iamanoc, un petit réseau social pour personnages fictifs, j'ai fait quelques tests et tout traiter dans le serveur SQL donne des temps de réponse de <50ms même avec 1 million de messages et d'évènements). Une boucle de requête UPDATE n'est alors plus un problème (car le temps réseau ne se glisse pas au milieu).
Je la présenterai dans quelques jours cette archi, je pense que cela pourra en intéresser quelques uns (dont toi Wink )

A défaut, si tu ne veux pas changer d'archi, utilise INSERT INTO ... (id, ...) VALUES (.../* là, tu groupes un batch de 500*/...) ON DUPLICATE KEY UPDATE moral=VALUES(moral), viande=VALUES(viande),... /*là, tu liste les colonnes de données */.
Cela ne fera qu'une requête qui a pour principe d'écraser la ligne de donnée si une clef UNIQUE (la PRIMARY KEY de l'id, généralement) existe déjà. Cela permet d'émuler des "UPDATE par batch".


RE: Boucle UPDATE, RAND - MeTaLLiQuE - 28-03-2017

Je n'ai jamais fait de procédure stockée. Je regarderai de ce côté voir. Et j'attends avec impatience alors (:

Concernant le INSERT INTO ON DUPLICATE KEY UPDATE, je ne connais pas l'ID (la clé primaire) car je voulais l'appliquer sur un RAND

En gros, à la version gros porc, ça donnerai :



<?php

  foreach($popList as $population => $habitation)
  {
    $reqConso = $bdd->prepare("SELECT * FROM consommations_population WHERE type=?");
    $reqConso->execute(array($population));
    $donConso = $reqConso->fetch();

    foreach($consoPop as $consommation => $niveauRequis)
    {
      if($donUsers['evolution'] >= $niveauRequis)
      {
        // La consommation totale que doit consommer vos populations
        $consoTotale = $donPop[$population]*$donConso[$consommation];

        if($donRess[$consommation] >= $consoTotale)
        {
          // On rajoute 20% de moral à la troupe
          $reqUpdateMoral = $bdd->prepare("UPDATE population SET moral=moral+20 WHERE id_user= :id_user AND categorie= :categorie");
          $reqUpdateMoral->bindValue(':id_user', $_SESSION['id'], PDO::PARAM_INT);
          $reqUpdateMoral->bindValue(':categorie', $population, PDO::PARAM_STR);
          $reqUpdateMoral->execute();
        } else {
          // Combien n'ont-il pas été nourris ?
          $notNourris = floor($donRess[$consommation]*$donPop[$population]/$consoTotale);

          // On sélectionne x troupe
          $reqSelectTroupe = $bdd->prepare("SELECT id FROM population WHERE id_user= :id_user AND categorie= :categorie ORDER BY RAND() LIMIT :limit");
          $reqSelectTroupe->bindValue(':id_user', $_SESSION['id'], PDO::PARAM_INT);
          $reqSelectTroupe->bindValue(':categorie', $population, PDO::PARAM_INT);
          $reqSelectTroupe->bindValue(':limit', $notNourris, PDO::PARAM_INT);
          $reqSelectTroupe->execute();
          while($donSelectTroupe = $reqSelectTroupe->fetch())
          {
            // On enlève 20% de moral à la troupe
            $reqUpdateMoral = $bdd->prepare("UPDATE population SET moral=moral-20 WHERE id= :id");
            $reqUpdateMoral->bindValue(':id', $donSelectTroupe['id'], PDO::PARAM_INT);
            $reqUpdateMoral->execute();
          }
        }
    }
  }
}
?>




RE: Boucle UPDATE, RAND - Ter Rowan - 28-03-2017

je ferais autrement :


- dans la table joueur ou village (ou ensemble de personnages que tu traites)

tu ajoutes un champ " %  nourris" que tu alimentes d'après tes calculs

- dans la table personnage tu rajoutes un champ "score nrt"

ca donne un truc :

id personnage / id village / score nrt / moral

là tu fais une ou deux requêtes (je ne maitrise pas assez sql) pour :
1) définir un rand(1 -> 100) pour le score nrt de chaque personnage
2) si score nrt de personnage <= %nourris de id village alors il mange à sa faim, sinon il ne mange pas à sa fin et donc update en conséquence.

mathématiquement c'est pas terrible (puisque statistiquement tu peux avoir bcp plus ou bcp moins de personnages  nourris que la cible du village)
mais d'un point de vue game play ça peut s'expliquer, des restes du coup d'avant (vive le pain perdu), du rationnement, de la déperdition (à mort les rats), etc...


RE: Boucle UPDATE, RAND - MeTaLLiQuE - 28-03-2017

Le moral n'est pas basé uniquement s'il a été nourri ou pas... Si la population travail (ou pas) ca impactera le moral, s'il est'trop taxé ou pas, ca impactera son moral, etc. ...

Puis ca reviendrai au même, il y aurait autant d'update car faudrait mettre à jour ligne par ligne...


RE: Boucle UPDATE, RAND - Xenos - 28-03-2017

Perso, je le tenterai sous la forme simple d'un UPDATE population p SET p.moral = p.moral + (SELECT +/-20 ... FROM consommations) de sorte que la subquery SELECT fournisse + ou - 20 en fontion des ressources disponibles (si cette subquery est faisable, j'ai pas trop regardé la logique métier).

Sinon, en restant hors SQL, tu peux déjà changer ton id = $donSelectTroupe en un IN (donSelectTroupe) qui ne fera qu'un UPDATE au lieu de 50. Après, au lieu d'exécuter la query à cet endroit, tu peux stocker la liste de ces IDs et n'exécuter qu'un seul UPDATE ... WHERE IN ($maListeStockee) à la fin de la boucle. Même principe pour l'autre UPDATE (où il te faut alors stocker la liste [id du joueur => [categories]])


RE: Boucle UPDATE, RAND - MeTaLLiQuE - 28-03-2017

je vais essayer, par contre, pour confirmer il n'y a pas de limite de modifications possibles sir l'update ? Je peux modifier 1, 100, 100000, lignes en même temps ?

Merci Smile


RE: Boucle UPDATE, RAND - Ter Rowan - 28-03-2017

on peut updater autant de lignes en une seule requête que l'on veut, il suffit d'avoir les bonnes conditions (et ma proposition correspond à créer les conditions pour faire une seule requête)

c'est sur que si tu fais un update par record ça risque pas de marcher


RE: Boucle UPDATE, RAND - Xenos - 28-03-2017

Si tu lockes la table avant de faire tes updates (START TRANSACTION READ WRITE par exemple), que ceux-ci sont simples (ie portent sur un ID PRIMARY) et qu'ils sont faits dans une procédure stockée, alors un UPDATE par record est acceptable (y'a quasiment toujours moyen de faire bien mieux, mais cela tient la charge).

Cet exemple met 10k lignes à jour en ~200ms sur MySQL 5.7; c'est franchement pas optimal, okay, mais c'est l'un des intérêts de l'approch à procédures: il sera toujours temps de faire de meilleures queries dans cette procédure quand les lenteurs se feront sentir Wink


/*
-- Init de la table, ~1M de lignes
CREATE TABLE IF NOT EXISTS `tst` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`value` INT(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2097153
;

-- Ouais, y'a peut-être mieux, mais osef
INSERT INTO tst (id) (SELECT 1);
INSERT INTO tst (id) (SELECT (id+1) FROM tst);
INSERT INTO tst (id) (SELECT (id+2) FROM tst);
INSERT INTO tst (id) (SELECT (id+4) FROM tst);
INSERT INTO tst (id) (SELECT (id+8) FROM tst);
INSERT INTO tst (id) (SELECT (id+16) FROM tst);
INSERT INTO tst (id) (SELECT (id+32) FROM tst);
INSERT INTO tst (id) (SELECT (id+64) FROM tst);
INSERT INTO tst (id) (SELECT (id+128) FROM tst);
INSERT INTO tst (id) (SELECT (id+256) FROM tst);
INSERT INTO tst (id) (SELECT (id+512) FROM tst);
INSERT INTO tst (id) (SELECT (id+1024) FROM tst);
INSERT INTO tst (id) (SELECT (id+2048) FROM tst);
INSERT INTO tst (id) (SELECT (id+4096) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192*2) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192*4) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192*8) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192*16) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192*32) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192*64) FROM tst);
INSERT INTO tst (id) (SELECT (id+8192*128) FROM tst);
*/

DELIMITER $

DROP PROCEDURE IF EXISTS tstproc;
CREATE PROCEDURE `tstproc`(
IN howmany INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
COMMENT ''
BEGIN

SET @i := howmany;
SET @prefix := FLOOR(1000*RAND());
START TRANSACTION READ WRITE;
WHILE @i > 0 DO
UPDATE tst SET tst.value = @prefix WHERE tst.id = @i;
SET @i := @i - 1;
END WHILE;
COMMIT;
END$

DELIMITER ;

-- 200ms en local pour 10k lignes;
-- y'a évidemment mieux en optimisant le contenu de la procédure,
-- mais c'est parfois un temps acceptable
CALL tstproc(10000);



RE: Boucle UPDATE, RAND - Ter Rowan - 28-03-2017

jamais vu que c'était une bonne pratique de faire des update ligne à ligne...