2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Minimized cached data on resource add/update

refs #9601

- on resource update/add we have cached mobiledoc, html etc
- we have to ensure we exclude the fields (same procdure happens on bootstrap)
- these excluded fields don't have to be cached
- otherwise memory usage is higher in general
- ensure we cache relations with a minimal field set on resource update/add
This commit is contained in:
kirrg001 2018-08-12 12:23:43 +02:00
parent 3ed5087deb
commit 860718f584
3 changed files with 230 additions and 17 deletions

View file

@ -38,7 +38,7 @@ class Resource extends EventEmitter {
}
update(obj) {
this.data = obj;
Object.assign(this.data, obj);
if (!this.isReserved()) {
return;

View file

@ -16,8 +16,32 @@ const resourcesConfig = [
modelOptions: {
modelName: 'Post',
filter: 'visibility:public+status:published+page:false',
reducedFields: true,
exclude: [
'title',
'mobiledoc',
'html',
'plaintext',
'amp',
'codeinjection_head',
'codeinjection_foot',
'meta_title',
'meta_description',
'custom_excerpt',
'og_image',
'og_title',
'og_description',
'twitter_image',
'twitter_title',
'twitter_description',
'custom_template',
'feature_image',
'locale'
],
withRelated: ['tags', 'authors'],
withRelatedPrimary: {
primary_tag: 'tags',
primary_author: 'authors'
},
withRelatedFields: {
tags: ['tags.id', 'tags.slug'],
authors: ['users.id', 'users.slug']
@ -33,7 +57,31 @@ const resourcesConfig = [
type: 'pages',
modelOptions: {
modelName: 'Post',
reducedFields: true,
exclude: [
'title',
'mobiledoc',
'html',
'plaintext',
'amp',
'codeinjection_head',
'codeinjection_foot',
'meta_title',
'meta_description',
'custom_excerpt',
'og_image',
'og_title',
'og_description',
'twitter_image',
'twitter_title',
'twitter_description',
'custom_template',
'feature_image',
'locale',
'tags',
'authors',
'primary_tag',
'primary_author'
],
filter: 'visibility:public+status:published+page:true'
},
events: {
@ -47,7 +95,11 @@ const resourcesConfig = [
keep: ['id', 'slug', 'updated_at', 'created_at'],
modelOptions: {
modelName: 'Tag',
reducedFields: true,
exclude: [
'description',
'meta_title',
'meta_description'
],
filter: 'visibility:public'
},
events: {
@ -60,7 +112,17 @@ const resourcesConfig = [
type: 'users',
modelOptions: {
modelName: 'User',
reducedFields: true,
exclude: [
'bio',
'website',
'location',
'facebook',
'twitter',
'accessibility',
'meta_title',
'meta_description',
'tour'
],
filter: 'visibility:public'
},
events: {
@ -161,7 +223,44 @@ class Resources {
}
_onResourceAdded(type, model) {
const resource = new Resource(type, model.toJSON());
const resourceConfig = _.find(resourcesConfig, {type: type});
const exclude = resourceConfig.modelOptions.exclude;
const withRelatedFields = resourceConfig.modelOptions.withRelatedFields;
const obj = _.omit(model.toJSON(), exclude);
if (withRelatedFields) {
_.each(withRelatedFields, (fields, key) => {
if (!obj[key]) {
return;
}
obj[key] = _.map(obj[key], (relation) => {
const relationToReturn = {};
_.each(fields, (field) => {
const fieldSanitized = field.replace(/^\w+./, '');
relationToReturn[fieldSanitized] = relation[fieldSanitized];
});
return relationToReturn;
});
});
const withRelatedPrimary = resourceConfig.modelOptions.withRelatedPrimary;
if (withRelatedPrimary) {
_.each(withRelatedPrimary, (relation, primaryKey) => {
if (!obj[primaryKey] || !obj[relation]) {
return;
}
const targetTagKeys = Object.keys(obj[relation].find((item) => {return item.id === obj[primaryKey].id;}));
obj[primaryKey] = _.pick(obj[primaryKey], targetTagKeys);
});
}
}
const resource = new Resource(type, obj);
debug('_onResourceAdded', type);
this.data[type].push(resource);
@ -195,7 +294,44 @@ class Resources {
this.data[type].every((resource) => {
if (resource.data.id === model.id) {
resource.update(model.toJSON());
const resourceConfig = _.find(resourcesConfig, {type: type});
const exclude = resourceConfig.modelOptions.exclude;
const withRelatedFields = resourceConfig.modelOptions.withRelatedFields;
const obj = _.omit(model.toJSON(), exclude);
if (withRelatedFields) {
_.each(withRelatedFields, (fields, key) => {
if (!obj[key]) {
return;
}
obj[key] = _.map(obj[key], (relation) => {
const relationToReturn = {};
_.each(fields, (field) => {
const fieldSanitized = field.replace(/^\w+./, '');
relationToReturn[fieldSanitized] = relation[fieldSanitized];
});
return relationToReturn;
});
});
const withRelatedPrimary = resourceConfig.modelOptions.withRelatedPrimary;
if (withRelatedPrimary) {
_.each(withRelatedPrimary, (relation, primaryKey) => {
if (!obj[primaryKey] || !obj[relation]) {
return;
}
const targetTagKeys = Object.keys(obj[relation].find((item) => {return item.id === obj[primaryKey].id;}));
obj[primaryKey] = _.pick(obj[primaryKey], targetTagKeys);
});
}
}
resource.update(obj);
// CASE: pretend it was added
if (!resource.isReserved()) {
@ -238,7 +374,7 @@ class Resources {
return;
}
delete this.data[type][index];
this.data[type].splice(index, 1);
resource.remove();
}

View file

@ -83,13 +83,47 @@ describe('Unit: services/url/Resources', function () {
queue.start.callsFake(function (options) {
options.event.should.eql('added');
const obj = _.find(resources.data.posts, {data: {slug: 'test-1234'}}).data;
Object.keys(obj).should.eql([
'id',
'uuid',
'slug',
'featured',
'page',
'status',
'visibility',
'created_at',
'updated_at',
'published_at',
'published_by',
'created_by',
'updated_by',
'tags',
'authors',
'author',
'primary_author',
'primary_tag',
'url',
'comment_id'
]);
should.exist(resources.getByIdAndType(options.eventData.type, options.eventData.id));
obj.tags.length.should.eql(1);
Object.keys(obj.tags[0]).should.eql(['id', 'slug']);
obj.authors.length.should.eql(1);
Object.keys(obj.authors[0]).should.eql(['id', 'slug']);
should.exist(obj.primary_author);
Object.keys(obj.primary_author).should.eql(['id', 'slug']);
should.exist(obj.primary_tag);
Object.keys(obj.primary_tag).should.eql(['id', 'slug']);
done();
});
models.Post.add({
title: 'test',
status: 'published'
slug: 'test-1234',
status: 'published',
tags: [{slug: 'tag-1', name: 'tag-name'}]
}, testUtils.context.owner)
.then(function () {
onEvents['post.published'](emitEvents['post.published']);
@ -110,12 +144,12 @@ describe('Unit: services/url/Resources', function () {
randomResource.reserve();
randomResource.addListener('updated', function () {
randomResource.data.title.should.eql('new title, wow');
randomResource.data.slug.should.eql('tada');
done();
});
models.Post.edit({
title: 'new title, wow'
slug: 'tada'
}, _.merge({id: randomResource.data.id}, testUtils.context.owner))
.then(function () {
onEvents['post.published.edited'](emitEvents['post.published.edited']);
@ -132,19 +166,62 @@ describe('Unit: services/url/Resources', function () {
queue.start.callsFake(function (options) {
options.event.should.eql('init');
const randomResource = resources.getAll().posts[Math.floor(Math.random() * (resources.getAll().posts.length - 0) + 0)];
const resourceToUpdate = _.find(resources.getAll().posts, (resource) => {
if (resource.data.tags.length && resource.data.authors.length) {
return true;
}
randomResource.update = sandbox.stub();
return false;
});
sandbox.spy(resourceToUpdate, 'update');
queue.start.callsFake(function (options) {
options.event.should.eql('added');
randomResource.update.calledOnce.should.be.true();
resourceToUpdate.update.calledOnce.should.be.true();
resourceToUpdate.data.slug.should.eql('eins-zwei');
const obj = _.find(resources.data.posts, {data: {id: resourceToUpdate.data.id}}).data;
Object.keys(obj).should.eql([
'id',
'uuid',
'slug',
'featured',
'page',
'status',
'visibility',
'created_at',
'created_by',
'updated_at',
'updated_by',
'published_at',
'published_by',
'tags',
'authors',
'author',
'primary_author',
'primary_tag',
'url',
'comment_id'
]);
should.exist(obj.tags);
Object.keys(obj.tags[0]).should.eql(['id', 'slug']);
should.exist(obj.authors);
Object.keys(obj.authors[0]).should.eql(['id', 'slug']);
should.exist(obj.primary_author);
Object.keys(obj.primary_author).should.eql(['id', 'slug']);
should.exist(obj.primary_tag);
Object.keys(obj.primary_tag).should.eql(['id', 'slug']);
done();
});
models.Post.edit({
title: 'new title, wow'
}, _.merge({id: randomResource.data.id}, testUtils.context.owner))
slug: 'eins-zwei'
}, _.merge({id: resourceToUpdate.data.id}, testUtils.context.owner))
.then(function () {
onEvents['post.published.edited'](emitEvents['post.published.edited']);
})