mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-22 08:16:09 +08:00
[local-sync] audit database indices
Summary: Fixes T7398 We were create unnecessary and duplicate indices for the IDs of all of our objects and increasing db write overhead. We were not creating the correct reverse index for our join tables. The search API'd db is already in scope of the accountId, this is an unnecessary constraint on the query Test Plan: manual Reviewers: spang, juan Reviewed By: juan Maniphest Tasks: T7398 Differential Revision: https://phab.nylas.com/D3606
This commit is contained in:
parent
6d72bb2aaf
commit
4a760ff5ec
|
@ -260,10 +260,7 @@ class ImapSearchClient {
|
||||||
const {Message} = db;
|
const {Message} = db;
|
||||||
return (await this._search(db, query)).flatMap((uids) => {
|
return (await this._search(db, query)).flatMap((uids) => {
|
||||||
return Message.findAll({
|
return Message.findAll({
|
||||||
where: {
|
where: {folderImapUID: uids},
|
||||||
accountId: this.account.id,
|
|
||||||
folderImapUID: uids,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}).flatMap((messages) => {
|
}).flatMap((messages) => {
|
||||||
return getThreadsForMessages(db, messages, limit);
|
return getThreadsForMessages(db, messages, limit);
|
||||||
|
|
|
@ -32,7 +32,7 @@ class EnsureMessageInSentFolderIMAP extends SyncbackTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseMessage = await Message.findById(messageId,
|
const baseMessage = await Message.findById(messageId,
|
||||||
{include: [{model: db.Folder}, {model: db.Label}]});
|
{include: [{model: db.Folder}, {model: db.Label}, {model: db.File}]});
|
||||||
|
|
||||||
if (!baseMessage) {
|
if (!baseMessage) {
|
||||||
throw new APIError(`Couldn't find message ${messageId} to stuff in sent folder`, 500)
|
throw new APIError(`Couldn't find message ${messageId} to stuff in sent folder`, 500)
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: SQLITE creates an index on the `primaryKey` (the ID) for you.
|
||||||
|
* This "Auto Index" is called `sqlite_autoindex_contacts_1`.
|
||||||
|
*
|
||||||
|
* If you run `EXPLAIN QUERY PLAN SELECT * FROM contacts WHERE id=1` you
|
||||||
|
* get:
|
||||||
|
* SEARCH TABLE contacts USING INDEX sqlite_autoindex_contacts_1
|
||||||
|
* (id=?)
|
||||||
|
*/
|
||||||
module.exports = (sequelize, Sequelize) => {
|
module.exports = (sequelize, Sequelize) => {
|
||||||
return sequelize.define('contact', {
|
return sequelize.define('contact', {
|
||||||
id: {type: Sequelize.STRING(65), primaryKey: true},
|
id: {type: Sequelize.STRING(65), primaryKey: true},
|
||||||
|
@ -8,12 +17,6 @@ module.exports = (sequelize, Sequelize) => {
|
||||||
name: Sequelize.STRING,
|
name: Sequelize.STRING,
|
||||||
email: Sequelize.STRING,
|
email: Sequelize.STRING,
|
||||||
}, {
|
}, {
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
unique: true,
|
|
||||||
fields: ['id'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
classMethods: {
|
classMethods: {
|
||||||
hash({email}) {
|
hash({email}) {
|
||||||
return crypto.createHash('sha256').update(email, 'utf8').digest('hex');
|
return crypto.createHash('sha256').update(email, 'utf8').digest('hex');
|
||||||
|
|
|
@ -13,17 +13,14 @@ module.exports = (sequelize, Sequelize) => {
|
||||||
accountId: { type: Sequelize.STRING, allowNull: false },
|
accountId: { type: Sequelize.STRING, allowNull: false },
|
||||||
contentType: Sequelize.STRING(500),
|
contentType: Sequelize.STRING(500),
|
||||||
}, {
|
}, {
|
||||||
|
indexes: [
|
||||||
|
{fields: ['messageId']},
|
||||||
|
],
|
||||||
classMethods: {
|
classMethods: {
|
||||||
associate: ({File, Message}) => {
|
associate: ({File, Message}) => {
|
||||||
File.belongsTo(Message)
|
File.belongsTo(Message)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
unique: true,
|
|
||||||
fields: ['id'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
instanceMethods: {
|
instanceMethods: {
|
||||||
async fetch({account, db, logger}) {
|
async fetch({account, db, logger}) {
|
||||||
const settings = Object.assign({}, account.connectionSettings, account.decryptedCredentials())
|
const settings = Object.assign({}, account.connectionSettings, account.decryptedCredentials())
|
||||||
|
|
|
@ -38,16 +38,6 @@ module.exports = (sequelize, Sequelize) => {
|
||||||
*/
|
*/
|
||||||
syncState: JSONColumn('syncState'),
|
syncState: JSONColumn('syncState'),
|
||||||
}, {
|
}, {
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
unique: true,
|
|
||||||
fields: ['role'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unique: true,
|
|
||||||
fields: ['id'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
classMethods: {
|
classMethods: {
|
||||||
associate({Folder, Message, Thread}) {
|
associate({Folder, Message, Thread}) {
|
||||||
Folder.hasMany(Message)
|
Folder.hasMany(Message)
|
||||||
|
|
|
@ -9,16 +9,6 @@ module.exports = (sequelize, Sequelize) => {
|
||||||
name: Sequelize.STRING,
|
name: Sequelize.STRING,
|
||||||
role: Sequelize.STRING,
|
role: Sequelize.STRING,
|
||||||
}, {
|
}, {
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
unique: true,
|
|
||||||
fields: ['role'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unique: true,
|
|
||||||
fields: ['id'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
classMethods: {
|
classMethods: {
|
||||||
associate({Label, Message, MessageLabel, Thread, ThreadLabel}) {
|
associate({Label, Message, MessageLabel, Thread, ThreadLabel}) {
|
||||||
Label.belongsToMany(Message, {through: MessageLabel})
|
Label.belongsToMany(Message, {through: MessageLabel})
|
||||||
|
|
|
@ -62,18 +62,10 @@ module.exports = (sequelize, Sequelize) => {
|
||||||
}),
|
}),
|
||||||
}, {
|
}, {
|
||||||
indexes: [
|
indexes: [
|
||||||
{
|
{fields: ['folderId']},
|
||||||
unique: true,
|
{fields: ['threadId']},
|
||||||
fields: ['id'],
|
{fields: ['gMsgId']}, // Use in `searchThreads`
|
||||||
},
|
{fields: ['folderImapUID']}, // Use in `searchThreads`
|
||||||
{
|
|
||||||
unique: false,
|
|
||||||
fields: ['folderId'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unique: false,
|
|
||||||
fields: ['threadId'],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeUpdate(message) {
|
beforeUpdate(message) {
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
|
/**
|
||||||
|
* CREATE TABLE IF NOT EXISTS `messageLabels` (
|
||||||
|
* `createdAt` DATETIME NOT NULL,
|
||||||
|
* `updatedAt` DATETIME NOT NULL,
|
||||||
|
* `labelId` VARCHAR(65) NOT NULL REFERENCES `labels` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
* `messageId` VARCHAR(65) NOT NULL REFERENCES `messages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
* PRIMARY KEY (`labelId`, `messageId`)
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* sqlite_autoindex_messageLabels_1 (labelId, messageId)
|
||||||
|
*/
|
||||||
module.exports = (sequelize) => {
|
module.exports = (sequelize) => {
|
||||||
return sequelize.define('messageLabel', {
|
return sequelize.define('messageLabel', {
|
||||||
}, {
|
}, {
|
||||||
|
indexes: [
|
||||||
|
// NOTE: When SQLite sets up this table, it creates an auto index in
|
||||||
|
// the order ['labelId', 'messageId']. This is the correct index we
|
||||||
|
// need for queries requesting Messages for a certain Label.
|
||||||
|
//
|
||||||
|
// We need to create one more index to allow queryes from the
|
||||||
|
// reverse direction requesting Labels for a certain Message.
|
||||||
|
{fields: ['messageId', 'labelId']},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
const {DatabaseTypes: {JSONColumn}} = require('isomorphic-core');
|
const {DatabaseTypes: {JSONColumn}} = require('isomorphic-core');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CREATE TABLE IF NOT EXISTS `syncbackRequests` (
|
||||||
|
* `id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` VARCHAR(255),
|
||||||
|
* `status` TEXT NOT NULL DEFAULT 'NEW',
|
||||||
|
* `error` TEXT,
|
||||||
|
* `props` TEXT,
|
||||||
|
* `responseJSON` TEXT,
|
||||||
|
* `accountId` VARCHAR(255) NOT NULL,
|
||||||
|
* `createdAt` DATETIME NOT NULL,
|
||||||
|
* `updatedAt` DATETIME NOT NULL
|
||||||
|
* );
|
||||||
|
*/
|
||||||
module.exports = (sequelize, Sequelize) => {
|
module.exports = (sequelize, Sequelize) => {
|
||||||
return sequelize.define('syncbackRequest', {
|
return sequelize.define('syncbackRequest', {
|
||||||
type: Sequelize.STRING,
|
type: Sequelize.STRING,
|
||||||
|
@ -13,6 +25,9 @@ module.exports = (sequelize, Sequelize) => {
|
||||||
responseJSON: JSONColumn('responseJSON'),
|
responseJSON: JSONColumn('responseJSON'),
|
||||||
accountId: { type: Sequelize.STRING, allowNull: false },
|
accountId: { type: Sequelize.STRING, allowNull: false },
|
||||||
}, {
|
}, {
|
||||||
|
indexes: [
|
||||||
|
{fields: ['status']},
|
||||||
|
],
|
||||||
instanceMethods: {
|
instanceMethods: {
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -23,7 +23,6 @@ module.exports = (sequelize, Sequelize) => {
|
||||||
participants: JSONArrayColumn('participants'),
|
participants: JSONArrayColumn('participants'),
|
||||||
}, {
|
}, {
|
||||||
indexes: [
|
indexes: [
|
||||||
{ fields: ['id'], unique: true },
|
|
||||||
{ fields: ['subject'] },
|
{ fields: ['subject'] },
|
||||||
{ fields: ['remoteThreadId'] },
|
{ fields: ['remoteThreadId'] },
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
|
/**
|
||||||
|
* CREATE TABLE IF NOT EXISTS `threadFolders` (
|
||||||
|
* `createdAt` DATETIME NOT NULL,
|
||||||
|
* `updatedAt` DATETIME NOT NULL,
|
||||||
|
* `threadId` VARCHAR(65) NOT NULL REFERENCES `threads` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
* `folderId` VARCHAR(65) NOT NULL REFERENCES `folders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
* PRIMARY KEY (`threadId`, `folderId`)
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* sqlite_autoindex_threadFolders_1 (threadId, folderId)
|
||||||
|
*/
|
||||||
module.exports = (sequelize) => {
|
module.exports = (sequelize) => {
|
||||||
return sequelize.define('threadFolder', {
|
return sequelize.define('threadFolder', {
|
||||||
}, {
|
}, {
|
||||||
|
indexes: [
|
||||||
|
// NOTE: When SQLite sets up this table, it creates an auto index in
|
||||||
|
// the order ['threadId', 'folderId']. This is the correct index we
|
||||||
|
// need for queries requesting Folders for a certain Thread.
|
||||||
|
//
|
||||||
|
// We need to create one more index to allow queryes from the
|
||||||
|
// reverse direction requesting Threads for a certain Folder.
|
||||||
|
{fields: ['folderId', 'threadId']},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
|
/**
|
||||||
|
* CREATE TABLE IF NOT EXISTS `threadLabels` (
|
||||||
|
* `createdAt` DATETIME NOT NULL,
|
||||||
|
* `updatedAt` DATETIME NOT NULL,
|
||||||
|
* `labelId` VARCHAR(65) NOT NULL REFERENCES `labels` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
* `threadId` VARCHAR(65) NOT NULL REFERENCES `threads` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
* PRIMARY KEY (`labelId`, `threadId`)
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* sqlite_autoindex_threadLabels_1 labelId, threadId
|
||||||
|
*/
|
||||||
module.exports = (sequelize) => {
|
module.exports = (sequelize) => {
|
||||||
return sequelize.define('threadLabel', {
|
return sequelize.define('threadLabel', {
|
||||||
}, {
|
}, {
|
||||||
|
indexes: [
|
||||||
|
// NOTE: When SQLite sets up this table, it creates an auto index in
|
||||||
|
// the order ['labelId', 'threadId']. This is the correct index we
|
||||||
|
// need for queries requesting Threads for a certain Label.
|
||||||
|
//
|
||||||
|
// We need to create one more index to allow queryes from the
|
||||||
|
// reverse direction requesting Labels for a certain Thread.
|
||||||
|
{fields: ['threadId', 'labelId']},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,8 @@ const TransactionConnector = require('./transaction-connector')
|
||||||
|
|
||||||
require('./database-extensions'); // Extends Sequelize on require
|
require('./database-extensions'); // Extends Sequelize on require
|
||||||
|
|
||||||
|
const ENABLE_SEQUELIZE_DEUBG_LOGGING = false
|
||||||
|
|
||||||
class LocalDatabaseConnector {
|
class LocalDatabaseConnector {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._cache = {};
|
this._cache = {};
|
||||||
|
@ -16,7 +18,7 @@ class LocalDatabaseConnector {
|
||||||
return new Sequelize(dbname, '', '', {
|
return new Sequelize(dbname, '', '', {
|
||||||
storage: storage,
|
storage: storage,
|
||||||
dialect: "sqlite",
|
dialect: "sqlite",
|
||||||
logging: false,
|
logging: ENABLE_SEQUELIZE_DEUBG_LOGGING ? console.log : false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue