Files
triggerdotdev--trigger.dev/.server-changes
Eric Allam 4658cd0721 perf(database): index WorkerDeployment on (environmentId, status, id) for the deployments list (#4591)
## What

Adds a composite index `@@index([environmentId, status, id])` to
`WorkerDeployment`.

The public deployments list (`GET /api/v1/deployments`) filters by
`status` and paginates by `id` descending. The existing indexes cover
`(environmentId, createdAt)` and the PK, but nothing covers `status`. So
for a status filter Postgres walks back through the environment's
deployments discarding non-matching statuses, reading roughly 350 rows
for every 1 returned (p99 ~1.1s on the busiest environments). The new
index makes the status filter index-satisfied and lets `id` serve both
the cursor range and the `ORDER BY id DESC`, bounding the read to a
single page.

Full composite (not partial) because callers filter by arbitrary status
values with no single dominant one.

## Query

```sql
SELECT ... FROM "WorkerDeployment"
WHERE "environmentId" = $1 AND "status" = $2 [AND "id" < $3]
ORDER BY "id" DESC LIMIT $4;
```

Source: `apps/webapp/app/routes/api.v1.deployments.ts`.

## Evidence

Reproduced on an isolated stack: one environment seeded with 7,000
deployments, the filtered status appearing 1 in 333 rows.

Before (no index):
```
Seq Scan on "WorkerDeployment"  (rows=21)
  Rows Removed by Filter: 6979
  Buffers: shared hit=206
Execution Time: 2.9 ms   (+ a sort for id desc)
```

After (with the index):
```
Index Scan Backward using "WorkerDeployment_environmentId_status_id_idx"
  Index Cond: (environmentId = $1 AND status = $2)
  Buffers: shared hit=23
Execution Time: 0.43 ms
```

Rows-removed-by-filter drops to 0; buffers 206 -> 23. The cursor
(mid-pagination) variant uses the same index with all three predicates
as the index condition. A dense/common status keeps the cheap PK
backward scan (already fine); the index targets exactly the rare-status
paths that were amplified.

End-to-end against the running webapp API: `?status=FAILED` returns the
correct newest-first page and paginates correctly across pages, and the
emitted SQL matches the query above.

## Rollout

- Index only, `CREATE INDEX CONCURRENTLY IF NOT EXISTS` in its own
migration file. Online-safe under write load.
- Pre-apply the index in production before the migration deploys, per
repo convention (the migration is then a no-op).
- Rollback: drop the index. No data migration.

refs TRI-13171
2026-08-12 14:03:39 +01:00
..

Server Changes

This directory tracks changes to server-only components (webapp, supervisor, etc.) that are not captured by changesets. Changesets only track published npm packages — server changes would otherwise go undocumented.

When to add a file

Server-only PRs: If your PR only changes apps/webapp/, apps/supervisor/, or other server components (and does NOT change anything in packages/), add a .server-changes/ file.

Mixed PRs (both packages and server): Just add a changeset as usual. No .server-changes/ file needed — the changeset covers it.

Package-only PRs: Just add a changeset as usual.

File format

Create a markdown file with a descriptive name:

.server-changes/fix-batch-queue-stalls.md

With this format:

---
area: webapp
type: fix
---

Speed up batch queue processing by removing stalls and fixing retry race

Fields

  • area (required): webapp | supervisor
  • type (required): feature | fix | improvement | breaking

Description

The body text (below the frontmatter) is a one-line description of the change. Keep it concise — it will appear in release notes.

Writing guidance

These entries are public-facing - they ship verbatim in user-visible release notes. A few rules to keep them clean:

  • Write for the user, not the reviewer. Lead with what the user notices or has to do. If a reader who doesn't know the codebase can't tell what changed for them, rewrite it.
  • One sentence is usually enough. The body is the bullet in the changelog. If you need a paragraph, you're probably describing the implementation rather than the change.
  • Describe behavior, not implementation. Skip internal scopes, middleware names, library specifics, framework internals. Users care about what's different for them, not how it's wired.
  • Never name internal tools or infra. Observability stacks, internal services, infra components, monitoring backends, CI surfaces, AWS specifics - none of these belong in user-facing notes.

Before / after:

  • "The image verification step now parses the manifest's layer media types and returns a new result the finalizer rejects." (describes the wiring; a user can't act on it)
  • "Deploying with an outdated CLI could produce an image that fails to start on every run. These deploys are now stopped before going live, with a message asking you to upgrade the CLI and re-deploy." (what the user sees and does)

Lifecycle

  1. Engineer adds a .server-changes/ file in their PR
  2. Files accumulate on main as PRs merge
  3. The changeset release PR includes these in its summary
  4. After the release merges, CI cleans up the consumed files

Examples

New feature:

---
area: webapp
type: feature
---

TRQL query language and the Query page

Bug fix:

---
area: webapp
type: fix
---

Fix schedule limit counting for orgs with custom limits

Improvement:

---
area: webapp
type: improvement
---

Use the replica for API auth queries to reduce primary load