Skip to content

refactor: pre-aggregate resolution rate in usage daily#39

Open
BJDG-CM wants to merge 3 commits into
gsainfoteam:mainfrom
BJDG-CM:main
Open

refactor: pre-aggregate resolution rate in usage daily#39
BJDG-CM wants to merge 3 commits into
gsainfoteam:mainfrom
BJDG-CM:main

Conversation

@BJDG-CM

@BJDG-CM BJDG-CM commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Changes

  • usage_dailytotal_answers, bad_answers 컬럼 추가
  • assistant 메시지 저장 시 total_answers 증가
  • 피드백 생성·변경 시 상태 변화에 따른 delta만큼 bad_answers 반영
  • 메시지 저장 및 피드백 갱신과 usage 집계를 동일 트랜잭션에서 처리
  • 대시보드의 resolutionRateusage_daily 집계값으로 계산
  • 기존 데이터 반영을 위한 idempotent backfill 스크립트 추가
  • 컬럼 제약조건 및 관련 단위 테스트 추가

Validation

  • Unit tests: 66 passed
  • E2E tests: 17 passed
  • Lint: passed
  • Build: passed

Deployment note

  • Migration 적용 후 bun run db:backfill:answers 실행 필요

Summary by CodeRabbit

  • 새 기능
    • 위젯별 usage_daily에 일일 총 답변/불량 답변 집계를 추가했습니다.
    • 백필 명령(db:backfill:answers)으로 기존 데이터를 일괄 계산할 수 있습니다.
  • 개선 사항
    • 답변 생성 및 피드백 변경 시 통계가 트랜잭션 단위로 정확히 반영됩니다.
    • 해결률 계산이 메시지 스캔 대신 누적 집계(총/불량) 기반으로 동작합니다.
  • 버그 수정
    • 잠금/경합 및 집계 누락 상황에서 자동 복구 흐름을 추가했습니다.

Copilot AI review requested due to automatic review settings July 10, 2026 08:38
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@BJDG-CM, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ace1e5b4-d611-47bb-b6ab-752fb5c39c1c

📥 Commits

Reviewing files that changed from the base of the PR and between f67905e and 940716f.

📒 Files selected for processing (5)
  • src/scripts/backfill-usage-answers.ts
  • src/scripts/usage-backfill.spec.ts
  • src/scripts/usage-backfill.ts
  • src/usage/usage.service.spec.ts
  • src/usage/usage.service.ts
📝 Walkthrough

Walkthrough

usage_daily에 일일 답변 및 불량 답변 집계를 추가하고, 실시간 반영과 통계 조회를 변경했습니다. 기존 assistant 메시지를 백필하는 CLI와 관련 마이그레이션, 서비스 및 테스트도 추가했습니다.

Changes

답변 집계 및 백필

Layer / File(s) Summary
답변 집계 스키마
drizzle/0008_add_usage_daily_answer_counts.sql, drizzle/meta/*, src/db/schema.ts, src/db/usage-daily.schema.spec.ts
usage_dailytotal_answersbad_answers를 추가하고 기본값, NOT NULL, 범위 및 상한 체크 제약을 정의했습니다.
실시간 답변 및 피드백 집계
src/usage/usage.service.ts, src/chat/services/chat.service.ts, src/usage/usage.service.spec.ts, src/chat/services/chat.service.spec.ts
assistant 메시지 생성과 피드백 rating 전이를 트랜잭션 내에서 usage_daily에 반영하고, 통계 조회가 일일 집계 컬럼을 사용하도록 변경했습니다.
기존 답변 백필
src/scripts/usage-backfill.ts, src/scripts/backfill-usage-answers.ts, src/scripts/usage-backfill.spec.ts, package.json
기존 assistant 메시지를 날짜·도메인·위젯별로 집계해 잠금 후 usage_daily에 절대값으로 upsert하는 CLI와 실행 스크립트를 추가했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

당근처럼 답변 수가 차곡차곡,
나쁜 답은 살짝 표시해요.
토끼가 피드백을 톡톡 세고,
오래된 기록도 다시 모아요.
집계 테이블, 깡총 완성! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 usage_daily에 해상도율 데이터를 사전 집계하도록 바꾸는 핵심 변경을 정확히 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors how “resolution rate” is computed by pre-aggregating assistant answer counts (total_answers) and BAD feedback counts (bad_answers) into usage_daily, avoiding expensive scans of messages/message_feedbacks during dashboard reads and keeping writes/aggregations in the same transaction.

Changes:

  • Add total_answers/bad_answers columns + CHECK constraints to usage_daily, and update dashboard stats to read these aggregates only.
  • Increment total_answers on assistant message insert; apply bad_answers deltas on feedback upsert, both transactionally with the source write.
  • Add an idempotent backfill implementation + CLI command to populate aggregates for existing data.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/usage/usage.service.ts Adds aggregation helpers (increment/apply-delta) and switches stats reads to usage_daily totals.
src/usage/usage.service.spec.ts Adds unit tests for delta computation and new aggregation write behaviors.
src/scripts/usage-backfill.ts Implements pure aggregation + transactional, locked idempotent backfill.
src/scripts/usage-backfill.spec.ts Tests bucket keying, aggregation logic, and backfill transactional/locking behavior.
src/scripts/backfill-usage-answers.ts Adds CLI entrypoint to execute the backfill with env-based DB config.
src/db/usage-daily.schema.spec.ts Verifies migration SQL adds columns/defaults and CHECK constraints.
src/db/schema.ts Extends usageDaily schema with new columns and CHECK constraints.
src/chat/services/chat.service.ts Wraps assistant message insert + total_answers increment in one tx; wraps feedback upsert + bad_answers delta in one tx with row locking.
src/chat/services/chat.service.spec.ts Updates tests to cover new transactional aggregation calls and delta attribution.
package.json Adds db:backfill:answers script for the new CLI.
drizzle/meta/0008_snapshot.json Updates Drizzle snapshot for the new usage_daily columns and constraints.
drizzle/meta/_journal.json Registers migration 0008 in the Drizzle journal.
drizzle/0008_add_usage_daily_answer_counts.sql Adds total_answers/bad_answers columns and integrity constraints.
bun.lock Moves patch-package to dependencies (lockfile update).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/scripts/usage-backfill.ts Outdated
Comment on lines +109 to +112
const lockTimeoutMs = Math.trunc(
options.lockTimeoutMs ?? DEFAULT_BACKFILL_LOCK_TIMEOUT_MS,
);
const log = options.logger ?? ((message: string) => console.log(message));
Comment on lines +51 to +55
async function main(): Promise<void> {
const lockTimeoutMs = Number(
process.env.BACKFILL_LOCK_TIMEOUT_MS ?? DEFAULT_BACKFILL_LOCK_TIMEOUT_MS,
);

Comment on lines +78 to +82
* 피드백 rating 전이에 따른 bad_answers 변화량을 계산한다.
* - 없음/GOOD → BAD: +1
* - BAD → 없음/GOOD: -1
* - 그 외(상태 불변, GOOD↔없음): 0
*/

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/scripts/usage-backfill.ts (1)

129-162: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

대규모 데이터셋에서 메모리 부족(OOM) 위험 — 전체 assistant 메시지를 한 번에 로드.

select 쿼리가 모든 assistant 메시지를 메모리로 로드합니다. 데이터가 수백만 건 이상이면 Node.js 프로세스가 OOM으로 종료될 수 있습니다. 커서 기반 배치 처리 또는 LIMIT/OFFSET 분할 처리를 권장합니다.

♻️ 제안: 배치 처리로 메모리 사용량 제한
+    const BATCH_SIZE = 10_000;
+    let offset = 0;
+    let allBuckets = new Map<string, UsageAnswerBucket>();
+
+    while (true) {
+      const batch = await tx
+        .select({
+          createdAt: messages.createdAt,
+          widgetKeyId: sessions.widgetKeyId,
+          pageUrl: sessions.pageUrl,
+          rating: messageFeedbacks.rating,
+        })
+        .from(messages)
+        .innerJoin(sessions, eq(messages.sessionId, sessions.id))
+        .leftJoin(messageFeedbacks, eq(messageFeedbacks.messageId, messages.id))
+        .where(eq(messages.role, 'assistant'))
+        .limit(BATCH_SIZE)
+        .offset(offset);
+
+      if (batch.length === 0) break;
+
+      const batchBuckets = aggregateUsageAnswerRows(batch as UsageAnswerRow[]);
+      for (const bucket of batchBuckets) {
+        const key = makeUsageBucketKey(bucket.widgetKeyId, bucket.date, bucket.domain);
+        const existing = allBuckets.get(key);
+        if (existing) {
+          existing.totalAnswers += bucket.totalAnswers;
+          existing.badAnswers += bucket.badAnswers;
+        } else {
+          allBuckets.set(key, bucket);
+        }
+      }
+      offset += BATCH_SIZE;
+    }
+
+    const buckets = Array.from(allBuckets.values());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scripts/usage-backfill.ts` around lines 129 - 162,
aggregateUsageAnswerRows에 전달할 assistant 메시지를 전체 조회하지 말고, 커서 기반 배치 또는 제한된 페이지 단위로
반복 조회하도록 변경하세요. 각 배치의 rows만 메모리에 유지하면서 aggregateUsageAnswerRows 결과를 누적·처리하고, 모든
배치가 완료된 뒤 usageDaily upsert를 수행하도록 관련 트랜잭션 흐름을 조정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/usage/usage.service.spec.ts`:
- Around line 314-321: FakeSelect.then의 멀티라인 시그니처가 Prettier 형식과 일치하지 않으므로 해당 메서드
선언을 Prettier로 포맷하고, 변경 후 prettier/prettier 정적 분석 오류가 사라지는지 확인하세요.

In `@src/usage/usage.service.ts`:
- Around line 27-29: UsageDbExecutor의 멀티라인 유니온 타입 정의가 Prettier 형식과 일치하지 않습니다. 해당
타입 선언에 Prettier를 실행해 자동 포맷하고, 변경 후 lint를 다시 실행해 prettier/prettier 오류가 해결되는지
확인하세요.
- Around line 218-222: Update applyBadAnswerDelta to tolerate a missing
usage_daily aggregation row instead of throwing InternalServerErrorException,
allowing the feedback transaction to complete; alternatively ensure
db:backfill:answers runs as a required deployment step before enabling this
path.

---

Nitpick comments:
In `@src/scripts/usage-backfill.ts`:
- Around line 129-162: aggregateUsageAnswerRows에 전달할 assistant 메시지를 전체 조회하지 말고,
커서 기반 배치 또는 제한된 페이지 단위로 반복 조회하도록 변경하세요. 각 배치의 rows만 메모리에 유지하면서
aggregateUsageAnswerRows 결과를 누적·처리하고, 모든 배치가 완료된 뒤 usageDaily upsert를 수행하도록 관련
트랜잭션 흐름을 조정하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 830e7783-e45c-45a8-a220-5434955d8187

📥 Commits

Reviewing files that changed from the base of the PR and between 9d97afa and 3fd394e.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • drizzle/0008_add_usage_daily_answer_counts.sql
  • drizzle/meta/0008_snapshot.json
  • drizzle/meta/_journal.json
  • package.json
  • src/chat/services/chat.service.spec.ts
  • src/chat/services/chat.service.ts
  • src/db/schema.ts
  • src/db/usage-daily.schema.spec.ts
  • src/scripts/backfill-usage-answers.ts
  • src/scripts/usage-backfill.spec.ts
  • src/scripts/usage-backfill.ts
  • src/usage/usage.service.spec.ts
  • src/usage/usage.service.ts

Comment thread src/usage/usage.service.spec.ts
Comment thread src/usage/usage.service.ts
Comment thread src/usage/usage.service.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/usage/usage.service.spec.ts`:
- Around line 218-225: FakeAggSelect.then의 멀티라인 유니온 타입 선언이 Prettier 형식과 일치하지
않습니다. 해당 메서드의 타입 시그니처를 Prettier로 포맷하고, 변경 후 lint를 실행해 prettier/prettier 오류가 없는지
확인하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 06c490f0-7c1c-48ec-a679-db9223539ef2

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd394e and 9b27491.

📒 Files selected for processing (5)
  • src/scripts/backfill-usage-answers.ts
  • src/scripts/usage-backfill.spec.ts
  • src/scripts/usage-backfill.ts
  • src/usage/usage.service.spec.ts
  • src/usage/usage.service.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/scripts/usage-backfill.ts
  • src/scripts/usage-backfill.spec.ts
  • src/scripts/backfill-usage-answers.ts

Comment thread src/usage/usage.service.spec.ts
- validate lockTimeoutMs (finite, positive, >=1ms) before SQL in runBackfill
- validate BACKFILL_LOCK_TIMEOUT_MS env before opening the DB connection
- self-heal applyBadAnswerDelta: guarded arithmetic fast path, and reconcile
  the exact bucket from source-of-truth (with row lock) when the aggregation
  row is missing/not-yet-backfilled instead of throwing 500
- fix computeBadAnswerDelta docstring to match the GOOD|BAD next signature
- add unit tests for the parser and reconcile paths

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/usage/usage.service.spec.ts`:
- Around line 218-225: FakeAggSelect.then의 멀티라인 유니온 타입 선언이 Prettier 규칙을 위반하고
있습니다. 해당 then 메서드의 onfulfilled 타입을 Prettier로 다시 포맷하거나 프로젝트의 Prettier 스타일에 맞게 정리한
뒤, prettier/prettier lint가 통과하는지 확인하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8d8e93b-b21a-4ac3-81e2-262a81371644

📥 Commits

Reviewing files that changed from the base of the PR and between 9b27491 and f67905e.

📒 Files selected for processing (5)
  • src/scripts/backfill-usage-answers.ts
  • src/scripts/usage-backfill.spec.ts
  • src/scripts/usage-backfill.ts
  • src/usage/usage.service.spec.ts
  • src/usage/usage.service.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/scripts/backfill-usage-answers.ts
  • src/scripts/usage-backfill.spec.ts
  • src/scripts/usage-backfill.ts
  • src/usage/usage.service.ts

Comment on lines +218 to +225
then<TResult1 = SourceRow[], TResult2 = never>(
onfulfilled?:
| ((value: SourceRow[]) => TResult1 | PromiseLike<TResult1>)
| null,
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
): PromiseLike<TResult1 | TResult2> {
return Promise.resolve(this.store.sourceRows).then(onfulfilled, onrejected);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Prettier 포맷 오류가 여전히 해결되지 않았습니다.

이전 리뷰에서 지적된 FakeAggSelect.then의 멀티라인 유니온 타입(220-221) prettier/prettier 오류가 아직 미해결 상태입니다. 정적 분석이 동일한 오류를 재보고하고 있습니다. 포매터를 실행하거나 아래 diff를 적용해 lint 통과를 확보하세요.

🧹 Proposed fix
   then<TResult1 = SourceRow[], TResult2 = never>(
     onfulfilled?:
-      | ((value: SourceRow[]) => TResult1 | PromiseLike<TResult1>)
-      | null,
+      ((value: SourceRow[]) => TResult1 | PromiseLike<TResult1>) | null,
     onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
   ): PromiseLike<TResult1 | TResult2> {
🧰 Tools
🪛 ESLint

[error] 220-221: Replace |·((value:·SourceRow[])·=>·TResult1·|·PromiseLike<TResult1>)⏎····· with ((value:·SourceRow[])·=>·TResult1·|·PromiseLike<TResult1>)

(prettier/prettier)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/usage/usage.service.spec.ts` around lines 218 - 225, FakeAggSelect.then의
멀티라인 유니온 타입 선언이 Prettier 규칙을 위반하고 있습니다. 해당 then 메서드의 onfulfilled 타입을 Prettier로
다시 포맷하거나 프로젝트의 Prettier 스타일에 맞게 정리한 뒤, prettier/prettier lint가 통과하는지 확인하세요.

Source: Linters/SAST tools

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