paint-brush
7 meilleures pratiques d'optimisation de base de données pour les développeurs Djangoby@pragativerma
4,879
4,879

7 meilleures pratiques d'optimisation de base de données pour les développeurs Django

Pragati Verma9m2022/08/30
Read on Terminal Reader
Read this story w/o Javascript

La gestion de base de données est l'un des aspects les plus cruciaux du développement backend. Une base de données correctement optimisée peut aider à réduire le temps de réponse et donc conduire à une meilleure expérience utilisateur. Comprendre les ensembles de requêtes dans Django est la clé de l'optimisation, par conséquent, souvenez-vous de ce qui suit : Les ensembles de requêtes sont paresseux, ce qui signifie qu'aucune requête de base de données correspondante n'est faite jusqu'à ce que vous effectuiez certaines actions sur l'ensemble de requêtes, comme une itération dessus. L'indexation de base de données est une technique permettant d'accélérer les requêtes lors de la récupération des enregistrements d'une base de données.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - 7 meilleures pratiques d'optimisation de base de données pour les développeurs Django
Pragati Verma HackerNoon profile picture


La gestion de base de données est l'un des aspects les plus cruciaux du développement backend. Une base de données correctement optimisée peut aider à réduire le temps de réponse et donc conduire à une meilleure expérience utilisateur.


Dans cet article, nous discuterons des moyens d'optimiser la base de données pour la vitesse dans les applications Django. Cependant, nous n'allons pas approfondir chaque concept individuellement, veuillez donc vous référer à la documentation officielle de Django pour plus de détails.


Comprendre les requêtes dans Django

Comprendre les ensembles de requêtes dans Django est la clé de l'optimisation, par conséquent, n'oubliez pas ce qui suit :


  • Les ensembles de requêtes sont paresseux, ce qui signifie qu'aucune demande de base de données correspondante n'est effectuée tant que vous n'avez pas effectué certaines actions sur l'ensemble de requêtes, telles que l'itération sur celui-ci.
  • Limitez toujours le résultat d'une requête de base de données en spécifiant le nombre de valeurs à renvoyer.
  • Dans Django, les ensembles de requêtes peuvent être évalués par itération, découpage, mise en cache et méthodes python telles que len() , count() etc. Assurez-vous de les utiliser au mieux.
  • Les ensembles de requêtes Django sont mis en cache, de sorte que si vous réutilisez le même ensemble de requêtes, plusieurs requêtes de base de données ne seront pas effectuées, minimisant ainsi l'accès à la base de données.
  • Récupérez tout ce dont vous auriez besoin en même temps, mais assurez-vous de ne récupérer que ce dont vous avez besoin.


Optimisation des requêtes dans Django

Indexation de la base de données

L'indexation de base de données est une technique permettant d'accélérer les requêtes lors de la récupération des enregistrements d'une base de données. Au fur et à mesure que la taille de l'application augmente, elle peut ralentir et les utilisateurs le remarqueront car il faudra beaucoup plus de temps pour obtenir les données requises. Ainsi, l'indexation est une opération non négociable lorsque l'on travaille avec de grandes bases de données qui génèrent un grand volume de données.


L'indexation est une méthode de tri d'un grand nombre de données en fonction de divers champs. Lorsque vous créez un index sur un champ dans une base de données, vous créez une autre structure de données qui contient la valeur du champ ainsi qu'un pointeur vers l'enregistrement auquel il est lié. Cette structure d'index est ensuite triée, rendant les recherches binaires possibles.


Par exemple, voici un modèle Django nommé Sale :


 # models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, ) charged_amount = models.PositiveIntegerField()


L'indexation de la base de données peut être ajoutée à un champ particulier lors de la définition d'un modèle Django comme suit :


 # models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField()


Si vous exécutez les migrations pour ce modèle, Django créera un index de base de données sur la table Sales, et il sera verrouillé jusqu'à ce que l'index soit terminé. Sur une configuration de développement local, avec une petite quantité de données et très peu de connexions, cette migration peut sembler instantanée, mais lorsque nous parlons de l'environnement de production, il existe de grands ensembles de données avec de nombreuses connexions simultanées qui peuvent entraîner des temps d'arrêt comme l'obtention d'un verrou et la création un index de base de données peut prendre beaucoup de temps.


Vous pouvez également créer un index unique pour deux champs, comme indiqué ci-dessous :


 # models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField() class Meta: indexes = [ ["sold_at", "charged_amount"]]


Mise en cache de la base de données

La mise en cache de la base de données est l'une des meilleures approches pour obtenir une réponse rapide d'une base de données. Il garantit que moins d'appels sont effectués vers la base de données, évitant ainsi la surcharge. Une opération de mise en cache standard suit la structure ci-dessous :



Django fournit un mécanisme de mise en cache qui peut utiliser différents backends de mise en cache comme Memcached et Redis qui vous permettent d'éviter d'exécuter les mêmes requêtes plusieurs fois.


Memcached est un système en mémoire open source qui garantit de fournir des résultats mis en cache en moins d'une milliseconde. Il est simple à configurer et à mettre à l'échelle. Redis, d'autre part, est une solution de mise en cache open source avec des caractéristiques similaires à Memcached. La plupart des applications hors ligne utilisent des données précédemment mises en cache, ce qui implique que la majorité des requêtes n'atteignent jamais la base de données.


Les sessions utilisateur doivent être enregistrées dans un cache de votre application Django, et comme Redis conserve les données sur disque, toutes les sessions des utilisateurs connectés proviennent du cache plutôt que de la base de données.


Pour utiliser Memcache avec Django, nous devons définir les éléments suivants :

  • BACKEND : Pour définir le backend du cache à utiliser.
  • LOCATION : valeurs ip:portip est l'adresse IP du démon Memcached et port est le port sur lequel Memcached s'exécute, ou l'URL pointant vers votre instance Redis, en utilisant le schéma approprié.


Pour activer la mise en cache de la base de données avec Memcached, installez pymemcache à l'aide de pip à l'aide de la commande suivante :


 pip install pymemcache


Ensuite, vous pouvez configurer les paramètres de cache dans votre settings.py comme suit :


 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', } }


Dans l'exemple ci-dessus, Memcached s'exécute sur le port 11211 de l'hôte local (127.0.0.1), à l'aide de la liaison pymemcache :


De même, pour activer la mise en cache de la base de données à l'aide de Redis, installez Redis à l'aide de pip à l'aide de la commande ci-dessous :


 pip install redis


Configurez ensuite les paramètres de cache dans votre settings.py en ajoutant le code suivant :


 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379', } }


Memcached et Redis peuvent également être utilisés pour stocker les jetons d'authentification des utilisateurs. Étant donné que chaque personne qui se connecte doit fournir un jeton, toutes ces procédures peuvent entraîner une surcharge importante de la base de données. L'utilisation de jetons mis en cache se traduira par un accès à la base de données considérablement plus rapide.


Utilisation de l'itérateur lorsque cela est possible

Un jeu de requêtes dans Django, généralement, met en cache son résultat lorsque l'évaluation se produit et pour toute opération ultérieure avec ce jeu de requêtes, il vérifie d'abord s'il y a des résultats en cache. Cependant, lorsque vous utilisez iterator() , il ne vérifie pas le cache et lit les résultats directement à partir de la base de données, ni n'enregistre les résultats dans le jeu de requêtes.


Maintenant, vous devez vous demander comment cela est utile. Considérez un ensemble de requêtes qui renvoie un grand nombre d'objets avec beaucoup de mémoire à mettre en cache mais qui ne doit être utilisé qu'une seule fois, dans ce cas, vous devez utiliser un iterator() .


Par exemple, dans le code suivant, tous les enregistrements seront extraits de la base de données, puis chargés dans la mémoire, puis nous parcourrons chacun :


 queryset = Product.objects.all() for each in queryset: do_something(each)


Alors que si nous utilisons iterator() , Django maintiendra la connexion SQL ouverte et lira chaque enregistrement, et appellera do_something() avant de lire l'enregistrement suivant :


 queryset = Product.objects.all().iterator() for each in queryset: do_something(each)


Utilisation d'une connexion à une base de données de persistance

Django crée une nouvelle connexion à la base de données pour chaque requête et la ferme une fois la requête terminée. Ce comportement est causé par CONN_MAX_AGE , qui a une valeur par défaut de 0. Mais combien de temps doit-il être défini ? Cela est déterminé par le volume de trafic sur votre site ; plus le volume est élevé, plus il faut de secondes pour maintenir la connexion. Il est généralement recommandé de commencer avec un petit nombre, tel que 60.


Vous devez encapsuler vos options supplémentaires dans OPTIONS , comme indiqué dans le Documentation :


 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dashboard', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'CONN_MAX_AGE': '60', } } }


Utilisation d'expressions de requête

Les expressions de requête définissent une valeur ou un calcul pouvant être utilisé dans une opération de mise à jour, de création, de filtrage, de tri, d'annotation ou d'agrégation.


Une expression de requête intégrée couramment utilisée dans Django est l'expression F. Voyons comment cela fonctionne et peut être utile.


Remarque : Ces expressions sont définies dans django.db.models.expressions et django.db.models.aggregates , mais pour plus de commodité, elles sont disponibles et généralement importées de django.db.models .


Expression F

Dans l'API Django Queryset, les expressions F() sont utilisées pour faire directement référence aux valeurs des champs du modèle. Il vous permet de vous référer aux valeurs de champ du modèle et d'effectuer des actions de base de données sur celles-ci sans avoir à les extraire de la base de données et dans la mémoire Python. Au lieu de cela, Django utilise l'objet F() pour produire une phrase SQL qui définit l'activité de base de données nécessaire.


Par exemple, disons que nous voulons augmenter le prix de tous les produits de 20 %, alors le code ressemblera à ceci :


 products = Product.objects.all() for product in products: product.price *= 1.2 product.save()


Cependant, si nous utilisons F() , nous pouvons le faire en une seule requête comme suit :


 from django.db.models import F Product.objects.update(price=F('price') * 1.2)


Utiliser select_related() et prefetch_related()

Django fournit select_related() et prefetch_related() pour optimiser vos ensembles de requêtes en minimisant le nombre de requêtes de base de données.


Selon la documentation officielle de Django :


select_related() "suit" les relations de clé étrangère, en sélectionnant des données d'objets connexes supplémentaires lorsqu'il exécute sa requête.


prefetch_related() effectue une recherche distincte pour chaque relation et effectue la « jonction » en Python.


select_related()

Nous utilisons select_related() lorsque l'élément à sélectionner est un objet unique, ce qui signifie un ForeignKey avant, OneToOne et un champ OneToOne arrière.


Vous pouvez utiliser select_related() pour créer une seule requête qui renvoie tous les objets liés pour une seule instance pour les connexions un-plusieurs et un-à-un. Lorsque la requête est exécutée, select_related() récupère toutes les données d'objets connexes supplémentaires à partir des relations de clé étrangère.


select_related() fonctionne en générant une jointure SQL et inclut les colonnes de l'objet associé dans l'expression SELECT . Par conséquent, select_related() renvoie les éléments associés dans la même requête de base de données.


Bien que select_related() produise une requête plus sophistiquée, les données acquises sont mises en cache, ainsi la gestion des données obtenues ne nécessite aucune demande de base de données supplémentaire.


La syntaxe ressemble simplement à ceci :


 queryset = Tweet.objects.select_related('owner').all()


prefetch_related()

En revanche, prefetch_related() est utilisé pour les connexions plusieurs à plusieurs et plusieurs à un. Il produit une seule requête qui inclut tous les modèles et les filtres sont donnés dans la requête.


La syntaxe ressemble simplement à ceci :


 Book.objects.prefetch_related('author').get(id=1).author.first_name


REMARQUE : Les relations ManyToMany ne doivent pas être gérées à l'aide de SQL, car de nombreux problèmes de performances peuvent survenir lors de la gestion de tables volumineuses. C'est pourquoi la méthode prefetch_related joint les tables à l'intérieur de Python en évitant de faire de grandes jointures SQL.


Découvrez la différence entre select_related() et prefetch_related() en détail ici .


Utilisation bulk_create() et bulk_update()

bulk_create() est une méthode qui crée la liste d'objets fournie dans la base de données avec une seule requête. De même, bulk_update() est une méthode qui met à jour les champs donnés sur les instances de modèle fournies avec une seule requête.


Par exemple, si nous avons un modèle de messages comme indiqué ci-dessous :


 class Post(models.Model): title = models.CharField(max_length=300, unique=True) time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title


Maintenant, disons que nous voulons ajouter plusieurs enregistrements de données à ce modèle, nous pouvons alors utiliser bulk_create() comme ceci :


 #articles articles = [Post(title="Hello python"), Post(title="Hello django"), Post(title="Hello bulk")] #insert data Post.objects.bulk_create(articles)


Et la sortie ressemblerait à ceci :


 >>> Post.objects.all() <QuerySet [<Post: Hello python>, <Post: Hello django>, <Post: Hello bulk>]>


Et si nous voulons mettre à jour les données, nous pouvons utiliser bulk_update() comme ceci :


 update_queries = [] a = Post.objects.get(id=14) b = Post.objects.get(id=15) c = Post.objects.get(id=16) #set update value a.title="Hello python updated" b.title="Hello django updated" c.title="Hello bulk updated" #append update_queries.extend((a, b, c)) Post.objects.bulk_update(update_queries, ['title'])


Et la sortie ressemblerait à ceci :


 >>> Post.objects.all() <QuerySet [<Post: Hello python updated>, <Post: Hello django updated>, <Post: Hello bulk updated>]>


Conclusion

Dans cet article, nous avons couvert les astuces pour optimiser les performances de la base de données, réduire les goulots d'étranglement et économiser les ressources dans une application Django.


J'espère que vous l'avez trouvé utile. Continue de lire!