fix: emoji cache

This commit is contained in:
Namekuji 2023-08-09 08:15:26 -04:00
parent 746e2fac41
commit 63c445554a
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
7 changed files with 58 additions and 22 deletions

View file

@ -137,7 +137,7 @@ export const prepared = {
select: { select: {
byNoteId: `SELECT * FROM reaction WHERE "noteId" IN ?`, byNoteId: `SELECT * FROM reaction WHERE "noteId" IN ?`,
byUserId: `SELECT * FROM reaction_by_userid WHERE "userId" IN ?`, byUserId: `SELECT * FROM reaction_by_userid WHERE "userId" IN ?`,
byNoteAndUser: `SELECT * FROM reaction WHERE "noteId" = ? AND "userId" = ?`, byNoteAndUser: `SELECT * FROM reaction WHERE "noteId" IN ? AND "userId" IN ?`,
byId: `SELECT * FROM reaction WHERE "id" IN ?`, byId: `SELECT * FROM reaction WHERE "id" IN ?`,
}, },
delete: `DELETE FROM reaction WHERE "noteId" = ? AND "userId" = ?`, delete: `DELETE FROM reaction WHERE "noteId" = ? AND "userId" = ?`,

View file

@ -36,6 +36,12 @@ export class Cache<T> {
await commander.set(_key, _value, "EX", this.ttl); await commander.set(_key, _value, "EX", this.ttl);
} }
public async exists(...keys: string[]): Promise<boolean> {
return (
(await redisClient.exists(keys.map((key) => this.prefixedKey(key)))) > 0
);
}
public async get(key: string | null, renew = false): Promise<T | undefined> { public async get(key: string | null, renew = false): Promise<T | undefined> {
const _key = this.prefixedKey(key); const _key = this.prefixedKey(key);
const cached = await redisClient.getBuffer(_key); const cached = await redisClient.getBuffer(_key);

View file

@ -152,9 +152,12 @@ export function aggregateNoteEmojis(notes: Note[]) {
export async function prefetchEmojis( export async function prefetchEmojis(
emojis: { name: string; host: string | null }[], emojis: { name: string; host: string | null }[],
): Promise<void> { ): Promise<void> {
const notCachedEmojis = emojis.filter( const notCachedEmojis: { name: string; host: string | null }[] = [];
async (emoji) => !(await EmojiCache.get(`${emoji.name} ${emoji.host}`)), for (const emoji of emojis) {
); if (!(await EmojiCache.exists(`${emoji.name} ${emoji.host}`))) {
notCachedEmojis.push(emoji);
}
}
const emojisQuery: any[] = []; const emojisQuery: any[] = [];
const hosts = new Set(notCachedEmojis.map((e) => e.host)); const hosts = new Set(notCachedEmojis.map((e) => e.host));
for (const host of hosts) { for (const host of hosts) {

View file

@ -28,10 +28,11 @@ import {
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import { IdentifiableError } from "@/misc/identifiable-error.js"; import { IdentifiableError } from "@/misc/identifiable-error.js";
import { import {
ScyllaNote, type ScyllaNote,
parseScyllaNote, parseScyllaNote,
prepared, prepared,
scyllaClient, scyllaClient,
parseScyllaReaction,
} from "@/db/scylla.js"; } from "@/db/scylla.js";
import { LocalFollowingsCache } from "@/misc/cache.js"; import { LocalFollowingsCache } from "@/misc/cache.js";
import { userByIdCache } from "@/services/user-cache.js"; import { userByIdCache } from "@/services/user-cache.js";
@ -91,10 +92,22 @@ async function populateMyReaction(
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
} }
const reaction = await NoteReactions.findOneBy({ let reaction: NoteReaction | null = null;
if (scyllaClient) {
const result = await scyllaClient.execute(
prepared.reaction.select.byNoteAndUser,
[[note.id], [meId]],
{ prepare: true },
);
if (result.rowLength > 0) {
reaction = parseScyllaReaction(result.first());
}
} else {
reaction = await NoteReactions.findOneBy({
userId: meId, userId: meId,
noteId: note.id, noteId: note.id,
}); });
}
if (reaction) { if (reaction) {
return convertLegacyReaction(reaction.reaction); return convertLegacyReaction(reaction.reaction);
@ -358,10 +371,20 @@ export const NoteRepository = db.getRepository(Note).extend({
.filter((n) => !!n.renoteId) .filter((n) => !!n.renoteId)
.map((n) => n.renoteId) as string[]; .map((n) => n.renoteId) as string[];
const targets = [...notes.map((n) => n.id), ...renoteIds]; const targets = [...notes.map((n) => n.id), ...renoteIds];
const myReactions = await NoteReactions.findBy({ let myReactions: NoteReaction[] = [];
if (scyllaClient) {
const result = await scyllaClient.execute(
prepared.reaction.select.byNoteAndUser,
[targets, [meId]],
{ prepare: true },
);
myReactions = result.rows.map(parseScyllaReaction);
} else {
myReactions = await NoteReactions.findBy({
userId: meId, userId: meId,
noteId: In(targets), noteId: In(targets),
}); });
}
for (const target of targets) { for (const target of targets) {
myReactionsMap.set( myReactionsMap.set(

View file

@ -1,5 +1,4 @@
import type { FindOptionsWhere } from "typeorm"; import type { FindOptionsWhere } from "typeorm";
import { DeepPartial } from "typeorm";
import { NoteReactions } from "@/models/index.js"; import { NoteReactions } from "@/models/index.js";
import type { NoteReaction } from "@/models/entities/note-reaction.js"; import type { NoteReaction } from "@/models/entities/note-reaction.js";
import define from "../../define.js"; import define from "../../define.js";

View file

@ -2,6 +2,7 @@ import { Notes } from "@/models/index.js";
import define from "../../define.js"; import define from "../../define.js";
import { getNote } from "../../common/getters.js"; import { getNote } from "../../common/getters.js";
import { ApiError } from "../../error.js"; import { ApiError } from "../../error.js";
import { scyllaClient } from "@/db/scylla.js";
export const meta = { export const meta = {
tags: ["notes"], tags: ["notes"],
@ -44,6 +45,7 @@ export default define(meta, paramDef, async (ps, user) => {
return await Notes.pack(note, user, { return await Notes.pack(note, user, {
// FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774) // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774)
detail: true, detail: true,
scyllaNote: !!scyllaClient
}).catch((err) => { }).catch((err) => {
if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
throw new ApiError(meta.errors.noSuchNote); throw new ApiError(meta.errors.noSuchNote);

View file

@ -22,7 +22,7 @@ import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js
import type { NoteReaction } from "@/models/entities/note-reaction.js"; import type { NoteReaction } from "@/models/entities/note-reaction.js";
import { IdentifiableError } from "@/misc/identifiable-error.js"; import { IdentifiableError } from "@/misc/identifiable-error.js";
import { prepared, scyllaClient } from "@/db/scylla.js"; import { prepared, scyllaClient } from "@/db/scylla.js";
import { populateEmojis } from "@/misc/populate-emojis.js"; import { EmojiCache } from "@/misc/populate-emojis.js";
export default async ( export default async (
user: { id: User["id"]; host: User["host"] }, user: { id: User["id"]; host: User["host"] },
@ -137,13 +137,16 @@ export default async (
// カスタム絵文字リアクションだったら絵文字情報も送る // カスタム絵文字リアクションだったら絵文字情報も送る
const decodedReaction = decodeReaction(_reaction); const decodedReaction = decodeReaction(_reaction);
const emoji = await Emojis.findOne({ const emoji = await EmojiCache.fetch(
`${decodedReaction.name} ${decodedReaction.host}`,
() =>
Emojis.findOne({
where: { where: {
name: decodedReaction.name, name: decodedReaction.name,
host: decodedReaction.host ?? IsNull(), host: decodedReaction.host ?? IsNull(),
}, },
select: ["name", "host", "originalUrl", "publicUrl"], }),
}); );
publishNoteStream(note.id, "reacted", { publishNoteStream(note.id, "reacted", {
reaction: decodedReaction.reaction, reaction: decodedReaction.reaction,