misskey/packages/backend/src/core/ReactionsBufferingService.ts

79 lines
2.1 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
const REDIS_PREFIX = 'reactionsBuffer';
@Injectable()
export class ReactionsBufferingService {
constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis, // TODO: 専用のRedisインスタンスにする
) {
}
@bindThis
public async create(note: MiNote, reaction: string) {
this.redisClient.hincrby(`${REDIS_PREFIX}:${note.id}`, reaction, 1);
}
@bindThis
public async delete(note: MiNote, reaction: string) {
this.redisClient.hincrby(`${REDIS_PREFIX}:${note.id}`, reaction, -1);
}
@bindThis
public async get(note: MiNote) {
const result = await this.redisClient.hgetall(`${REDIS_PREFIX}:${note.id}`);
const delta = {};
for (const [name, count] of Object.entries(result)) {
delta[name] = parseInt(count);
}
return delta;
}
@bindThis
public async getMany(notes: MiNote[]) {
const deltas = new Map<MiNote['id'], Record<string, number>>();
const pipeline = this.redisClient.pipeline();
for (const note of notes) {
pipeline.hgetall(`${REDIS_PREFIX}:${note.id}`);
}
const results = await pipeline.exec();
for (let i = 0; i < notes.length; i++) {
const note = notes[i];
const result = results![i][1];
const delta = {};
for (const [name, count] of Object.entries(result)) {
delta[name] = parseInt(count);
}
deltas.set(note.id, delta);
}
return deltas;
}
// TODO: scanは重い可能性があるので、別途 bufferedNoteIds を直接Redis上に持っておいてもいいかもしれない
@bindThis
public async bake() {
const bufferedNoteIds = [];
let cursor = '0';
do {
const result = await this.redisClient.scan(cursor, 'MATCH', `${REDIS_PREFIX}:*`, 'COUNT', '1000');
cursor = result[0];
bufferedNoteIds.push(...result[1]);
} while (cursor !== '0');
console.log(bufferedNoteIds);
}
}