-
-
Notifications
You must be signed in to change notification settings - Fork 622
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
[8.0][IMP] Performance issues in mail_tracking addons #97
Conversation
tracking_email.recipient_address) | ||
if partners: | ||
partners.email_score_calculate() | ||
# partners = self.tracking_ids_recalculate( |
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.
Why not make it non stored?
3b4f687
to
e456ad5
Compare
We are running this in one production instance, if works well, then I'll add new tests to improve codecov |
e456ad5
to
718f884
Compare
raise e | ||
finally: | ||
if not current: | ||
env.cr.close() |
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.
Not sure if you can use with env.cr:
or with env.cr.savepoint():
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.
This pattern is equivalent to with
, but in this case if more explicit and easier to debug if we need it in future. See: http://effbot.org/zone/python-with-statement.htm
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.
I don't see a functional difference though? This pattern fails Lint & I am not sure that debugging is a good enough reason to keep it.
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.
Thanks @lasley, I didn't read transaction related guidelines before. In a multi-db instance we need to get a new env (and cursor) because ORM can initialize it (several databases, none selected). But I can make it better following guidelines. I'm doing it right now
Could you please add some docstrings and comments, specially in the performance-critical parts? |
raise e | ||
finally: | ||
if not current: | ||
env.cr.close() |
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.
I don't see a functional difference though? This pattern fails Lint & I am not sure that debugging is a good enough reason to keep it.
@@ -23,14 +24,19 @@ class MailTrackingEmail(models.Model): | |||
_rec_name = 'display_name' | |||
_description = 'MailTracking email' | |||
|
|||
# This table is going to growth fast and to infinite, so we index: |
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.
s/growth/grow
tracking_field: [(5, False, False)] | ||
}) | ||
return objects | ||
def _email_score_tracking_filter(self, domain, order='time desc', |
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.
@api.model
if score > 100.0: | ||
score = 100.0 | ||
if score < 0.0: |
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.
elif
- save some resources - if it already matched the 100, it definitely won't be less than 0
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.
You're right
score = 50.0 | ||
trackings = self._email_score_tracking_filter() | ||
for tracking in trackings: | ||
for tracking in self: | ||
if tracking.state in ('error',): |
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.
This would be cleaner & more efficient in a dictionary:
states = {
'error': -50.0,
'opened': 5.0,
...
}
score += states.get(tracking.state, 0.0)
partners.email_score_calculate() | ||
# Commit to DB to release exclusive lock | ||
if not testing: | ||
self.env.cr.commit() # pragma: no cover |
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.
Why can we not use savepoint here?
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.
when using savepoint, if everything is going well (no exceptions), then transaction is committed?
And, if we are in testing mode, transaction is committed too?
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.
Hmmm ok looking at the intent here, we may not want a Savepoint. It's not immediately committing the transaction, and will roll back entirely if another transaction afterwards fails (assuming that transaction is also not within a savepoint).
Think of it like a save-guard - if the transaction within the Savepoint fails, it will rollback just within that savepoint. If the transaction fails outside of a savepoint, all mutations & savepoints in that transaction will fail.
Note this is a Postgres feature, so it's significantly easier to play with in direct SQL if you're curious. Surprisingly, the Postgres docs on Savepoint are pretty nice & the Odoo implementation matches well.
Where is the exclusive lock that is mentioned in the comments actually created?
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.
I can see this Odoo code: https://github.com/odoo/odoo/blob/d81258ab8e0dcb6c0df3a47ab8084ec99b7d3e24/openerp/sql_db.py#L390
And this PostgreSQL doc: https://www.postgresql.org/docs/9.1/static/sql-release-savepoint.html
According to that, RELEASE SAVEPOINT just remove the savepoint. But in this case, I need to have the event (and mail state) visible to other isolated cursors and release exclusivelock to mail_tracking_email and mail_tracking_event tables. With this commit, then other writes (for instance to res_partner table) can be done after reading mail_tracking_email or mail_tracking_event tables.
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.
After thinking twice, I remove this commit. Now the addon is not reading mail_tracking_email nor email_tracking_event tables after create/write on them, so I don't need this.
tracking_emails_count = fields.Integer( | ||
string="Tracking emails count", store=True, readonly=True, | ||
string="Tracking emails count", readonly=True, store=False, |
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.
AFAIK computed, non-stored attrs without an inverse are implicitly read only
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.
ok
@@ -5,7 +5,7 @@ | |||
import logging | |||
try: | |||
from openerp.addons.mail_tracking.hooks import column_add_with_value | |||
except ImportError: | |||
except ImportError: # pragma: no cover |
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.
Please remove this pragma & vote on OCA/maintainer-quality-tools#372
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.
Already merged, thanks 😄
@lasley Thanks for your review. Changes done |
with reg.cursor() as new_cr: | ||
new_env = api.Environment(new_cr, SUPERUSER_ID, {}) | ||
res = callback(env, tracking_id, event_type, **kw) | ||
new_env.cr.commit() |
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.
Seeing as we've confirmed that we're awesome and doing things right, we'll need to disable lint on this line (I'm assuming, wait for Travis to find out for sure):
new_env.cr.commit() #pylint: disable=E8102
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.
Confirmed
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.
This check is for avoiding bad commit() calls without control. If you are sure about the use, then we can put the pylint disable sentence.
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.
One note on pylint, but we'll wait for Travis for confirmation
pylint is cool 😄 |
These improvements have been running well in a production instance today, with one mass mailing sending and Mailgun webhooks been received and 6 workers. |
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.
OK, thanks
@@ -35,49 +48,37 @@ def _request_metadata(self): | |||
'ua_family': request.user_agent.browser or False, | |||
} | |||
|
|||
def _tracking_open(self, env, tracking_id, event_type, **kw): | |||
tracking_email = env['mail.tracking.email'].search([ | |||
('id', '=', tracking_id), |
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.
Use search_count
for optimizing this (see the tip in https://www.odoo.com/documentation/8.0/reference/orm.html#common-orm-methods)
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.
I use tracking_email recordset after that
compute="_compute_tracking_emails_count") | ||
email_bounced = fields.Boolean(string="Email bounced", index=True) |
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.
Don't put string, as it's automatically inferred from the variable name.
email_score = fields.Float( | ||
string="Email score", readonly=True, default=50.0) | ||
string="Email score", readonly=True, |
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.
The same about the string attribute
[IMP] mail_tracking performance and bounce process
[IMP] mail_tracking performance and bounce process
Syncing from upstream OCA/social (11.0)
This PR fix some performance issues:
self.env.cr
is not closed properly.index=True
totime
andrecipient_address
fields ofmail.tracking.email
modelemail_score
is now a non-stored computed fieldAlso includes some new features for processing bounced emails easier:
email_bounced
to partners and mail contacts. To easy filter them and do manual operations: call the partner, remove the partner, opt-out, ... Also prepare easy inherited methods for programming automatic operations in customer addon.