Qu'est-ce qui qualifie «trop de requêtes de base de données» dans le code?

17

C'est une discussion moi-même et certains de mes collègues ont et ont pensé que je viendrais ici et voir s'il y avait un consensus général à ce sujet.

Il se résume essentiellement aux 2 avis suivants sur les appels de base de données: 1. Faites un grand appel pour obtenir tout ce qui peut être nécessaire pour réduire le nombre d'appels de base de données 2. Faites des appels séparés plus petits en fonction de ce qui est demandé pour réduire la taille de Appels DB

Là où cela entre particulièrement en jeu, c'est dans le code commun. Nous allons utiliser l'exemple d'une classe Employee car c'est assez simple.

Supposons que votre classe Employé possède 10 attributs de valeur (prénom, nom, embauché, etc.), puis 2 attributs de classe ... 1 pointant vers une classe Département, puis 1 superviseur qui pointe vers un autre objet Employé.

Dans l'état d'esprit n ° 1, vous effectuez un appel qui renvoie les données Employé ainsi que les champs nécessaires pour remplir les attributs Département et Superviseur ... ou au moins les champs les plus souvent utilisés à partir de ces sous-objets.

Dans l'état d'esprit n ° 2, vous remplissez uniquement l'objet Employé au début, puis vous remplissez uniquement les objets Département et Superviseur si et quand ils sont réellement demandés.

La position de 2 est assez simple ... minimisez la taille des demandes et le nombre d'objets de base de données à frapper chaque fois qu'une de ces demandes est effectuée. La position # 1 est que même si elle pouvait être implémentée correctement, le simple fait que le code devrait établir plusieurs connexions va exercer une pression supplémentaire sur la connexion entre le serveur Web et la base de données au lieu de la réduire.

La force motrice derrière cette recherche est que la quantité de trafic entre notre serveur Web et le serveur de base de données devient incontrôlable.

user107775
la source
7
D'après mon expérience, il n'y a pas de "bonne réponse" à cela. Il y a un équilibre entre la latence et le débit. Une faible latence peut tolérer de nombreuses petites requêtes ou même une grande; cependant, les liens à latence élevée ont tendance à mieux déplacer beaucoup de données à la fois. Néanmoins, si le débit est faible dans une configuration à latence élevée, il vaut mieux aller chercher de plus petits morceaux pour être plus réactif.
3
Probablement lié au problème n + 1 stackoverflow.com/questions/97197/…
Valera Kolupaev
@Valera: pour plus de commodité, voici le lien publié sur cette question: realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=n1selects
rwong
4
"la quantité de trafic entre notre serveur Web et notre serveur de base de données devient incontrôlable." Qu'est-ce que ça veut dire? Pouvez-vous être précis sur le vrai problème? Avez-vous des problèmes de performances? Avez-vous fait le profilage et la mesure? Veuillez fournir les résultats réels des mesures réelles dans le cadre de la question. Sinon, nous devinons juste.
S.Lott

Réponses:

8

Si la force motrice derrière cette question est trop de trafic, avez-vous étudié la mise en cache des objets fréquemment utilisés? Par exemple: après avoir obtenu les objets Employé et Département et Superviseur, il serait peut-être judicieux de leur ajouter un cache afin que s'ils sont à nouveau demandés dans un proche avenir, ils soient déjà dans le cache et n'aient pas besoin d'être récupérés encore. Bien sûr, le cache devra laisser expirer les objets rarement utilisés et devra également pouvoir supprimer les objets qui ont été modifiés par l'application et enregistrés de nouveau dans la base de données.

Selon le langage et les frameworks que vous utilisez, il peut déjà y avoir un framework de mise en cache qui peut faire une partie (ou la plupart) de ce dont vous avez besoin. Si vous utilisez Java, vous pouvez consulter le cache Apache Commons (je ne l'ai pas utilisé depuis un certain temps, et même s'il semble dormant, il est toujours disponible et il était assez décent la dernière fois que je l'ai utilisé).

FrustratedWithFormsDesigner
la source
3

Optez toujours pour la lisibilité et la clarté la première fois que vous écrivez quelque chose. Vous pouvez ensuite refaçonner si et quand vous en avez besoin. Effectuez des tests de charge pour trouver les goulots d'étranglement, dans de nombreux cas, ce n'est pas le nombre d'appels à l'origine du problème, mais ceux qui sont mal écrits.

Quant à ce qui en classe trop, cela dépend de l'application. Pour la plupart des applications Web, tout ce qui est inférieur à 30 secondes est à peu près acceptable. Je parlerais à vos utilisateurs de leurs attentes.

Tom Squires
la source
Qu'est-ce qui constitue un appel db mal écrit?
nu everest
3

Votre question semble basée sur l'hypothèse que vous devez deviner quelles données seront nécessaires pour une page donnée. Ce n'est pas le cas. Ce n'est pas aussi simple que l'approche naïve, mais vous pouvez concevoir votre code afin que vous sachiez si vous allez avoir besoin d'attributs de département ou de superviseur avant de faire des appels à la base de données.

Karl Bielefeldt
la source
3

Ce sont les règles que j'utilise, peut-être qu'elles vous seront utiles.

  1. Mesurez d'abord! Je ne regarderai même pas le code qui "pourrait être lent" à moins que je puisse réellement voir le trafic circuler vers cette ressource et que cette ressource répond lentement.
  2. 1 requête = K requêtes. Le nombre de fois que je parle à la base de données est entièrement déterminé par le type de ressource demandée; et jamais par la nature de la demande ou de l'état de cette ressource; Dans votre exemple, il s'agit probablement d'au plus 3 requêtes: 1 pour les employés, 1 pour les départements et 1 pour les superviseurs; Peu importe le nombre de chacun d'entre eux.
  3. Ne demandez pas ce que vous n'utiliserez pas . S'il s'agit de HTTP dont nous parlons, il n'y a aucun sens à interroger les données pour plus tard; il n'y a pas plus tard; chaque demande part d'une table rase. Parfois, j'ai besoin de la plupart des colonnes d'une table, mais à l'occasion, je n'en ai besoin que d'une ou deux; quand je connais exactement les champs dont j'ai besoin, je demanderai juste cela.
  4. Jetez le matériel au problème. Les serveurs sont bon marché; Parfois, vous pouvez obtenir suffisamment de performances simplement en déplaçant la base de données vers une boîte plus robuste; ou envoyer des requêtes à une réplique en lecture seule.
  5. Commencez par invalider le cache, puis implémentez la mise en cache. L'envie de mettre des données souvent utilisées ou difficiles à interroger dans un cache est forte; mais trop souvent, l'expulsion des données inutilisées ou l'expiration des données remplacées est ignorée. Si vous savez comment extraire des données du cache; alors vous êtes sûr de le mettre dans le cache; S'il s'avère plus coûteux d'invalider le cache que de simplement faire la requête; alors vous n'aviez pas besoin d'un cache.
SingleNegationElimination
la source
2

Les deux stratégies ici sont parfaitement valables. Il ya des avantages et des inconvénients pour chacun:

Un appel pour les 3 objets:

  • fonctionnera plus rapidement
  • vous obtiendrez exactement ce dont vous avez besoin dans le cas où vous en avez besoin
  • ne sera probablement utilisable que dans un seul cas (ce peut être un cas très courant cependant)
  • sera plus difficile à entretenir
  • devra être maintenue plus souvent (car elle changera si l'un des schémas des 3 objets ou les données nécessaires changent)

Un appel par objet (3 appels au total)

  • Vous donne un appel général pour remplir une seule instance de chaque type d'objet; ils peuvent ensuite être utilisés indépendamment
  • Sera plus facile à gérer car la structure de la requête sera plus simple.
  • Sera plus lent (pas nécessairement 3 fois plus lent, mais les frais généraux sont augmentés pour les mêmes données)
  • Peut provoquer des problèmes avec la récupération de données inutiles (extraire l'enregistrement entier lorsque vous avez besoin d'un champ est un gaspillage)
  • Peut provoquer N + 1 problèmes lorsqu'une relation plusieurs-à-un existe, si la requête à enregistrement unique est envoyée N fois, une par enregistrement dans la collection.
KeithS
la source
En réponse à quelques-unes de vos préoccupations (n ​​° 3 et 5 dans la deuxième liste) ... Et si le superviseur et le service ne sont utilisés que 1/3 (ou moins) du temps? Que faire si le code a été conçu pour obtenir tous les enfants dès que l'objet List <> codé pour les contenir a été référencé pour la première fois? ... est-ce que cela soulagerait la plupart de la méfiance?
user107775
Si les objets auxiliaires ne sont que rarement nécessaires, dans le cas général, cela fonctionnera plus rapidement (moins de données à récupérer) mais le pire des cas sera plus lent (mêmes données ou plus récupérées, en utilisant trois fois la surcharge de communication de votre ordinateur). En ce qui concerne le problème N + 1, vous devez simplement être en mesure d'architecturer la requête qui récupère une liste d'objets pour pouvoir le faire en fonction de la clé étrangère du côté "un" de la relation, puis tirer plusieurs lignes sur le résultat de la requête. Vous ne pouvez pas utiliser une version de la requête qui doit avoir la clé primaire de l'enregistrement.
KeithS
1

Pour moi, trop de demandes de base de données font plus de demandes que nécessaire pour charger les données dont vous avez besoin à un moment donné.

Donc, si vous n'avez pas besoin des données, ne perdez pas de mémoire pour éviter un deuxième voyage plus tard. Mais si vous avez besoin de la quantité de données, vous devez minimiser les appels vers la base de données.

Ayez donc les deux options et utilisez chacune là où la situation l'exige.

EDIT: Gardez à l'esprit que cela dépend également de votre situation. Si c'est une WebApp par exemple, vous devriez avoir des considérations différentes que si c'est une application de bureau accédant à la base de données au sein de votre réseau, par opposition à l'ensemble du Web pour la WepApp.

AJC
la source
Qu'en est-il si vous écrivez du code commun et que vous n'êtes pas sûr de la manière dont votre code sera utilisé. Peut-être que vous n'envisageriez jamais quelqu'un qui n'a pas besoin du superviseur, mais il s'avère que l'application sur laquelle vous travaillez est la seule qui en a besoin. Bien sûr, vous pouvez écrire des fonctions séparées ... une pour ne pas l'inclure et une autre pour l'inclure, mais à quel moment votre code commun commence-t-il à nécessiter trop de connaissances détaillées pour être utilisé?
user107775
@ user107775 J'écris habituellement seulement deux fonctions pour chaque cas; un qui renvoie uniquement les valeurs de propriété et un qui renvoie la classe avec toutes les classes liées. C'est parce que la plupart du temps, vous n'avez besoin que des propriétés. De cette façon, vous n'avez pas besoin de connaissances détaillées, juste l'un obtient les bases et l'autre tout. Je trouve que c'est un équilibre raisonnable. (Cependant, certains cas spécifiques appellent une optimisation plus poussée, mais c'est au cas par cas).
AJC
1

Se connecter à la base de données, envoyer la demande et la faire analyser prend généralement beaucoup de temps par rapport à la récupération des résultats, de sorte que la tendance générale est de concaténer autant de requêtes que possible en une seule demande.

Pourtant, faire tout cela d'un seul coup rendra le code impossible à maintenir. Au lieu de cela, il est généralement atteint par une couche d'abstraction supplémentaire: le code planifie plusieurs demandes au fur et à mesure de leurs besoins, puis le moteur analyse cela comme une grande demande (éventuellement en utilisant du cache en cours de route), puis les réponses sont envoyées selon les besoins.

Bien sûr, tous ne peuvent pas toujours être récupérés dans une seule requête - vous aurez souvent une requête qui fournit les données nécessaires à la construction de la prochaine requête, vous devrez donc la répéter. Il est toujours préférable d'échelonner des paquets de requêtes et d'en effectuer autant que possible en même temps que des centaines de petites prises de vue dans la base de données.

Donc, planifiez ce dont vous avez besoin, demandez-le et récupérez-le, si plus est nécessaire, demandez-le et récupérez-le à nouveau, puis utilisez les données pour générer du contenu. Évitez absolument d'utiliser des requêtes de base de données comme l'initialisation de variables locales dispersées dans tout le code.

SF.
la source
1

Nous ne savons pas assez sur votre candidature pour savoir quel choix vous êtes coupable d'optimiser trop tôt. À quelle fréquence les données du superviseur sont-elles utilisées? On dirait que cela pourrait être un gaspillage, mais nous ne savons pas. Si vous les gardez séparés, vous pourrez peut-être surveiller votre système pour voir à quelle fréquence ils finissent par être utilisés ensemble. Vous pouvez alors décider de les combiner en un seul appel. Sinon, si vous commencez à créer un goulot d'étranglement avec ce gros appel, où commencez-vous à dépanner? Difficile d'identifier ce qui a du sens à omettre. D'autres champs de données peuvent s'ajouter à ce processus.

Il serait intéressant de savoir dans quelle mesure cela provient de la mémoire de la base de données par rapport au disque. Rien ne me fait sentir que le service est plus ou moins susceptible de changer par rapport à l'adresse.

JeffO
la source