iceshrimp/packages/backend/src/server/api/common/generate-following-query.ts
Laura Hausmann 8ecf361870
[backend] Implement heuristics for home timeline queries
After lots of performance analysis, we've ended up with a cutoff value of 250 posts in the last 7d, after which we should switch which query plan to nudge postgres towards. This should greatly improve performance of users who were previously performance edge cases.
2023-11-22 00:14:54 +01:00

49 lines
1.7 KiB
TypeScript

import { Brackets, SelectQueryBuilder } from "typeorm";
import { User } from "@/models/entities/user.js";
import { Followings, Notes } from "@/models/index.js";
import { Cache } from "@/misc/cache.js";
import { apiLogger } from "@/server/api/logger.js";
const cache = new Cache<number>("homeTlQueryData", 60 * 60 * 24);
const cutoff = 250; // 250 posts in the last 7 days, constant determined by comparing benchmarks for cutoff values between 100 and 2500
const logger = apiLogger.createSubLogger("heuristics");
export async function generateFollowingQuery(
q: SelectQueryBuilder<any>,
me: { id: User["id"] },
): Promise<void> {
const followingQuery = Followings.createQueryBuilder("following")
.select("following.followeeId")
.where("following.followerId = :meId");
const heuristic = await cache.fetch(me.id, async () => {
let curr = new Date();
let prev = new Date();
prev.setDate(prev.getDate() - 7);
return Notes.createQueryBuilder('note')
.where(`note.createdAt > :prev`, { prev })
.andWhere(`note.createdAt < :curr`, { curr })
.andWhere(`note.userId = ANY(array(${followingQuery.getQuery()} UNION ALL VALUES (:meId)))`, { meId: me.id })
.getCount()
.then(res => {
logger.info(`Calculating heuristics for user ${me.id} took ${new Date().getTime() - curr.getTime()}ms`);
return res;
});
});
const shouldUseUnion = heuristic < cutoff ;
q.andWhere(
new Brackets((qb) => {
if (shouldUseUnion) {
qb.where(`note.userId = ANY(array(${followingQuery.getQuery()} UNION ALL VALUES (:meId)))`);
} else {
qb.where(`note.userId = :meId`);
qb.orWhere(`note.userId IN (${followingQuery.getQuery()})`);
}
}),
)
q.setParameters({ meId: me.id });
}