DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Uncategorized»10 conseils pour optimiser les requêtes PostgreSQL dans votre projet Django
    Uncategorized

    10 conseils pour optimiser les requêtes PostgreSQL dans votre projet Django

    février 27, 2023
    10 conseils pour optimiser les requêtes PostgreSQL dans votre projet Django
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Cet article présente des techniques d’optimisation qui ont donné d’excellents résultats chez GitGuardian. Vous trouverez des techniques d’optimisation de base et avancées utilisant Django et PostgreSQL, mais la plupart des principes dont nous discutons sont suffisamment généraux pour être appliqués à d’autres frameworks ou bases de données relationnelles. Cependant, une compréhension de base des transactions de base de données et du fonctionnement des ORM est nécessaire pour en tirer le meilleur parti.

    Mais ne me croyez pas, testez-les vous-même ! Nous fournissons un Django Playground entièrement fonctionnel où vous pouvez exécuter toutes les requêtes présentées dans cet article. C’est ici.

    1. L’optimisation est un état d’esprit

    Les théories des bases de données fournissent de nombreuses bonnes pratiques que nous devrions toujours suivre… au début. Parce que, parfois, à mesure que l’utilisation de votre application augmente, ces pratiques peuvent se révéler insuffisantes ou ne pas fonctionner comme prévu.

    Rappelez-vous toujours qu’il n’y a pas de recette magique pour tirer le meilleur parti de votre base de données dans tous les cas. Comme vous le lirez plus loin dans cet article, certaines astuces seront super efficaces dans certains cas et ne fonctionneront pas bien dans d’autres. Au fur et à mesure que votre application continue de vivre (et, espérons-le, de croître), vous pouvez passer de l’une à l’autre, revenant à l’optimisation qui vous a rendu fier il y a quelques mois. Ne voyez pas cela comme un échec ; c’était utile pendant un moment. Ce qui est important ici, c’est de continuer à surveiller, tester, mesurer et améliorer.

    Ainsi, le premier et le plus important conseil est que l’optimisation n’est pas une tâche ponctuelle. C’est un processus sans fin.

    2. Une bonne méthode pour itérer rapidement

    Django ORM est un outil super pratique pour persister et interroger vos concepts métier en utilisant du code pythonic au lieu de SQL. Mais, vous devez voir le SQL généré pour comprendre comment votre code s’exécutera. De nombreux outils offrent cette capacité, mais lorsque vous recherchez la meilleure optimisation, plus vous restez proche de votre code, mieux c’est. Tester les requêtes directement dans Django Shell (ou un Notebook) peut enfin être le moyen le plus rapide d’itérer et de trouver la meilleure solution.

    Saviez-vous que Django vous permet d’afficher toutes les requêtes effectuées par un bloc de code ?

    Considérons le code suivant :

    from books.models import Person
    from django.db import connection, reset_queries 
    
    reset_queries() 
    qs = Person.objects.only("id") 
    person = qs.first() 
    print("SQL Query", qs[:10].query) 
    print("PostgreSQL query: ", connection.queries[0]) # needs DEBUG=True 
    print("pg explain analyze:", qs[:10].explain(ANALYZE=True))

    Et sa sortie :

    SQL Query SELECT "books_person"."id" FROM "books_person" LIMIT 10
    PostgreSQL query:  {'sql': 'SELECT "books_person"."id" FROM "books_person" ORDER BY "books_person"."id" ASC LIMIT 1', 'time': '0.000'}
    pg explain analyze: Limit  (cost=0.00..0.20 rows=10 width=8) (actual time=0.013..0.016 rows=10 loops=1)
      ->  Seq Scan on books_person  (cost=0.00..20251.18 rows=1000018 width=8) (actual time=0.012..0.014 rows=10 loops=1)
    Planning Time: 0.100 ms
    Execution Time: 0.034 ms

    Django vous donne accès à la requête SQL liée à un Queryset, mais vous pouvez également lister les requêtes exécutées au niveau de la connexion, et pour les utilisateurs chanceux de PostgreSQL, vous pouvez même obtenir le plan exécuté pour la requête, et l’heure d’exécution. C’est un excellent point de départ pour comparer des idées sur la façon d’optimiser une requête.

    3. Soyez conscient : la production n’est pas comme votre machine locale

    Dans l’exemple précédent, nous avons extrait le plan d’exécution exécuté par PostgreSQL. Mais si vous comparez le même code exécuté sur votre machine locale et votre production, vous pouvez obtenir des résultats très différents.

    Pourquoi? Au fur et à mesure que vos tables grandissent, PostgreSQL les optimise (ou non !) et, en fonction des nombreux paramètres du cluster de bases de données, le planificateur peut prendre des décisions différentes. Par exemple, si une table est petite, le coût d’utilisation d’un index peut ne pas être inférieur à celui d’une analyse complète. Ainsi, sur votre machine locale avec trois utilisateurs dans votre base de données, vous pouvez obtenir des résultats différents de ceux de votre instance de production avec des millions (pensons bien).

    Sur votre ordinateur local, vous ne devriez vous préoccuper que du nombre de requêtes que vous effectuez et de leur taille/complexité. Laissez l’analyse approfondie du plan d’exécution pour les requêtes exécutées sur l’environnement de prod (ou un environnement de type prod). Même là, faites attention aux statistiques de vos tables. Si les tableaux changent souvent, les statistiques peuvent ne pas être à jour et peuvent entraîner de mauvaises décisions de la part du planificateur (vous pouvez activer le vide automatique pour vous assurer que vos statistiques sont toujours mises à jour). Par exemple, avec de mauvaises statistiques, le planificateur peut « penser » que votre table est petite et utiliser une analyse complète, car en fait, la table est pleine de nouvelles lignes, et l’utilisation de l’index est le bon choix.

    4. Sélectionnez uniquement ce dont vous avez besoin

    Parlons maintenant de quelques bonnes pratiques de base qui doivent toujours être suivies. L’exécution d’une requête depuis votre application peut être décomposée en trois tâches principales :

    1. Envoi de la requête à la base de données.
    2. Exécution de la requête.
    3. Renvoyer les résultats.

    Nous nous concentrons souvent sur la deuxième partie, qui peut être liée à la mémoire et au processeur, en fonction de la requête et des données, mais les première et troisième parties, qui sont davantage liées au réseau, sont également importantes.

    Vous pouvez prétendre que la requête sera toujours petite… et peut-être êtes-vous trop confiant quant au fonctionnement des ORM. Vous avez peut-être également oublié l’heure que vous avez filtrée à l’aide d’une liste :

    Person.objects.filter(email__in=all_population_of_north_america)

    Dans cet exemple, le SQL généré lui-même peut être énorme. Même si votre table est petite et qu’aucune ligne ne correspond à la requête, le temps d’envoi de la requête sera très long. Dans le pire des cas, cela pourrait même générer une erreur.

    La plupart du temps, cependant, le résultat est souvent beaucoup plus grand que la requête. Donc, si vous n’avez besoin de travailler que sur un petit ensemble de champs, n’hésitez pas à utiliser only() ou defer():

    Person.objects.filter(name="tolstoy").defer(bio)  # will not retrieve Tolstoy's bio
    Person.objects.filter(name="tolstoy").only("name", "email") # will only retrieve name and email

    Dans le deuxième exemple, sachez que si vous souhaitez accéder à un champ qui n’est pas répertorié dans only(), Django exécutera une autre requête pour la récupérer. Si vous faites cela en boucle, cela représente beaucoup plus de requêtes. Dumping connection.queries peut être utile pour identifier de tels cas et décider si vous devez ajouter un champ au only() clause.

    Après avoir reçu les données, Django instanciera des modèles pour chaque ligne renvoyée. Si vous voulez obtenir des valeurs de champ, utilisez values() ou values_list() pour gagner beaucoup de temps.

    Pour être complet sur le sujet, et si vous envisagez de sérialiser le résultat de votre requête, sachez que plus vous récupérez, plus vous devrez transformer (et peut-être envoyer à certain client). La pagination, le cas échéant, accélérera l’exécution de vos requêtes et réduira également considérablement le temps de traitement du résultat de la requête et de son envoi au client.

    5. Indexez ce que vous recherchez

    Dans les bases de données, les index utilisent des structures de données optimisées pour éviter de rechercher chaque ligne d’une table lors de la recherche de celles correspondant à une certaine condition. Bien que les index puissent augmenter considérablement la taille de votre base de données, ils constituent une pierre angulaire des performances de la base de données, économisant beaucoup de temps et de CPU.

    C’est une bonne pratique SQL bien connue pour indexer les colonnes qui sont utilisées dans WHERE, ORDER BYet GROUP BY clauses, et vous pouvez facilement trouver où vous devez ajouter des index en regardant les coûteux « scans Seq » (c’est-à-dire les scans complets) dans un plan d’exécution. Au minimum, indexez toujours vos clés primaires, vos clés étrangères (Django le fait automatiquement) et tous les champs que vous utilisez souvent dans le filtrage.

    Ce n’est pas tout. Lors de la création d’un index, vous devez choisir avec soin son type d’index ; sinon, il pourrait être moins efficace que prévu, ou pire, PostgreSQL pourrait même ne pas l’utiliser ! Par exemple, HASH les index ne peuvent fonctionner que sur un = comparaison et n’affectent pas une ORDER BYalors que B-TREE les index seront également utilisés sur LIKE, les inégalités et les opérations de tri. L’index GIN fonctionnera sur des types de données complexes comme array ou jsonb.

    Enfin, soyez prudent : les fonctions peuvent saboter vos index. Prenons un exemple :

    Disons que nous voulons rechercher des personnes par leur nom. Au début, nous pensons naïvement que nous allons bien indexer le champ name :

    class Person(models.Model):
        email = models.CharField(max_length=255)
        name = models.CharField(max_length=255, index=True)

    Maintenant, imaginons que nous voulions effectuer une recherche insensible à la casse avec ce filtre :

    Person.objects.filter(name__iexact="tolstoy").only('email')

    Ce qui se traduit par :

    SELECT email FROM books_person WHERE UPPER(name) = UPPER('tolstoy');
    Execution Time: 4740.735 ms

    Dans ce cas, notre index régulier est inutile car nous utilisons le UPPER fonction sur l’indice. Au lieu de cela, nous devrions définir l’index comme suit :

    class Person(models.Model):
        email = models.CharField(max_length=255)
        name = models.CharField(max_length=255)
      
        class Meta:
            indexes = [
                Index(
                    Upper('name'),
                    name="books_person_name_upper_idx",
                ),
            ]

    Ou en SQL :

    CREATE INDEX users_user_name_upper_idx on books_person (UPPER(name)); 
    Execution Time: 470.121 ms

    6. Select_related et prefetch_related ne sont pas toujours la meilleure correspondance

    Lorsque vous travaillez sur un modèle Django, vous avez souvent besoin d’accéder à ses relations. Pour démontrer cela, nous créons un deuxième modèle, Book, qui est lié à une personne par le biais d’une clé étrangère d’auteur et d’une relation plusieurs à plusieurs lecteurs (une personne peut lire plusieurs livres, un livre peut être lu par plusieurs personnes. J’espère qu’il ce sera le cas pour cet article) :

    Class Book(models.Model):
       title = models.CharField(max_length=256)
       author = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="writings")
       readers = models.ManyToManyField(Person, related_name="readings")

    Si vous devez parcourir les livres et accéder aux informations sur leur auteur, vous pouvez simplement faire :

    for book in Book.objects.all():
        author = book.author

    C’est généralement une mauvaise idée car cela générera…

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.