6
votes

Quels sont les cordes / indices appropriés pour aider à faire des découvertes plus performantes dans les rails?

J'ai une configuration de données relationnelle relativement importante de 4 profondes comme ceci: xxx pré>

client_applications code>: (potentiellement 1 000 d'enregistrements)
- ... code>
- compte_id code>
- public_key code>
- supprimé_at code> p>

client_application_versions code>: (potentiellement 10 000 d'enregistrements)
- ... code>
- client_application_id code>
- public_key code>
- supprimé_at code> p>

cloud_logs code>: (potentiellement 1 000 000 d'enregistrements)
- ...
- client_application_version_id code>
- public_key code>
- supprimé_at code> p>

journaux code>: (potentiellement 1 000 000 000 d'enregistrements)
- ... code>
- cloud_log_id code>
- public_key code>
- time_stamp code>
- supprimé_at code> p>


Je suis toujours en développement, de sorte que la structure et la configuration ne sont pas définies dans la pierre, mais j'espère que c'est configuré OK. Utilisation des rails 3.2.11 et Innodb MySQL. La base de données est actuellement remplie d'un ensemble de données (comparé à la taille éventuelle de DB) de données ( LOGS CODE> A seulement 500 000 lignes) J'ai 4 requêtes scopées, dont 3 sont problématiques, pour récupérer des journaux.

  1. Première page de journaux, commandée par Timeestamp, limitée par Compte_id code>, Client_Application.public_key code>, client_aplication_key code> (plus de 100 secondes) li>
  2. Première page de journaux, commandé par Timeestamp, limitée par Compte_id code>, client_aplication.public_key code> (plus de 100 secondes) li>
  3. Première page de journaux, commandé par Timeestamp, limitée par compte_id code> (plus de 100 secondes) li>
  4. Première page de la première page des journaux classés par horodatage (~ 2 secondes) li> ol>

    J'utilise des collaborateurs pour faire ces appels pour faire ces appels: P>

    CLIENT_APPLICATIONS:
      PRIMARY KEY  (`id`),
      UNIQUE KEY `index_client_applications_on_key` (`key`),
      KEY `index_client_applications_on_account_id` (`account_id`),
      KEY `index_client_applications_on_deleted_at` (`deleted_at`),
      KEY `index_client_applications_on_public_key` (`public_key`)
    
    CLIENT_APPLICATION_VERSIONS:
      PRIMARY KEY  (`id`),
      KEY `index_client_application_versions_on_client_application_id` (`client_application_id`),
      KEY `index_client_application_versions_on_deleted_at` (`deleted_at`),
      KEY `index_client_application_versions_on_public_key` (`public_key`)
    
    CLOUD_LOGS:
      PRIMARY KEY  (`id`),
      KEY `index_cloud_logs_on_api_client_version_id` (`api_client_version_id`),
      KEY `index_cloud_logs_on_client_application_version_id` (`client_application_version_id`),
      KEY `index_cloud_logs_on_deleted_at` (`deleted_at`),
      KEY `index_cloud_logs_on_device_id` (`device_id`),
      KEY `index_cloud_logs_on_public_key` (`public_key`),
      KEY `index_cloud_logs_on_received_at` (`received_at`)
    
    LOGS:
      PRIMARY KEY  (`id`),
      KEY `index_logs_on_class_name` (`class_name`),
      KEY `index_logs_on_cloud_log_id_and_deleted_at_and_timestamp` (`cloud_log_id`,`deleted_at`,`timestamp`),
      KEY `index_logs_on_cloud_log_id_and_deleted_at` (`cloud_log_id`,`deleted_at`),
      KEY `index_logs_on_cloud_log_id` (`cloud_log_id`),
      KEY `index_logs_on_deleted_at` (`deleted_at`),
      KEY `index_logs_on_file_name` (`file_name`),
      KEY `index_logs_on_method_name` (`method_name`),
      KEY `index_logs_on_public_key` (`public_key`),
      KEY `index_logs_on_timestamp` USING BTREE (`timestamp`)
    


8 commentaires

Lorsque vous appelez effectivement les données, vous devez appeler votre portée, puis "Find_each", consultez la méthode AS API Doc. Il charge vos données par lots (par défaut: 1000). Cela empêche AR de charger toutes vos données en mémoire en même temps.


@ cpuguy83 est-ce différent de l'utilisation de la limite 100 offset 0 que j'ai maintenant uniquement charger un sous-ensemble limité d'enregistrements?


@ConeyBeare, UP et les votes préférés sont pas une raison pour ré-ouvrir une question ... le dernier est cependant.


Utilisation de FiltrySort Dans votre explication indique qu'il existe des colonnes TEXT / BLOB dans l'une des tables jointes ou les paramètres de mémoire de votre MySQL sont trop conservateurs. Vous voulez vous débarrasser de cela


J'ai 2 champs de texte dans l'ensemble de la DB, mais aucun d'entre eux ne fait partie des clauses de commande ou de condition ici. Pouvez-vous expliquer votre commentaire un peu plus loin, peut-être dans sa propre réponse?


Ce n'est pas ce que le fichier trit signifie MySQLperformanceBlog .COM / 2009/03/05 / ...


Avez-vous examiné à l'aide de procédures stockées? C'est génial pour ce genre de choses.


J'aimerais l'éviter que les rails ne disposent pas d'un grand soutien pour les procédures stockées.


7 Réponses :


0
votes

Malheureusement, mon expérience d'optimisation des rails a été utilisée avec PostgreSQL. La majeure partie ne s'applique probablement pas. J'ai quelques suggestions qui seront probablement applicables, cependant:

Essayez d'utiliser Joignées au lieu de Inclus dans vos scopes - Inclut est utilisé pour déclencher le chargement impatient - il est tout à fait possible que certains du ralentissement Vous voyez est que des modèles inutiles sont chargés. Même si ce n'est pas, en utilisant joint devrait plutôt produire une requête plus lisible - c'est inclut qui alias toutes vos colonnes comme "T2_R8", etc.

En outre, vous voudrez vous assurer que toutes les colonnes qui pourraient être filtrées sont indexées - de manière générale, les colonnes qui se terminent par _id vont potentiellement être référencées de cette manière et devrait probablement être indexé, ainsi que n'importe quel filtrage spécifiquement par les champs (comme client_application_version_key )


3 commentaires

Pour votre deuxième point, vous dites qu'ils devraient être combinés des indices? Comme client_application_id_and_public_key? Ils ont déjà chacun des indices sur les colonnes publique_key.


Ah, non, j'ai mal interprété la question légèrement - ce sont ceux que je voulais dire. Je ne sais pas réellement si la combinaison des indices vous aidera - cela ressent un peu trop de la base de données.


J'ai échangé la compensation des jointures et je n'ai sauté que quelques secondes, mais cela rend la SQL plus lisible. J'ai mis à jour le SQL et expliquer dans la question pour refléter cette



0
votes

J'écris cela une solution possible à ma propre question, dans l'espoir qu'une meilleure réponse viendra. Actuellement, la base de données est configurée complètement et par le livre relationnel.

ClientApplication         has_many => ClientApplicationVersions
ClientApplication         has_many => Logs
ClientApplicationVersions has_many => CloudLogs
ClientApplicationVersions has_many => Logs
CloudLogs                 has_many => Logs


1 commentaires

J'ai essayé chaque solution sur cette page et que ceci soit la seule solution qui m'a eu dans la plage MS, pas des secondes. ID et clés rarement si jamais changez, la dénormalisation était la meilleure approche pour ce type de trouvaille.



0
votes

essayer de répondre à chacune de vos questions:

  1. certainement! Tout ce que vous cherchez devriez probablement être indexé. Si ce n'est pas indexé, vous devez effectuer une numérisation de table complète. En plus des ID de l'association, qui auraient été créés lorsque vous avez effectué votre migration initiale si vous avez utilisé la fonction références code> dans create_table code>, vous recherchez au moins les éléments suivants:

    • Logs.Timestamp Li>
    • client_application_versions.public_key li>
    • client_applications.public_key li>
    • logs.deletted_at li> ul>

      Ceux-ci devraient probablement être tous indexés. Et bien sûr, si vous n'avez pas utilisé références code> lors de la définition de vos clés étrangères de l'association, puis ajoutez-les également. Il y a bien sûr un compromis avec des indices. Ils sont comme une magie pour des lectures, mais elles peuvent ralentir vos écritures de manière significative. Le degré auquel ils vous ralentissent ou vous accélérer sont probablement fortement dépendants de la base de données. P> li>

    • Je ne pense pas. Votre code de rails se ressemble de droit à moi. Le seul commentaire que j'ai est que périmètre code> est vraiment juste une manière abrégée de définir une fonction. Je pense qu'il serait plus facile de lire si vous venez de définir la fonction directement: P>

      ClientApplication.find_all_by_account_id(1).where(public_key: 'p0kZudG0').joins(:client_application_version).where("client_application_versions.public_key=?",'0HgoJRyE').logs.page(1)
      
    • Peut-être! Malheureusement, ce n'est pas une question facile à répondre car elle dépend vraiment de quoi ressemble les données. Avez-vous vraiment besoin de milliards de journaux dans la même table de base de données? Y a-t-il un moyen de briser naturellement les données peut-être dans différentes tables avec le même schéma? Vous voudrez peut-être également faire des recherches sur la base de données. P> li> ol>

      J'espère que cela aide. P>

      EDIT: stry> P>

      Pourquoi faites-vous la requête en direction de la plupart des douleurs? Avez-vous essayé de le transformer de: p> xxx pré>

      à quelque chose comme ceci: p> xxx pré>

      Vous devez définir des champs à faire cela plus lisible, mais j'espère que vous obtenez l'idée. P> p>


6 commentaires

1) Donc, j'ai déjà des indices sur tous ceux que vous avez mentionnés, comme indiqué dans l'explication, la requête est toujours en cours d'exécution d'une fichiers 2) J'utilise également le gemme has_scope, donc je ne me dérange pas la syntaxe de la portée 3) un "journal" est le niveau le plus bas que je peux briser ce modèle jusqu'à. Au fil du temps, je peux probablement mettre des journaux plus anciens dans une table différente, mais il y aura certainement un journal des journaux actuels, et cela ne devrait pas être lent pour seulement 500k.


Ah oui. Je ne vois pas de logs.Timestamps cependant qui est probablement important.


J'ai les combinés, y compris Cloud_Log_id_and_TimeStamp, donc si l'optimiseur de la requête MySQL choisit d'utiliser le cloud_log_id One et non le combiné, je pense que la racine du problème réside ailleurs. Je vais essayer cependant, mais je ne m'attends pas à ce que ce soit différent.


On dirait que je dispose déjà d'un index sur l'horodatage seul, alors MySQL choisit simplement de ne pas l'utiliser. Vous êtes votre édition sur «Douleur», je ne suis pas clair pourquoi vous pensez que ce serait plus rapide? Il me semble que vous faites maintenant 3 appels DB différents au lieu d'un.


Essaie. Je pense que ce ne serait que une requête (une fois que vous obtenez la syntaxe droite), mais que vous apportez plus d'appels, ce n'est pas nécessairement une mauvaise chose, surtout si cela évite de se joindre à des rangées de table dont vous n'avez pas besoin.


Eh bien, Arel compile ceci vers la même requête si je le retrouvaille dans le style arel. Sinon, cette approche prend 3 questions (une pour obtenir le client_application, une pour obtenir des versions, puis un rubis. Sélectionnez pour obtenir le bon, puis un autre pour obtenir les journaux) à 98 secondes.



0
votes

Lorsque je rencontre un problème avec la performance d'une requête comme celle-ci, je regarde ce que fait les rails et comprendre s'il y a un meilleur moyen d'obtenir ce que je veux. La plupart du temps, les requêtes des rails seront tout simplement bien, mais parfois, vous réalisez que vous pouvez obtenir ce que vous avez besoin d'une manière plus rapide / nettoyeuse.

Vous pourrez peut-être obtenir ce que vous voulez dans 2 questions, mais je commencerais en rompant les jointures et en voyant comment la requête effectue si vous vous nourrissez dans les données que vous receviez des jointures.

Avez-vous testé les résultats sans la limite et le décalage? Je voudrais exclure le tri et la partie limitante et observerais la performance. J'ai déjà connu de gros problèmes avec la limite et le décalage avant, et il existe des moyens d'accorder la manière dont MySQL peut gérer le tri en mémoire au lieu d'utiliser une table et des fichiers Temps comme le sont actuellement.

Modifier

Vous pouvez d'abord interroger pour les identifiants, puis interroger pour toutes les colonnes basées sur les identifiants.

Sélectionnez Journaux .Id à partir de Journaux Joindre intérieur Cloud_Logs sur Journaux . Cloud_Log_ID = cloud_logs . ID cloud_logs . client_application_version_id = 49 et (logs (logs) Et (cloud_logs.deletted_at est null) ordre par logs.TimeStamp Desc Limite 100

est-ce que la requête est rapide? (Il devrait être en mesure d'obtenir les identifiants de l'index sans numériser la table) Un changement plus invasif pourrait être de partitionner vos données au niveau de la DB, mais je pense qu'il est trop tôt pour suggérer cela.


5 commentaires

w / o limite de compensation est de 57 secondes. Sortir la commande est proche instantanée. Je ne peux pas vraiment faire soit en production car il y aura des milliers de lignes retournées sans la pagination, et le modèle doit être commandé par le plus récent.


J'ai essayé de la rompre comme Geoff mentionné auparavant, les journaux fetch sont toujours le problème. gist.github.com/4621738 Il est étranger environ 60 secondes ce matin, mais tous les tests identiques précédents avait couru à environ 100 secondes.


impressionnant! Maintenant, vous avez répondu à la question de savoir s'il s'agit d'un problème de carotte des rails. Il semblerait que ce ne soit pas une question de rails, mais plutôt un problème avec la manière d'optimiser MySQL lors de l'utilisation de limites et de compensation avec beaucoup de données. Maintenant, vous pouvez rechercher cela avec les mots-clés MySQL appropriés. Ceux-ci peuvent être bons démarrages: Slideshare.net/eeeaver/efficient-pagination-utilisation- mysql Expliqué. COM / 2009/10/23 / ...


La requête modifiée que vous avez présentée manque quelques-uns des éléments clés et rejoint, notamment le client_aplication_version.public_key et client_appount.account_id et client_Application.Public_key. Pensez-vous que la stratégie fonctionnerait toujours avec les jointures supplémentaires et où les champs?


J'asipé principalement de répondre à votre question sur les rails responsables et comment déterminer où le problème réside. Je pense que vous avez pu obtenir des rails devant, en supprimant la limite et en offset, et vous pouvez désormais creuser d'autres moyens de gérer ce problème commun. (qui ne sont pas liés aux champs / indices ou rails) Avez-vous pu obtenir une meilleure direction à travers les liens que j'ai fournis? (ou en creusant dans un réglage spécifique MySQL pour cela?)



1
votes

l'affichant avec une meilleure structure, la requête ressemble à ceci (déjà réorganisé) xxx pré>

Lorsque vous regardez Cav1 / CL1 et CAV2 / CL2, on peut voir, que cav2 et cl2 ne sont jamais utilisés. Il n'y a pas de filtre qui leur est mis à part de la déclaration sur. P>

Par conséquent, CAV1 est lié à la bonne accouplement, CAV2 n'est pas lié à un compte, et contient plutôt tous les comptes correspondants. Ce n'est pas un problème pour le résultat de la requête, mais pour la taille du tampon de jointure. P>

Suppression des jointures (et des parties d'entre eux) rendement: P>

alter table client_application_versions add key (`client_application_id`, `public_key`);


9 commentaires

L'exécution de cette façon comme SQL était définitivement une amélioration. Il court entre 36 et 45 secondes: GIST.GITUB.COM/4635180 Je ne pouvais pas le faire courir comme ceci en utilisant des rails c ... Seulement Find_By_SQL


Pouvez-vous fournir l'explication de la requête réduite?


Ah, n'a pas remarqué au début. Dans le gist, vous avez supprimé les signes de commentaire - . Veuillez supprimer les lignes ou ajouter les signes de commentaire. l'expliquer pour la requête résultante est l'une des choses utiles


Oh, je ne savais pas que c'étaient des signes de commentaire! Laissez-moi courir à nouveau, brb


Pouvez-vous fournir les définitions de la table pour cloud_logs, client_application_versions et client_applications? show create tabled_logs; Afficher la table Create # Cree Table Client_Application_versions; Afficher la table Créer une table Client_Applications; Edition: La partie inférieure avec les touches serait suffisante


Voici les définitions d'index actuelles pour toutes les tables pertinentes: Gist.github.com/4635448


ajouté une table d'alter ci-dessus


L'optimiseur choisit toujours de ne pas utiliser le nouvel Index: gist.github.com/4636692


Vous pouvez indiquer MySQL pour les utiliser. voir au dessus



0
votes

Votre index est défini de manière incorrecte index_logs_on_cloud_log_id_and_deletted_at_and_timestamp

La partie de vos requêtes qui prend trop de temps est la clause par , et que vous commandez par horodatage mais le mode horodatage est la dernière clé de votre index. Dans MySQL, des clés ultérieures dans un index ne seront pas utilisées pour optimiser l'ordre par à moins que les touches précédentes soient des constantes dans le où la clause . Voir http://dev.mysql.com/doc/ Refman / 5.0 / fr / Commande par-optimisation.html

Pour commencer, créez simplement un index sur horodatage et voyez si cela accélère vos requêtes.


1 commentaires

C'est intéressant. J'ai déjà un index sur Timeestamp et l'optimiseur choisit de ne pas l'utiliser. Voici les index: cloud.coneybeare.net/mr6s Si je devais réorganiser l'ordre de l'erreur incorrecte Index ci-dessus, que recommanderiez-vous?



0
votes

Eh bien, c'est un peu difficile, car pour avoir des performances, vous devrez sacrifier la lisibilité, ou l'inverse. Alors, répondant à vos questions:

  1. Les index sont une idée, mais le succès peut varier en fonction des tailles de table et de la fréquence de la fréquence et de la combinaison de clés des requêtes. Mais il me semble que vous interrogez la même chose, vous vous commandez juste différemment. Alors ... pourquoi ne pas utiliser de vues de dB? Leur mise en œuvre dans les rails suce, mais elle est utilisable: https://github.com/eladmeidar/plainviews
  2. (Oubliant si vous allez avec ma suggestion de vue) Oui, vous pouvez. N'utilisez pas de champs. Arel les a fabriqués pratiquement obsolètes et, dans votre exemple, le plus définitivement, car vous pouvez être aussi descriptif que pour l'arelle. Et juste une autre chose: n'écrivez pas SQL avec Arel. Vous manquerez beaucoup de goodies comme la renommée de tables pour s'adapter aux jointures et aux trucs, qui peuvent gâcher vos index. Quelque chose de plus comme ceci: p>

    YourObject.joins(:client_application).
               where(ClientApplication.arel_table[:public_key].eq(client_application_key))
    
  3. dépend de l'endroit où vous allez l'utiliser. Si la requête est noyau de votre fonctionnalité de l'application, vous devez étudier davantage de tâches alternatives de la rendre plus performante. Par exemple, les DBS fournissent de nombreuses fonctionnalités que les cadres Web (et en particulier les rails) ne préconisent pas, comme les vues mentionnées ou les procédures stockées. Comment pouvez-vous exploiter cela et maintenir votre code la lisibilité du code est le défi quotidien de nos collègues développeurs. li> ol>

    Mais j'ai toujours une question: pourquoi n'avez-vous pas utilisé mongodb? http://nosql.mypopecu.com/post/1016320617/mongodb-is -Web-échelle p> p>


1 commentaires

Et d'ailleurs, ne pas aller à la pleine arelle mentionnée au point 2 était peut-être la raison pour laquelle certaines jointures intérieures ont été répétées lors de votre premier essai (il suffit de lire des scones de manière plus approfondie).