Skip to content

perf(ai): 大規模tagging/CCIP batch投入を非同期dispatch化 - #572

Merged
hmjn023 merged 2 commits into
developfrom
perf/async-batch-dispatch
Jul 4, 2026
Merged

perf(ai): 大規模tagging/CCIP batch投入を非同期dispatch化#572
hmjn023 merged 2 commits into
developfrom
perf/async-batch-dispatch

Conversation

@hmjn023

@hmjn023 hmjn023 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

概要

Issue #569 に対応し、大規模tagging/CCIP batch開始時の30秒timeoutやPostgreSQL parameter上限問題を解消します。

変更内容

  • scan APIを対象件数(count)のみ返すように変更
  • start APIをfilter/source/forceのみ受け取り、親job + dispatcher jobを即時作成
  • dispatcherがkeyset paginationで500件ずつ子jobをbulk INSERT
  • 親job payloadからprocessedJobIdsを廃止し、processed/failed countのみ管理
  • 子job resultにparentProcessedフラグを書き、再実行時の二重カウントを防止
  • CCIP用 batch_ccip_dispatch job typeを追加
  • 進捗・失敗を構造化ログとrealtime eventへ出力
  • UIからscan結果の選択グリッドを廃止

技術詳細

  • DBスキーマ変更は行っていません
  • 単体テストを追加・更新済み
  • OpenAPI仕様書を再生成済み

Closes #569

Summary by CodeRabbit

  • 新機能
    • タグ付け・CCIP抽出のAIバッチ開始を「媒体ソース単位」で開始できるようになりました。
  • 改善
    • 対象スキャンは一覧ではなく件数で確認でき、進捗・完了/失敗の反映がより正確になりました。
    • バッチ処理の開始方式を整理し、重複抑止と集計を安定化しました。
    • AI連携APIのバッチ開始エンドポイント/入力仕様を更新しました。
  • UI変更
    • 管理画面からスキャン結果の表示やページング、選択操作の一部を見直しました。

- scan APIを対象件数(count)のみ返すように変更
- start APIをfilter/source/forceのみ受け取り、親job + dispatcher jobを即時作成
- dispatcherがkeyset paginationで500件ずつ子jobをbulk INSERT
- 親job payloadからprocessedJobIdsを廃止し、processed/failed countのみ管理
- 子job resultにparentProcessedフラグを書き、再実行時の二重カウントを防止
- CCIP用batch_ccip_dispatch job typeを追加
- 進捗・失敗を構造化ログとrealtime eventへ出力
- UIからscan結果の選択グリッドを廃止

Closes #569
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

tagging と CCIP のバッチ開始処理を、全件ID送信と即時大量ジョブ作成から、親ジョブとdispatchジョブによる非同期投入へ変更した。スキャン結果は件数返却に変わり、進捗集計は BatchProgress ベースに統一された。UI とクライアント配線も新しい契約に追従している。

Changes

バッチ投入非同期dispatch化

Layer / File(s) Summary
バッチ契約とスキーマ変更
packages/core/src/domain/tagging/schemas.ts, packages/core/src/domain/contract/ai.contract.ts, packages/core/src/domain/repositories/job-repository.ts, apps/server/public/openapi.json
batchTargetCountResponseSchemabatchParentPayloadSchema を追加し、AI契約とOpenAPIのバッチ系入出力を更新した。IJobRepository には BatchProgressincrementFailedCount が追加された。
ジョブ集計リポジトリ実装
packages/db/src/repositories/job-repository.ts
incrementProgressincrementFailedCount が `BatchProgress
CCIPバッチdispatchと完了確定
apps/server/src/application/services/job-dispatch-service.ts, apps/server/src/infrastructure/jobs/ccip-jobs.ts
batch_ccip_dispatch の分岐が追加され、dispatchジョブの生成、重複除外、ページング、子ジョブ投入、親最終化の流れが実装された。
taggingバッチdispatchと親進捗処理
apps/server/src/infrastructure/jobs/tagging-jobs.ts, apps/server/src/infrastructure/jobs/job-worker.ts
自動タグ付けの親進捗集約とバルクdispatchが更新され、JobWorker はプロセッサ戻り値を完了結果として渡すようになった。
AIルータのバッチハンドラ更新
apps/server/src/infrastructure/api/routers/ai-router.ts
スキャン系が件数集計に変わり、start系が親ジョブとdispatchジョブを起票する構成へ変更された。
AIクライアントとmanagerルート配線
apps/server/src/infrastructure/api-clients/ai-api.ts, apps/tauri/src/infrastructure/api-clients/ai-api.ts, apps/server/src/routes/manager.tsx, apps/tauri/src/routes/manager.tsx
startBatchTaggingWithIds が廃止され、startBatchTagging と CCIP 系の新しい引数形へ追従した。manager ルートの wiring も更新された。
manager画面とhookの件数ベース化
packages/ui/src/hooks/use-manager-page.ts, packages/ui/src/screens/manager-screen.tsx, packages/ui/src/media-sidebar.tsx
選択メディアIDとページング状態が削除され、スキャン結果は件数表示に変わり、一覧UIも削除された。
ジョブとUI周辺のテスト更新
apps/server/src/tests/unit/**
進捗集計、親完了/失敗確定、processor戻り値伝播、parentId必須化を検証するテストが更新された。

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

Sequence Diagram(s)

sequenceDiagram
  participant AiRouter
  participant JobDispatchService
  participant CcipJobs
  participant CcipVectorService
  participant JobRepository

  AiRouter->>JobRepository: batch_ccip_parent / batch_ccip_dispatch を作成
  JobDispatchService->>CcipJobs: processBatchCcipDispatchJob(job)
  CcipJobs->>JobRepository: 既存子ジョブを除外して medias をページング取得
  CcipJobs->>CcipVectorService: listRecords で既存ベクトルを照合
  CcipJobs->>JobRepository: extract_ccip_vector 子ジョブをchunk挿入
  CcipJobs->>JobRepository: 親payload.total更新と job-progress 発行
  JobDispatchService->>CcipJobs: processCcipExtractionJob(childJob)
  CcipJobs->>JobRepository: incrementProgress / incrementFailedCount
  CcipJobs->>JobRepository: finalizeBatchParent で completed / failed を確定
Loading

Possibly related PRs

  • hmjn023/solid-imager#556: CCIP抽出ジョブ処理と extract_ccip_vector 系の流れを直接拡張しており、同じジョブ設計に関連する。
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 主要変更である大規模tagging/CCIP batchの非同期dispatch化を簡潔に表しており、内容と整合しています。
Linked Issues check ✅ Passed scanの件数返却、dispatch job追加、chunk投入、idempotent化、親payloadの定数化、UI/テスト更新まで要件を満たしています。
Out of Scope Changes check ✅ Passed バッチ非同期化に伴うAPI/契約/UI/テスト/ジョブ処理の変更に収まっており、明確な無関係変更は見当たりません。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/async-batch-dispatch

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

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/hooks/use-manager-page.ts (1)

421-438: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

result.success が偽のときのフィードバックが欠落しています。

handleStartBatchTagging(440-461)には失敗時の else 分岐がありますが、handleStartBatchCcipExtraction にはありません。result.success または result.jobId が偽の場合、ステータスが「Starting...」のまま残り、ユーザーにエラーが伝わりません。整合性を合わせてください。

🛠 修正案
 			if (result.success && result.jobId) {
 				toast.success(result.message);
 				setTaggingStatus("Batch CCIP extraction in progress...");
 				setActiveJobId(result.jobId);
+			} else {
+				toast.error("Failed to start batch CCIP extraction.");
+				setTaggingStatus("Failed to start batch CCIP extraction.");
 			}
🤖 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 `@packages/ui/src/hooks/use-manager-page.ts` around lines 421 - 438,
`handleStartBatchCcipExtraction` is missing the failure path that
`handleStartBatchTagging` already has, so when
`actions.startBatchCcipExtraction` returns a non-success result or no `jobId`,
the UI stays on “Starting...” with no user feedback. Update
`handleStartBatchCcipExtraction` to mirror the `handleStartBatchTagging` pattern
by adding an explicit `else` branch after the `result.success && result.jobId`
check, and set both a toast error and an error `setTaggingStatus` message using
the returned result/message so the user sees the failure state.
🧹 Nitpick comments (3)
apps/tauri/src/routes/manager.tsx (1)

61-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

新設したラッパー関数を再利用してインライン定義の重複を解消することを推奨します。

apps/tauri/src/infrastructure/api-clients/ai-api.tsscanBatchCcipTargets / startBatchCcipExtraction のラッパーが追加されていますが、ここでは client.ai.* を直接呼ぶインライン定義になっており重複しています。加えてインライン側は force: boolean(必須)で、ラッパー側は force?: boolean(任意)と入力型が食い違っています。server 側 manager.tsx と同様にラッパーを import して利用する形に揃えると、契約の一貫性と保守性が向上します。

♻️ 提案: ラッパーを import して利用
 import {
 	scanBatchCcipTargets,
 	scanBatchTaggingTargets,
+	startBatchCcipExtraction,
 	startBatchTagging,
 } from "~/infrastructure/api-clients/ai-api";
 	scanBatchTaggingTargets,
 	startBatchTagging,
-	scanBatchCcipTargets: (input: { force: boolean; mediaSourceId?: string }) =>
-		client.ai.scanBatchCcipTargets(input),
-	startBatchCcipExtraction: (input: {
-		force: boolean;
-		mediaSourceId?: string;
-	}) => client.ai.startBatchCcipExtraction(input),
+	scanBatchCcipTargets,
+	startBatchCcipExtraction,
 	findDuplicateMedia,
🤖 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 `@apps/tauri/src/routes/manager.tsx` around lines 61 - 66, Reuse the newly
added AI API wrapper functions instead of keeping inline client calls in
manager.tsx: replace the direct client.ai.scanBatchCcipTargets and
client.ai.startBatchCcipExtraction definitions with imports from ai-api.ts.
Align the input contract to the wrapper signatures, especially the force field
being optional in the wrapper, so the manager route and the shared API client
stay consistent. Use the scanBatchCcipTargets and startBatchCcipExtraction
symbols from ai-api.ts as the single source of truth and remove the duplicated
inline definitions.
packages/ui/src/screens/manager-screen.tsx (1)

250-253: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

renderMediaCard を削除
ManagerScreenProps でしか使われていないため、renderMediaCard は不要です。ManagerScreenProps から外し、併せて MediaSafe の import も整理してください。

🤖 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 `@packages/ui/src/screens/manager-screen.tsx` around lines 250 - 253,
`renderMediaCard` is only used for `ManagerScreenProps`, so remove that prop
from the `ManagerScreen` component contract and clean up the related `MediaSafe`
import. Update the `ManagerScreen` type/implementation so it no longer expects
or references `renderMediaCard`, and ensure any now-unused imports or symbols
are removed from the same module.
apps/server/src/infrastructure/jobs/ccip-jobs.ts (1)

118-134: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

ccipVectorService.listRecords がループの各反復で全件取得されています。

force が false の場合、while ループの反復ごとに listRecords(mediaSourceId) を呼び、全レコードを Map に構築しています。対象が大きいほど反復回数が増え、同じ全件取得を繰り返すため負荷が高くなります。ループ外で一度取得して使い回すか、対象 mediaId に絞って取得する方法をご検討ください。

🤖 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 `@apps/server/src/infrastructure/jobs/ccip-jobs.ts` around lines 118 - 134, The
`ccipVectorService.listRecords(mediaSourceId)` call inside the `while`-loop in
`ccip-jobs.ts` is re-fetching all records on every iteration when `force` is
false. Move the record lookup out of the loop in the job flow around
`targetRows`/`rows.filter`, cache the `Map` once per media source, and reuse it
across iterations; if possible, further narrow the fetch to only the `mediaId`s
being processed.
🤖 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 `@apps/server/src/infrastructure/jobs/ccip-jobs.ts`:
- Around line 74-83: The existing `existingChild` query in `ccip-jobs.ts`
compares `jobs.payload->>'mediaId'` as text against `medias.id` as uuid, which
can fail in PostgreSQL. Update the SQL expression in the
`db.select(...).where(and(...))` block to explicitly cast the extracted
`mediaId` to uuid before comparing it to `medias.id`, and keep the change
localized to the `existingChild` condition.
- Around line 163-179: The parent job finalize logic in ccip-jobs should not
depend on dispatch timing, because parent total is still 0 while child jobs are
created as pending and can cause premature completion/failed transitions. Update
the flow around jobRepo.update, RealtimeEventBus.publishJob, and the batch
dispatch completion path to separate “dispatch finished” from “parent can
finalize,” and use the parent job state (parentJob / parentId) to explicitly
mark the parent completed when dispatchedCount is 0 so it does not remain
in_progress.

In `@apps/server/src/infrastructure/jobs/tagging-jobs.ts`:
- Around line 249-263: Update the parent job bookkeeping in tagging-jobs around
bulk_tagging_dispatch so the parent payload total is set before any auto_tagging
progress can evaluate completion. In the flow using jobRepo.update,
RealtimeEventBus.publishJob, and the finalize logic around
bulk_tagging_dispatch/auto_tagging, ensure dispatch updates the parent first and
suppress finalize while dispatching is still in progress. Also handle
dispatchedCount === 0 by explicitly marking the parent job as completed instead
of leaving it in_progress.

In `@packages/db/src/repositories/job-repository.ts`:
- Around line 610-611: The progressKey binding in the job repository update
query should normalize missing values to null instead of letting undefined pass
through. Update the SQL in the same place that uses WHERE id = ${id} and AND
(${progressKey} IS NULL OR EXISTS (...)) so the progressKey interpolation
matches the child CTE handling by using the null-coalescing form. Keep the fix
localized around the incrementProgress path in job-repository.ts so the IS NULL
branch behaves consistently.

---

Outside diff comments:
In `@packages/ui/src/hooks/use-manager-page.ts`:
- Around line 421-438: `handleStartBatchCcipExtraction` is missing the failure
path that `handleStartBatchTagging` already has, so when
`actions.startBatchCcipExtraction` returns a non-success result or no `jobId`,
the UI stays on “Starting...” with no user feedback. Update
`handleStartBatchCcipExtraction` to mirror the `handleStartBatchTagging` pattern
by adding an explicit `else` branch after the `result.success && result.jobId`
check, and set both a toast error and an error `setTaggingStatus` message using
the returned result/message so the user sees the failure state.

---

Nitpick comments:
In `@apps/server/src/infrastructure/jobs/ccip-jobs.ts`:
- Around line 118-134: The `ccipVectorService.listRecords(mediaSourceId)` call
inside the `while`-loop in `ccip-jobs.ts` is re-fetching all records on every
iteration when `force` is false. Move the record lookup out of the loop in the
job flow around `targetRows`/`rows.filter`, cache the `Map` once per media
source, and reuse it across iterations; if possible, further narrow the fetch to
only the `mediaId`s being processed.

In `@apps/tauri/src/routes/manager.tsx`:
- Around line 61-66: Reuse the newly added AI API wrapper functions instead of
keeping inline client calls in manager.tsx: replace the direct
client.ai.scanBatchCcipTargets and client.ai.startBatchCcipExtraction
definitions with imports from ai-api.ts. Align the input contract to the wrapper
signatures, especially the force field being optional in the wrapper, so the
manager route and the shared API client stay consistent. Use the
scanBatchCcipTargets and startBatchCcipExtraction symbols from ai-api.ts as the
single source of truth and remove the duplicated inline definitions.

In `@packages/ui/src/screens/manager-screen.tsx`:
- Around line 250-253: `renderMediaCard` is only used for `ManagerScreenProps`,
so remove that prop from the `ManagerScreen` component contract and clean up the
related `MediaSafe` import. Update the `ManagerScreen` type/implementation so it
no longer expects or references `renderMediaCard`, and ensure any now-unused
imports or symbols are removed from the same module.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f149915-25da-442b-b50a-20a48817a377

📥 Commits

Reviewing files that changed from the base of the PR and between 05c6a1d and e664011.

📒 Files selected for processing (21)
  • apps/server/public/openapi.json
  • apps/server/src/application/services/job-dispatch-service.ts
  • apps/server/src/infrastructure/api-clients/ai-api.ts
  • apps/server/src/infrastructure/api/routers/ai-router.ts
  • apps/server/src/infrastructure/jobs/ccip-jobs.ts
  • apps/server/src/infrastructure/jobs/job-worker.ts
  • apps/server/src/infrastructure/jobs/tagging-jobs.ts
  • apps/server/src/routes/manager.tsx
  • apps/server/src/tests/unit/application/services/media-service.test.ts
  • apps/server/src/tests/unit/infrastructure/jobs/ccip-jobs.test.ts
  • apps/server/src/tests/unit/infrastructure/jobs/job-worker.test.ts
  • apps/server/src/tests/unit/infrastructure/jobs/tagging-jobs.test.ts
  • apps/tauri/src/infrastructure/api-clients/ai-api.ts
  • apps/tauri/src/routes/manager.tsx
  • packages/core/src/domain/contract/ai.contract.ts
  • packages/core/src/domain/repositories/job-repository.ts
  • packages/core/src/domain/tagging/schemas.ts
  • packages/db/src/repositories/job-repository.ts
  • packages/ui/src/hooks/use-manager-page.ts
  • packages/ui/src/media-sidebar.tsx
  • packages/ui/src/screens/manager-screen.tsx
💤 Files with no reviewable changes (1)
  • packages/ui/src/media-sidebar.tsx

Comment thread apps/server/src/infrastructure/jobs/ccip-jobs.ts
Comment thread apps/server/src/infrastructure/jobs/ccip-jobs.ts
Comment thread apps/server/src/infrastructure/jobs/tagging-jobs.ts
Comment thread packages/db/src/repositories/job-repository.ts Outdated
…uidにキャスト\n- 子job handlerでtotal === 0時はfinalizeをスキップ\n- dispatchedCount === 0時に親jobを即completedに\n- ccip listRecordsをループ外で1回取得\n- tauri manager.tsxでai-apiラッパーを再利用\n- ManagerScreenから未使用のrenderMediaCard propを削除\n- handleStartBatchCcipExtractionに失敗パス追加

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/infrastructure/jobs/tagging-jobs.ts (1)

249-268: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

total 更新後に finalize を再判定してください

  • total をここで確定したあとに processed + failed >= total を見直して finalizeBatchParent を呼ばないと、先に完了した子ジョブがいても親ジョブが in_progress のまま残ります。
  • job-progressprocessed: 0 も、実際の parentPayload.processed とズレます。
🤖 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 `@apps/server/src/infrastructure/jobs/tagging-jobs.ts` around lines 249 - 268,
After setting the batch parent payload’s total via jobRepo.update in
tagging-jobs, re-evaluate whether the parent is now complete and call
finalizeBatchParent when parentPayload.processed + parentPayload.failed is
greater than or equal to the newly updated total. Also update the job-progress
publish in the non-empty branch to use the current parent progress instead of
hardcoding processed: 0, so the state stays aligned with parentPayload and the
parent job does not remain stuck in_progress.
🤖 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.

Outside diff comments:
In `@apps/server/src/infrastructure/jobs/tagging-jobs.ts`:
- Around line 249-268: After setting the batch parent payload’s total via
jobRepo.update in tagging-jobs, re-evaluate whether the parent is now complete
and call finalizeBatchParent when parentPayload.processed + parentPayload.failed
is greater than or equal to the newly updated total. Also update the
job-progress publish in the non-empty branch to use the current parent progress
instead of hardcoding processed: 0, so the state stays aligned with
parentPayload and the parent job does not remain stuck in_progress.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: baea4aa9-6d04-47b1-ad68-2cead5827794

📥 Commits

Reviewing files that changed from the base of the PR and between e664011 and f350d8a.

📒 Files selected for processing (7)
  • apps/server/src/infrastructure/jobs/ccip-jobs.ts
  • apps/server/src/infrastructure/jobs/tagging-jobs.ts
  • apps/server/src/routes/manager.tsx
  • apps/tauri/src/routes/manager.tsx
  • packages/db/src/repositories/job-repository.ts
  • packages/ui/src/hooks/use-manager-page.ts
  • packages/ui/src/screens/manager-screen.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/db/src/repositories/job-repository.ts
  • packages/ui/src/hooks/use-manager-page.ts
  • apps/server/src/infrastructure/jobs/ccip-jobs.ts

@hmjn023
hmjn023 merged commit bdc4a07 into develop Jul 4, 2026
1 check passed
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.

1 participant