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:
parent
3ed5087deb
commit
860718f584
3 changed files with 230 additions and 17 deletions
|
@ -38,7 +38,7 @@ class Resource extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
update(obj) {
|
update(obj) {
|
||||||
this.data = obj;
|
Object.assign(this.data, obj);
|
||||||
|
|
||||||
if (!this.isReserved()) {
|
if (!this.isReserved()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -16,8 +16,32 @@ const resourcesConfig = [
|
||||||
modelOptions: {
|
modelOptions: {
|
||||||
modelName: 'Post',
|
modelName: 'Post',
|
||||||
filter: 'visibility:public+status:published+page:false',
|
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'],
|
withRelated: ['tags', 'authors'],
|
||||||
|
withRelatedPrimary: {
|
||||||
|
primary_tag: 'tags',
|
||||||
|
primary_author: 'authors'
|
||||||
|
},
|
||||||
withRelatedFields: {
|
withRelatedFields: {
|
||||||
tags: ['tags.id', 'tags.slug'],
|
tags: ['tags.id', 'tags.slug'],
|
||||||
authors: ['users.id', 'users.slug']
|
authors: ['users.id', 'users.slug']
|
||||||
|
@ -33,7 +57,31 @@ const resourcesConfig = [
|
||||||
type: 'pages',
|
type: 'pages',
|
||||||
modelOptions: {
|
modelOptions: {
|
||||||
modelName: 'Post',
|
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'
|
filter: 'visibility:public+status:published+page:true'
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
|
@ -47,7 +95,11 @@ const resourcesConfig = [
|
||||||
keep: ['id', 'slug', 'updated_at', 'created_at'],
|
keep: ['id', 'slug', 'updated_at', 'created_at'],
|
||||||
modelOptions: {
|
modelOptions: {
|
||||||
modelName: 'Tag',
|
modelName: 'Tag',
|
||||||
reducedFields: true,
|
exclude: [
|
||||||
|
'description',
|
||||||
|
'meta_title',
|
||||||
|
'meta_description'
|
||||||
|
],
|
||||||
filter: 'visibility:public'
|
filter: 'visibility:public'
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
|
@ -60,7 +112,17 @@ const resourcesConfig = [
|
||||||
type: 'users',
|
type: 'users',
|
||||||
modelOptions: {
|
modelOptions: {
|
||||||
modelName: 'User',
|
modelName: 'User',
|
||||||
reducedFields: true,
|
exclude: [
|
||||||
|
'bio',
|
||||||
|
'website',
|
||||||
|
'location',
|
||||||
|
'facebook',
|
||||||
|
'twitter',
|
||||||
|
'accessibility',
|
||||||
|
'meta_title',
|
||||||
|
'meta_description',
|
||||||
|
'tour'
|
||||||
|
],
|
||||||
filter: 'visibility:public'
|
filter: 'visibility:public'
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
|
@ -161,7 +223,44 @@ class Resources {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onResourceAdded(type, model) {
|
_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);
|
debug('_onResourceAdded', type);
|
||||||
this.data[type].push(resource);
|
this.data[type].push(resource);
|
||||||
|
@ -195,7 +294,44 @@ class Resources {
|
||||||
|
|
||||||
this.data[type].every((resource) => {
|
this.data[type].every((resource) => {
|
||||||
if (resource.data.id === model.id) {
|
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
|
// CASE: pretend it was added
|
||||||
if (!resource.isReserved()) {
|
if (!resource.isReserved()) {
|
||||||
|
@ -238,7 +374,7 @@ class Resources {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this.data[type][index];
|
this.data[type].splice(index, 1);
|
||||||
resource.remove();
|
resource.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,13 +83,47 @@ describe('Unit: services/url/Resources', function () {
|
||||||
|
|
||||||
queue.start.callsFake(function (options) {
|
queue.start.callsFake(function (options) {
|
||||||
options.event.should.eql('added');
|
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));
|
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();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
models.Post.add({
|
models.Post.add({
|
||||||
title: 'test',
|
slug: 'test-1234',
|
||||||
status: 'published'
|
status: 'published',
|
||||||
|
tags: [{slug: 'tag-1', name: 'tag-name'}]
|
||||||
}, testUtils.context.owner)
|
}, testUtils.context.owner)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
onEvents['post.published'](emitEvents['post.published']);
|
onEvents['post.published'](emitEvents['post.published']);
|
||||||
|
@ -110,12 +144,12 @@ describe('Unit: services/url/Resources', function () {
|
||||||
randomResource.reserve();
|
randomResource.reserve();
|
||||||
|
|
||||||
randomResource.addListener('updated', function () {
|
randomResource.addListener('updated', function () {
|
||||||
randomResource.data.title.should.eql('new title, wow');
|
randomResource.data.slug.should.eql('tada');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
models.Post.edit({
|
models.Post.edit({
|
||||||
title: 'new title, wow'
|
slug: 'tada'
|
||||||
}, _.merge({id: randomResource.data.id}, testUtils.context.owner))
|
}, _.merge({id: randomResource.data.id}, testUtils.context.owner))
|
||||||
.then(function () {
|
.then(function () {
|
||||||
onEvents['post.published.edited'](emitEvents['post.published.edited']);
|
onEvents['post.published.edited'](emitEvents['post.published.edited']);
|
||||||
|
@ -132,19 +166,62 @@ describe('Unit: services/url/Resources', function () {
|
||||||
queue.start.callsFake(function (options) {
|
queue.start.callsFake(function (options) {
|
||||||
options.event.should.eql('init');
|
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) {
|
queue.start.callsFake(function (options) {
|
||||||
options.event.should.eql('added');
|
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();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
models.Post.edit({
|
models.Post.edit({
|
||||||
title: 'new title, wow'
|
slug: 'eins-zwei'
|
||||||
}, _.merge({id: randomResource.data.id}, testUtils.context.owner))
|
}, _.merge({id: resourceToUpdate.data.id}, testUtils.context.owner))
|
||||||
.then(function () {
|
.then(function () {
|
||||||
onEvents['post.published.edited'](emitEvents['post.published.edited']);
|
onEvents['post.published.edited'](emitEvents['post.published.edited']);
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue