Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions turbo/apps/web/src/db/migrations/0051_add_performance_indexes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- Migration: Add performance indexes for high-frequency queries
-- Issue: #1284 - Add missing database indexes for high-frequency query columns

-- agent_runs: Composite index for user listing with time-based sorting
-- Optimizes: GET /v1/runs, user run listing queries
CREATE INDEX "idx_agent_runs_user_created" ON "agent_runs"("user_id", "created_at" DESC);

-- agent_runs: Partial index for cron cleanup job
-- Optimizes: /api/cron/cleanup-sandboxes (finds running sandboxes with stale heartbeats)
-- Only indexes rows where status='running', keeping index small
CREATE INDEX "idx_agent_runs_running_heartbeat" ON "agent_runs"("last_heartbeat_at")
WHERE "status" = 'running';

-- agent_runs: Partial index for schedule run history
-- Optimizes: schedule-service.ts getRecentRuns query
-- Only indexes scheduled runs (schedule_id IS NOT NULL)
CREATE INDEX "idx_agent_runs_schedule_created" ON "agent_runs"("schedule_id", "created_at" DESC)
WHERE "schedule_id" IS NOT NULL;

-- agent_sessions: Composite index for findOrCreate pattern
-- Optimizes: agent-session-service.ts findOrCreate() method
-- Covers queries with (userId, agentComposeId, artifactName) combinations
CREATE INDEX "idx_agent_sessions_user_compose_artifact"
ON "agent_sessions"("user_id", "agent_compose_id", "artifact_name");
62 changes: 39 additions & 23 deletions turbo/apps/web/src/db/schema/agent-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
text,
jsonb,
timestamp,
index,
} from "drizzle-orm/pg-core";
import { agentComposeVersions } from "./agent-compose";

Expand All @@ -13,26 +14,41 @@ import { agentComposeVersions } from "./agent-compose";
* Created when developer executes agent via SDK
* References immutable compose version for reproducibility
*/
export const agentRuns = pgTable("agent_runs", {
id: uuid("id").defaultRandom().primaryKey(),
userId: text("user_id").notNull(), // Clerk user ID - owner of this run
agentComposeVersionId: varchar("agent_compose_version_id", { length: 64 })
.references(() => agentComposeVersions.id)
.notNull(),
resumedFromCheckpointId: uuid("resumed_from_checkpoint_id"),
// References agent_schedules.id if this run was triggered by a schedule
// No FK constraint to avoid circular dependency with agent_schedules
scheduleId: uuid("schedule_id"),
status: varchar("status", { length: 20 }).notNull(),
prompt: text("prompt").notNull(),
vars: jsonb("vars"),
// Secret names for validation (values never stored - must be provided at runtime)
secretNames: jsonb("secret_names").$type<string[]>(),
sandboxId: varchar("sandbox_id", { length: 255 }),
result: jsonb("result"),
error: text("error"),
createdAt: timestamp("created_at").defaultNow().notNull(),
startedAt: timestamp("started_at"),
completedAt: timestamp("completed_at"),
lastHeartbeatAt: timestamp("last_heartbeat_at"),
});
export const agentRuns = pgTable(
"agent_runs",
{
id: uuid("id").defaultRandom().primaryKey(),
userId: text("user_id").notNull(), // Clerk user ID - owner of this run
agentComposeVersionId: varchar("agent_compose_version_id", { length: 64 })
.references(() => agentComposeVersions.id)
.notNull(),
resumedFromCheckpointId: uuid("resumed_from_checkpoint_id"),
// References agent_schedules.id if this run was triggered by a schedule
// No FK constraint to avoid circular dependency with agent_schedules
scheduleId: uuid("schedule_id"),
status: varchar("status", { length: 20 }).notNull(),
prompt: text("prompt").notNull(),
vars: jsonb("vars"),
// Secret names for validation (values never stored - must be provided at runtime)
secretNames: jsonb("secret_names").$type<string[]>(),
sandboxId: varchar("sandbox_id", { length: 255 }),
result: jsonb("result"),
error: text("error"),
createdAt: timestamp("created_at").defaultNow().notNull(),
startedAt: timestamp("started_at"),
completedAt: timestamp("completed_at"),
lastHeartbeatAt: timestamp("last_heartbeat_at"),
},
(table) => [
// Composite index for user listing with time-based sorting
index("idx_agent_runs_user_created").on(table.userId, table.createdAt),
// Partial index for cron cleanup (only running status)
index("idx_agent_runs_running_heartbeat")
.on(table.lastHeartbeatAt)
.where("status = 'running'" as never),
// Partial index for schedule history (only scheduled runs)
index("idx_agent_runs_schedule_created")
.on(table.scheduleId, table.createdAt)
.where("schedule_id IS NOT NULL" as never),
],
);
54 changes: 33 additions & 21 deletions turbo/apps/web/src/db/schema/agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
text,
timestamp,
jsonb,
index,
} from "drizzle-orm/pg-core";
import { agentComposes } from "./agent-compose";
import { conversations } from "./conversation";
Expand All @@ -20,24 +21,35 @@ import { conversations } from "./conversation";
* - vars: Template variables for compose expansion
* - secretNames: Secret names for validation (values never stored)
*/
export const agentSessions = pgTable("agent_sessions", {
id: uuid("id").defaultRandom().primaryKey(),
userId: text("user_id").notNull(),
agentComposeId: uuid("agent_compose_id")
.references(() => agentComposes.id, { onDelete: "cascade" })
.notNull(),
// Immutable compose version ID (SHA-256 hash) fixed at session creation
// If null (legacy sessions), resolveSession falls back to HEAD version
agentComposeVersionId: varchar("agent_compose_version_id", { length: 255 }),
conversationId: uuid("conversation_id").references(() => conversations.id, {
onDelete: "set null",
}),
artifactName: varchar("artifact_name", { length: 255 }),
vars: jsonb("vars").$type<Record<string, string>>(),
// Secret names for validation (values never stored - must be provided at runtime)
secretNames: jsonb("secret_names").$type<string[]>(),
// Volume versions snapshot at session creation for reproducibility
volumeVersions: jsonb("volume_versions").$type<Record<string, string>>(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
export const agentSessions = pgTable(
"agent_sessions",
{
id: uuid("id").defaultRandom().primaryKey(),
userId: text("user_id").notNull(),
agentComposeId: uuid("agent_compose_id")
.references(() => agentComposes.id, { onDelete: "cascade" })
.notNull(),
// Immutable compose version ID (SHA-256 hash) fixed at session creation
// If null (legacy sessions), resolveSession falls back to HEAD version
agentComposeVersionId: varchar("agent_compose_version_id", { length: 255 }),
conversationId: uuid("conversation_id").references(() => conversations.id, {
onDelete: "set null",
}),
artifactName: varchar("artifact_name", { length: 255 }),
vars: jsonb("vars").$type<Record<string, string>>(),
// Secret names for validation (values never stored - must be provided at runtime)
secretNames: jsonb("secret_names").$type<string[]>(),
// Volume versions snapshot at session creation for reproducibility
volumeVersions: jsonb("volume_versions").$type<Record<string, string>>(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(table) => [
// Composite index for findOrCreate pattern
index("idx_agent_sessions_user_compose_artifact").on(
table.userId,
table.agentComposeId,
table.artifactName,
),
],
);