Files
Eric Allam 34ca7667d3 Various perf improvements to prevent event loop lag (#1186)
* WIP

* Handle tasks that have failed but are being auto yielded

* Limit trace view to 25k event records, add a download run logs button

Also added two new indexes to TaskEvent:

```
/// Used on eventRepository.getTraceSummary()
  @@index([traceId, startTime])
  // Used for getting all logs for a run
  @@index([runId])
```

* perf improvements on eventRepository.getSpan()

* v2: Add a 5 minute timeout for run execution requests in dev

* v3: Include presigned urls for downloading large payloads and outputs when using runs.retrieve

* v3: better handle large task payloads and outputs

* Change to 512KB

* v2: paginate trigger schedules endpoint

* v3: add 3MB limit on batch and single payloads

* Update task payload and output limits
2024-06-26 15:22:35 +01:00

111 lines
2.6 KiB
TypeScript

import { AwsClient } from "aws4fetch";
import { env } from "~/env.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { singleton } from "~/utils/singleton";
import { startActiveSpan } from "./tracer.server";
export const r2 = singleton("r2", initializeR2);
function initializeR2() {
if (!env.OBJECT_STORE_ACCESS_KEY_ID || !env.OBJECT_STORE_SECRET_ACCESS_KEY) {
return;
}
return new AwsClient({
accessKeyId: env.OBJECT_STORE_ACCESS_KEY_ID,
secretAccessKey: env.OBJECT_STORE_SECRET_ACCESS_KEY,
});
}
export async function uploadToObjectStore(
filename: string,
data: string,
contentType: string,
environment: AuthenticatedEnvironment
): Promise<string> {
return await startActiveSpan("uploadToObjectStore()", async (span) => {
if (!r2) {
throw new Error("Object store credentials are not set");
}
if (!env.OBJECT_STORE_BASE_URL) {
throw new Error("Object store base URL is not set");
}
span.setAttributes({
projectRef: environment.project.externalRef,
environmentSlug: environment.slug,
filename: filename,
});
const url = new URL(env.OBJECT_STORE_BASE_URL);
url.pathname = `/packets/${environment.project.externalRef}/${environment.slug}/${filename}`;
logger.debug("Uploading to object store", { url: url.href });
const response = await r2.fetch(url.toString(), {
method: "PUT",
headers: {
"Content-Type": contentType,
},
body: data,
});
if (!response.ok) {
throw new Error(`Failed to upload output to ${url}: ${response.statusText}`);
}
return url.href;
});
}
export async function generatePresignedRequest(
projectRef: string,
envSlug: string,
filename: string,
method: "PUT" | "GET" = "PUT"
) {
if (!env.OBJECT_STORE_BASE_URL) {
return;
}
if (!r2) {
return;
}
const url = new URL(env.OBJECT_STORE_BASE_URL);
url.pathname = `/packets/${projectRef}/${envSlug}/${filename}`;
url.searchParams.set("X-Amz-Expires", "300"); // 5 minutes
const signed = await r2.sign(
new Request(url, {
method,
}),
{
aws: { signQuery: true },
}
);
logger.debug("Generated presigned URL", {
url: signed.url,
headers: Object.fromEntries(signed.headers),
projectRef,
envSlug,
filename,
});
return signed;
}
export async function generatePresignedUrl(
projectRef: string,
envSlug: string,
filename: string,
method: "PUT" | "GET" = "PUT"
) {
const signed = await generatePresignedRequest(projectRef, envSlug, filename, method);
return signed?.url;
}