From 01e09bb2c27299ba6563f71f48704ca7a8e988ba Mon Sep 17 00:00:00 2001 From: "Victor Piryns (pivi)" Date: Mon, 11 Dec 2023 15:05:39 +0000 Subject: [PATCH] [PERF] pos_sale: memoize `_compute_currency_rate` of `pos.order` Description: Saving a new `res.config.settings` (for ex: checking "Use QR code on ticket") can be extremely slow on database with a large number of `pos.order`. Analysis: Saving a new `res.config.settings` creates a new instance of the config. This will invoke a check on the python constraints calling eventually `_check_currencies` of `pos.config`, which will invoke the compute of `currency_id` setting the field as dirty. The `_compute_currency_rate` of `pos.order` depends on the `currency_id` of the current `pos.config` (via a related). This means the compute will be called with *all* `pos.order` ever made. As the compute itself makes a few calls to `_get_conversion_rate`, which makes a query, it can become slow when the method is called frequently. Solution: Memoize the call to `_get_conversion_rate`, as similarly done in `account.move.line` from d8d47f9ff8554f4b39487fd2f13c153c7d6f958d to reduce the number queries necessary. Benchmark: The `create` request to `res.config.settings` on a database with over 240k `pos.order` took | | Before | After | |-------------|-----------|----------| | Timing | 36.9 secs | 4.3 secs | | Query count | 47k | 627 | Reference: opw-3632273 closes odoo/odoo#148136 X-original-commit: 1c6f7f23b2c0dbd009d8d56689c7a98792990f7f Signed-off-by: Joseph Caburnay (jcb) Signed-off-by: Piryns Victor (pivi) --- addons/point_of_sale/models/pos_order.py | 14 ++++++++++++-- addons/pos_sale/models/pos_order.py | 13 ++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index ba771928d96..e58476a4584 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -3,7 +3,7 @@ import logging from datetime import datetime from markupsafe import Markup -from functools import partial +from functools import partial, lru_cache from itertools import groupby from collections import defaultdict @@ -418,8 +418,18 @@ class PosOrder(models.Model): @api.depends('date_order', 'company_id', 'currency_id', 'company_id.currency_id') def _compute_currency_rate(self): + @lru_cache + def get_rate(from_currency, to_currency, company, date): + return self.env['res.currency']._get_conversion_rate( + from_currency=from_currency, + to_currency=to_currency, + company=company, + date=date, + ) for order in self: - order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order) + # date_order is a datetime, but the rates are looked up on a date basis, + # therefor converting the date_order to a date helps with sharing entries in the lru_cache + order.currency_rate = get_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order.date()) @api.depends('lines.is_total_cost_computed') def _compute_is_total_cost_computed(self): diff --git a/addons/pos_sale/models/pos_order.py b/addons/pos_sale/models/pos_order.py index 534ccc869a3..a8b02052461 100644 --- a/addons/pos_sale/models/pos_order.py +++ b/addons/pos_sale/models/pos_order.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from functools import lru_cache from odoo import api, fields, models, _ from odoo.tools import float_compare, float_is_zero @@ -24,9 +25,19 @@ class PosOrder(models.Model): @api.depends('date_order', 'company_id') def _compute_currency_rate(self): + @lru_cache + def get_rate(from_currency, to_currency, company, date): + return self.env['res.currency']._get_conversion_rate( + from_currency=from_currency, + to_currency=to_currency, + company=company, + date=date, + ) for order in self: date_order = order.date_order or fields.Datetime.now() - order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, date_order) + # date_order is a datetime, but the rates are looked up on a date basis, + # therefor converting the date_order to a date helps with sharing entries in the lru_cache + order.currency_rate = get_rate(order.company_id.currency_id, order.currency_id, order.company_id, date_order.date()) def _prepare_invoice_vals(self): invoice_vals = super(PosOrder, self)._prepare_invoice_vals()