speed up direct upload preparation
This commit is contained in:
parent
81a0b29633
commit
9575873efb
@ -29,6 +29,10 @@ const DEFAULT_PART_SIZE_BYTES = 8 * 1024 * 1024;
|
||||
const PRESIGNED_URL_EXPIRES_SECONDS = 15 * 60;
|
||||
const DIRECT_UPLOAD_PREFIX = "uploads/direct";
|
||||
|
||||
function elapsedMs(startedAt: number) {
|
||||
return Date.now() - startedAt;
|
||||
}
|
||||
|
||||
const allowedDirectUploadTypes = new Set([
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
@ -141,6 +145,7 @@ async function createPartUrls(params: {
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const requestStartedAt = Date.now();
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
@ -159,6 +164,16 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
logger.info("direct_upload_request_start", {
|
||||
action: payload.action,
|
||||
size: "size" in payload ? payload.size : null,
|
||||
contentType: "contentType" in payload ? payload.contentType : null,
|
||||
purpose: "purpose" in payload ? payload.purpose || null : null,
|
||||
clientUploadId:
|
||||
"clientUploadId" in payload ? payload.clientUploadId || null : null,
|
||||
});
|
||||
|
||||
const resolveStartedAt = Date.now();
|
||||
const target = await resolveS3UploadTarget().catch((error) => {
|
||||
logger.error("resolve_storage_target_failed", error);
|
||||
return null;
|
||||
@ -170,6 +185,13 @@ export async function POST(request: Request) {
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
logger.info("direct_upload_storage_target_resolved", {
|
||||
action: payload.action,
|
||||
elapsedMs: elapsedMs(resolveStartedAt),
|
||||
provider: target.config.provider || "s3",
|
||||
bucket: target.config.bucket,
|
||||
hasPublicUrl: Boolean(target.config.publicUrl),
|
||||
});
|
||||
|
||||
const client = createS3Client(target.config);
|
||||
const bucket = target.config.bucket;
|
||||
@ -203,6 +225,7 @@ export async function POST(request: Request) {
|
||||
);
|
||||
|
||||
if (payload.size < MULTIPART_THRESHOLD_BYTES) {
|
||||
const presignStartedAt = Date.now();
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
@ -213,6 +236,13 @@ export async function POST(request: Request) {
|
||||
const url = await getSignedUrl(client, command, {
|
||||
expiresIn: PRESIGNED_URL_EXPIRES_SECONDS,
|
||||
});
|
||||
logger.info("direct_upload_single_presigned", {
|
||||
elapsedMs: elapsedMs(presignStartedAt),
|
||||
totalElapsedMs: elapsedMs(requestStartedAt),
|
||||
size: payload.size,
|
||||
contentType,
|
||||
clientUploadId: payload.clientUploadId || null,
|
||||
});
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": contentType,
|
||||
@ -232,6 +262,7 @@ export async function POST(request: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
const multipartStartedAt = Date.now();
|
||||
const createResult = await client.send(
|
||||
new CreateMultipartUploadCommand({
|
||||
Bucket: bucket,
|
||||
@ -256,6 +287,15 @@ export async function POST(request: Request) {
|
||||
uploadId,
|
||||
partCount,
|
||||
});
|
||||
logger.info("direct_upload_multipart_prepared", {
|
||||
elapsedMs: elapsedMs(multipartStartedAt),
|
||||
totalElapsedMs: elapsedMs(requestStartedAt),
|
||||
size: payload.size,
|
||||
partCount,
|
||||
partSize,
|
||||
contentType,
|
||||
clientUploadId: payload.clientUploadId || null,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
mode: "multipart",
|
||||
@ -271,6 +311,7 @@ export async function POST(request: Request) {
|
||||
|
||||
if (payload.action === "abort") {
|
||||
const key = assertUserOwnedKey(payload.key, session.user.id);
|
||||
const abortStartedAt = Date.now();
|
||||
await client.send(
|
||||
new AbortMultipartUploadCommand({
|
||||
Bucket: bucket,
|
||||
@ -278,6 +319,11 @@ export async function POST(request: Request) {
|
||||
UploadId: payload.uploadId,
|
||||
})
|
||||
);
|
||||
logger.info("direct_upload_multipart_aborted", {
|
||||
elapsedMs: elapsedMs(abortStartedAt),
|
||||
totalElapsedMs: elapsedMs(requestStartedAt),
|
||||
clientUploadId: payload.clientUploadId || null,
|
||||
});
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
@ -291,6 +337,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
if (payload.uploadId) {
|
||||
const completeMultipartStartedAt = Date.now();
|
||||
const parts = [...(payload.parts || [])].sort(
|
||||
(a, b) => a.PartNumber - b.PartNumber
|
||||
);
|
||||
@ -311,14 +358,25 @@ export async function POST(request: Request) {
|
||||
},
|
||||
})
|
||||
);
|
||||
logger.info("direct_upload_multipart_completed", {
|
||||
elapsedMs: elapsedMs(completeMultipartStartedAt),
|
||||
partCount: parts.length,
|
||||
clientUploadId: payload.clientUploadId || null,
|
||||
});
|
||||
}
|
||||
|
||||
const headStartedAt = Date.now();
|
||||
const head = await client.send(
|
||||
new HeadObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
})
|
||||
);
|
||||
logger.info("direct_upload_head_object_completed", {
|
||||
elapsedMs: elapsedMs(headStartedAt),
|
||||
uploadMode: payload.uploadId ? "multipart" : "single",
|
||||
clientUploadId: payload.clientUploadId || null,
|
||||
});
|
||||
|
||||
const actualSize =
|
||||
typeof head.ContentLength === "number" ? head.ContentLength : null;
|
||||
@ -329,6 +387,7 @@ export async function POST(request: Request) {
|
||||
const url = buildS3PublicUrl(target.config, key);
|
||||
const resolvedContentType =
|
||||
head.ContentType || contentType || getContentType(payload.filename);
|
||||
const assetStartedAt = Date.now();
|
||||
const asset = await upsertMediaAsset({
|
||||
userId: session.user.id,
|
||||
mediaType: "image",
|
||||
@ -349,6 +408,13 @@ export async function POST(request: Request) {
|
||||
clientUploadId: payload.clientUploadId || null,
|
||||
},
|
||||
});
|
||||
logger.info("direct_upload_asset_registered", {
|
||||
elapsedMs: elapsedMs(assetStartedAt),
|
||||
totalElapsedMs: elapsedMs(requestStartedAt),
|
||||
uploadMode: payload.uploadId ? "multipart" : "single",
|
||||
assetId: asset?.id ?? null,
|
||||
clientUploadId: payload.clientUploadId || null,
|
||||
});
|
||||
|
||||
await maybeEnqueueStorageMigrationTask({
|
||||
uploaded: { url, pathname: key },
|
||||
|
||||
@ -7,6 +7,7 @@ const SERVER_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
|
||||
const DIRECT_UPLOAD_PART_CONCURRENCY = 4;
|
||||
const DIRECT_UPLOAD_SUPPRESS_MS = 2 * 60 * 1000;
|
||||
const CLIENT_UPLOAD_ID_FIELD = "clientUploadId";
|
||||
const DIRECT_UPLOAD_INITIATE_TIMEOUT_MS = 3 * 1000;
|
||||
const DIRECT_UPLOAD_ACTION_TIMEOUT_MS = 15 * 1000;
|
||||
const DIRECT_UPLOAD_ABORT_TIMEOUT_MS = 8 * 1000;
|
||||
const XHR_UPLOAD_TOTAL_TIMEOUT_MS = 6 * 60 * 1000;
|
||||
@ -725,7 +726,8 @@ async function fetchDirectUpload(
|
||||
purpose: file.purpose,
|
||||
clientUploadId: file.clientUploadId,
|
||||
},
|
||||
signal
|
||||
signal,
|
||||
DIRECT_UPLOAD_INITIATE_TIMEOUT_MS
|
||||
);
|
||||
|
||||
if (upload.mode === "single") {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user