Skip to content

Commit a0815c8

Browse files
authored
Clickhouse performance improvements (#2175)
* Runs filter by org id and add created at to ordering * CopyableText can accept an alternative value for copying * The runs table now shows the ID instead of run number * Paginating back/forwards fix * The task stats need org id and project id too
1 parent 8a5e7f3 commit a0815c8

File tree

10 files changed

+141
-53
lines changed

10 files changed

+141
-53
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ import { SimpleTooltip } from "~/components/primitives/Tooltip";
44
import { useCopy } from "~/hooks/useCopy";
55
import { cn } from "~/utils/cn";
66

7-
export function CopyableText({ value, className }: { value: string; className?: string }) {
7+
export function CopyableText({
8+
value,
9+
copyValue,
10+
className,
11+
}: {
12+
value: string;
13+
copyValue?: string;
14+
className?: string;
15+
}) {
816
const [isHovered, setIsHovered] = useState(false);
9-
const { copy, copied } = useCopy(value);
17+
const { copy, copied } = useCopy(copyValue ?? value);
1018

1119
return (
1220
<span

apps/webapp/app/components/runs/v3/TaskRunsTable.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import {
5252
TaskRunStatusCombo,
5353
} from "./TaskRunStatus";
5454
import { useEnvironment } from "~/hooks/useEnvironment";
55+
import { CopyableText } from "~/components/primitives/CopyableText";
56+
import { ClipboardField } from "~/components/primitives/ClipboardField";
5557

5658
type RunsTableProps = {
5759
total: number;
@@ -134,7 +136,7 @@ export function TaskRunsTable({
134136
)}
135137
</TableHeaderCell>
136138
)}
137-
<TableHeaderCell alignment="right">Run #</TableHeaderCell>
139+
<TableHeaderCell>ID</TableHeaderCell>
138140
<TableHeaderCell>Task</TableHeaderCell>
139141
<TableHeaderCell>Version</TableHeaderCell>
140142
<TableHeaderCell
@@ -306,8 +308,21 @@ export function TaskRunsTable({
306308
/>
307309
</TableCell>
308310
)}
309-
<TableCell to={path} alignment="right" isTabbableCell>
310-
{formatNumber(run.number)}
311+
<TableCell to={path} isTabbableCell>
312+
<SimpleTooltip
313+
content={run.friendlyId}
314+
button={
315+
<span className="flex h-6 items-center gap-1">
316+
<CopyableText
317+
value={run.friendlyId.slice(-8)}
318+
copyValue={run.friendlyId}
319+
className="font-mono"
320+
/>
321+
</span>
322+
}
323+
asChild
324+
disableHoverableContent
325+
/>
311326
</TableCell>
312327
<TableCell to={path}>
313328
<span className="flex items-center gap-x-1">

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export class NextRunListPresenter {
4545
) {}
4646

4747
public async call(
48+
organizationId: string,
4849
environmentId: string,
4950
{
5051
userId,
@@ -190,6 +191,7 @@ export class NextRunListPresenter {
190191
});
191192

192193
const { runs, pagination } = await runsRepository.listRuns({
194+
organizationId,
193195
environmentId,
194196
projectId,
195197
tasks,

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import {
2-
PrismaClientOrTransaction,
3-
RuntimeEnvironmentType,
2+
type PrismaClientOrTransaction,
3+
type RuntimeEnvironmentType,
44
type TaskTriggerSource,
55
} from "@trigger.dev/database";
66
import { $replica } from "~/db.server";
77
import { clickhouseClient } from "~/services/clickhouseInstance.server";
88
import {
9-
AverageDurations,
9+
type AverageDurations,
1010
ClickHouseEnvironmentMetricsRepository,
11-
CurrentRunningStats,
12-
DailyTaskActivity,
13-
EnvironmentMetricsRepository,
11+
type CurrentRunningStats,
12+
type DailyTaskActivity,
13+
type EnvironmentMetricsRepository,
1414
PostgrestEnvironmentMetricsRepository,
1515
} from "~/services/environmentMetricsRepository.server";
1616
import { singleton } from "~/utils/singleton";
@@ -32,9 +32,13 @@ export class TaskListPresenter {
3232
) {}
3333

3434
public async call({
35+
organizationId,
36+
projectId,
3537
environmentId,
3638
environmentType,
3739
}: {
40+
organizationId: string;
41+
projectId: string;
3842
environmentId: string;
3943
environmentType: RuntimeEnvironmentType;
4044
}) {
@@ -76,18 +80,24 @@ export class TaskListPresenter {
7680
// IMPORTANT: Don't await these, we want to return the promises
7781
// so we can defer the loading of the data
7882
const activity = this.environmentMetricsRepository.getDailyTaskActivity({
83+
organizationId,
84+
projectId,
7985
environmentId,
8086
days: 6, // This actually means 7 days, because we want to show the current day too
8187
tasks: slugs,
8288
});
8389

8490
const runningStats = this.environmentMetricsRepository.getCurrentRunningStats({
91+
organizationId,
92+
projectId,
8593
environmentId,
8694
days: 6,
8795
tasks: slugs,
8896
});
8997

9098
const durations = this.environmentMetricsRepository.getAverageDurations({
99+
organizationId,
100+
projectId,
91101
environmentId,
92102
days: 6,
93103
tasks: slugs,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
125125

126126
try {
127127
const { tasks, activity, runningStats, durations } = await taskListPresenter.call({
128+
organizationId: project.organizationId,
129+
projectId: project.id,
128130
environmentId: environment.id,
129131
environmentType: environment.type,
130132
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
128128
}
129129

130130
const presenter = new NextRunListPresenter($replica, clickhouseClient);
131-
const list = presenter.call(environment.id, {
131+
const list = presenter.call(project.organizationId, environment.id, {
132132
userId,
133133
projectId: project.id,
134134
tasks,

apps/webapp/app/services/environmentMetricsRepository.server.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,24 @@ export type AverageDurations = Record<string, number>;
1010

1111
export interface EnvironmentMetricsRepository {
1212
getDailyTaskActivity(options: {
13+
organizationId: string;
14+
projectId: string;
1315
environmentId: string;
1416
days: number;
1517
tasks: string[];
1618
}): Promise<DailyTaskActivity>;
1719

1820
getCurrentRunningStats(options: {
21+
organizationId: string;
22+
projectId: string;
1923
environmentId: string;
2024
days: number;
2125
tasks: string[];
2226
}): Promise<CurrentRunningStats>;
2327

2428
getAverageDurations(options: {
29+
organizationId: string;
30+
projectId: string;
2531
environmentId: string;
2632
days: number;
2733
tasks: string[];
@@ -177,10 +183,14 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric
177183
constructor(private readonly options: ClickHouseEnvironmentMetricsRepositoryOptions) {}
178184

179185
public async getDailyTaskActivity({
186+
organizationId,
187+
projectId,
180188
environmentId,
181189
days,
182190
tasks,
183191
}: {
192+
organizationId: string;
193+
projectId: string;
184194
environmentId: string;
185195
days: number;
186196
tasks: string[];
@@ -190,6 +200,8 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric
190200
}
191201

192202
const [queryError, activity] = await this.options.clickhouse.taskRuns.getTaskActivity({
203+
organizationId,
204+
projectId,
193205
environmentId,
194206
days,
195207
});
@@ -210,10 +222,14 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric
210222
}
211223

212224
public async getCurrentRunningStats({
225+
organizationId,
226+
projectId,
213227
environmentId,
214228
days,
215229
tasks,
216230
}: {
231+
organizationId: string;
232+
projectId: string;
217233
environmentId: string;
218234
days: number;
219235
tasks: string[];
@@ -223,6 +239,8 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric
223239
}
224240

225241
const [queryError, stats] = await this.options.clickhouse.taskRuns.getCurrentRunningStats({
242+
organizationId,
243+
projectId,
226244
environmentId,
227245
days,
228246
});
@@ -242,10 +260,14 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric
242260
}
243261

244262
public async getAverageDurations({
263+
organizationId,
264+
projectId,
245265
environmentId,
246266
days,
247267
tasks,
248268
}: {
269+
organizationId: string;
270+
projectId: string;
249271
environmentId: string;
250272
days: number;
251273
tasks: string[];
@@ -255,6 +277,8 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric
255277
}
256278

257279
const [queryError, durations] = await this.options.clickhouse.taskRuns.getAverageDurations({
280+
organizationId,
281+
projectId,
258282
environmentId,
259283
days,
260284
});

apps/webapp/app/services/runsRepository.server.ts

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { ClickHouse } from "@internal/clickhouse";
2-
import { Tracer } from "@internal/tracing";
3-
import { Logger, LogLevel } from "@trigger.dev/core/logger";
4-
import { TaskRunStatus } from "@trigger.dev/database";
5-
import { PrismaClient } from "~/db.server";
1+
import { type ClickHouse } from "@internal/clickhouse";
2+
import { type Tracer } from "@internal/tracing";
3+
import { type Logger, type LogLevel } from "@trigger.dev/core/logger";
4+
import { type TaskRunStatus } from "@trigger.dev/database";
5+
import { type PrismaClient } from "~/db.server";
66

77
export type RunsRepositoryOptions = {
88
clickhouse: ClickHouse;
@@ -13,6 +13,7 @@ export type RunsRepositoryOptions = {
1313
};
1414

1515
export type ListRunsOptions = {
16+
organizationId: string;
1617
projectId: string;
1718
environmentId: string;
1819
//filters
@@ -43,11 +44,14 @@ export class RunsRepository {
4344
async listRuns(options: ListRunsOptions) {
4445
const queryBuilder = this.options.clickhouse.taskRuns.queryBuilder();
4546
queryBuilder
46-
.where("environment_id = {environmentId: String}", {
47-
environmentId: options.environmentId,
47+
.where("organization_id = {organizationId: String}", {
48+
organizationId: options.organizationId,
4849
})
4950
.where("project_id = {projectId: String}", {
5051
projectId: options.projectId,
52+
})
53+
.where("environment_id = {environmentId: String}", {
54+
environmentId: options.environmentId,
5155
});
5256

5357
if (options.tasks && options.tasks.length > 0) {
@@ -115,17 +119,17 @@ export class RunsRepository {
115119
if (options.page.direction === "forward") {
116120
queryBuilder
117121
.where("run_id < {runId: String}", { runId: options.page.cursor })
118-
.orderBy("run_id DESC")
122+
.orderBy("created_at DESC, run_id DESC")
119123
.limit(options.page.size + 1);
120124
} else {
121125
queryBuilder
122126
.where("run_id > {runId: String}", { runId: options.page.cursor })
123-
.orderBy("run_id DESC")
127+
.orderBy("created_at ASC, run_id ASC")
124128
.limit(options.page.size + 1);
125129
}
126130
} else {
127131
// Initial page - no cursor provided
128-
queryBuilder.orderBy("run_id DESC").limit(options.page.size + 1);
132+
queryBuilder.orderBy("created_at DESC, run_id DESC").limit(options.page.size + 1);
129133
}
130134

131135
const [queryError, result] = await queryBuilder.execute();
@@ -143,38 +147,33 @@ export class RunsRepository {
143147
let previousCursor: string | null = null;
144148

145149
//get cursors for next and previous pages
146-
if (options.page.cursor) {
147-
switch (options.page.direction) {
148-
case "forward":
149-
previousCursor = runIds.at(0) ?? null;
150-
if (hasMore) {
151-
// The next cursor should be the last run ID from this page
152-
nextCursor = runIds[options.page.size - 1];
153-
}
154-
break;
155-
case "backward":
156-
// No need to reverse since we're using DESC ordering consistently
157-
if (hasMore) {
158-
previousCursor = runIds[options.page.size - 1];
159-
}
160-
nextCursor = runIds.at(0) ?? null;
161-
break;
162-
default:
163-
// This shouldn't happen if cursor is provided, but handle it
164-
if (hasMore) {
165-
nextCursor = runIds[options.page.size - 1];
166-
}
167-
break;
150+
const direction = options.page.direction ?? "forward";
151+
switch (direction) {
152+
case "forward": {
153+
previousCursor = options.page.cursor ? runIds.at(0) ?? null : null;
154+
if (hasMore) {
155+
// The next cursor should be the last run ID from this page
156+
nextCursor = runIds[options.page.size - 1];
157+
}
158+
break;
168159
}
169-
} else {
170-
// Initial page - no cursor
171-
if (hasMore) {
172-
// The next cursor should be the last run ID from this page
173-
nextCursor = runIds[options.page.size - 1];
160+
case "backward": {
161+
const reversedRunIds = [...runIds].reverse();
162+
if (hasMore) {
163+
previousCursor = reversedRunIds.at(1) ?? null;
164+
nextCursor = reversedRunIds.at(options.page.size) ?? null;
165+
} else {
166+
nextCursor = reversedRunIds.at(options.page.size - 1) ?? null;
167+
}
168+
169+
break;
174170
}
175171
}
176172

177-
const runIdsToReturn = hasMore ? runIds.slice(0, -1) : runIds;
173+
const runIdsToReturn =
174+
options.page.direction === "backward" && hasMore
175+
? runIds.slice(1, options.page.size + 1)
176+
: runIds.slice(0, options.page.size);
178177

179178
const runs = await this.options.prisma.taskRun.findMany({
180179
where: {

0 commit comments

Comments
 (0)