Skip to content

[19.0][IMP] mail_tracking: index the messages that need action - #328

Merged
OCA-git-bot merged 1 commit into
OCA:19.0from
vauxoo-dev:19.0-mail_tracking-needs-action-index-moy
Nov 1, 2026
Merged

OCA-git-bot merged 1 commit into
OCA:19.0from
vauxoo-dev:19.0-mail_tracking-needs-action-index-moy

Conversation

@moylop260

@moylop260 moylop260 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Problem

res.users._init_messaging is called by the web client every time it loads its messaging data, and mail_tracking extends it to add the "Failed" mailbox counter:

So the query runs once per web client load, for every user. mail_tracking_needs_action is not indexed, and only a handful of messages carry it (it is set when a tracking gets a failed state and cleared by set_need_action_done). Without an index PostgreSQL starts from the failed mail.tracking.email rows and probes mail_message by primary key once per tracking, discarding almost all of them. The cost grows with the number of failed trackings, not with the number of flagged messages.

Change

A partial index on the flagged messages, declared with models.Index:

_mail_tracking_needs_action_index = models.Index(
    "(id) WHERE mail_tracking_needs_action IS TRUE"
)

index=True on the field would not help (a plain btree over a boolean that is almost always false), and btree_not_null keeps every row because the default is False, not NULL. The partial index only contains the flagged rows, and a write only touches it when the flag changes.

Measurements

Staging copy of a production database: mail_message 122M rows (53 GB), mail_tracking_email with 29,442 rows in a failed state, 25,772 flagged messages. SQL taken from the slow-query report of a test pipeline (the one get_failed_count generates for a user with 4 failed messages), run with EXPLAIN (ANALYZE, BUFFERS) on a clone of that database, same server and settings before and after.

Before After
Cold cache 152.8 s 6.2 s
Warm cache 5.1 s 142 ms
Plan scan 29,442 failed trackings, 29,323 primary key probes on mail_message, 4 rows kept partial index over 25,772 flagged messages, 4 rows kept
Buffers touched (hit + read) 236,328 19,241
Index size none 584 kB (table: 53 GB)
CREATE INDEX none 13 min 38 s on 122M rows (maintenance_work_mem 2 GB, 2 parallel workers)

In production, the same query was the top time consumer of the Playwright pass of that pipeline: 3 min 10 s over 37 executions, up to 38 s each.

Write cost: updates of 5,000 rows were within the noise of other work on the same server (with and without the index), so I cannot give a clean figure; by construction the index entry is only written when mail_tracking_needs_action changes.

Size

Sizes on the same staging copy, to show how light the index is next to the table it serves:

Object Rows Size
mail_message table (with TOAST) 121.8M 59 GB
mail_message indexes (all) 32 GB
mail_message_pkey 121.8M 2,610 MB
mail_message__message_id_index (largest) 17 GB
mail_tracking_email table / indexes 7.1M 2,487 MB / 801 MB
New partial index 25,772 flagged 584 kB

The new index is about 0.02% of the primary key and about 0.001% of the table, because it only holds the 0.02% of the messages that are flagged.

Notes

  • The index is built once, by the first update after this change; later updates keep it, because Odoo only rebuilds an index whose definition differs from the comment stored on it. On a large database that build (13 min on 122M rows) blocks writes to mail_message, so INSTALL.md explains how to create it beforehand with CREATE INDEX CONCURRENTLY, and the same comment the ORM stores on the index, so the update keeps it.
  • This is independent of DRAFT: [IMP] mail_tracking: optimise _search with Domain-based SQL subqueries #292, which optimises the _search of mail.tracking.email (the access check) and does not change this plan.
  • No test: index creation is not something the test suite should pin; the measurement above is the evidence.

@pedrobaeza pedrobaeza added this to the 19.0 milestone Oct 30, 2026
Comment thread mail_tracking/models/mail_message.py Resolved
Comment thread mail_tracking/models/mail_message.py Resolved
Comment thread mail_tracking/models/mail_message.py Resolved
@moylop260
moylop260 force-pushed the 19.0-mail_tracking-needs-action-index-moy branch from f8d4527 to fa42860 Compare October 30, 2026 14:54
Comment thread mail_tracking/readme/INSTALL.md Resolved
@moylop260
moylop260 force-pushed the 19.0-mail_tracking-needs-action-index-moy branch from fa42860 to 91f347a Compare October 31, 2026 13:29
res.users._init_messaging calls mail.message.get_failed_count, so every
web client load (/mail/data) searches is_failed_message. That search
filters on mail_tracking_needs_action, which has no index. Only a
handful of messages carry the flag, but PostgreSQL starts from the
failed trackings and probes mail_message by primary key once per
tracking. With 122M messages and 29k failed trackings the query took
152 s cold and 5 s warm.

Add a partial index on the flagged messages. It is tiny (584 kB on that
database), and a write only touches it when the flag changes.

The index is built once, by the first update after this change. Later
updates keep it, because Odoo only rebuilds an index whose definition
differs from the one stored in its comment. On a very large table that
build blocks writes (13 minutes on 122M messages), so INSTALL.md explains
how to create it beforehand with CREATE INDEX CONCURRENTLY and the
comment the ORM stores on it.
@moylop260
moylop260 force-pushed the 19.0-mail_tracking-needs-action-index-moy branch from 91f347a to 8b718a7 Compare October 31, 2026 13:32

@pedrobaeza pedrobaeza left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

/ocabot merge patch

@OCA-git-bot

Copy link
Copy Markdown
Contributor

On my way to merge this fine PR!
Prepared branch 19.0-ocabot-merge-pr-328-by-pedrobaeza-bump-patch, awaiting test results.

@OCA-git-bot
OCA-git-bot merged commit 8c0ffdd into OCA:19.0 Nov 1, 2026
10 checks passed
@OCA-git-bot

Copy link
Copy Markdown
Contributor

Congratulations, your PR was merged at 175e491. Thanks a lot for contributing to OCA. ❤️

@moylop260
moylop260 deleted the 19.0-mail_tracking-needs-action-index-moy branch November 1, 2026 16:20
@moylop260

Copy link
Copy Markdown
Contributor Author

Gracias Pedro

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants