cache recent favorites, renotes, and reactions in the connection to speed up rePackNote
This commit is contained in:
parent
4c2a0fed63
commit
bd22ae0d80
3 changed files with 86 additions and 23 deletions
|
|
@ -315,19 +315,14 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
@bindThis
|
||||
public async populateMyRenotes(notes: Packed<'Note'>[], meId: string, _hint_?: {
|
||||
myRenotes: Map<string, boolean>;
|
||||
myRenotes: Set<string>;
|
||||
}): Promise<Set<string>> {
|
||||
const fetchedRenotes = new Set<string>();
|
||||
const toFetch = new Set<string>();
|
||||
|
||||
if (_hint_) {
|
||||
for (const note of notes) {
|
||||
const fromHint = _hint_.myRenotes.get(note.id);
|
||||
|
||||
// null means we know there's no renote, so just skip it.
|
||||
if (fromHint === false) continue;
|
||||
|
||||
if (fromHint) {
|
||||
if (_hint_.myRenotes.has(note.id)) {
|
||||
fetchedRenotes.add(note.id);
|
||||
} else {
|
||||
toFetch.add(note.id);
|
||||
|
|
@ -355,19 +350,14 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
@bindThis
|
||||
public async populateMyFavorites(notes: Packed<'Note'>[], meId: string, _hint_?: {
|
||||
myFavorites: Map<string, boolean>;
|
||||
myFavorites: Set<string>;
|
||||
}): Promise<Set<string>> {
|
||||
const fetchedFavorites = new Set<string>();
|
||||
const toFetch = new Set<string>();
|
||||
|
||||
if (_hint_) {
|
||||
for (const note of notes) {
|
||||
const fromHint = _hint_.myFavorites.get(note.id);
|
||||
|
||||
// null means we know there's no favorite, so just skip it.
|
||||
if (fromHint === false) continue;
|
||||
|
||||
if (fromHint) {
|
||||
if (_hint_.myFavorites.has(note.id)) {
|
||||
fetchedFavorites.add(note.id);
|
||||
} else {
|
||||
toFetch.add(note.id);
|
||||
|
|
|
|||
|
|
@ -10,13 +10,16 @@ import type { Packed } from '@/misc/json-schema.js';
|
|||
import type { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
||||
import type { MiFollowing, MiUserProfile, NoteFavoritesRepository, NoteReactionsRepository, NotesRepository } from '@/models/_.js';
|
||||
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||
import { isJsonObject } from '@/misc/json-value.js';
|
||||
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import type { ChannelsService } from './ChannelsService.js';
|
||||
import type { EventEmitter } from 'events';
|
||||
import type Channel from './channel.js';
|
||||
|
|
@ -43,14 +46,27 @@ export default class Connection {
|
|||
public userIdsWhoMeMutingRenotes: Set<string> = new Set();
|
||||
public userMutedInstances: Set<string> = new Set();
|
||||
public userMutedThreads: Set<string> = new Set();
|
||||
public myRecentReactions: Map<string, string> = new Map();
|
||||
public myRecentRenotes: Set<string> = new Set();
|
||||
public myRecentFavorites: Set<string> = new Set();
|
||||
private fetchIntervalId: NodeJS.Timeout | null = null;
|
||||
private closingConnection = false;
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.noteReactionsRepository)
|
||||
private readonly noteReactionsRepository: NoteReactionsRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private readonly notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.noteFavoritesRepository)
|
||||
private readonly noteFavoritesRepository: NoteFavoritesRepository,
|
||||
|
||||
private channelsService: ChannelsService,
|
||||
private notificationService: NotificationService,
|
||||
private cacheService: CacheService,
|
||||
public readonly cacheService: CacheService,
|
||||
private readonly queryService: QueryService,
|
||||
private channelFollowingService: ChannelFollowingService,
|
||||
loggerService: LoggerService,
|
||||
|
||||
|
|
@ -68,7 +84,7 @@ export default class Connection {
|
|||
@bindThis
|
||||
public async fetch() {
|
||||
if (this.user == null) return;
|
||||
const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes, threadMutings] = await Promise.all([
|
||||
const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes, threadMutings, myRecentReactions, myRecentFavorites, myRecentRenotes] = await Promise.all([
|
||||
this.cacheService.userProfileCache.fetch(this.user.id),
|
||||
this.cacheService.userFollowingsCache.fetch(this.user.id),
|
||||
this.channelFollowingService.userFollowingChannelsCache.fetch(this.user.id),
|
||||
|
|
@ -76,6 +92,25 @@ export default class Connection {
|
|||
this.cacheService.userBlockedCache.fetch(this.user.id),
|
||||
this.cacheService.renoteMutingsCache.fetch(this.user.id),
|
||||
this.cacheService.threadMutingsCache.fetch(this.user.id),
|
||||
this.noteReactionsRepository.find({
|
||||
where: { userId: this.user.id },
|
||||
select: { noteId: true, reaction: true },
|
||||
order: { id: 'desc' },
|
||||
take: 100,
|
||||
}),
|
||||
this.noteFavoritesRepository.find({
|
||||
where: { userId: this.user.id },
|
||||
select: { noteId: true },
|
||||
order: { id: 'desc' },
|
||||
take: 100,
|
||||
}),
|
||||
this.queryService
|
||||
.andIsRenote(this.notesRepository.createQueryBuilder('note'), 'note')
|
||||
.andWhere({ userId: this.user.id })
|
||||
.orderBy({ id: 'DESC' })
|
||||
.limit(100)
|
||||
.select('note.renoteId', 'renoteId')
|
||||
.getRawMany<{ renoteId: string }>(),
|
||||
]);
|
||||
this.userProfile = userProfile;
|
||||
this.following = following;
|
||||
|
|
@ -85,6 +120,9 @@ export default class Connection {
|
|||
this.userIdsWhoMeMutingRenotes = userIdsWhoMeMutingRenotes;
|
||||
this.userMutedInstances = new Set(userProfile.mutedInstances);
|
||||
this.userMutedThreads = threadMutings;
|
||||
this.myRecentReactions = new Map(myRecentReactions.map(r => [r.noteId, r.reaction]));
|
||||
this.myRecentFavorites = new Set(myRecentFavorites.map(f => f.noteId ));
|
||||
this.myRecentRenotes = new Set(myRecentRenotes.map(r => r.renoteId ));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
|||
|
|
@ -34,18 +34,35 @@ export default abstract class Channel {
|
|||
return this.connection.userProfile;
|
||||
}
|
||||
|
||||
protected get cacheService() {
|
||||
return this.connection.cacheService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cacheService.userFollowingsCache to avoid stale data
|
||||
*/
|
||||
protected get following() {
|
||||
return this.connection.following;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO use onChange to keep these in sync?
|
||||
* @deprecated use cacheService.userMutingsCache to avoid stale data
|
||||
*/
|
||||
protected get userIdsWhoMeMuting() {
|
||||
return this.connection.userIdsWhoMeMuting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cacheService.renoteMutingsCache to avoid stale data
|
||||
*/
|
||||
protected get userIdsWhoMeMutingRenotes() {
|
||||
return this.connection.userIdsWhoMeMutingRenotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cacheService.userBlockedCache to avoid stale data
|
||||
*/
|
||||
protected get userIdsWhoBlockingMe() {
|
||||
return this.connection.userIdsWhoBlockingMe;
|
||||
}
|
||||
|
|
@ -54,6 +71,9 @@ export default abstract class Channel {
|
|||
return this.connection.userMutedInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cacheService.threadMutingsCache to avoid stale data
|
||||
*/
|
||||
protected get userMutedThreads() {
|
||||
return this.connection.userMutedThreads;
|
||||
}
|
||||
|
|
@ -66,6 +86,18 @@ export default abstract class Channel {
|
|||
return this.connection.subscriber;
|
||||
}
|
||||
|
||||
protected get myRecentReactions() {
|
||||
return this.connection.myRecentReactions;
|
||||
}
|
||||
|
||||
protected get myRecentRenotes() {
|
||||
return this.connection.myRecentRenotes;
|
||||
}
|
||||
|
||||
protected get myRecentFavorites() {
|
||||
return this.connection.myRecentFavorites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a note is visible to the current user *excluding* blocks and mutes.
|
||||
*/
|
||||
|
|
@ -161,13 +193,16 @@ export default abstract class Channel {
|
|||
// Hide notes before everything else, since this modifies fields that the other functions will check.
|
||||
await this.noteEntityService.hideNotes(notes, this.user.id);
|
||||
|
||||
// TODO cache reaction/renote/favorite hints in the connection.
|
||||
// Those functions accept partial hints and will fetch anything else.
|
||||
|
||||
const [myReactions, myRenotes, myFavorites, myThreadMutings, myNoteMutings] = await Promise.all([
|
||||
this.noteEntityService.populateMyReactions(notes, this.user.id),
|
||||
this.noteEntityService.populateMyRenotes(notes, this.user.id),
|
||||
this.noteEntityService.populateMyFavorites(notes, this.user.id),
|
||||
this.noteEntityService.populateMyReactions(notes, this.user.id, {
|
||||
myReactions: this.myRecentReactions,
|
||||
}),
|
||||
this.noteEntityService.populateMyRenotes(notes, this.user.id, {
|
||||
myRenotes: this.myRecentRenotes,
|
||||
}),
|
||||
this.noteEntityService.populateMyFavorites(notes, this.user.id, {
|
||||
myFavorites: this.myRecentFavorites,
|
||||
}),
|
||||
this.noteEntityService.populateMyTheadMutings(notes, this.user.id),
|
||||
this.noteEntityService.populateMyNoteMutings(notes, this.user.id),
|
||||
]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue