썸네일을 일괄적으로 생성하는 경우 발생하던 Out of Memory 문제 해결

This commit is contained in:
static
2025-07-08 04:31:19 +09:00
parent 69b31ad9af
commit 18660844e6
2 changed files with 62 additions and 5 deletions

View File

@@ -1,5 +1,6 @@
<script module lang="ts">
const subtexts = {
queued: "대기 중",
"generation-pending": "준비 중",
generating: "생성하는 중",
"upload-pending": "업로드를 기다리는 중",

View File

@@ -8,6 +8,7 @@ import type { FileThumbnailUploadRequest } from "$lib/server/schemas";
import { requestFileDownload } from "$lib/services/file";
export type GenerationStatus =
| "queued"
| "generation-pending"
| "generating"
| "upload-pending"
@@ -23,6 +24,10 @@ interface File {
const workingFiles = new Map<number, Writable<GenerationStatus>>();
let queue: (() => void)[] = [];
let memoryUsage = 0;
const MEMORY_LIMIT = 100 * 1024 * 1024; // 100 MiB
export const persistentStates = $state({
files: [] as File[],
});
@@ -86,18 +91,66 @@ const requestThumbnailUpload = limitFunction(
{ concurrency: 4 },
);
const enqueue = async (
status: Writable<GenerationStatus> | undefined,
fileInfo: FileInfo,
priority = false,
) => {
if (status) {
status.set("queued");
} else {
status = writable("queued");
workingFiles.set(fileInfo.id, status);
persistentStates.files = persistentStates.files.map((file) =>
file.id === fileInfo.id ? { ...file, status } : file,
);
}
let resolver;
const promise = new Promise((resolve) => {
resolver = resolve;
});
if (priority) {
queue = [() => resolver!(), ...queue];
} else {
queue.push(resolver!);
}
await promise;
};
export const requestThumbnailGeneration = async (fileInfo: FileInfo) => {
let status = workingFiles.get(fileInfo.id);
if (status && get(status) !== "error") return;
status = writable("generation-pending");
workingFiles.set(fileInfo.id, status);
persistentStates.files = persistentStates.files.map((file) =>
file.id === fileInfo.id ? { ...file, status } : file,
);
if (workingFiles.values().some((status) => get(status) !== "error")) {
await enqueue(status, fileInfo);
}
while (memoryUsage >= MEMORY_LIMIT) {
await enqueue(status, fileInfo, true);
}
if (status) {
status.set("generation-pending");
} else {
status = writable("generation-pending");
workingFiles.set(fileInfo.id, status);
persistentStates.files = persistentStates.files.map((file) =>
file.id === fileInfo.id ? { ...file, status } : file,
);
}
let fileSize = 0;
try {
const file = await requestFileDownload(fileInfo.id, fileInfo.contentIv!, fileInfo.dataKey!);
fileSize = file.byteLength;
memoryUsage += fileSize;
if (memoryUsage < MEMORY_LIMIT) {
queue.shift()?.();
}
const thumbnail = await generateThumbnail(
status,
file,
@@ -110,5 +163,8 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => {
}
} catch {
status.set("error");
} finally {
memoryUsage -= fileSize;
queue.shift()?.();
}
};