Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(timeline): Optimizing for CDN Caching #834

Merged
merged 12 commits into from
Dec 21, 2024
12 changes: 9 additions & 3 deletions packages/backend/src/server/api/stream/channels/antenna.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class AntennaChannel extends Channel {
public static readonly requireCredential = true as const;
public static readonly kind = 'read:account';
private antennaId: string;
private idOnly: boolean;

constructor(
private noteEntityService: NoteEntityService,
Expand All @@ -29,6 +30,7 @@ class AntennaChannel extends Channel {
@bindThis
public async init(params: any) {
this.antennaId = params.antennaId as string;
this.idOnly = params.idOnly ?? false;

// Subscribe stream
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
Expand All @@ -49,9 +51,13 @@ class AntennaChannel extends Channel {

if (this.isNoteMutedOrBlocked(note)) return;

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
} else {
this.send(data.type, data.body);
}
Expand Down
12 changes: 9 additions & 3 deletions packages/backend/src/server/api/stream/channels/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ChannelChannel extends Channel {
public static readonly shouldShare = false;
public static readonly requireCredential = false as const;
private channelId: string;
private idOnly: boolean;

constructor(
private noteEntityService: NoteEntityService,
Expand All @@ -29,6 +30,7 @@ class ChannelChannel extends Channel {
@bindThis
public async init(params: any) {
this.channelId = params.channelId as string;
this.idOnly = params.idOnly ?? false;

// Subscribe stream
this.subscriber.on('notesStream', this.onNote);
Expand All @@ -55,9 +57,13 @@ class ChannelChannel extends Channel {
}
}

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}

@bindThis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class GlobalTimelineChannel extends Channel {
public static readonly requireCredential = false as const;
private withRenotes: boolean;
private withFiles: boolean;
private idOnly: boolean;

constructor(
private metaService: MetaService,
Expand All @@ -38,6 +39,7 @@ class GlobalTimelineChannel extends Channel {

this.withRenotes = params.withRenotes ?? true;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;

// Subscribe events
this.subscriber.on('notesStream', this.onNote);
Expand Down Expand Up @@ -85,9 +87,13 @@ class GlobalTimelineChannel extends Channel {
}
}

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}

@bindThis
Expand Down
12 changes: 9 additions & 3 deletions packages/backend/src/server/api/stream/channels/home-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class HomeTimelineChannel extends Channel {
public static readonly kind = 'read:account';
private withRenotes: boolean;
private withFiles: boolean;
private idOnly: boolean;

constructor(
private noteEntityService: NoteEntityService,
Expand All @@ -32,6 +33,7 @@ class HomeTimelineChannel extends Channel {
public async init(params: any) {
this.withRenotes = params.withRenotes ?? true;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;

this.subscriber.on('notesStream', this.onNote);
}
Expand Down Expand Up @@ -89,9 +91,13 @@ class HomeTimelineChannel extends Channel {
}
}

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}

@bindThis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class HybridTimelineChannel extends Channel {
private withRenotes: boolean;
private withReplies: boolean;
private withFiles: boolean;
private idOnly: boolean;

constructor(
private metaService: MetaService,
Expand All @@ -41,6 +42,7 @@ class HybridTimelineChannel extends Channel {
this.withRenotes = params.withRenotes ?? true;
this.withReplies = params.withReplies ?? false;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;

// Subscribe events
this.subscriber.on('notesStream', this.onNote);
Expand Down Expand Up @@ -103,9 +105,13 @@ class HybridTimelineChannel extends Channel {
}
}

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}

@bindThis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class LocalTimelineChannel extends Channel {
private withRenotes: boolean;
private withReplies: boolean;
private withFiles: boolean;
private idOnly: boolean;

constructor(
private metaService: MetaService,
Expand All @@ -40,6 +41,7 @@ class LocalTimelineChannel extends Channel {
this.withRenotes = params.withRenotes ?? true;
this.withReplies = params.withReplies ?? false;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;

// Subscribe events
this.subscriber.on('notesStream', this.onNote);
Expand Down Expand Up @@ -88,9 +90,13 @@ class LocalTimelineChannel extends Channel {
}
}

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}

@bindThis
Expand Down
12 changes: 9 additions & 3 deletions packages/backend/src/server/api/stream/channels/role-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class RoleTimelineChannel extends Channel {
public static readonly shouldShare = false;
public static readonly requireCredential = false as const;
private roleId: string;
private idOnly: boolean;

constructor(
private noteEntityService: NoteEntityService,
Expand All @@ -31,6 +32,7 @@ class RoleTimelineChannel extends Channel {
@bindThis
public async init(params: any) {
this.roleId = params.roleId as string;
this.idOnly = params.idOnly ?? false;

this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent);
}
Expand Down Expand Up @@ -71,9 +73,13 @@ class RoleTimelineChannel extends Channel {
}
}

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
} else {
this.send(data.type, data.body);
}
Expand Down
12 changes: 9 additions & 3 deletions packages/backend/src/server/api/stream/channels/user-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class UserListChannel extends Channel {
private listUsersClock: NodeJS.Timeout;
private withFiles: boolean;
private withRenotes: boolean;
private idOnly: boolean;

constructor(
private userListsRepository: UserListsRepository,
Expand All @@ -40,6 +41,7 @@ class UserListChannel extends Channel {
this.listId = params.listId as string;
this.withFiles = params.withFiles ?? false;
this.withRenotes = params.withRenotes ?? true;
this.idOnly = params.idOnly ?? false;

// Check existence and owner
const listExist = await this.userListsRepository.exists({
Expand Down Expand Up @@ -128,9 +130,13 @@ class UserListChannel extends Channel {
}
}

this.connection.cacheNote(note);

this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}

@bindThis
Expand Down
75 changes: 75 additions & 0 deletions packages/backend/src/server/web/ClientServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,81 @@ export class ClientServerService {
}
});

fastify.get<{ Params: { note: string; } }>('/notes/:note.json', async (request, reply) => {
const note = await this.notesRepository.findOneBy({
id: request.params.note,
visibility: In(['public', 'home']),
});

if (note) {
try {
const _note = await this.noteEntityService.pack(note, null);
reply.header('Content-Type', 'application/json; charset=utf-8');
reply.header('Cache-Control', 'public, max-age=600');
return reply.send(_note);
} catch (err) {
reply.header('Cache-Control', 'max-age=10, must-revalidate');
if (err instanceof IdentifiableError) {
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${err.message}`, {
path: request.routeOptions.url,
params: request.params,
query: request.query,
id: err.id,
error: {
message: err.message,
code: 'INTERNAL_ERROR',
stack: err.stack,
},
});
const httpStatusCode = err.id === '85ab9bd7-3a41-4530-959d-f07073900109' ? 403 : 500;
reply.code(httpStatusCode);
return reply.send({
message: err.message,
code: 'INTERNAL_ERROR',
id: err.id,
kind: 'server',
httpStatusCode,
info: {
message: err.message,
code: err.name,
id: err.id,
},
});
} else {
const error = err as Error;
const errId = randomUUID();
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, {
path: request.routeOptions.url,
params: request.params,
query: request.query,
id: errId,
error: {
message: error.message,
code: error.name,
stack: error.stack,
},
});
reply.code(500);
return reply.send({
message: 'Internal error occurred. Please contact us if the error persists.',
code: 'INTERNAL_ERROR',
id: 'b9f2a7f9-fe64-434b-9484-cb1f804d1a80',
kind: 'server',
httpStatusCode: 500,
info: {
message: error.message,
code: error.name,
id: errId,
},
});
}
}
} else {
reply.code(404);
return;
}
});

// Page
fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => {
const { username, host } = Acct.parse(request.params.user);
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ export const waitFire = async <C extends keyof misskey.Channels>(user: UserToken
if (timer) clearTimeout(timer);
res(true);
}
}, params);
}, { ...params, idOnly: false });
} catch (e) {
rej(e);
}
Expand Down
Loading
Loading