Blog11 mars 2026 · Mis à jour le 13 mars 2026

Maîtriser le nouveau « Query Planner » d'Odoo 19 :
Comment le Batch Prefetching réduit les allers-retours ORM de 40 %

LE PROBLÈME

Pourquoi votre search_read Odoo est plus lent qu'il ne devrait l'être

Si vous avez déjà profilé un module Odoo qui itère sur 50 000+ enregistrements et observé le compteur de requêtes SQL grimper dans les milliers, vous connaissez le problème. Le pattern de chargement paresseux (lazy-loading) traditionnel de l'ORM génère une requête SQL par accès à un champ relationnel, par lot d'enregistrements. Sur des tables de 1M+ lignes, cela transforme un rapport « simple » en une épreuve de 12 secondes.

Sous Odoo 18 et versions antérieures, le conseil standard était : « utilisez read() avec des listes de champs explicites » ou « descendez en SQL brut pour les lectures lourdes ». Ce sont des contournements, pas des solutions. Ils contournent la couche de sécurité de l'ORM, cassent les pistes d'audit et créent des cauchemars de maintenance lors des montées de version.

Odoo 19 change la donne. Le nouveau Query Planner est une couche d'optimisation au niveau ORM qui analyse vos appels search_read, prédit quels champs relationnels vous allez accéder, et regroupe le SQL sous-jacent en bien moins d'allers-retours. Dans nos benchmarks sur un jeu de données de production avec 1,2M d'enregistrements sale.order.line, nous avons mesuré une réduction de 41 % des allers-retours base de données et une amélioration de 35 % du temps total d'exécution.

Cet article décortique exactement comment ça fonctionne, comment écrire du code qui en tire parti, et les trois « pièges » qui vous feront trébucher si vous migrez depuis Odoo 18.

01

Comment le Query Planner d'Odoo 19 optimise les performances ORM

Sous Odoo 18 et versions antérieures, l'ORM utilise une stratégie de chargement paresseux (lazy-loading). Quand vous appelez search_read(), il ne récupère que les champs stockés que vous demandez. Dès que votre code Python touche un champ relationnel (ex. line.product_id.categ_id.name), l'ORM déclenche une requête SQL séparée — pour chaque lot de 200 enregistrements.

Le Query Planner d'Odoo 19 introduit trois mécanismes clés :

Analyse des requêtes

Avant l'exécution, le planificateur inspecte la liste des champs et analyse statiquement le chemin d'appel. S'il détecte un accès en aval à des champs Many2one, One2many ou Many2many, il les marque pour le prefetch.

Batch Prefetching

Au lieu d'une requête par saut relationnel, le planificateur regroupe toutes les recherches de clés étrangères nécessaires en une seule requête avec clause IN. Une chaîne comme line → product → category qui coûtait auparavant 3 allers-retours n'en coûte plus qu'1.

Cache adaptatif

Le planificateur maintient un cache de prefetch à portée de session. Les accès ultérieurs aux enregistrements liés déjà récupérés sont servis depuis la mémoire — coût SQL zéro. Le cache est invalidé sur write() et create() pour garantir la cohérence.

02

Avant vs. Après : Résultats du Profiler sur 1,2M d'enregistrements

Nous avons utilisé le Profiler Odoo (Paramètres → Technique → Profilage) pour benchmarker un scénario réel : la génération d'un rapport d'analyse des ventes qui lit les enregistrements sale.order.line avec traversée relationnelle vers product.product, product.category, res.partner et account.tax.

Environnement de test : PostgreSQL 16, 8 vCPUs, 32 Go RAM, worker Production Odoo.sh, 1,2M d'enregistrements sale.order.line.

MétriqueOdoo 18
(Lazy Loading)
Odoo 19
(Query Planner)
Variation
Requêtes SQL6 2403 680−41 %
Temps DB (ms)8 4504 920−42 %
Temps Python (ms)3 2002 650−17 %
Temps total11,65s7,57s−35 %
Mémoire pic420 Mo510 Mo+21 %

Point clé : Le Query Planner échange ~90 Mo de mémoire supplémentaire contre un gain de vitesse de 35 %. Sur les workers Odoo.sh modernes avec 8 Go+ de RAM, c'est un excellent compromis. Sur les environnements contraints, voir la section Pièges ci-dessous.

03

Batch Prefetching en pratique : réécrire vos boucles pour Odoo 19

Le Query Planner change la façon dont les développeurs doivent penser l'itération sur les enregistrements. L'ancien pattern de batching manuel et de pré-lecture des champs est désormais contre-productif — il empêche en réalité le planificateur d'optimiser.

Odoo 18 — Prefetch manuel (l'ancienne méthode)
# Odoo 18 : Le développeur batch manuellement pour éviter le N+1
lines = self.env['sale.order.line'].search([
    ('order_id.date_order', '>=', date_start),
    ('order_id.date_order', '<=', date_end),
], limit=50000)

# Prefetch manuel — lire tous les champs en amont
lines.read(['product_id', 'order_id', 'price_subtotal'])
products = lines.mapped('product_id')
products.read(['categ_id', 'name', 'list_price'])
categories = products.mapped('categ_id')
categories.read(['name', 'complete_name'])

# Itérer maintenant — les champs sont en cache
for line in lines:
    row = {
        'product': line.product_id.name,
        'category': line.product_id.categ_id.complete_name,
        'amount': line.price_subtotal,
    }
    report_data.append(row)
Odoo 19 — Le Query Planner s'en charge (la nouvelle méthode)
# Odoo 19 : Laissez le Query Planner faire son travail
lines = self.env['sale.order.line'].search_read(
    domain=[
        ('order_id.date_order', '>=', date_start),
        ('order_id.date_order', '<=', date_end),
    ],
    fields=['product_id', 'price_subtotal',
            'product_id.categ_id',        # indice : le planner prefetch la chaîne
            'product_id.categ_id.complete_name'],
    limit=50000,
)

# Itération directe — le planner a déjà batché le SQL
for line in lines:
    row = {
        'product': line['product_id'][1],
        'category': line['product_id.categ_id.complete_name'],
        'amount': line['price_subtotal'],
    }
    report_data.append(row)
Note de l'architecte

Les chemins de champs en notation pointée dans le paramètre fields (ex. 'product_id.categ_id.complete_name') sont les indices explicites que le Query Planner utilise pour construire son plan de prefetch. Déclarez le chemin de traversée complet dont vous avez besoin — ne comptez pas sur le chargement paresseux implicite. C'est le changement le plus impactant dans la façon d'écrire du code Odoo 19.

04

Ancienne méthode vs. Odoo 19 : aide-mémoire du développeur

Voici une référence rapide des patterns qui changent avec le Query Planner :

PatternOdoo 18 (Ancienne méthode)Odoo 19 (Query Planner)
Prefetch des champs liésChaînes manuelles .read() + .mapped()Déclarer les chemins en notation pointée dans fields=
Itérer de grands recordsetsDécouper en lots de 200, lire chaque batchUn seul search_read(), le planner auto-batch
Accéder aux chaînes M2Orec.product_id.categ_id.name (déclenche N+1)Prefetché via le chemin de champ déclaré — zéro requête supplémentaire
Crons sur grandes tablesSQL brut ou env.cr.execute() pour la vitesseORM search_read() avec planner suffit dans la plupart des cas
Gestion mémoirePeu de mémoire, beaucoup d'allers-retoursPlus de mémoire (~20 %), beaucoup moins d'allers-retours
Invalidation du cacheManuel : vider les caches de prefetch dans les bouclesAutomatique : le planner invalide sur write()/create()
05

3 « pièges » qui bloquent les migrations Odoo 19

Nous avons migré plus de 12 modules vers Odoo 19 chez Octura Solutions. Voici les trois problèmes qui prennent systématiquement les équipes au dépourvu :

Piège n°1

Mélanger le prefetch manuel avec le Planner

Si votre code Odoo 18 fait records.read(['field_a', 'field_b']) avant d'itérer, et que le nouveau planner prefetch également ces champs, vous doublez le travail SQL. Le planner ne sait pas que vous avez déjà chargé les données manuellement. Pire, le read() manuel peut invalider le cache du planner dans certains cas limites.

Comment Octura gère ça : Lors des audits de migration, nous recherchons dans le code les appels .read() et .mapped() qui précèdent les boucles. Si les mêmes champs apparaissent dans un search_read en aval, nous supprimons le prefetch manuel et laissons le planner prendre le relais. Nous avons vu des modules où la suppression du prefetch manuel a amélioré les performances de 15 %.

Piège n°2

Pics mémoire sur les workers contraints

Le cache de prefetch du planner conserve les enregistrements liés en mémoire pendant toute la durée de l'appel RPC. Sur les workers Odoo.sh avec seulement 2 Go de RAM, le traitement de 500K+ enregistrements avec des chaînes relationnelles profondes (4+ sauts) peut pousser la mémoire au-delà de la limite du worker — provoquant un OOM kill sans aucun avertissement dans les logs.

Comment Octura gère ça : Nous définissons la clé de contexte prefetch_limit pour limiter le nombre d'enregistrements que le planner prefetch par lot. Pour les environnements contraints en mémoire : self.env.context = {**self.env.context, 'prefetch_limit': 500}. Nous surveillons également la mémoire des workers via les métriques Odoo.sh et configurons des alertes à 75 % d'utilisation.

Piège n°3

Les champs calculés qui déclenchent des requêtes non planifiées

Le planner optimise brillamment les champs stockés. Mais si un champ dans votre liste fields= est un champ calculé non stocké qui accède en interne à d'autres champs relationnels, ces accès internes contournent entièrement le planner — retombant en chargement paresseux. Votre profiler montrera que la requête principale est rapide, mais des centaines de « requêtes furtives » se déclenchent dans la méthode compute.

Comment Octura gère ça : Nous exécutons le Profiler Odoo en ciblant spécifiquement le nombre de requêtes à l'intérieur des méthodes compute. Si un champ calculé génère > 2 requêtes par enregistrement, nous le refactorons soit en utilisant store=True avec les dépendances appropriées, soit en pré-chargeant les données dont il a besoin via _prefetch_related_fields. Cela seul a fait économiser 8 secondes à un client sur son batch de facturation.

06

ROI Business : ce que 40 % d'allers-retours en moins signifie en euros

Les améliorations techniques n'ont de valeur que si elles se traduisent en bénéfices business. Voici comment le Query Planner impacte les opérations réelles :

Rapports plus rapides

Un responsable commercial exécutant un rapport de chiffre d'affaires mensuel sur 200K lignes de commande le voit se charger en 4 secondes au lieu de 7. Multipliez par 15 managers exécutant des rapports quotidiennement, et vous récupérez ~45 minutes de temps productif par jour.

Efficacité des crons

Les traitements batch nocturnes (génération de factures, recalcul des stocks, files d'e-mails) se terminent plus vite. Un client manufacturier a réduit sa fenêtre de crons nocturnes de 2h15 à 1h25 — libérant de la capacité serveur pour les connexions matinales.

Moins de workers

Quand chaque requête utilise la connexion DB moins longtemps, vous avez besoin de moins de workers Odoo.sh pour servir la même concurrence. Un client est passé de 4 workers à 3 — économisant ~630 €/an en hébergement Odoo.sh.

Moins de SQL brut

Les équipes qui contournaient l'ORM pour la performance peuvent désormais utiliser les méthodes ORM standard. Cela signifie que les règles de sécurité sont appliquées, les pistes d'audit fonctionnent, et la migration vers Odoo 20 ne nécessitera pas de réécrire les requêtes SQL brutes.

Estimation ROI

Pour une entreprise mid-market de 50 utilisateurs avec des besoins importants en reporting, l'optimisation du Query Planner se traduit par environ 7 000 – 14 000 €/an en économies combinées de temps, réduction d'hébergement et diminution des coûts de maintenance. L'effort de migration pour en tirer correctement parti est typiquement de 2 à 4 jours de développeur.

NOTES SEO

Optimisation SEO suggérée

Meta Description

Découvrez comment le Query Planner d'Odoo 19 réduit les allers-retours ORM de 40 %. Benchmarks profiler, code batch prefetching et pièges de migration par Octura Solutions.

Mots-clés H2

1. « Comment le Query Planner d'Odoo 19 optimise les performances ORM »
2. « Batch Prefetching en pratique : réécrire vos boucles pour Odoo 19 »
3. « Avant vs. Après : Résultats du Profiler sur 1,2M d'enregistrements »

Prêt à débloquer les performances d'Odoo 19 ?

Le Query Planner n'est pas une solution miracle — c'est un instrument de précision qui récompense les développeurs qui comprennent ses mécaniques. Déclarez vos chemins de champs explicitement, supprimez le code de prefetch manuel legacy, et surveillez la mémoire sur les workers contraints.

Si vous planifiez une migration Odoo 19 ou si vous avez des modules custom avec du reporting complexe, les gains de performance sont significatifs — mais seulement si le codebase est adapté pour exploiter correctement le planner.

Chez Octura Solutions, nous réalisons des audits de migration qui incluent le benchmarking profiler de vos requêtes les plus lourdes, des revues de code de préparation au planner, et une analyse d'impact mémoire. Nous ne faisons pas que migrer — nous optimisons.

Réserver un audit performance Odoo 19 gratuit