Skip to content

Commit 900be0f

Browse files
committed
perf(webapp): skip queue search count
Skip the count query when filtering queues and paginate search results with hasMore instead.
1 parent eb498d1 commit 900be0f

6 files changed

Lines changed: 308 additions & 44 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Speed up queue search by skipping count on filtered queries and using hasMore pagination

apps/webapp/app/components/primitives/Pagination.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,23 @@ import { LinkButton } from "./Buttons";
77
export function PaginationControls({
88
currentPage,
99
totalPages,
10+
hasNextPage,
1011
showPageNumbers = true,
1112
}: {
1213
currentPage: number;
1314
totalPages: number;
15+
/** When set, Next/visibility use this instead of totalPages (filtered lists without a total count). */
16+
hasNextPage?: boolean;
1417
showPageNumbers?: boolean;
1518
}) {
1619
const location = useLocation();
17-
if (totalPages <= 1) {
20+
const isFilteredMode = hasNextPage !== undefined;
21+
const showPagination = isFilteredMode
22+
? currentPage > 1 || hasNextPage
23+
: totalPages > 1;
24+
const nextDisabled = isFilteredMode ? !hasNextPage : currentPage === totalPages;
25+
26+
if (!showPagination) {
1827
return null;
1928
}
2029

@@ -42,8 +51,8 @@ export function PaginationControls({
4251
TrailingIcon={ChevronRightIcon}
4352
shortcut={{ key: "k" }}
4453
tooltip="Next"
45-
disabled={currentPage === totalPages}
46-
className={cn("px-2", currentPage !== totalPages ? "group" : "")}
54+
disabled={nextDisabled}
55+
className={cn("px-2", !nextDisabled ? "group" : "")}
4756
/>
4857
</>
4958
) : (
@@ -66,23 +75,21 @@ export function PaginationControls({
6675
<div
6776
className={cn(
6877
"order-2 h-6 w-px bg-charcoal-600 transition-colors peer-hover/next:bg-charcoal-550 peer-hover/prev:bg-charcoal-550",
69-
currentPage === 1 && currentPage === totalPages && "opacity-30"
78+
currentPage === 1 && nextDisabled && "opacity-30"
7079
)}
7180
/>
7281

73-
<div
74-
className={cn("peer/next order-3", currentPage === totalPages && "pointer-events-none")}
75-
>
82+
<div className={cn("peer/next order-3", nextDisabled && "pointer-events-none")}>
7683
<LinkButton
7784
to={pageUrl(location, currentPage + 1)}
7885
variant="secondary/small"
7986
TrailingIcon={ChevronRightIcon}
8087
shortcut={{ key: "k" }}
8188
tooltip="Next"
82-
disabled={currentPage === totalPages}
89+
disabled={nextDisabled}
8390
className={cn(
8491
"flex items-center rounded-l-none border-l-0 pl-[0.5625rem] pr-2",
85-
currentPage === totalPages && "cursor-not-allowed opacity-50"
92+
nextDisabled && "cursor-not-allowed opacity-50"
8693
)}
8794
/>
8895
</div>

apps/webapp/app/presenters/v3/QueueListPresenter.server.ts

Lines changed: 120 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { TaskQueueType } from "@trigger.dev/database";
1+
import { Prisma, TaskQueueType } from "@trigger.dev/database";
2+
import { type PrismaClientOrTransaction } from "~/db.server";
23
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
34
import { determineEngineVersion } from "~/v3/engineVersion.server";
45
import { engine } from "~/v3/runEngine.server";
@@ -13,11 +14,50 @@ const typeToDBQueueType: Record<"task" | "custom", TaskQueueType> = {
1314
custom: TaskQueueType.NAMED,
1415
};
1516

17+
export type QueueListFilteredPagination = {
18+
mode: "filtered";
19+
currentPage: number;
20+
hasMore: boolean;
21+
};
22+
23+
export type QueueListUnfilteredPagination = {
24+
mode: "unfiltered";
25+
currentPage: number;
26+
totalPages: number;
27+
count: number;
28+
};
29+
30+
export type QueueListPagination = QueueListFilteredPagination | QueueListUnfilteredPagination;
31+
32+
function buildQueueListWhere(
33+
environmentId: string,
34+
query: string | undefined,
35+
type: "task" | "custom" | undefined
36+
): Prisma.TaskQueueWhereInput {
37+
const trimmedQuery = query?.trim();
38+
39+
return {
40+
runtimeEnvironmentId: environmentId,
41+
version: "V2",
42+
name: trimmedQuery
43+
? {
44+
contains: trimmedQuery,
45+
mode: "insensitive",
46+
}
47+
: undefined,
48+
type: type ? typeToDBQueueType[type] : undefined,
49+
};
50+
}
51+
1652
export class QueueListPresenter extends BasePresenter {
1753
private readonly perPage: number;
1854

19-
constructor(perPage: number = DEFAULT_ITEMS_PER_PAGE) {
20-
super();
55+
constructor(
56+
perPage: number = DEFAULT_ITEMS_PER_PAGE,
57+
prismaClient?: PrismaClientOrTransaction,
58+
replicaClient?: PrismaClientOrTransaction
59+
) {
60+
super(prismaClient, replicaClient);
2161
this.perPage = Math.min(perPage, MAX_ITEMS_PER_PAGE);
2262
}
2363

@@ -33,26 +73,14 @@ export class QueueListPresenter extends BasePresenter {
3373
perPage?: number;
3474
type?: "task" | "custom";
3575
}) {
36-
const hasFilters = (query !== undefined && query.length > 0) || type !== undefined;
37-
38-
// Get total count for pagination
39-
const totalQueues = await this._replica.taskQueue.count({
40-
where: {
41-
runtimeEnvironmentId: environment.id,
42-
version: "V2",
43-
name: query
44-
? {
45-
contains: query,
46-
mode: "insensitive",
47-
}
48-
: undefined,
49-
type: type ? typeToDBQueueType[type] : undefined,
50-
},
51-
});
76+
const hasFilters = Boolean(query?.trim()) || type !== undefined;
5277

53-
//check the engine is the correct version
5478
const engineVersion = await determineEngineVersion({ environment });
5579
if (engineVersion === "V1") {
80+
const totalQueues = await this._replica.taskQueue.count({
81+
where: buildQueueListWhere(environment.id, query, type),
82+
});
83+
5684
if (totalQueues === 0) {
5785
const oldQueue = await this._replica.taskQueue.findFirst({
5886
where: {
@@ -78,10 +106,30 @@ export class QueueListPresenter extends BasePresenter {
78106
};
79107
}
80108

109+
if (hasFilters) {
110+
const { queues, hasMore } = await this.getFilteredQueues(environment, query, page, type);
111+
112+
return {
113+
success: true as const,
114+
queues,
115+
pagination: {
116+
mode: "filtered" as const,
117+
currentPage: page,
118+
hasMore,
119+
},
120+
hasFilters,
121+
};
122+
}
123+
124+
const totalQueues = await this._replica.taskQueue.count({
125+
where: buildQueueListWhere(environment.id, query, type),
126+
});
127+
81128
return {
82129
success: true as const,
83-
queues: await this.getQueuesWithPagination(environment, query, page, type),
130+
queues: await this.getUnfilteredQueues(environment, page, type),
84131
pagination: {
132+
mode: "unfiltered" as const,
85133
currentPage: page,
86134
totalPages: Math.ceil(totalQueues / this.perPage),
87135
count: totalQueues,
@@ -91,24 +139,47 @@ export class QueueListPresenter extends BasePresenter {
91139
};
92140
}
93141

94-
private async getQueuesWithPagination(
142+
private async getFilteredQueues(
95143
environment: AuthenticatedEnvironment,
96144
query: string | undefined,
97145
page: number,
98146
type: "task" | "custom" | undefined
99147
) {
100148
const queues = await this._replica.taskQueue.findMany({
101-
where: {
102-
runtimeEnvironmentId: environment.id,
103-
version: "V2",
104-
name: query
105-
? {
106-
contains: query,
107-
mode: "insensitive",
108-
}
109-
: undefined,
110-
type: type ? typeToDBQueueType[type] : undefined,
149+
where: buildQueueListWhere(environment.id, query, type),
150+
select: {
151+
friendlyId: true,
152+
name: true,
153+
orderableName: true,
154+
concurrencyLimit: true,
155+
concurrencyLimitBase: true,
156+
concurrencyLimitOverriddenAt: true,
157+
concurrencyLimitOverriddenBy: true,
158+
type: true,
159+
paused: true,
160+
},
161+
orderBy: {
162+
orderableName: "asc",
111163
},
164+
skip: (page - 1) * this.perPage,
165+
take: this.perPage + 1,
166+
});
167+
168+
const hasMore = queues.length > this.perPage;
169+
170+
return {
171+
queues: await this.enrichQueues(environment, queues.slice(0, this.perPage)),
172+
hasMore,
173+
};
174+
}
175+
176+
private async getUnfilteredQueues(
177+
environment: AuthenticatedEnvironment,
178+
page: number,
179+
type: "task" | "custom" | undefined
180+
) {
181+
const queues = await this._replica.taskQueue.findMany({
182+
where: buildQueueListWhere(environment.id, undefined, type),
112183
select: {
113184
friendlyId: true,
114185
name: true,
@@ -127,6 +198,23 @@ export class QueueListPresenter extends BasePresenter {
127198
take: this.perPage,
128199
});
129200

201+
return this.enrichQueues(environment, queues);
202+
}
203+
204+
private async enrichQueues(
205+
environment: AuthenticatedEnvironment,
206+
queues: {
207+
friendlyId: string;
208+
name: string;
209+
orderableName: string | null;
210+
concurrencyLimit: number | null;
211+
concurrencyLimitBase: number | null;
212+
concurrencyLimitOverriddenAt: Date | null;
213+
concurrencyLimitOverriddenBy: string | null;
214+
type: TaskQueueType;
215+
paused: boolean;
216+
}[]
217+
) {
130218
const results = await Promise.all([
131219
engine.lengthOfQueues(
132220
environment,
@@ -149,7 +237,6 @@ export class QueueListPresenter extends BasePresenter {
149237

150238
const overriddenByMap = new Map(overriddenByUsers.map((u) => [u.id, u]));
151239

152-
// Transform queues to include running and queued counts
153240
return queues.map((queue) =>
154241
toQueueItem({
155242
friendlyId: queue.friendlyId,

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,10 @@ export default function Page() {
440440
<QueueFilters />
441441
<PaginationControls
442442
currentPage={pagination.currentPage}
443-
totalPages={pagination.totalPages}
443+
totalPages={pagination.mode === "unfiltered" ? pagination.totalPages : 1}
444+
hasNextPage={
445+
pagination.mode === "filtered" ? pagination.hasMore : undefined
446+
}
444447
showPageNumbers={false}
445448
/>
446449
</div>

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
6464
paused: queue.paused,
6565
})),
6666
currentPage: result.pagination.currentPage,
67-
hasMore: result.pagination.currentPage < result.pagination.totalPages,
67+
hasMore: result.pagination.mode === "filtered" ? result.pagination.hasMore : false,
6868
hasFilters: result.hasFilters,
6969
};
7070
}

0 commit comments

Comments
 (0)