|
Publicité ' | |||||||||||||||||||||||
|
|
#1 | ||||||
![]() ![]() Inscription : décembre 2006 Messages : 1 567 ![]() |
Bonjour,
je viens à vous pour un besoin simple (au moins dans sa définition): - j'ai un jeu (amateur) dans lequel à chaque partie jouée le joueur gagne ou perd des points, ce qui fait évoluer son score global. A partir du score on peut établir un classement de l'ensemble des joueurs que le joueur peut parcourir à sa guise dans le jeu (le plus haut score est le premier, ...). - on a divers classements pour diverses périodes (annuel, mensuel, hebdomadaire, ...) ; chaque classement ne prend en compte que les parties jouées pendant la période concernée. - question volumétrie, le jeu rassemble quelques 150 000 joueurs inscrits et une partie est jouée en moyenne toutes les 1.5 secondes (donc les scores des joueurs et donc les classements évoluent à cette fréquence aussi). Voilà pour la présentation. Mon principal souci est qu'en l'état actuel des choses, faire des requêtes sur la base mange pas mal de ressources au serveur (calculer le rang d'un joueur, récupérer la liste des joueurs du rang (xxx) à (xxx + 10) pour les afficher, etc...) ; j'aimerais trouver une solution qui soit la plus économe possible. Concernant la structure (simplifiée) de ma base, on a: - une table 'profil' qui rassemble les caractéristiques du joueur (id, pseudo, avatar, ...). ID_PROFILE bigint(20) PSEUDO varchar(25) AVATAR smallint(6) PRIMARY: (ID_PROFILE) INDEXES: aucun (en dehors du primary) - plusieurs tables, une pour chaque type de période de classement: charts_yearly, charts_monthly, charts_weekly, ... PERIOD int(11) ID_PROFILE bigint(20) SCORE int(11) PRIMARY: (PERIOD, ID_PROFILE) INDEXES: (PERIOD, ID_PROFILE, SCORE), (SCORE) Le champ 'PERIOD' est un identifiant pour la période concernée. Par exemple dans charts_monthly, PERIOD vaut (année * 100 + mois), donc un enregistrement avec PERIOD = 201012 représente le classement pour le joueur PROFILE pour ses parties jouées en décembre 2010. On a donc 0 ou 1 enregistrement pour chaque couple (PERIOD, ID_PROFILE) ; zéro si le joueur n'a joué aucune partie pendant la période donnée. Le champ 'SCORE' est le cumul des points gagnés/perdus à chaque partie. Les requêtes qui sont faites sur la base: (Les valeurs entre [ ] sont les valeurs injectées dans la requête en fonction du contexte) A la fin d'une partie, pour créer/mettre à jour un classement (exemple ici, le classement annuel ; il y a autant de requêtes faites que de types de classement (annuel, mensuel, hebdomadaire, ...): Code :
Code :
Récupérer les joueurs classés entre le rang (begin) et le rang (begin+length): Code :
Voilà. Après un premier test sur mon laptop une base avec 150 000 profils et 300 000 entrées dans charts_yearly (deux entrées par profil pour 2010 et 2011), je mets près de 600ms lorsque je veux récupérer les 10 joueurs classés du 100 000 ème au 100 010 ème (cf. la dernière requête). Cette dernière requête semble peu affectée par le nombre d'enregistrements dans la table (j'obtiens les même temps avec charts_weekly qui a 7 millions d'enregistrements), mais beaucoup plus par les valeurs de LIMIT: il est beaucoup plus lent de récupérer les rangs [100 000, 100 010] que les rangs [100, 110]. EDIT: avec PhpMyAdmin, un profiling de la requête donne ceci: ![]() Et encore, c'est ce que j'obtiens sur mon laptop (un core-i5 + SSD) ; le serveur de prod actuel étant un modeste Pentium Dual Core E5200 AVEC 2Go de RAM. Sachant que les joueurs consultent très régulièrement le classement mais que ce n'est pas la seule chose que le serveur ait à gérer (loin de là), vous comprenez aisément mon souci. Si vous aviez des conseils avisés, je serais évidemment preneur PS: Pour info le principal soft qui utilise le serveur MySQL est le serveur de jeu (en Java, via JDBC) ; il y a une connexion distincte pour chaque 'fonctionnalité' (gestion des utilisateurs + authentification, gestion des classements, ...) ; à terme, un site web devra pouvoir également fournir ces infos.
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
||||||
|
|
00
|
|
|
#2 | |||||||||
![]() ![]() |
Citation:
À ce niveau là ce n'est plus de l'amateurisme ! Citation:
Citation:
=> Met ID_PROFILE en premier dans la clé primaire. => Met un index individuel sur la seconde colonne de la clé primaire, donc maintenant sur PERIOD. Dans un index multi-colonnes, que ce soit la clé primaire ou un index ordinaire, seule la première colonne est indexée individuellement ; les suivantes ne le sont que par rapport à la précédente. Comme je pense que tu dois chercher autant par chacune des deux colonnes, il te faut cet index supplémentaire sur la seconde colonne de la clé primaire. Quel est l'intérêt d'ajouter SCORE dans un index multi-colonnes par rapport à la clé primaire ? Citation:
Code :
La syntaxe normalisée depuis 1992 pour les jointures utilise l'opérateur JOIN ! ![]() Citation:
http://sqlpro.developpez.com/cours/quoi-indexer/ http://sqlpro.developpez.com/optimis...ntenanceIndex/ http://sqlpro.developpez.com/optimisation/indexation/ http://sqlpro.developpez.com/optimis...ntenanceIndex/
__________________
Philippe Leménager. Ingénieur d'étude à l'École Nationale de Formation Agronomique. Mon blog sur la conception des BDD, le langage SQL, le PHP avec Zend Framework... « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau) À la maison comme au bureau, j'utilise Mandriva Linux ou Mageïa ! Soutenons l'industrie logicielle française ! Linuxiens, comptez-vous ! |
|||||||||
|
00
|
|
|
#3 | ||||||||||
![]() ![]() Inscription : décembre 2006 Messages : 1 567 ![]() |
Tout d'abord merci pour avoir pris la peine de répondre à mon roman-fleuve
Citation:
Citation:
Citation:
Mais vu mon (non) niveau en BdD, je pense qu'il vaut mieux que je te fasse confiance ![]() Citation:
Citation:
Citation:
Citation:
J'ai également quelques spécificités pour le serveur (qui se doit d'être relativement générique par rapport à la structure de la BdD, c'est un peu long à expliquer), mais merci pour l'info, je porterai une attention particulière à cette histoire de '*'. Citation:
Et merci pour les liens. Questions subsidiares: 1) j'effectue différentes requêtes pour différents besoins ; certaines ne sont pas critiques (genre récupérer le classement), mais d'autres ont besoin d'être traitées en priorité (authentification par exemple) ; les LOW_PRIORITY et HIGH_PRIORITY peuvent-ils m'aider de façon efficace ? A défaut: - peut-on définir plusieurs utilisateurs sur ma BdD avec différents niveaux de priorité - éventuellement lancer deux démons de deux BdD sur le serveur, celle avec les requêtes 'non prioritaires' étant une réplication (slave) de la première. Bref, quelles solutions s'offrent à moi pour prioriser mes requêtes ? 2) question configuration du démon Mysql, j'imagine que le plus important est de laisser un maximum de RAM à MySql pour qu'il mette en cache un maximum de choses. Dois-je tripoter les variables de mon '/etc/mysql/my.cnf' ou il est capable de s'adapter tout seul en fonction de la mémoire inutiliée sur la machine (ce qui ne semble pas être le cas, vue la RAM non utilisée actuellement) ? Genre: Code :
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
||||||||||
|
|
00
|
|
|
#4 | ||||||||||
![]() ![]() |
Citation:
Donc, bis repetita : Citation:
Citation:
En plus, ID_PROFILE sert dans une condition de jointure ! Raison de plus pour mettre cette colonne en premier afin qu'elle ait son index individuel et pour mettre un index individuel sur la période. Quoique si en fait tu cherches toujours joueur + période, l'index individuel sur la période devient inutile. Mais si tu cherches aussi la période sans le joueur, alors il te faut l'index individuel. Citation:
Code :
Citation:
Même avec 150 000 joueurs, une requête pour chercher un joueur lors de son authentification ne doit prendre qu'une toute petite fraction de seconde. Avant d'envisager ces choses compliquées que je n'ai moi-même jamais mises en oeuvre, teste sans ! Citation:
Ce qui est sûr, c'est qu'un serveur de bases de données doit être généreusement pourvu en mémoire vive car le SGBD doit travailler quasi exclusivement avec.
__________________
Philippe Leménager. Ingénieur d'étude à l'École Nationale de Formation Agronomique. Mon blog sur la conception des BDD, le langage SQL, le PHP avec Zend Framework... « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau) À la maison comme au bureau, j'utilise Mandriva Linux ou Mageïa ! Soutenons l'industrie logicielle française ! Linuxiens, comptez-vous ! |
||||||||||
|
00
|
|
|
#5 | |||
![]() ![]() Inscription : décembre 2006 Messages : 1 567 ![]() |
Citation:
Citation:
Mes questions portent en fait sur l'implémentation en cours d'une 'v2' de mon serveur actuel. Citation:
Dans mon cas, j'ai 2Go de RAM pour le serveur. Admettons que le système + mon démon de serveur de jeu mangent en tout 300Mo de RAM. Ce serait bête qu'à cause d'une bêtise de configuration, le serveur MySQL se limite à quelques centaines de Mo alors que je pourrais lui filer sans problème 1.5Go pour lui tout seul. D'où ma question de savoir si les valeurs exprimées dans le fichier de configuration sont des valeurs 'minimales' (que le serveur MySQL peut dépasser) ou si ce sont des valeurs 'plafond' qu'il ne dépassera pas même s'il reste de la RAM inutilisée).
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|||
|
|
00
|
|
|
#6 | |
![]() ![]() |
Citation:
Si la durée moyenne d'une requête est de 10 millisecondes, le serveur peut exécuter 100 requêtes par seconde. Avec 5 millions de parties en 1 mois, ça te fait une moyenne de 5M / 30 / 24 / 3600 = 1,92 partie par seconde. Même avec plusieurs requêtes pour une partie, je pense que tu n'auras pas de problème de ce côté. Pour le reste, je m'en remets à plus spécialistes que moi. Au fait, c'est quoi ton jeu ? Celui de ta signature ? BounceBox ?
__________________
Philippe Leménager. Ingénieur d'étude à l'École Nationale de Formation Agronomique. Mon blog sur la conception des BDD, le langage SQL, le PHP avec Zend Framework... « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau) À la maison comme au bureau, j'utilise Mandriva Linux ou Mageïa ! Soutenons l'industrie logicielle française ! Linuxiens, comptez-vous ! |
|
|
00
|
|
|
#7 | |||
![]() ![]() Inscription : décembre 2006 Messages : 1 567 ![]() |
Citation:
Citation:
Citation:
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|||
|
|
00
|
|
|
#8 | ||
![]() ![]() |
Citation:
Lis notamment le lien que je t'ai donné sur une étude d'optimisation par l'exemple. Je crois que c'est dans ce document que SQLPro démontre comment réduire de plus de 90% le temps d'exécution d'une requête. Ça s'applique à SQL Server et certaines choses ne sont pas applicables à MySQL mais d'autres telles que celles que je t'ai dites sont tout à fait applicables. Citation:
J'irai jeter un oeil à ton jeu, même si je préfère jouer sur vrai billard... carambole, pas américain !
__________________
Philippe Leménager. Ingénieur d'étude à l'École Nationale de Formation Agronomique. Mon blog sur la conception des BDD, le langage SQL, le PHP avec Zend Framework... « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau) À la maison comme au bureau, j'utilise Mandriva Linux ou Mageïa ! Soutenons l'industrie logicielle française ! Linuxiens, comptez-vous ! |
||
|
00
|
|
|
#9 | |||||
![]() ![]() Inscription : décembre 2006 Messages : 1 567 ![]() |
Citation:
Grosso modo, même chose si je réduis le nombre de champs récupérés dans la jointure avec 'profiles' (par exemple en remplaçant p.* par un simple p.PSEUDO). Code :
Première surprise: si je supprime complètement la jointure avec 'profiles' pour ne travailler que sur la table 'charts_weekly', la requête prend alors trois fois moins de temps (200ms environ). Deuxième surprise: j'ai poussé la logique un peu plus loin en séparant clairement les deux requêtes: d'abord un SELECT sur charts_weekly dont le résultat est repris et joint ensuite dans un second SELECT sur profiles. Cette requête (ci-dessous) qui retourne exactement le même résultat que le tout premier test tourne également aux alentours de 200ms. J'ai donc une réduction du temps d'exécution d'un facteur 3 juste pour une réécriture de la requête ! Code :
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|||||
|
|
00
|
|
|
#10 |
![]() ![]() Inscription : décembre 2006 Messages : 1 567 ![]() |
Je reviens vers vous pour deux questions supplémentaires:
1/ MySQL semble supporter la partitionnement de tables depuis la version 5.1 ; je me dis que ça pourrait être plus intelligent de regrouper l'ensemble de mes tables (charts_yearly, charts_monthly, ...) dans une seule table partitionnée (pour les perfs). Cela a-t-il des conséquences (genre sur les locks, les perfs, ...) ? 2/ Admettons qu'on n'aie plus un seul jeu mais plusieurs qui partagent le même serveur physique ; vaut-il mieux avoir un démon MySQL distinct pour chaque jeu (et donc chaque base) ou bien vaut-il mieux avoir un seul démon MySQL qui héberge toutes les bases ? Quels avantages / inconvénients dans chaque cas ? Merci d'avance
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|
|
00
|
Copyright © 2000-2012 - www.developpez.com