fix: decrement counts of boost and reply

This commit is contained in:
Namekuji 2023-08-12 14:21:48 -04:00
parent 2e6c04a6cb
commit d0665c2fc2
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
5 changed files with 140 additions and 7 deletions

View file

@ -6,6 +6,7 @@ DROP INDEX IF EXISTS home_by_id;
DROP TABLE IF EXISTS home_timeline; DROP TABLE IF EXISTS home_timeline;
DROP MATERIALIZED VIEW IF EXISTS local_timeline; DROP MATERIALIZED VIEW IF EXISTS local_timeline;
DROP MATERIALIZED VIEW IF EXISTS global_timeline; DROP MATERIALIZED VIEW IF EXISTS global_timeline;
DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id_and_user_id;
DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id; DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id;
DROP MATERIALIZED VIEW IF EXISTS note_by_userid; DROP MATERIALIZED VIEW IF EXISTS note_by_userid;
DROP INDEX IF EXISTS note_by_id; DROP INDEX IF EXISTS note_by_id;

View file

@ -97,6 +97,17 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS note_by_renote_id AS
PRIMARY KEY ("renoteId", "createdAt", "createdAtDate", "userId", "userHost", "visibility") PRIMARY KEY ("renoteId", "createdAt", "createdAtDate", "userId", "userHost", "visibility")
WITH CLUSTERING ORDER BY ("createdAt" DESC); WITH CLUSTERING ORDER BY ("createdAt" DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS note_by_renote_id_and_user_id AS
SELECT "renoteId", "userId", "createdAt", "createdAtDate", "userHost", "visibility", "id" FROM note
WHERE "renoteId" IS NOT NULL
AND "createdAt" IS NOT NULL
AND "createdAtDate" IS NOT NULL
AND "userId" IS NOT NULL
AND "userHost" IS NOT NULL
AND "visibility" IS NOT NULL
PRIMARY KEY (("renoteId", "userId"), "createdAt", "createdAtDate", "userHost", "visibility")
WITH CLUSTERING ORDER BY ("createdAt" DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS global_timeline AS CREATE MATERIALIZED VIEW IF NOT EXISTS global_timeline AS
SELECT * FROM note SELECT * FROM note
WHERE "createdAtDate" IS NOT NULL WHERE "createdAtDate" IS NOT NULL

View file

@ -1,16 +1,36 @@
import { prepared, scyllaClient } from "@/db/scylla";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
/**
* Sum of notes that have the given userId and renoteId
*/
export async function countSameRenotes( export async function countSameRenotes(
userId: string, userId: string,
renoteId: string, renoteId: string,
excludeNoteId: string | undefined, excludeNoteId: string | undefined,
): Promise<number> { ): Promise<number> {
// 指定したユーザーの指定したノートのリノートがいくつあるか数える if (scyllaClient) {
const result = await scyllaClient.execute(
`SELECT "renoteId","userId","createdAt","id" FROM note_by_renote_id_and_user_id WHERE "renoteId" = ? AND "userId" = ?`,
[renoteId, userId],
{ prepare: true },
);
if (excludeNoteId) {
const renotes = result.rows
.map((row) => row.get("id") as string)
.filter((id) => id !== excludeNoteId);
return renotes.length;
}
return result.rowLength;
}
const query = Notes.createQueryBuilder("note") const query = Notes.createQueryBuilder("note")
.where("note.userId = :userId", { userId }) .where("note.userId = :userId", { userId })
.andWhere("note.renoteId = :renoteId", { renoteId }); .andWhere("note.renoteId = :renoteId", { renoteId });
// 指定した投稿を除く // Exclude if specified
if (excludeNoteId) { if (excludeNoteId) {
query.andWhere("note.id != :excludeNoteId", { excludeNoteId }); query.andWhere("note.id != :excludeNoteId", { excludeNoteId });
} }

View file

@ -6,6 +6,7 @@ import { ApiError } from "../../error.js";
import { SECOND, HOUR } from "@/const.js"; import { SECOND, HOUR } from "@/const.js";
import type { Note } from "@/models/entities/note.js"; import type { Note } from "@/models/entities/note.js";
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js"; import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
import { userByIdCache } from "@/services/user-cache.js";
export const meta = { export const meta = {
tags: ["notes"], tags: ["notes"],
@ -59,6 +60,12 @@ export default define(meta, paramDef, async (ps, user) => {
} }
for (const note of renotes) { for (const note of renotes) {
deleteNote(await Users.findOneByOrFail({ id: user.id }), note); deleteNote(
await userByIdCache.fetch(
user.id,
() => Users.findOneByOrFail({ id: user.id }),
),
note,
);
} }
}); });

View file

@ -22,7 +22,12 @@ import { countSameRenotes } from "@/misc/count-same-renotes.js";
import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js"; import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js";
import { deliverToRelays } from "../relay.js"; import { deliverToRelays } from "../relay.js";
import meilisearch from "@/db/meilisearch.js"; import meilisearch from "@/db/meilisearch.js";
import { parseHomeTimeline, prepared, scyllaClient } from "@/db/scylla.js"; import {
parseHomeTimeline,
parseScyllaNote,
prepared,
scyllaClient,
} from "@/db/scylla.js";
/** /**
* 稿 * 稿
@ -41,13 +46,102 @@ export default async function (
note.renoteId && note.renoteId &&
(await countSameRenotes(user.id, note.renoteId, note.id)) === 0 (await countSameRenotes(user.id, note.renoteId, note.id)) === 0
) { ) {
if (scyllaClient) {
const result = await scyllaClient.execute(
prepared.note.select.byId,
[note.renoteId],
{ prepare: true },
);
if (result.rowLength > 0) {
const renote = parseScyllaNote(result.first());
const count = isNaN(renote.renoteCount) ? 0 : renote.renoteCount;
const score = isNaN(renote.score) ? 0 : renote.score;
await scyllaClient.execute(
prepared.note.update.renoteCount,
[
Math.max(count - 1, 0),
Math.max(score - 1, 0),
renote.createdAt,
renote.createdAt,
renote.userId,
renote.userHost ?? "local",
renote.visibility,
],
{ prepare: true },
);
const homeTimelines = await scyllaClient
.execute(prepared.homeTimeline.select.byId, [renote.id], {
prepare: true,
})
.then((result) => result.rows.map(parseHomeTimeline));
// Do not issue BATCH because different home timelines involve different partitions
for (const timeline of homeTimelines) {
scyllaClient.execute(
prepared.homeTimeline.update.renoteCount,
[
Math.max(count - 1, 0),
Math.max(score - 1, 0),
timeline.feedUserId,
timeline.createdAtDate,
timeline.createdAt,
timeline.userId,
],
{ prepare: true },
);
}
}
} else {
Notes.decrement({ id: note.renoteId }, "renoteCount", 1); Notes.decrement({ id: note.renoteId }, "renoteCount", 1);
Notes.decrement({ id: note.renoteId }, "score", 1); Notes.decrement({ id: note.renoteId }, "score", 1);
} }
}
if (note.replyId) { if (note.replyId) {
if (scyllaClient) {
const result = await scyllaClient.execute(
prepared.note.select.byId,
[note.replyId],
{ prepare: true },
);
if (result.rowLength > 0) {
const reply = parseScyllaNote(result.first());
const count = isNaN(reply.repliesCount) ? 0 : reply.repliesCount;
await scyllaClient.execute(
prepared.note.update.repliesCount,
[
Math.max(count - 1, 0),
reply.createdAt,
reply.createdAt,
reply.userId,
reply.userHost ?? "local",
reply.visibility,
],
{ prepare: true },
);
const homeTimelines = await scyllaClient
.execute(prepared.homeTimeline.select.byId, [reply.id], {
prepare: true,
})
.then((result) => result.rows.map(parseHomeTimeline));
// Do not issue BATCH because different home timelines involve different partitions
for (const timeline of homeTimelines) {
scyllaClient.execute(
prepared.homeTimeline.update.repliesCount,
[
Math.max(count - 1, 0),
timeline.feedUserId,
timeline.createdAtDate,
timeline.createdAt,
timeline.userId,
],
{ prepare: true },
);
}
}
} else {
await Notes.decrement({ id: note.replyId }, "repliesCount", 1); await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
} }
}
if (!quiet) { if (!quiet) {
publishNoteStream(note.id, "deleted", { publishNoteStream(note.id, "deleted", {