Merge pull request #110 from jorilallo/queryperf

Query Improvements
This commit is contained in:
Jori Lallo
2017-07-06 23:22:16 -07:00
committed by GitHub
10 changed files with 100 additions and 115 deletions

View File

@@ -18,7 +18,7 @@ class DocumentViewersStore {
this.isFetching = true; this.isFetching = true;
try { try {
const res = await client.get( const res = await client.post(
'/views.list', '/views.list',
{ {
id: this.documentId, id: this.documentId,

View File

@@ -1,13 +1,13 @@
require('./init'); require('./init');
var app = require('./server').default; const app = require('./server').default;
var http = require('http'); const http = require('http');
var server = http.createServer(app.callback()); const server = http.createServer(app.callback());
server.listen(process.env.PORT || '3000'); server.listen(process.env.PORT || '3000');
server.on('error', (err) => { server.on('error', err => {
throw err; throw err;
}); });
server.on('listening', () => { server.on('listening', () => {
var address = server.address(); const address = server.address();
console.log('Listening on %s%s', address.address, address.port); console.log(`Listening on http://localhost:${address.port}`);
}); });

View File

@@ -24,7 +24,7 @@ router.post('collections.create', auth(), async ctx => {
}); });
ctx.body = { ctx.body = {
data: await presentCollection(ctx, atlas, true), data: await presentCollection(ctx, atlas),
}; };
}); });
@@ -33,7 +33,7 @@ router.post('collections.info', auth(), async ctx => {
ctx.assertPresent(id, 'id is required'); ctx.assertPresent(id, 'id is required');
const user = ctx.state.user; const user = ctx.state.user;
const atlas = await Collection.findOne({ const atlas = await Collection.scope('withRecentDocuments').findOne({
where: { where: {
id, id,
teamId: user.teamId, teamId: user.teamId,
@@ -43,7 +43,7 @@ router.post('collections.info', auth(), async ctx => {
if (!atlas) throw httpErrors.NotFound(); if (!atlas) throw httpErrors.NotFound();
ctx.body = { ctx.body = {
data: await presentCollection(ctx, atlas, true), data: await presentCollection(ctx, atlas),
}; };
}); });
@@ -58,16 +58,10 @@ router.post('collections.list', auth(), pagination(), async ctx => {
limit: ctx.state.pagination.limit, limit: ctx.state.pagination.limit,
}); });
// Collectiones const data = await Promise.all(
let data = []; collections.map(async atlas => await presentCollection(ctx, atlas))
await Promise.all(
collections.map(async atlas => {
return data.push(await presentCollection(ctx, atlas, true));
})
); );
data = _.orderBy(data, ['updatedAt'], ['desc']);
ctx.body = { ctx.body = {
pagination: ctx.state.pagination, pagination: ctx.state.pagination,
data, data,

View File

@@ -8,7 +8,6 @@ import { presentDocument } from '../presenters';
import { Document, Collection, Star, View } from '../models'; import { Document, Collection, Star, View } from '../models';
const router = new Router(); const router = new Router();
router.post('documents.list', auth(), pagination(), async ctx => { router.post('documents.list', auth(), pagination(), async ctx => {
let { sort = 'updatedAt', direction } = ctx.body; let { sort = 'updatedAt', direction } = ctx.body;
if (direction !== 'ASC') direction = 'DESC'; if (direction !== 'ASC') direction = 'DESC';
@@ -19,9 +18,12 @@ router.post('documents.list', auth(), pagination(), async ctx => {
order: [[sort, direction]], order: [[sort, direction]],
offset: ctx.state.pagination.offset, offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit, limit: ctx.state.pagination.limit,
include: [{ model: Star, as: 'starred', where: { userId: user.id } }],
}); });
let data = await Promise.all(documents.map(doc => presentDocument(ctx, doc))); const data = await Promise.all(
documents.map(document => presentDocument(ctx, document))
);
ctx.body = { ctx.body = {
pagination: ctx.state.pagination, pagination: ctx.state.pagination,
@@ -42,7 +44,7 @@ router.post('documents.viewed', auth(), pagination(), async ctx => {
limit: ctx.state.pagination.limit, limit: ctx.state.pagination.limit,
}); });
let data = await Promise.all( const data = await Promise.all(
views.map(view => presentDocument(ctx, view.document)) views.map(view => presentDocument(ctx, view.document))
); );
@@ -60,12 +62,17 @@ router.post('documents.starred', auth(), pagination(), async ctx => {
const views = await Star.findAll({ const views = await Star.findAll({
where: { userId: user.id }, where: { userId: user.id },
order: [[sort, direction]], order: [[sort, direction]],
include: [{ model: Document }], include: [
{
model: Document,
include: [{ model: Star, as: 'starred', where: { userId: user.id } }],
},
],
offset: ctx.state.pagination.offset, offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit, limit: ctx.state.pagination.limit,
}); });
let data = await Promise.all( const data = await Promise.all(
views.map(view => presentDocument(ctx, view.document)) views.map(view => presentDocument(ctx, view.document))
); );
@@ -94,8 +101,7 @@ router.post('documents.info', auth(), async ctx => {
ctx.body = { ctx.body = {
data: await presentDocument(ctx, document, { data: await presentDocument(ctx, document, {
includeCollection: document.private, includeViews: true,
includeCollaborators: true,
}), }),
}; };
}); });
@@ -108,16 +114,8 @@ router.post('documents.search', auth(), async ctx => {
const documents = await Document.searchForUser(user, query); const documents = await Document.searchForUser(user, query);
const data = []; const data = await Promise.all(
await Promise.all( documents.map(async document => await presentDocument(ctx, document))
documents.map(async document => {
data.push(
await presentDocument(ctx, document, {
includeCollection: true,
includeCollaborators: true,
})
);
})
); );
ctx.body = { ctx.body = {
@@ -200,11 +198,7 @@ router.post('documents.create', auth(), async ctx => {
} }
ctx.body = { ctx.body = {
data: await presentDocument(ctx, newDocument, { data: await presentDocument(ctx, newDocument),
includeCollection: true,
includeCollaborators: true,
collection: ownerCollection,
}),
}; };
}); });
@@ -230,11 +224,7 @@ router.post('documents.update', auth(), async ctx => {
} }
ctx.body = { ctx.body = {
data: await presentDocument(ctx, document, { data: await presentDocument(ctx, document),
includeCollection: true,
includeCollaborators: true,
collection: collection,
}),
}; };
}); });
@@ -273,11 +263,7 @@ router.post('documents.move', auth(), async ctx => {
} }
ctx.body = { ctx.body = {
data: await presentDocument(ctx, document, { data: await presentDocument(ctx, document),
includeCollection: true,
includeCollaborators: true,
collection: collection,
}),
}; };
}); });

View File

@@ -1,9 +1,10 @@
// @flow
import debug from 'debug'; import debug from 'debug';
const debugCache = debug('cache'); const debugCache = debug('cache');
export default function cache() { export default function cache() {
return async function cacheMiddleware(ctx, next) { return async function cacheMiddleware(ctx: Object, next: Function) {
ctx.cache = {}; ctx.cache = {};
ctx.cache.set = async (id, value) => { ctx.cache.set = async (id, value) => {

View File

@@ -60,6 +60,16 @@ const Collection = sequelize.define(
as: 'documents', as: 'documents',
foreignKey: 'atlasId', foreignKey: 'atlasId',
}); });
Collection.addScope('withRecentDocuments', {
include: [
{
as: 'documents',
limit: 10,
model: models.Document,
order: [['updatedAt', 'DESC']],
},
],
});
}, },
}, },
instanceMethods: { instanceMethods: {

View File

@@ -115,7 +115,32 @@ const Document = sequelize.define(
}, },
classMethods: { classMethods: {
associate: models => { associate: models => {
Document.belongsTo(models.User); Document.belongsTo(models.Collection, {
as: 'collection',
foreignKey: 'atlasId',
});
Document.belongsTo(models.User, {
as: 'createdBy',
foreignKey: 'createdById',
});
Document.belongsTo(models.User, {
as: 'updatedBy',
foreignKey: 'lastModifiedById',
});
Document.hasMany(models.Star, {
as: 'starred',
});
Document.addScope(
'defaultScope',
{
include: [
{ model: models.Collection, as: 'collection' },
{ model: models.User, as: 'createdBy' },
{ model: models.User, as: 'updatedBy' },
],
},
{ override: true }
);
}, },
findById: async id => { findById: async id => {
if (isUUID(id)) { if (isUUID(id)) {

View File

@@ -1,8 +1,9 @@
// @flow
import _ from 'lodash'; import _ from 'lodash';
import { Document } from '../models'; import { Collection } from '../models';
import presentDocument from './document'; import presentDocument from './document';
async function present(ctx, collection, includeRecentDocuments = false) { async function present(ctx: Object, collection: Collection) {
ctx.cache.set(collection.id, collection); ctx.cache.set(collection.id, collection);
const data = { const data = {
@@ -13,31 +14,21 @@ async function present(ctx, collection, includeRecentDocuments = false) {
type: collection.type, type: collection.type,
createdAt: collection.createdAt, createdAt: collection.createdAt,
updatedAt: collection.updatedAt, updatedAt: collection.updatedAt,
recentDocuments: undefined,
documents: undefined,
}; };
if (collection.type === 'atlas') if (collection.type === 'atlas') {
data.documents = await collection.getDocumentsStructure(); data.documents = await collection.getDocumentsStructure();
}
if (includeRecentDocuments) { if (collection.documents) {
const documents = await Document.findAll({ data.recentDocuments = await Promise.all(
where: { collection.documents.map(
atlasId: collection.id, async document =>
}, await presentDocument(ctx, document, { includeCollaborators: true })
limit: 10, )
order: [['updatedAt', 'DESC']],
});
const recentDocuments = [];
await Promise.all(
documents.map(async document => {
recentDocuments.push(
await presentDocument(ctx, document, {
includeCollaborators: true,
})
);
})
); );
data.recentDocuments = _.orderBy(recentDocuments, ['updatedAt'], ['desc']);
} }
return data; return data;

View File

@@ -1,17 +1,16 @@
import { Collection, Star, User, View } from '../models'; // @flow
import _ from 'lodash';
import { User, Document, View } from '../models';
import presentUser from './user'; import presentUser from './user';
import presentCollection from './collection'; import presentCollection from './collection';
async function present(ctx, document, options) { async function present(ctx: Object, document: Document, options: Object = {}) {
options = { options = {
includeCollection: true,
includeCollaborators: true, includeCollaborators: true,
includeViews: true, includeViews: false,
...options, ...options,
}; };
ctx.cache.set(document.id, document); ctx.cache.set(document.id, document);
const userId = ctx.state.user.id;
const data = { const data = {
id: document.id, id: document.id,
url: document.getUrl(), url: document.getUrl(),
@@ -21,16 +20,19 @@ async function present(ctx, document, options) {
html: document.html, html: document.html,
preview: document.preview, preview: document.preview,
createdAt: document.createdAt, createdAt: document.createdAt,
createdBy: undefined, createdBy: presentUser(ctx, document.createdBy),
updatedAt: document.updatedAt, updatedAt: document.updatedAt,
updatedBy: undefined, updatedBy: presentUser(ctx, document.updatedBy),
team: document.teamId, team: document.teamId,
collaborators: [], collaborators: [],
starred: !!document.starred,
collection: undefined,
views: undefined,
}; };
data.starred = !!await Star.findOne({ if (document.private) {
where: { documentId: document.id, userId }, data.collection = await presentCollection(ctx, document.collection);
}); }
if (options.includeViews) { if (options.includeViews) {
data.views = await View.sum('count', { data.views = await View.sum('count', {
@@ -38,42 +40,15 @@ async function present(ctx, document, options) {
}); });
} }
if (options.includeCollection) {
data.collection = await ctx.cache.get(document.atlasId, async () => {
const collection =
options.collection ||
(await Collection.findOne({
where: {
id: document.atlasId,
},
}));
return presentCollection(ctx, collection);
});
}
if (options.includeCollaborators) { if (options.includeCollaborators) {
// This could be further optimized by using ctx.cache // This could be further optimized by using ctx.cache
data.collaborators = await User.findAll({ data.collaborators = await User.findAll({
where: { where: {
id: { id: { $in: _.takeRight(document.collaboratorIds, 10) || [] },
$in: document.collaboratorIds || [],
},
}, },
}).map(user => presentUser(ctx, user)); }).map(user => presentUser(ctx, user));
} }
const createdBy = await ctx.cache.get(
document.createdById,
async () => await User.findById(document.createdById)
);
data.createdBy = await presentUser(ctx, createdBy);
const updatedBy = await ctx.cache.get(
document.lastModifiedById,
async () => await User.findById(document.lastModifiedById)
);
data.updatedBy = await presentUser(ctx, updatedBy);
return data; return data;
} }

View File

@@ -1,4 +1,7 @@
function present(ctx, team) { // @flow
import { Team } from '../models';
function present(ctx: Object, team: Team) {
ctx.cache.set(team.id, team); ctx.cache.set(team.id, team);
return { return {