Repository navigation
[19.0] [IMP] queue_job: clean old jobs from the base autovacuum #1014
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) | ||
| from openupgradelib import openupgrade | ||
|
|
||
|
|
||
| @openupgrade.migrate() | ||
| def migrate(env, version): | ||
| # Old jobs are now removed by the base autovacuum cron | ||
| openupgrade.delete_records_safely_by_xml_id( | ||
| env, | ||
| ["queue_job.ir_cron_autovacuum_queue_jobs"], | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,10 +4,10 @@ | |||||||||
| import logging | ||||||||||
| import random | ||||||||||
| import time | ||||||||||
| from datetime import datetime, timedelta | ||||||||||
|
|
||||||||||
| from odoo import api, exceptions, fields, models | ||||||||||
| from odoo.tools import config, html_escape | ||||||||||
| from odoo.fields import Domain | ||||||||||
| from odoo.tools import html_escape | ||||||||||
| from odoo.tools.sql import create_index | ||||||||||
|
|
||||||||||
| from odoo.addons.base_sparse_field.models.fields import Serialized | ||||||||||
|
|
@@ -144,7 +144,7 @@ def init(self): | |||||||||
| ), | ||||||||||
| comment=("Queue Job: partial index for identity_key on active states"), | ||||||||||
| ) | ||||||||||
| # Used by <queue.job>.autovacuum | ||||||||||
| # Used by <queue.job>._gc_old_jobs | ||||||||||
| create_index( | ||||||||||
| cr, | ||||||||||
| "queue_job_channel_date_done_date_created_index", | ||||||||||
|
|
@@ -408,33 +408,41 @@ def _needaction_domain_get(self): | |||||||||
| """ | ||||||||||
| return [("state", "=", "failed")] | ||||||||||
|
|
||||||||||
| def autovacuum(self): | ||||||||||
| """Delete all jobs done based on the removal interval defined on the | ||||||||||
| channel | ||||||||||
| @api.autovacuum | ||||||||||
| def _gc_old_jobs(self): | ||||||||||
| """Delete the jobs done or cancelled before the removal interval of | ||||||||||
| their channel, in batches of 1000. | ||||||||||
|
|
||||||||||
| Called from a cron. | ||||||||||
| Jobs of an unknown channel (renamed, deleted) use the removal interval | ||||||||||
| of the root channel. | ||||||||||
|
|
||||||||||
| Called from the base autovacuum cron, which commits after each batch | ||||||||||
| and calls it again while it returns remaining jobs. | ||||||||||
| """ | ||||||||||
| for channel in self.env["queue.job.channel"].search([]): # pylint: disable=no-search-all | ||||||||||
| deadline = datetime.now() - timedelta(days=int(channel.removal_interval)) | ||||||||||
| # Delete in chunks using a stable order (matches composite index) | ||||||||||
| while True: | ||||||||||
| jobs = self.search( | ||||||||||
| [ | ||||||||||
| "|", | ||||||||||
| ("date_done", "<=", deadline), | ||||||||||
| ("date_cancelled", "<=", deadline), | ||||||||||
| ("channel", "=", channel.complete_name), | ||||||||||
| ], | ||||||||||
| order="date_done, date_created", | ||||||||||
| limit=1000, | ||||||||||
| ) | ||||||||||
| if jobs: | ||||||||||
| jobs.sudo().unlink() | ||||||||||
| if not config["test_enable"]: | ||||||||||
| self.env.cr.commit() # pylint: disable=E8102 | ||||||||||
| else: | ||||||||||
| break | ||||||||||
| return True | ||||||||||
| channels = self.env["queue.job.channel"].search([]) # pylint: disable=no-search-all | ||||||||||
| root = self.env.ref("queue_job.channel_root") | ||||||||||
| intervals = [ | ||||||||||
| (Domain("channel", "=", channel.complete_name), channel.removal_interval) | ||||||||||
| for channel in channels | ||||||||||
| ] | ||||||||||
| intervals.append( | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Different approach is ok 👍
Suggested change
|
||||||||||
| ( | ||||||||||
| Domain("channel", "not in", channels.mapped("complete_name")), | ||||||||||
| root.removal_interval, | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
| domain = Domain.OR( | ||||||||||
| channel_domain | ||||||||||
| & ( | ||||||||||
| Domain("date_done", "<=", f"-{days}d") | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Works but cannot be backported as-is. The code from the proposal now = fields.Datetime.now()
def done_before(deadline):
return Domain("date_done", "<=", deadline) | Domain(
"date_cancelled", "<=", deadline
)
domain = Domain.OR(
Domain("channel", "=", channel)
& done_before(
now - timedelta(days=get_interval(channel))
)
for channel in old_jobs_channels
)is more verbose but was made to be retrocompatible so that we can backport the change seemlessly. |
||||||||||
| | Domain("date_cancelled", "<=", f"-{days}d") | ||||||||||
| ) | ||||||||||
| for channel_domain, days in intervals | ||||||||||
| ) | ||||||||||
| limit = 1000 | ||||||||||
| jobs = self.search(domain, order="date_done, date_created", limit=limit + 1) | ||||||||||
| jobs[:limit].sudo().unlink() | ||||||||||
| return len(jobs[:limit]), len(jobs) > limit | ||||||||||
|
|
||||||||||
| def related_action_open_record(self): | ||||||||||
| """Open a form view with the record(s) of the job. | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
manual bump for the migration script