Ghost/core/server/index.js
Kevin Ansfield d675278b0b
Prevented scheduling of recurring analytics jobs when not using emails (#12441)
no issue

- recurring jobs spin up worker threads which can be quite CPU intensive even when not performing much processing, this can be problematic in environments where there are many Ghost instances running
- updated the email job scheduling to be skipped on bootup when there are no emails in the database and to be started when the first email is created as long as we're not in testing env
- increase analytics job schedule from every 2 minutes to every 5 minutes to help spread the load further across instances
2020-12-02 08:17:44 +00:00

214 lines
7.3 KiB
JavaScript

/**
* make sure overrides get's called first!
* - keeping the overrides import here works for installing Ghost as npm!
*
* the call order is the following:
* - root index requires core module
* - core index requires server
* - overrides is the first package to load
*/
require('./overrides');
const debug = require('ghost-ignition').debug('boot:init');
const Promise = require('bluebird');
const config = require('../shared/config');
const {events, i18n} = require('./lib/common');
const logging = require('../shared/logging');
const migrator = require('./data/db/migrator');
const urlUtils = require('./../shared/url-utils');
let parentApp;
// Frontend Components
const themeService = require('../frontend/services/themes');
const appService = require('../frontend/services/apps');
const frontendSettings = require('../frontend/services/settings');
function initialiseServices() {
// CASE: When Ghost is ready with bootstrapping (db migrations etc.), we can trigger the router creation.
// Reason is that the routers access the routes.yaml, which shouldn't and doesn't have to be validated to
// start Ghost in maintenance mode.
// Routing is a bridge between the frontend and API
const routing = require('../frontend/services/routing');
// We pass the themeService API version here, so that the frontend services are less tightly-coupled
routing.bootstrap.start(themeService.getApiVersion());
const settings = require('./services/settings');
const permissions = require('./services/permissions');
const xmlrpc = require('./services/xmlrpc');
const slack = require('./services/slack');
const {mega} = require('./services/mega');
const webhooks = require('./services/webhooks');
const scheduling = require('./adapters/scheduling');
debug('`initialiseServices` Start...');
const getRoutesHash = () => frontendSettings.getCurrentHash('routes');
return Promise.join(
// Initialize the permissions actions and objects
permissions.init(),
xmlrpc.listen(),
slack.listen(),
mega.listen(),
webhooks.listen(),
settings.syncRoutesHash(getRoutesHash),
appService.init(),
scheduling.init({
// NOTE: When changing API version need to consider how to migrate custom scheduling adapters
// that rely on URL to lookup persisted scheduled records (jobs, etc.). Ref: https://github.com/TryGhost/Ghost/pull/10726#issuecomment-489557162
apiUrl: urlUtils.urlFor('api', {version: 'v3', versionType: 'admin'}, true)
})
).then(function () {
debug('XMLRPC, Slack, MEGA, Webhooks, Scheduling, Permissions done');
// Initialise analytics events
if (config.get('segment:key')) {
require('./analytics-events').init();
}
}).then(function () {
debug('...`initialiseServices` End');
});
}
async function initializeRecurringJobs() {
// we don't want to kick off scheduled/recurring jobs that will interfere with tests
if (process.env.NODE_ENV.match(/^testing/)) {
return;
}
if (config.get('backgroundJobs:emailAnalytics')) {
const emailAnalyticsJobs = require('./services/email-analytics/jobs');
await emailAnalyticsJobs.scheduleRecurringJobs();
}
}
/**
* - initialise models
* - initialise i18n
* - load all settings into settings cache (almost every component makes use of this cache)
* - load active theme
* - create our express apps (site, admin, api)
* - start the ghost server
* - enable maintenance mode if migrations are missing
*/
const minimalRequiredSetupToStartGhost = (dbState) => {
const settings = require('./services/settings');
const jobService = require('./services/jobs');
const models = require('./models');
const GhostServer = require('./ghost-server');
let ghostServer;
// Initialize Ghost core internationalization
i18n.init();
debug('Default i18n done for core');
models.init();
debug('Models done');
return settings.init()
.then(() => {
debug('Settings done');
return frontendSettings.init();
})
.then(() => {
debug('Frontend settings done');
return themeService.init();
})
.then(() => {
debug('Themes done');
parentApp = require('./web/parent/app')();
debug('Express Apps done');
return new GhostServer(parentApp);
})
.then((_ghostServer) => {
ghostServer = _ghostServer;
ghostServer.registerCleanupTask(async () => {
await jobService.shutdown();
});
// CASE: all good or db was just initialised
if (dbState === 1 || dbState === 2) {
events.emit('db.ready');
return initialiseServices()
.then(() => {
initializeRecurringJobs();
})
.then(() => {
return ghostServer;
});
}
// CASE: migrations required, put blog into maintenance mode
if (dbState === 4) {
logging.info('Blog is in maintenance mode.');
config.set('maintenance:enabled', true);
migrator.migrate()
.then(() => {
return settings.reinit().then(() => {
events.emit('db.ready');
return initialiseServices();
});
})
.then(() => {
config.set('maintenance:enabled', false);
logging.info('Blog is out of maintenance mode.');
return GhostServer.announceServerReadiness();
})
.then(() => {
initializeRecurringJobs();
})
.catch((err) => {
return GhostServer.announceServerReadiness(err)
.finally(() => {
logging.error(err);
setTimeout(() => {
process.exit(-1);
}, 100);
});
});
return ghostServer;
}
});
};
/**
* Connect to database.
* Check db state.
*/
const isDatabaseInitialisationRequired = () => {
const db = require('./data/db/connection');
let dbState;
return migrator.getState()
.then((state) => {
dbState = state;
// CASE: db initialisation required, wait till finished
if (dbState === 2) {
return migrator.dbInit();
}
// CASE: is db incompatible? e.g. you can't connect a 0.11 database with Ghost 1.0 or 2.0
if (dbState === 3) {
return migrator.isDbCompatible(db)
.then(() => {
dbState = 2;
return migrator.dbInit();
});
}
})
.then(() => {
return minimalRequiredSetupToStartGhost(dbState);
});
};
module.exports = isDatabaseInitialisationRequired;