Skip to content

Fix for Schedules list page slow loading #1992

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2025
Merged

Conversation

matt-aitken
Copy link
Member

@matt-aitken matt-aitken commented Apr 29, 2025

The Schedules list page was slow to load because we were getting the possible BackgroundWorkerTask.

This had become very slow because distinct Prisma wasn't use the Postgres DISTINCT feature – it was loading all of them into memory… this could be millions in some cases if you had a lot of versions.

  • The concurrently index needs applying manually in Test
  • The concurrently index needs applying manually in Prod
-- CreateIndex
CREATE INDEX CONCURRENTLY IF NOT EXISTS "BackgroundWorker_runtimeEnvironmentId_createdAt_idx" ON "BackgroundWorker" ("runtimeEnvironmentId", "createdAt" DESC);

Summary by CodeRabbit

  • New Features
    • Improved performance for environment-based schedule queries by adding a new database index to quickly retrieve the latest background worker for a given environment.
  • Refactor
    • Updated schedule filtering to use a single environment context instead of supporting multiple environments.
    • Simplified filter options and responses by removing multi-environment selection and related data from the schedule list.
    • Enhanced validation to ensure schedules are tied to a valid environment.
  • Chores
    • Database schema updated to include a new index for optimized queries.

Getting BackgroundWorkerTask was very slow (Prisma was getting every single one…)
Copy link

changeset-bot bot commented Apr 29, 2025

⚠️ No Changeset found

Latest commit: cfc7cd0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Apr 29, 2025

Walkthrough

This set of changes refactors the schedule listing and editing functionality to operate strictly within a single environment context, replacing previous support for multi-environment filtering. The ScheduleListPresenter and EditSchedulePresenter now require a single environmentId or environmentSlug parameter, validating environment existence and fetching the latest background worker for that environment. Corresponding UI components and API routes are updated to reflect this change by removing multi-environment filters. Additionally, a new database index is introduced on the BackgroundWorker table to optimize queries retrieving the latest worker by environment and creation time.

Changes

File(s) Change Summary
apps/webapp/app/components/runs/v3/ScheduleFilters.tsx Removed the environments filter from the filter schema and related parsing logic.
apps/webapp/app/presenters/v3/ScheduleListPresenter.server.ts Refactored to require a single environmentId parameter, updated queries and logic to remove multi-environment support, validated environment existence, and adjusted response accordingly.
apps/webapp/app/presenters/v3/EditSchedulePresenter.server.ts Updated method signature to include environmentSlug; changed task query logic to filter by environment and latest background worker with validation.
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules/route.tsx Updated loader and page logic to use environmentId directly and removed handling of possible environments and total pages.
apps/webapp/app/routes/api.v1.schedules.ts Changed presenter invocation to use environmentId instead of an array of environments.
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.edit.$scheduleParam/route.tsx Updated loader to pass environmentSlug to EditSchedulePresenter.
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx Changed param parsing to use EnvironmentParamSchema and passed environmentSlug to EditSchedulePresenter.
internal-packages/database/prisma/migrations/20250429100819_background_worker_index_environment_id_and_created_at/migration.sql Added a migration to create a new index on BackgroundWorker(runtimeEnvironmentId, createdAt DESC).
internal-packages/database/prisma/schema.prisma Added an index to the BackgroundWorker model for [runtimeEnvironmentId, createdAt(sort: Desc)].

Sequence Diagram(s)

sequenceDiagram
    participant UI as User Interface
    participant Loader as Loader/Route
    participant Presenter as ScheduleListPresenter
    participant DB as Database

    UI->>Loader: Request schedules for environment
    Loader->>Presenter: call({ projectId, environmentId, ... })
    Presenter->>DB: Validate environmentId exists
    alt Environment not found
        Presenter-->>Loader: Throw ServiceValidationError
    else Environment found
        Presenter->>DB: Fetch latest BackgroundWorker for environmentId
        alt No worker found
            Presenter-->>Loader: Return empty schedule list
        else Worker found
            Presenter->>DB: Query schedules/tasks for environmentId and worker
            Presenter-->>Loader: Return schedules/tasks
        end
    end
    Loader-->>UI: Render schedules/tasks
Loading
sequenceDiagram
    participant UI as User Interface
    participant Loader as Loader/Route
    participant Presenter as EditSchedulePresenter
    participant DB as Database

    UI->>Loader: Request schedule edit page with environmentSlug
    Loader->>Presenter: call({ userId, projectSlug, environmentSlug, friendlyId })
    Presenter->>DB: Find environment by slug
    alt Environment not found
        Presenter-->>Loader: Throw ServiceValidationError (404)
    else Environment found
        Presenter->>DB: Fetch latest BackgroundWorker for environment
        alt No worker found
            Presenter-->>Loader: Return empty task list
        else Worker found
            Presenter->>DB: Query scheduled tasks filtered by worker, project, environment, trigger source
            Presenter-->>Loader: Return sorted task slugs
        end
    end
    Loader-->>UI: Render edit schedule page with tasks
Loading

Possibly related PRs

  • triggerdotdev/trigger.dev#1845: Refactors the TaskListPresenter to use a single environmentId parameter, closely mirroring the shift from multi-environment to single environment filtering in this PR.

Suggested reviewers

  • nicktrn
  • ericallam

Poem

In a field of code, a bunny hops,
From many environments, now just one it stops.
Filters trimmed, the queries neat,
With a fresh new index, performance can’t be beat.
Schedules sorted, tasks in line,
This single path feels quite divine.
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34db26d and cfc7cd0.

📒 Files selected for processing (3)
  • apps/webapp/app/presenters/v3/EditSchedulePresenter.server.ts (4 hunks)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.edit.$scheduleParam/route.tsx (1 hunks)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx (3)
apps/webapp/app/services/session.server.ts (1)
  • requireUserId (25-35)
apps/webapp/app/utils/pathBuilder.ts (1)
  • EnvironmentParamSchema (26-28)
apps/webapp/app/presenters/v3/EditSchedulePresenter.server.ts (1)
  • EditSchedulePresenter (25-150)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / 🧪 Unit Tests
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.edit.$scheduleParam/route.tsx (1)

18-18: LGTM: Environment context parameter added correctly

The addition of the environmentSlug parameter correctly aligns with the updated EditSchedulePresenter.call method signature, which now requires an environment slug to scope scheduled tasks to a single environment.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx (3)

5-5: LGTM: Import updated to use EnvironmentParamSchema

Correctly updated the import to use EnvironmentParamSchema which includes the environment parameter.


11-11: LGTM: Route parameter parsing updated

Properly updated to parse the route parameters using EnvironmentParamSchema which extracts the envParam.


17-17: LGTM: Environment context parameter added correctly

The addition of the environmentSlug parameter correctly aligns with the updated EditSchedulePresenter.call method signature, which now requires an environment slug to scope scheduled tasks to a single environment.

apps/webapp/app/presenters/v3/EditSchedulePresenter.server.ts (4)

1-8: LGTM: Imports properly updated for new dependency requirements

Import statements correctly updated to support environment-specific querying and error handling:

  • Type imports are now explicitly marked with type
  • Added imports for findEnvironmentBySlug, findCurrentWorkerFromEnvironment, and ServiceValidationError

13-13: LGTM: Type definition updated with required environmentSlug parameter

Type definition correctly updated to include the required environmentSlug parameter.


32-87: LGTM: Optimized implementation for environment-scoped task retrieval

The implementation has been properly updated to:

  1. Find a specific environment by slug
  2. Throw a 404 error if the environment doesn't exist
  3. Get the latest background worker for that environment
  4. Query only the tasks relevant to that specific environment and worker

This change directly addresses the performance issue mentioned in the PR objectives by avoiding the retrieval of all possible BackgroundWorkerTask entries.


95-95: LGTM: Task sorting applied consistently

The sort operation is applied to the task slugs, ensuring consistent ordering of the results.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
apps/webapp/app/presenters/v3/ScheduleListPresenter.server.ts (1)

243-245: ⚠️ Potential issue

page may be undefined → Prisma will receive NaN.

If the caller omits the page parameter, (page - 1) evaluates to NaN, which Prisma rejects.

-      skip: (page - 1) * pageSize,
+      skip: ((page ?? 1) - 1) * pageSize,

Or give page a default in the function signature: page = 1.

🧹 Nitpick comments (2)
apps/webapp/app/presenters/v3/ScheduleListPresenter.server.ts (2)

124-131: Query only the required column to save memory & I/O.

You only use slug later, but findMany currently retrieves full rows. Slim the query:

-const possibleTasks = await this._replica.backgroundWorkerTask.findMany({
-  where: {
-    workerId: latestWorker.id,
-    projectId: project.id,
-    runtimeEnvironmentId: environmentId,
-    triggerSource: "SCHEDULED",
-  },
-});
+const possibleTasks = await this._replica.backgroundWorkerTask.findMany({
+  where: {
+    workerId: latestWorker.id,
+    projectId: project.id,
+    runtimeEnvironmentId: environmentId,
+    triggerSource: "SCHEDULED",
+  },
+  select: { slug: true },
+});

This reduces the amount of data shipped from the database.


276-279: Guard against 0 total pages.

Math.ceil(0 / pageSize) returns 0, which may break pagination components expecting a minimum of 1.

-      totalPages: Math.ceil(totalCount / pageSize),
+      totalPages: Math.max(1, Math.ceil(totalCount / pageSize)),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f579afb and 34db26d.

📒 Files selected for processing (6)
  • apps/webapp/app/components/runs/v3/ScheduleFilters.tsx (1 hunks)
  • apps/webapp/app/presenters/v3/ScheduleListPresenter.server.ts (6 hunks)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules/route.tsx (1 hunks)
  • apps/webapp/app/routes/api.v1.schedules.ts (1 hunks)
  • internal-packages/database/prisma/migrations/20250429100819_background_worker_index_environment_id_and_created_at/migration.sql (1 hunks)
  • internal-packages/database/prisma/schema.prisma (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / 🧪 Unit Tests
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
internal-packages/database/prisma/schema.prisma (1)

1630-1631: Adding index for efficient background worker retrieval by environment.

This index optimizes querying for the latest background worker in a given environment by indexing runtimeEnvironmentId and createdAt (in descending order). This is a good performance improvement that addresses the slow loading issue on the Schedules list page.

internal-packages/database/prisma/migrations/20250429100819_background_worker_index_environment_id_and_created_at/migration.sql (1)

1-2: Excellent use of concurrent index creation.

Using CREATE INDEX CONCURRENTLY is the correct approach here as it prevents table locking during index creation, which is critical for production databases. The index will help optimize background worker queries as per the PR objective.

apps/webapp/app/components/runs/v3/ScheduleFilters.tsx (1)

43-45: LGTM: Adapted filter destructuring for single environment model.

This change correctly removes environments from the destructuring pattern, aligning with the refactoring from multi-environment filtering to single environment context.

apps/webapp/app/routes/api.v1.schedules.ts (1)

103-103: LGTM: Switched to single environment ID parameter.

This update properly converts from the previous environments array parameter to a single environmentId parameter, which matches the schema changes and will utilize the new database index for better performance.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules/route.tsx (1)

97-103: Validate that filters cannot silently overwrite environmentId.

environmentId is passed first and then the spread operator injects the parsed filters.
If, in the future, ScheduleListFilters re-gains a property called environmentId, it would overwrite the explicitly-supplied value and introduce hard-to-trace bugs.

Consider making the property name in the filters object more explicit (e.g. runtimeEnvironmentIdFilter) or adding a TypeScript utility type that prevents such collisions.

apps/webapp/app/presenters/v3/ScheduleListPresenter.server.ts (3)

90-94: 👍 Good defensive check for environment ownership.

Adding an explicit validation and surfacing a ServiceValidationError avoids leaking data from other environments.


100-121: Early return may hide existing schedules if no worker is found.

If an environment has schedules configured but, for whatever reason, the latest worker record has been purged or never created, users will see an empty list even though schedules exist.
Please confirm this is the intended UX, or consider falling back to:

  1. Querying tasks without the workerId filter, or
  2. Returning schedules but an empty possibleTasks array.

If needed I can supply a verification script that checks for environments with schedules but without a BackgroundWorker.


286-289: The returned filters object dropped the type field.

Down-stream UI or API consumers that rely on echoing the active filter set will now lose the type context. Re-add it unless you have confirmed nothing reads it.

-      filters: {
-        tasks,
-        search,
-      },
+      filters: {
+        tasks,
+        search,
+        type,
+      },

@matt-aitken matt-aitken merged commit 83d0e87 into main Apr 29, 2025
11 of 12 checks passed
@matt-aitken matt-aitken deleted the schedule-list-performance branch April 29, 2025 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants