add note attribute cache to speed up tree loading

This commit is contained in:
zadam 2020-05-02 18:19:41 +02:00
parent ed52f93bbb
commit 9be1d1f697
4 changed files with 61 additions and 24 deletions

View File

@ -1,5 +1,6 @@
import server from '../services/server.js'; import server from '../services/server.js';
import Attribute from './attribute.js'; import Attribute from './attribute.js';
import noteAttributeCache from "../services/note_attribute_cache.js";
const LABEL = 'label'; const LABEL = 'label';
const LABEL_DEFINITION = 'label-definition'; const LABEL_DEFINITION = 'label-definition';
@ -156,9 +157,9 @@ class NoteShort {
getOwnedAttributes(type, name) { getOwnedAttributes(type, name) {
const attrs = this.attributes const attrs = this.attributes
.map(attributeId => this.treeCache.attributes[attributeId]) .map(attributeId => this.treeCache.attributes[attributeId])
.filter(attr => !!attr); .filter(Boolean); // filter out nulls;
return this.__filterAttrs(attrs, type, name) return this.__filterAttrs(attrs, type, name);
} }
/** /**
@ -167,43 +168,45 @@ class NoteShort {
* @returns {Attribute[]} all note's attributes, including inherited ones * @returns {Attribute[]} all note's attributes, including inherited ones
*/ */
getAttributes(type, name) { getAttributes(type, name) {
const ownedAttributes = this.getOwnedAttributes(); if (!(this.noteId in noteAttributeCache)) {
const ownedAttributes = this.getOwnedAttributes();
const attrArrs = [ const attrArrs = [
ownedAttributes ownedAttributes
]; ];
for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) { for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) {
const templateNote = this.treeCache.getNoteFromCache(templateAttr.value); const templateNote = this.treeCache.notes[templateAttr.value];
if (templateNote) { if (templateNote) {
attrArrs.push(templateNote.getAttributes()); attrArrs.push(templateNote.getAttributes());
}
}
if (this.noteId !== 'root') {
for (const parentNote of this.getParentNotes()) {
// these virtual parent-child relationships are also loaded into frontend tree cache
if (parentNote.type !== 'search') {
attrArrs.push(parentNote.getInheritableAttributes());
} }
} }
if (this.noteId !== 'root') {
for (const parentNote of this.getParentNotes()) {
// these virtual parent-child relationships are also loaded into frontend tree cache
if (parentNote.type !== 'search') {
attrArrs.push(parentNote.getInheritableAttributes());
}
}
}
noteAttributeCache.attributes[this.noteId] = attrArrs.flat();
} }
const attributes = attrArrs.flat(); return this.__filterAttrs(noteAttributeCache.attributes[this.noteId], type, name);
return this.__filterAttrs(attributes, type, name);
} }
__filterAttrs(attributes, type, name) { __filterAttrs(attributes, type, name) {
if (type && name) { if (!type && !name) {
return attributes;
} else if (type && name) {
return attributes.filter(attr => attr.type === type && attr.name === name); return attributes.filter(attr => attr.type === type && attr.name === name);
} else if (type) { } else if (type) {
return attributes.filter(attr => attr.type === type); return attributes.filter(attr => attr.type === type);
} else if (name) { } else if (name) {
return attributes.filter(attr => attr.name === name); return attributes.filter(attr => attr.name === name);
} else {
return attributes;
} }
} }

View File

@ -101,6 +101,15 @@ export default class LoadResults {
this.options.includes(name); this.options.includes(name);
} }
/**
* @return {boolean} true if there are changes which could affect the attributes (including inherited ones)
*/
hasAttributeRelatedChanges() {
return Object.keys(this.noteIdToSourceId).length === 0
&& this.branches.length === 0
&& this.attributes.length === 0;
}
isEmpty() { isEmpty() {
return Object.keys(this.noteIdToSourceId).length === 0 return Object.keys(this.noteIdToSourceId).length === 0
&& this.branches.length === 0 && this.branches.length === 0

View File

@ -0,0 +1,20 @@
/**
* Purpose of this class is to cache list of attributes for notes.
*
* Cache invalidation granularity is global - whenever a write operation is detected to notes, branches or attributes
* we invalidate the whole cache. That's OK, since the purpose for this is to speed up batch read-only operations, such
* as loading the tree which uses attributes heavily.
*/
class NoteAttributeCache {
constructor() {
this.attributes = {};
}
invalidate() {
this.attributes = {};
}
}
const noteAttributeCache = new NoteAttributeCache();
export default noteAttributeCache;

View File

@ -6,6 +6,7 @@ import Branch from "../entities/branch.js";
import Attribute from "../entities/attribute.js"; import Attribute from "../entities/attribute.js";
import options from "./options.js"; import options from "./options.js";
import treeCache from "./tree_cache.js"; import treeCache from "./tree_cache.js";
import noteAttributeCache from "./note_attribute_cache.js";
const $outstandingSyncsCount = $("#outstanding-syncs-count"); const $outstandingSyncsCount = $("#outstanding-syncs-count");
@ -359,6 +360,10 @@ async function processSyncRows(syncRows) {
}); });
if (!loadResults.isEmpty()) { if (!loadResults.isEmpty()) {
if (loadResults.hasAttributeRelatedChanges()) {
noteAttributeCache.invalidate();
}
const appContext = (await import("./app_context.js")).default; const appContext = (await import("./app_context.js")).default;
await appContext.triggerEvent('entitiesReloaded', {loadResults}); await appContext.triggerEvent('entitiesReloaded', {loadResults});
} }