로그 관련 테이블 인덱스 현황
- system_event - Read 있음, 인덱스 필요 ✅
- event_log - Write-only, 인덱스 불필요 ❌
- task_execution/control_execution은 인덱스 추가 필요합니다!
1️⃣ system_event ✅ (매우 좋음)
// packages/core/src/system-event/system-event.sql.ts (line 29-59)\
export const systemEvent = pgTable('system_event', { ... }, (table) => [ index('system_event_entity_type_idx').on(table.entityType), index('system_event_entity_id_idx').on(table.entityId), index('system_event_entity_type_entity_id_idx').on(table.entityType, table.entityId), index('system_event_created_at_idx').on(table.createdAt), index('system_event_entity_type_created_at_idx').on(table.entityType, table.createdAt), index('system_event_entity_id_created_at_idx').on(table.entityId, table.createdAt), index('system_event_data_gin_idx').using('gin', table.data), ]);
// ✅ 6개 인덱스
// ✅ 시계열 쿼리 최적화 (created_at)
// ✅ 필터링 최적화 (entity_type, entity_id)
// ✅ JSONB 검색 최적화 (GIN 인덱스)
2️⃣ event_log ❌ (심각!)
*// packages/core/src/sync-state/sync-state.sql.ts (line 10-14)* export *const* eventLog = pgTable('event_log', { eventId: text('event_id').primaryKey(), eventType: text('event_type').notNull(), processedAt: timestamp('processed_at').defaultNow().notNull(), });
// ❌ 인덱스 0개 (Primary key만)
// ❌ eventType 인덱스 없음
// ❌ processedAt 인덱스 없음
사용 패턴:
// packages/core/src/sync-state/index.ts (line 46-50)
export *async* *function* exists(eventId: string) { return await db.query.eventLog.findFirst({ where: eq(eventLog.eventId, eventId), *// ✅ PK 조회 (빠름)* }); } *// 하지만 이런 쿼리도 있을 수 있음:* *// SELECT * FROM event_log WHERE event_type = 'asset.created';* *// ❌ Full table scan!* *// SELECT * FROM event_log WHERE processed_at > NOW() - INTERVAL '7 days';* *// ❌ Full table scan!*
3️⃣ task_execution ❌❌ (매우 심각!)
// packages/core/src/task-execution/task-execution.sql.ts (line 13-31)
id: uuid('id').defaultRandom().primaryKey(), taskId: uuid('task_id').references(() => activity.id).notNull(), assetId: uuid('asset_id').references(() => asset.id).notNull(), startedAt: timestamp('started_at').notNull().defaultNow(), completedAt: timestamp('completed_at'), createdBy: uuid('created_by').notNull(), status: jsonb('status').$type<TaskLib.ExecutionStatus>().notNull(), results: jsonb('results').$type<TaskLib.Results>(), feasibility: jsonb('feasibility').$type<TaskFeasibility.Result>(), metadata: jsonb('metadata').$type<TaskExecutionMetadata.Select>().notNull(), });
export *const* taskExecution = pgTable('task_execution', { *// ❌ 인덱스 0개!* *// ❌ taskId 인덱스 없음 (자주 조회하는데!)* *// ❌ assetId 인덱스 없음 (자주 조회하는데!)* *// ❌ startedAt 인덱스 없음 (시계열 쿼리인데!)* *// ❌ status 인덱스 없음* **실제 사용 예상:** *// 특정 asset의 실행 이력 조회* SELECT * FROM task_execution WHERE asset_id = '...' ORDER BY started_at DESC LIMIT 10; *// ❌ Full table scan + 전체 정렬!* *// 특정 task의 최근 실행들*
SELECT * FROM task_execution WHERE task_id = '...' AND started_at > NOW() - INTERVAL '7 days' ORDER BY started_at DESC; *// ❌ Full table scan!* *// 실행 중인 task 조회* SELECT * FROM task_execution WHERE status->>'state' = 'started'; *// ❌ Full table scan + JSONB 파싱!*
4️⃣ control_execution ❌❌ (매우 심각!)
*// packages/core/src/control-execution/control-execution.sql.ts (line 12-37)* export *const* controlExecution = pgTable('control_execution', { id: uuid('id').defaultRandom().primaryKey(), assetsToControlsId: uuid('assets_to_controls_id').references(...), assetFrameworkControlsId: uuid('asset_framework_controls_id').references(...), startedAt: timestamp('started_at').notNull().defaultNow(), completedAt: timestamp('completed_at'), status: text('status', { enum: ControlLib.executionStatuses }).notNull(), feasibility: jsonb('feasibility').$type<TaskFeasibility.Result>(), results: jsonb('results').$type<TaskLib.Results>(), taskExecutionId: uuid('task_execution_id').references(...).notNull(), }); *// ❌ 인덱스 0개!* *// ❌ assetsToControlsId 인덱스 없음* *// ❌ assetFrameworkControlsId 인덱스 없음* *// ❌ startedAt 인덱스 없음* *// ❌ taskExecutionId 인덱스 없음*
실제 사용 (Asset.getAssetControls):
// packages/core/src/asset/index.ts (line 1539-1548)
controlExecutions: { orderBy: [desc(controlExecution.startedAt)], limit: 1, columns: { id: true, status: true, startedAt: true, completedAt: true, }, },*// 쿼리 예상:* SELECT * FROM control_execution WHERE assets_to_controls_id = '...' ORDER BY started_at DESC LIMIT 1; *// ❌ Full table scan + 전체 정렬!*
5️⃣ activity ⚠️ (큰 문제는 아니지만)
// packages/core/src/activity/activity.sql.ts (line 11-24)
export *const* activity = pgTable('activity', { id: uuid('id').defaultRandom().primaryKey(), name: text('name').notNull(), description: text('description').notNull(), createdAt: timestamp('created_at').notNull().defaultNow(), metadata: jsonb('metadata').$type<TaskMetaData>().notNull(), requirements: text('requirements').array().notNull(), type: text('type', { enum: TaskLib.typeSchema.options }).notNull(), }); *// ❌ 인덱스 0개* *// ⚠️ 하지만 activity는 CRUD가 적으므로 덜 심각* *// ⚠️ name 검색 시 느릴 수 있음*
심각도 순위
테이블 | 인덱스 | 심각도 | 이유 |
system_event | 6개 ✅ | 없음 | 완벽 |
task_execution | 0개 ❌ | 🔴 심각 | 시계열 데이터 + 자주 조회 + FK 없음 |
control_execution | 0개 ❌ | 🔴 심각 | 시계열 데이터 + 자주 조회 + FK 없음 |
event_log | 0개 ❌ | 🟡 중간 | 단순 idempotency check만 함 |
activity | 0개 ⚠️ | 🟢 낮음 | CRUD 적음, 조회 빈도 낮음 |
현재 Asset 테이블 인덱스 (line 79-87)
// ❌ 인덱스 3개만 있음
index('asset_archived_idx').on(table.archived), index('asset_status_archived_idx').on(table.status, table.archived), index('asset_archived_created_at_idx').on(table.archived, table.createdAt),
문제: 핵심 컬럼에 인덱스 없음
*// listWithDetails3 함수에서 자주 사용하는 필터들* *// 1. category 필터링 (line 469-480)* if (filters?.category) { categoryCondition = inArray(asset.category, categoryValues); } *// ❌ category 인덱스 없음 → Full table scan!* *// 2. name 검색 (line 464-466)* if (search) { baseConditions.push(like(sql`LOWER(${asset.name})`, searchTerm)); } *// ❌ name 인덱스 없음 → Full table scan!* *// 3. name 정렬 (line 596)* orderBy: [asc(sql`lower(${asset.name})`)] *// ❌ name 인덱스 없음 → 전체 데이터 정렬!* *// 4. sources 필터링 (line 495-510)* *// assetSummary → artifact join 필요* *// ❌ 최적화 안 됨*
비교: system_event vs asset
테이블 | 인덱스 수 | 주요 인덱스 |
system_event | 6개 ✅ | entity_type, entity_id, created_at, 복합 인덱스들 |
asset | 3개만 ❌ | archived, status+archived, archived+created_at |
Seonglae Cho