This commit is contained in:
syuilo 2024-09-20 10:19:51 +09:00
parent 59b8ca7e4e
commit 09b1773e2f
2 changed files with 38 additions and 14 deletions

View File

@ -8,6 +8,7 @@ import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MiNote } from '@/models/Note.js'; import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { NotesRepository } from '@/models/_.js';
const REDIS_PREFIX = 'reactionsBuffer'; const REDIS_PREFIX = 'reactionsBuffer';
@ -16,6 +17,9 @@ export class ReactionsBufferingService {
constructor( constructor(
@Inject(DI.redis) @Inject(DI.redis)
private redisClient: Redis.Redis, // TODO: 専用のRedisインスタンスにする private redisClient: Redis.Redis, // TODO: 専用のRedisインスタンスにする
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
) { ) {
} }
@ -30,8 +34,8 @@ export class ReactionsBufferingService {
} }
@bindThis @bindThis
public async get(note: MiNote) { public async get(noteId: MiNote['id']) {
const result = await this.redisClient.hgetall(`${REDIS_PREFIX}:${note.id}`); const result = await this.redisClient.hgetall(`${REDIS_PREFIX}:${noteId}`);
const delta = {}; const delta = {};
for (const [name, count] of Object.entries(result)) { for (const [name, count] of Object.entries(result)) {
delta[name] = parseInt(count); delta[name] = parseInt(count);
@ -40,23 +44,23 @@ export class ReactionsBufferingService {
} }
@bindThis @bindThis
public async getMany(notes: MiNote[]) { public async getMany(noteIds: MiNote['id'][]) {
const deltas = new Map<MiNote['id'], Record<string, number>>(); const deltas = new Map<MiNote['id'], Record<string, number>>();
const pipeline = this.redisClient.pipeline(); const pipeline = this.redisClient.pipeline();
for (const note of notes) { for (const noteId of noteIds) {
pipeline.hgetall(`${REDIS_PREFIX}:${note.id}`); pipeline.hgetall(`${REDIS_PREFIX}:${noteId}`);
} }
const results = await pipeline.exec(); const results = await pipeline.exec();
for (let i = 0; i < notes.length; i++) { for (let i = 0; i < noteIds.length; i++) {
const note = notes[i]; const noteId = noteIds[i];
const result = results![i][1]; const result = results![i][1];
const delta = {}; const delta = {};
for (const [name, count] of Object.entries(result)) { for (const [name, count] of Object.entries(result)) {
delta[name] = parseInt(count); delta[name] = parseInt(count);
} }
deltas.set(note.id, delta); deltas.set(noteId, delta);
} }
return deltas; return deltas;
@ -74,5 +78,30 @@ export class ReactionsBufferingService {
} while (cursor !== '0'); } while (cursor !== '0');
console.log(bufferedNoteIds); console.log(bufferedNoteIds);
const deltas = await this.getMany(bufferedNoteIds);
console.log(deltas);
// clear
const pipeline = this.redisClient.pipeline();
for (const noteId of bufferedNoteIds) {
pipeline.del(`${REDIS_PREFIX}:${noteId}`);
}
await pipeline.exec();
// TODO: SQL一個にまとめたい
for (const [noteId, delta] of deltas) {
const sqls = [] as string[];
for (const [reaction, count] of Object.entries(delta)) {
sqls.push(`jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + ${count})::text::jsonb)`);
}
this.notesRepository.createQueryBuilder().update()
.set({
reactions: () => sqls.join(' || '),
})
.where('id = :id', { id: noteId })
.execute();
}
} }
} }

View File

@ -6,13 +6,11 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js'; import type { Packed } from '@/misc/json-schema.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js'; import type { MiNote } from '@/models/Note.js';
import type { MiNoteReaction } from '@/models/NoteReaction.js';
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js'; import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DebounceLoader } from '@/misc/loader.js'; import { DebounceLoader } from '@/misc/loader.js';
@ -49,9 +47,6 @@ export class NoteEntityService implements OnModuleInit {
constructor( constructor(
private moduleRef: ModuleRef, private moduleRef: ModuleRef,
@Inject(DI.redis)
private redisClient: Redis.Redis, // TODO: 専用のRedisインスタンスにする
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -423,7 +418,7 @@ export class NoteEntityService implements OnModuleInit {
if (notes.length === 0) return []; if (notes.length === 0) return [];
const rbt = true; const rbt = true;
const reactionsDeltas = rbt ? await this.reactionsBufferingService.getMany(notes) : new Map(); const reactionsDeltas = rbt ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : new Map();
const meId = me ? me.id : null; const meId = me ? me.id : null;
const myReactionsMap = new Map<MiNote['id'], string | null>(); const myReactionsMap = new Map<MiNote['id'], string | null>();