mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
perf: Hold 10s cache on user.collectionIds (#11579)
This commit is contained in:
+72
-53
@@ -53,6 +53,8 @@ import DeleteAttachmentTask from "@server/queues/tasks/DeleteAttachmentTask";
|
||||
import type { APIContext } from "@server/types";
|
||||
import { VerificationCode } from "@server/utils/VerificationCode";
|
||||
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
|
||||
import { CacheHelper } from "@server/utils/CacheHelper";
|
||||
import { RedisPrefixHelper } from "@server/utils/RedisPrefixHelper";
|
||||
import { ValidationError } from "../errors";
|
||||
import Attachment from "./Attachment";
|
||||
import AuthenticationProvider from "./AuthenticationProvider";
|
||||
@@ -471,65 +473,82 @@ class User extends ParanoidModel<
|
||||
* @returns An array of collection ids
|
||||
*/
|
||||
public collectionIds = async (options: FindOptions<Collection> = {}) => {
|
||||
const collectionStubs = await Collection.findAll({
|
||||
attributes: ["id"],
|
||||
where: {
|
||||
teamId: this.teamId,
|
||||
[Op.or]: [
|
||||
...(this.isGuest
|
||||
? []
|
||||
: [
|
||||
{
|
||||
permission: {
|
||||
[Op.in]: Object.values(CollectionPermission),
|
||||
const hasOptions =
|
||||
options.transaction || options.paranoid === false || options.lock;
|
||||
|
||||
const fetchCollectionIds = async () => {
|
||||
const collectionStubs = await Collection.findAll({
|
||||
attributes: ["id"],
|
||||
where: {
|
||||
teamId: this.teamId,
|
||||
[Op.or]: [
|
||||
...(this.isGuest
|
||||
? []
|
||||
: [
|
||||
{
|
||||
permission: {
|
||||
[Op.in]: Object.values(CollectionPermission),
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
{
|
||||
"$memberships.id$": { [Op.ne]: null },
|
||||
},
|
||||
{
|
||||
"$groupMemberships.id$": { [Op.ne]: null },
|
||||
},
|
||||
],
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: "memberships",
|
||||
attributes: [],
|
||||
required: false,
|
||||
where: {
|
||||
userId: this.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
association: "groupMemberships",
|
||||
attributes: [],
|
||||
required: false,
|
||||
include: [
|
||||
]),
|
||||
{
|
||||
association: "group",
|
||||
attributes: [],
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
association: "groupUsers",
|
||||
attributes: [],
|
||||
required: true,
|
||||
where: {
|
||||
userId: this.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
"$memberships.id$": { [Op.ne]: null },
|
||||
},
|
||||
{
|
||||
"$groupMemberships.id$": { [Op.ne]: null },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
paranoid: true,
|
||||
...options,
|
||||
});
|
||||
include: [
|
||||
{
|
||||
association: "memberships",
|
||||
attributes: [],
|
||||
required: false,
|
||||
where: {
|
||||
userId: this.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
association: "groupMemberships",
|
||||
attributes: [],
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
association: "group",
|
||||
attributes: [],
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
association: "groupUsers",
|
||||
attributes: [],
|
||||
required: true,
|
||||
where: {
|
||||
userId: this.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
paranoid: true,
|
||||
...options,
|
||||
});
|
||||
|
||||
return Array.from(new Set(collectionStubs.map((c) => c.id)));
|
||||
return Array.from(new Set(collectionStubs.map((c) => c.id)));
|
||||
};
|
||||
|
||||
if (hasOptions) {
|
||||
return fetchCollectionIds();
|
||||
}
|
||||
|
||||
return (
|
||||
(await CacheHelper.getDataOrSet<string[]>(
|
||||
RedisPrefixHelper.getUserCollectionIdsKey(this.id),
|
||||
fetchCollectionIds,
|
||||
10
|
||||
)) ?? []
|
||||
);
|
||||
};
|
||||
|
||||
updateActiveAt = async (ctx: Context, force = false) => {
|
||||
|
||||
@@ -21,6 +21,8 @@ import type { DocumentPermission } from "@shared/types";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import { ValidationError } from "@server/errors";
|
||||
import type { APIContext } from "@server/types";
|
||||
import { CacheHelper } from "@server/utils/CacheHelper";
|
||||
import { RedisPrefixHelper } from "@server/utils/RedisPrefixHelper";
|
||||
import Collection from "./Collection";
|
||||
import Document from "./Document";
|
||||
import GroupMembership from "./GroupMembership";
|
||||
@@ -222,6 +224,15 @@ class UserMembership extends IdModel<
|
||||
});
|
||||
}
|
||||
|
||||
@AfterCreate
|
||||
static async invalidateCollectionIdsAfterCreate(model: UserMembership) {
|
||||
if (model.collectionId) {
|
||||
await CacheHelper.clearData(
|
||||
RedisPrefixHelper.getUserCollectionIdsKey(model.userId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterUpdate
|
||||
static async updateSourcedMemberships(
|
||||
model: UserMembership,
|
||||
@@ -297,6 +308,15 @@ class UserMembership extends IdModel<
|
||||
await model.insertEvent(context, "remove_user");
|
||||
}
|
||||
|
||||
@AfterDestroy
|
||||
static async invalidateCollectionIdsAfterDestroy(model: UserMembership) {
|
||||
if (model.collectionId) {
|
||||
await CacheHelper.clearData(
|
||||
RedisPrefixHelper.getUserCollectionIdsKey(model.userId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate all sourced permissions for a given permission.
|
||||
*/
|
||||
|
||||
@@ -32,4 +32,14 @@ export class RedisPrefixHelper {
|
||||
public static getEmbedCheckKey(url: string) {
|
||||
return `embed:${url}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets key for caching a user's accessible collection IDs.
|
||||
*
|
||||
* @param userId The user ID to generate a key for.
|
||||
* @returns the cache key string.
|
||||
*/
|
||||
public static getUserCollectionIdsKey(userId: string) {
|
||||
return `uc:${userId}`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user