(core) speed up a step in document deletion

Summary:
The `_repairWorkspaceGuests` method is slow for workspaces with large numbers of documents.  It makes a query that produces a lot of rows.  The query itself is tolerable, but TypeORM processing uses enough CPU to be a likely culprit in some production instability.  This diff splits the query into two pieces that are logically independent, but which when combined were resulting in the number of rows being the product of the two pieces.  Once split, there is also a where clause that can be applied to one of the pieces.

The purpose of the method is to add every user that a document within a workspace is shared with to a "guest" group of the workspace itself.  The design of "guest" groups is not ideal, but this diff leaves the design unchanged and is intended only to speed up operation.

Made some small tweaks to the timing of a flakey test, and temporarily recreated the `samples` directory removed in a previous diff (this is currently breaking tests badly on a fresh worker without a `samples` directory lying around)

Test Plan: added test; existing tests pass

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D2844
This commit is contained in:
Paul Fitzpatrick 2021-06-02 15:07:13 -04:00
parent 69ac8fb4b3
commit 29c2b35dcc
2 changed files with 23 additions and 10 deletions

View File

@ -2420,20 +2420,27 @@ export class HomeDBManager extends EventEmitter {
*/
private async _repairWorkspaceGuests(scope: Scope, wsId: number, transaction?: EntityManager): Promise<void> {
return await this._runInTransaction(transaction, async manager => {
// Get guest group for workspace.
const wsQuery = this._workspace(scope, wsId, {manager})
.leftJoinAndSelect('workspaces.aclRules', 'acl_rules')
.leftJoinAndSelect('acl_rules.group', 'groups')
.leftJoinAndSelect('workspaces.docs', 'docs')
.leftJoinAndSelect('docs.aclRules', 'doc_acl_rules')
.leftJoinAndSelect('doc_acl_rules.group', 'doc_groups')
.leftJoinAndSelect('doc_groups.memberUsers', 'doc_users');
const workspace: Workspace = (await wsQuery.getOne())!;
.leftJoinAndSelect('acl_rules.group', 'groups');
const workspace: Workspace = (await wsQuery.getOne())!;
const wsGuestGroup = workspace.aclRules.map(aclRule => aclRule.group)
.find(_grp => _grp.name === roles.GUEST);
if (!wsGuestGroup) {
throw new Error(`_repairWorkspaceGuests error: could not find ${roles.GUEST} ACL group`);
}
wsGuestGroup.memberUsers = this._filterEveryone(getResourceUsers(workspace.docs));
// Get explicitly added users of docs inside the workspace, as a separate query
// to avoid multiplying rows and to allow filtering the result in sql.
const wsWithDocsQuery = this._workspace(scope, wsId, {manager})
.leftJoinAndSelect('workspaces.docs', 'docs')
.leftJoinAndSelect('docs.aclRules', 'doc_acl_rules')
.leftJoinAndSelect('doc_acl_rules.group', 'doc_groups')
.leftJoinAndSelect('doc_groups.memberUsers', 'doc_users')
.andWhere('doc_users.id is not null');
const wsWithDocs = await wsWithDocsQuery.getOne();
wsGuestGroup.memberUsers = this._filterEveryone(getResourceUsers(wsWithDocs?.docs || []));
await manager.save(wsGuestGroup);
});
}

View File

@ -153,9 +153,15 @@ export class FlexServer implements GristServer {
log.info(`== Grist version is ${version.version} (commit ${version.gitcommit})`);
this.info.push(['appRoot', this.appRoot]);
// This directory hold Grist documents.
this.docsRoot = fse.realpathSync(path.resolve((this.options && this.options.dataDir) ||
process.env.GRIST_DATA_DIR ||
getAppPathTo(this.appRoot, 'samples')));
const docsRoot = path.resolve((this.options && this.options.dataDir) ||
process.env.GRIST_DATA_DIR ||
getAppPathTo(this.appRoot, 'samples'));
// Create directory if it doesn't exist.
// TODO: track down all dependencies on 'samples' existing in tests and
// in dev environment, and remove them. Then it would probably be best
// to simply fail if the docs root directory does not exist.
fse.mkdirpSync(docsRoot);
this.docsRoot = fse.realpathSync(docsRoot);
this.info.push(['docsRoot', this.docsRoot]);
const homeUrl = process.env.APP_HOME_URL;