mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-12 20:44:30 +08:00
4a77f318e5
Summary: We had to add the id to the index as well (along with is_search_indexed) so that the query worked correctly since it sorts by id. Also added a default value of 0 for boolean attributes so that we don't have to query for two different values (0 and null), which was causing sqlite to have to do a final b-tree sort which is slow. Test Plan: Run locally, verify query plan with EXPLAIN QUERY PLAN Reviewers: evan, juan Reviewed By: juan Differential Revision: https://phab.nylas.com/D3712
106 lines
3 KiB
JavaScript
106 lines
3 KiB
JavaScript
import {
|
|
DatabaseStore,
|
|
} from 'nylas-exports'
|
|
|
|
const FRACTION_CPU_AVAILABLE = 0.05;
|
|
const MAX_TIME_SLICE_MILLIS = 100;
|
|
const CHUNK_SIZE = 10;
|
|
const MIN_TIMEOUT = 100;
|
|
const MAX_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
|
|
export default class SearchIndexer {
|
|
constructor() {
|
|
this._searchableModels = {};
|
|
this._hasIndexingToDo = false;
|
|
this._lastTimeStart = null;
|
|
this._lastTimeStop = null;
|
|
}
|
|
|
|
registerSearchableModel(klass, indexCallback) {
|
|
this._searchableModels[klass.name] = {klass, cb: indexCallback};
|
|
}
|
|
|
|
unregisterSearchableModel(klass) {
|
|
delete this._searchableModels[klass.name];
|
|
}
|
|
|
|
async _getNewItemsToIndex() {
|
|
const results = await Promise.all(Object.keys(this._searchableModels).map((modelName) => {
|
|
const modelClass = this._searchableModels[modelName].klass;
|
|
const query = DatabaseStore.findAll(modelClass)
|
|
.where(modelClass.attributes.isSearchIndexed.equal(false))
|
|
.order(modelClass.attributes.id.ascending())
|
|
.limit(CHUNK_SIZE);
|
|
// console.info(query.sql());
|
|
return query;
|
|
}));
|
|
return results.reduce((acc, curr) => acc.concat(curr), []);
|
|
}
|
|
|
|
_indexItems(items) {
|
|
for (const item of items) {
|
|
this._searchableModels[item.constructor.name].cb(item);
|
|
}
|
|
}
|
|
|
|
notifyHasIndexingToDo() {
|
|
if (this._hasIndexingToDo) {
|
|
return;
|
|
}
|
|
this._hasIndexingToDo = true;
|
|
this._scheduleRun();
|
|
}
|
|
|
|
_computeNextTimeout() {
|
|
if (!this._lastTimeStop || !this._lastTimeStart) {
|
|
return MIN_TIMEOUT;
|
|
}
|
|
const spanMillis = this._lastTimeStop.getTime() - this._lastTimeStart.getTime();
|
|
const multiplier = 1.0 / FRACTION_CPU_AVAILABLE;
|
|
return Math.min(Math.max(spanMillis * multiplier, MIN_TIMEOUT), MAX_TIMEOUT);
|
|
}
|
|
|
|
_scheduleRun() {
|
|
// console.info(`SearchIndexer: setting timeout for ${this._computeNextTimeout()} ms`);
|
|
setTimeout(() => this.run(), this._computeNextTimeout());
|
|
}
|
|
|
|
run() {
|
|
if (!this._hasIndexingToDo) {
|
|
return;
|
|
}
|
|
|
|
const start = new Date();
|
|
let current = new Date();
|
|
let firstIter = true;
|
|
let numItemsIndexed = 0;
|
|
|
|
const indexNextChunk = (unindexedItems) => {
|
|
if (firstIter) {
|
|
this._lastTimeStart = start;
|
|
firstIter = false;
|
|
}
|
|
|
|
if (unindexedItems.length === 0) {
|
|
this._hasIndexingToDo = false;
|
|
this._lastTimeStop = new Date();
|
|
// console.info(`Finished indexing ${numItemsIndexed} items, took ${current.getTime() - start.getTime()} ms`);
|
|
return;
|
|
}
|
|
|
|
this._indexItems(unindexedItems);
|
|
numItemsIndexed += unindexedItems.length;
|
|
current = new Date();
|
|
|
|
if (current.getTime() - start.getTime() <= MAX_TIME_SLICE_MILLIS) {
|
|
this._getNewItemsToIndex().then(indexNextChunk);
|
|
return;
|
|
}
|
|
|
|
this._lastTimeStop = new Date();
|
|
// console.info(`SearchIndexer: Finished indexing ${numItemsIndexed} items, took ${current.getTime() - start.getTime()} ms`);
|
|
this._scheduleRun();
|
|
};
|
|
this._getNewItemsToIndex().then(indexNextChunk);
|
|
}
|
|
}
|