perf(webapp): index EnvironmentVariableValue.environmentId (#3675)

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
This commit is contained in:
Eric Allam
2026-05-20 13:43:44 +01:00
committed by GitHub
parent 6b46a34c46
commit aec7e0a93d
4 changed files with 16 additions and 3 deletions
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---
Speed up env-var lookups on the projects API by indexing `EnvironmentVariableValue.environmentId`.
@@ -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<CreateResult> {
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] }
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY IF NOT EXISTS "EnvironmentVariableValue_environmentId_idx"
ON "EnvironmentVariableValue"("environmentId");
@@ -2020,6 +2020,7 @@ model EnvironmentVariableValue {
lastUpdatedBy Json?
@@unique([variableId, environmentId])
@@index([environmentId])
}
model Checkpoint {