Skip to content
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

Merged
merged 6 commits into from
Oct 13, 2016

Conversation

antespi
Copy link
Contributor

@antespi antespi commented Oct 11, 2016

This PR fix some performance issues:

  • [Fixed] In some cases self.env.cr is not closed properly.
  • Add index=True to time and recipient_address fields of mail.tracking.email model
  • Many2many relation between partners (and mail contacts) and mail tracking emails deleted
  • email_score is now a non-stored computed field

Also includes some new features for processing bounced emails easier:

  • Adds a menu for listing mass mailing emails and events
  • Add a boolean field 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.

@antespi antespi added this to the 8.0 milestone Oct 11, 2016
@antespi antespi changed the title [IMP] Performance issues in mail_tracking addons [8.0][IMP] Performance issues in mail_tracking addons Oct 11, 2016
tracking_email.recipient_address)
if partners:
partners.email_score_calculate()
# partners = self.tracking_ids_recalculate(
Copy link
Member

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?

@antespi antespi force-pushed the 8.0-mail_tracking_performance branch 6 times, most recently from 3b4f687 to e456ad5 Compare October 12, 2016 23:09
@antespi
Copy link
Contributor Author

antespi commented Oct 13, 2016

We are running this in one production instance, if works well, then I'll add new tests to improve codecov

raise e
finally:
if not current:
env.cr.close()
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@antespi antespi Oct 13, 2016

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

Copy link
Contributor

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.

Copy link
Contributor Author

@antespi antespi Oct 13, 2016

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

@yajo
Copy link
Member

yajo commented Oct 13, 2016

Could you please add some docstrings and comments, specially in the performance-critical parts?

raise e
finally:
if not current:
env.cr.close()
Copy link
Contributor

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:
Copy link
Contributor

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',
Copy link
Contributor

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:
Copy link
Contributor

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

Copy link
Contributor Author

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',):
Copy link
Contributor

@lasley lasley Oct 13, 2016

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
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

@lasley lasley Oct 13, 2016

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

@antespi antespi Oct 13, 2016

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,
Copy link
Contributor

@lasley lasley Oct 13, 2016

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

Copy link
Contributor Author

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
Copy link
Contributor

@lasley lasley Oct 13, 2016

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Already merged, thanks 😄

@antespi
Copy link
Contributor Author

antespi commented Oct 13, 2016

@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()
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirmed

Copy link
Member

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.

Copy link
Contributor

@lasley lasley left a 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

@antespi
Copy link
Contributor Author

antespi commented Oct 13, 2016

pylint is cool 😄

@antespi
Copy link
Contributor Author

antespi commented Oct 13, 2016

These improvements have been running well in a production instance today, with one mass mailing sending and Mailgun webhooks been received and 6 workers.

Copy link
Member

@rafaelbn rafaelbn left a 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),
Copy link
Member

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)

Copy link
Contributor Author

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)
Copy link
Member

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,
Copy link
Member

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

@pedrobaeza pedrobaeza merged commit 55250b9 into OCA:8.0 Oct 13, 2016
@pedrobaeza pedrobaeza deleted the 8.0-mail_tracking_performance branch October 13, 2016 20:51
cubells pushed a commit to Tecnativa/social that referenced this pull request Jan 3, 2017
[IMP] mail_tracking performance and bounce process
cubells pushed a commit to Tecnativa/social that referenced this pull request Jan 3, 2017
[IMP] mail_tracking performance and bounce process
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants