From aec7e0a93d24537b6bbf63a408714d8de5d63dd5 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 20 May 2026 13:43:44 +0100 Subject: [PATCH] perf(webapp): index EnvironmentVariableValue.environmentId (#3675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Env-var lookups via `GET /api/v1/projects/:projectRef/envvars/:slug/:name` run a Prisma `findMany` on `EnvironmentVariableValue` filtered by `environmentId` + `isSecret`. The only existing indexes are the primary key and a unique on `(variableId, environmentId)`, so `environmentId` is never the leading column — the planner falls back to a Parallel Seq Scan over the whole table to find what is, in practice, a handful of rows per environment. Two changes: - Add a btree index on `EnvironmentVariableValue(environmentId)` so the planner switches to an index scan. The composite `(variableId, environmentId)` unique stays in place; the new index is purely additive. - Route the `findMany` inside `getEnvironmentWithRedactedSecrets` through the read replica via a new `replicaClient` constructor param on the repository (defaulting to `$replica`, mirroring how `prismaClient` defaults to `prisma`). Writes and read-after-write methods stay on the primary. ## Test plan - [ ] `pnpm run typecheck --filter webapp` - [ ] Confirm `EXPLAIN` plan flips from Parallel Seq Scan to an index scan - [ ] Existing env-var route tests still pass --- .server-changes/env-var-value-environment-id-index.md | 6 ++++++ .../environmentVariablesRepository.server.ts | 9 ++++++--- .../migration.sql | 3 +++ internal-packages/database/prisma/schema.prisma | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .server-changes/env-var-value-environment-id-index.md create mode 100644 internal-packages/database/prisma/migrations/20260520120000_add_environment_variable_value_environment_id_idx/migration.sql diff --git a/.server-changes/env-var-value-environment-id-index.md b/.server-changes/env-var-value-environment-id-index.md new file mode 100644 index 00000000000..1c063859237 --- /dev/null +++ b/.server-changes/env-var-value-environment-id-index.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Speed up env-var lookups on the projects API by indexing `EnvironmentVariableValue.environmentId`. diff --git a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts index f2ca46d4d3a..9cc41995664 100644 --- a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts +++ b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts @@ -2,7 +2,7 @@ import { Prisma, type PrismaClient, type RuntimeEnvironmentType } from "@trigger import type { AuthenticatedEnvironment } from "@trigger.dev/core/v3/auth/environment"; import { z } from "zod"; import { environmentFullTitle } from "~/components/environments/EnvironmentLabel"; -import { $transaction, prisma } from "~/db.server"; +import { $replica, $transaction, prisma, type PrismaReplicaClient } from "~/db.server"; import { env } from "~/env.server"; import { getSecretStore } from "~/services/secrets/secretStore.server"; import { generateFriendlyId } from "../friendlyIdentifiers"; @@ -47,7 +47,10 @@ function parseSecretKey(key: string) { const SecretValue = z.object({ secret: z.string() }); export class EnvironmentVariablesRepository implements Repository { - constructor(private prismaClient: PrismaClient = prisma) {} + constructor( + private prismaClient: PrismaClient = prisma, + private replicaClient: PrismaReplicaClient = $replica + ) {} async create(projectId: string, options: CreateEnvironmentVariables): Promise { const project = await this.prismaClient.project.findFirst({ @@ -582,7 +585,7 @@ export class EnvironmentVariablesRepository implements Repository { const variables = await this.getEnvironment(projectId, environmentId, parentEnvironmentId); // Get the keys of all secret variables - const secretValues = await this.prismaClient.environmentVariableValue.findMany({ + const secretValues = await this.replicaClient.environmentVariableValue.findMany({ where: { environmentId: parentEnvironmentId ? { in: [environmentId, parentEnvironmentId] } diff --git a/internal-packages/database/prisma/migrations/20260520120000_add_environment_variable_value_environment_id_idx/migration.sql b/internal-packages/database/prisma/migrations/20260520120000_add_environment_variable_value_environment_id_idx/migration.sql new file mode 100644 index 00000000000..9aaf4d930c6 --- /dev/null +++ b/internal-packages/database/prisma/migrations/20260520120000_add_environment_variable_value_environment_id_idx/migration.sql @@ -0,0 +1,3 @@ +-- CreateIndex +CREATE INDEX CONCURRENTLY IF NOT EXISTS "EnvironmentVariableValue_environmentId_idx" + ON "EnvironmentVariableValue"("environmentId"); diff --git a/internal-packages/database/prisma/schema.prisma b/internal-packages/database/prisma/schema.prisma index 7e32a96d805..ae7cb34bf5b 100644 --- a/internal-packages/database/prisma/schema.prisma +++ b/internal-packages/database/prisma/schema.prisma @@ -2020,6 +2020,7 @@ model EnvironmentVariableValue { lastUpdatedBy Json? @@unique([variableId, environmentId]) + @@index([environmentId]) } model Checkpoint {