Skip to content

Conversation

@cawo-odoo
Copy link
Contributor

@cawo-odoo cawo-odoo commented Sep 19, 2025

improves 13adede

Queries run through client-side cursors will make postgresql materialze the whole of the result immediately (which is actually, why cr.rowcount is always available right after execute in this case). With server side cursors (named cursors) on the other hand, tuples are materialized when they are fetched. This is why running the query for ids through the client-side cursor just to be able to access cr.rowcount, can cause an out-of-memory exception from PostgreSQL.

We fix this by wrapping the query in a CREATE TABLE AS statement that inserts returned ids into a temporary table. We then use a named_cursor to fetch ids from this table in chunks, server-side.

Another approach would have been to just wrap the query in a SELECT count(*) query and run this once to get the count. The approach using CREATE TABLE AS has been chosen over that solution to support queries that include DML statements (e.g. UPDATE ... RETURNING) that affect the results of the compute, as it allows us to run the query on the main (client) cursor, while still using a named_cursor for fetching the ids memory-efficiently.

@cawo-odoo cawo-odoo requested review from a team and aj-fuentes September 19, 2025 09:26
@robodoo
Copy link
Contributor

robodoo commented Sep 19, 2025

Pull request status dashboard

@cawo-odoo
Copy link
Contributor Author

Testing this now, we'll know in ~6h

@cawo-odoo
Copy link
Contributor Author

cawo-odoo commented Sep 20, 2025

Tests with the currently effected DB look good (on PG17).

Last fixup is to fix PG12 - PG15 (require an alias for aggreagates on a subquery)

@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch 3 times, most recently from 7c490a9 to ee6740b Compare September 22, 2025 07:13
@cawo-odoo
Copy link
Contributor Author

pushed fixup to take this case into regard: https://runbot.odoo.com/runbot/build/89536248

@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch from 3e2c407 to 0eafd6a Compare September 22, 2025 08:42
@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch from 71d2b20 to fbe98ac Compare September 22, 2025 12:14
@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch 2 times, most recently from 7ad9bac to 0be7109 Compare September 22, 2025 12:31
@KangOl
Copy link
Contributor

KangOl commented Sep 22, 2025

upgradeci retry with always only account hr_recruitment_extract hr_expense sale_stock sale_subscription crm l10n_it_edi sale purchase mrp

@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch 3 times, most recently from 342ad22 to 3a35af4 Compare September 22, 2025 13:13
src/util/orm.py Outdated
yield id_
def get_ids():
with named_cursor(cr, itersize=2**20) as ncr:
ncr.execute("SELECT * FROM _upgrade_rf")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it fail if the query is wrong. Avoid id-injection to the ORM.

Suggested change
ncr.execute("SELECT * FROM _upgrade_rf")
ncr.execute("SELECT id::int FROM _upgrade_rf")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't cast it to int. On some (really) huge tables, it's not excluded that they has to promote the id to bigint.

Copy link
Contributor

@aj-fuentes aj-fuentes Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an optional stuff. But note that we could get select name from table as query. Whether this is an issue for the ORM or not it would depend on the running version... The error later could be harder to understand than a type cast error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an optional stuff. But note that we could get select name from table as query. Whether this is an issue for the ORM or not it would depend on the running version... The error later could be harder to understand than a type cast error.

If we really want to do that, I would make it such that it runs a normal create table first. In there, we can specify the column type and create the PK all in one go.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to :) I just wanted a quick/simple way to avoid the issue. We could cast to bigint as well, but it may have some perf impact.

@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch 2 times, most recently from c19f6e0 to 16b6057 Compare September 22, 2025 14:07
@cawo-odoo
Copy link
Contributor Author

Last fixup is to fix:

  File "/upgrade-util/src/util/orm.py", line 292, in recompute_fields
    cr.execute(format_query(cr, "CREATE UNLOGGED TABLE _upgrade_rf(id) AS ({})", query))
  File "/tmp/tmp2rppghvk/odoo/17.0/odoo/sql_db.py", line 335, in execute
    res = self._obj.execute(query, params)
psycopg2.errors.SyntaxError: syntax error at or near "DELETE"
LINE 2:             DELETE FROM account_edi_document aed
                    ^

@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch from 59d723c to 27827d3 Compare September 23, 2025 09:32
@cawo-odoo
Copy link
Contributor Author

cawo-odoo commented Sep 23, 2025

aj-fuentes approved these changes

🎉 Test on big DB is still running, results should come in before tomorrow.

EDIT: bundle tested successfully on big DB. @KangOl

@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch from 27827d3 to c9b3b39 Compare September 24, 2025 14:10
@cawo-odoo
Copy link
Contributor Author

Pushed a small simplification that came to mind while applying the same idea to iter_browse

@aj-fuentes
Copy link
Contributor

I think your last patch is leaving behind the table _upgrade_rf when count is zero.

@cawo-odoo
Copy link
Contributor Author

cawo-odoo commented Sep 24, 2025

I think your last patch is leaving behind the table _upgrade_rf when count is zero.

Oh, well. I forgot (to think - thanks for doing it for me) about the early return ... too bad then. Changing it back.

@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch from c9b3b39 to 27827d3 Compare September 24, 2025 14:39
improves 13adede

Queries run through client-side cursors will make postgresql materialze the
whole of the result immediately (which is actually, why `cr.rowcount` is always
available right after `execute` in this case). With server side cursors (named
cursors) on the other hand, tuples are materialized when they are fetched.
This is why running the `query` for ids through the client-side cursor just to
be able to access `cr.rowcount`, can cause an out-of-memory exception from
PostgreSQL.

We fix this by wrapping the query in a `CREATE TABLE AS` statement that inserts
returned ids into a temporary table. We then use a named_cursor to fetch ids
from this table in chunks, server-side.

Another approach would have been to just wrap the query in a `SELECT count(*)`
query and run this once to get the `count`. The approach using `CREATE TABLE
AS` has been chosen over that solution to support queries that include DML
statements (e.g. `UPDATE ... RETURNING`) that affect the results of the
compute, as it allows us to run the query on the main (client) cursor, while
still using a named_cursor for fetching the ids memory-efficiently.
@cawo-odoo cawo-odoo force-pushed the master-fix_recompute_fields_pg_mem_error-cawo branch from 27827d3 to d66aa37 Compare September 25, 2025 13:30
@cawo-odoo
Copy link
Contributor Author

Updated commit message. From my side this is ready and it is still the same what Alvaro approved already.

Copy link
Contributor

@KangOl KangOl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants