-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
Optimised getLastSeenEventTimestamp
function
#20134
Changes from 7 commits
3b7f005
ca3b914
a74672b
c904569
deda65a
18d815a
2dcaa40
41b41ce
e5a3c77
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,7 @@ | ||
// For information on writing migrations, see https://www.notion.so/ghost/Database-migrations-eb5b78c435d741d2b34a582d57c24253 | ||
|
||
const {createAddColumnMigration} = require('../../utils'); | ||
|
||
module.exports = createAddColumnMigration('emails', 'latest_event_timestamp', { | ||
type: 'dateTime', nullable: true | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
const _ = require('lodash'); | ||
const debug = require('@tryghost/debug')('services:email-analytics'); | ||
const db = require('../../../data/db'); | ||
|
||
|
@@ -14,30 +13,36 @@ module.exports = { | |
async getLastSeenEventTimestamp() { | ||
const startDate = new Date(); | ||
|
||
// three separate queries is much faster than using max/greatest (with coalesce to handle nulls) across columns | ||
let {maxDeliveredAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first() || {}; | ||
let {maxOpenedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first() || {}; | ||
let {maxFailedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first() || {}; | ||
|
||
if (maxDeliveredAt && !(maxDeliveredAt instanceof Date)) { | ||
// SQLite returns a string instead of a Date | ||
maxDeliveredAt = new Date(maxDeliveredAt); | ||
} | ||
|
||
if (maxOpenedAt && !(maxOpenedAt instanceof Date)) { | ||
// SQLite returns a string instead of a Date | ||
maxOpenedAt = new Date(maxOpenedAt); | ||
// First, try to fetch the max latest_event_timestamp | ||
let {maxTimestamp} = await db.knex('emails').select(db.knex.raw('MAX(latest_event_timestamp) as maxTimestamp')).first() || {}; | ||
|
||
if (maxTimestamp) { | ||
if (!(maxTimestamp instanceof Date)) { | ||
maxTimestamp = new Date(maxTimestamp); | ||
} | ||
} else { | ||
let {maxDeliveredAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first() || {}; | ||
let {maxOpenedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first() || {}; | ||
let {maxFailedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first() || {}; | ||
|
||
if (maxDeliveredAt && !(maxDeliveredAt instanceof Date)) { | ||
// SQLite returns a string instead of a Date | ||
maxDeliveredAt = new Date(maxDeliveredAt); | ||
} | ||
|
||
if (maxOpenedAt && !(maxOpenedAt instanceof Date)) { | ||
// SQLite returns a string instead of a Date | ||
maxOpenedAt = new Date(maxOpenedAt); | ||
} | ||
|
||
if (maxFailedAt && !(maxFailedAt instanceof Date)) { | ||
// SQLite returns a string instead of a Date | ||
maxFailedAt = new Date(maxFailedAt); | ||
} | ||
} | ||
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. I'm guessing we're keeping the old logic to smoothen the rollout, as existing emails won't have the 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. You could also return early here, to separate the new logic from the old one, e.g.
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. Yes correct, I'll add a note in the comments. |
||
|
||
if (maxFailedAt && !(maxFailedAt instanceof Date)) { | ||
// SQLite returns a string instead of a Date | ||
maxFailedAt = new Date(maxFailedAt); | ||
} | ||
|
||
const lastSeenEventTimestamp = _.max([maxDeliveredAt, maxOpenedAt, maxFailedAt]); | ||
debug(`getLastSeenEventTimestamp: finished in ${Date.now() - startDate}ms`); | ||
|
||
return lastSeenEventTimestamp; | ||
return maxTimestamp; | ||
}, | ||
|
||
async aggregateEmailStats(emailId) { | ||
|
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.
Maybe worth explaining why we need to convert to a Date here?
// SQLite returns a string instead of a Date