# Rule 1 启动机制分析 > Date: 2026-04-28 20:10 > Version: V4.0 --- ## 启动方式概览 Rule 1 有两种启动机制: | 方式 | 触发源 | 时机 | 文件 | |------|--------|------|------| | **方式 A** | Processor 完成 | 自动触发 | `job_worker.rs` | | **方式 B** | Jobs 表 | Job Worker 轮询 | `job_runner.rs` | --- ## 方式 A: Processor 完成后自动触发 ### 流程图 ``` Processor 执行 (processor.rs) ↓ processor_results 表更新 ↓ check_and_complete_job() (job_worker.rs) ↓ 检查前提条件: has_asr && has_asrx ↓ tokio::spawn(execute_rule1) ↓ Rule 1 Chunking (rule1_ingest.rs) ``` ### 前提条件检查 **位置**: `src/worker/job_worker.rs:248-252` ```rust // 检查完成的处理器 let has_asr = completed_processors.iter().any(|p| p == "asr"); let has_asrx = completed_processors.iter().any(|p| p == "asrx"); let has_cut = completed_processors.iter().any(|p| p == "cut"); let has_face = completed_processors.iter().any(|p| p == "face"); let has_yolo = completed_processors.iter().any(|p| p == "yolo"); ``` ### Rule 触发矩阵 | Rule | 前提条件 | 优先级 | 功能 | |------|----------|--------|------| | **Rule 1** | `has_asr && has_asrx` | P1 | Sentence Chunking | | **Rule 3** | `has_cut && has_asr` | P1 | Scene Chunking | | **Identity Agent** | `has_face && has_asrx` | P3 | Person Identity | | **5W1H Agent** | `has_cut && has_asr` | P4 | Story Summary | ### 触发代码 **位置**: `src/worker/job_worker.rs:260-281` ```rust if has_asr && has_asrx { info!("📝 Prerequisites met for Rule 1 Chunking. Starting ingestion..."); let db_clone = self.db.clone(); let uuid_clone = uuid.to_string(); tokio::spawn(async move { match db_clone.get_video_by_uuid(&uuid_clone).await { Ok(Some(video)) => { let fps = video.fps; match rule1_ingest::execute_rule1(&db_clone, &uuid_clone, fps).await { Ok(count) => info!("✅ Rule 1 Ingestion completed: {} chunks inserted.", count), Err(e) => error!("❌ Rule 1 Ingestion failed: {}", e), } } Ok(None) => error!("Video not found for chunking: {}", uuid_clone), Err(e) => error!("Failed to get video info for chunking: {}", e), } }); } ``` --- ## 方式 B: Job Worker 轮询 ### 流程图 ``` Job Worker 启动 (job_runner.rs) ↓ 轮询 jobs 表 (QUEUED 状态) ↓ 原子更新 status = 'RUNNING' ↓ 根据 rule 字段执行 ↓ rule = "rule1" → execute_rule1() ``` ### Job 表结构 ```sql CREATE TABLE dev.jobs ( id UUID PRIMARY KEY, asset_uuid VARCHAR(32) NOT NULL, processor_list TEXT[], assigned_processor_id UUID, rule VARCHAR(20), -- Rule 标识 status VARCHAR(20) DEFAULT 'QUEUED', total_frames BIGINT DEFAULT 0, processed_frames BIGINT DEFAULT 0, error_message TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); ``` ### Job 获取逻辑 **位置**: `src/core/worker/job_runner.rs:47-62` ```rust let job_row: Option<(String, String, String, String, String, i64)> = sqlx::query_as( r#" UPDATE dev.jobs SET status = 'RUNNING', updated_at = NOW() WHERE id = ( SELECT id FROM dev.jobs WHERE status = 'QUEUED' ORDER BY created_at ASC LIMIT 1 FOR UPDATE SKIP LOCKED -- 防止并发竞争 ) RETURNING id::text, asset_uuid, rule, status, processor_list, total_frames "#, ) .fetch_optional(&self.pool) .await?; ``` ### Rule 执行逻辑 **位置**: `src/core/worker/job_runner.rs:76-86` ```rust let result = match rule.as_str() { "rule1" => { let fps = self.get_asset_fps(&asset_uuid).await?; let db = PostgresDb::from_pool(self.pool.clone()); chunk::rule1_ingest::execute_rule1(&db, &asset_uuid, fps).await } _ => { tracing::warn!("Unknown rule type: {}", rule); Ok(0) } }; ``` --- ## 执行时机对比 | 场景 | 方式 A | 方式 B | |------|--------|--------| | **实时处理** | Processor 完成后立即触发 | 依赖 Job Worker 轮询间隔 | | **并发处理** | 多个视频可并行 | 串行处理(单 worker) | | **重试机制** | Processor 失败则不触发 | Job 可重新 QUEUED | | **适用场景** | 自动化处理 | 手动触发/定时任务 | --- ## 当前状态分析 ### Jobs 表 ```sql SELECT id, asset_uuid, rule, status FROM dev.jobs WHERE rule IS NOT NULL; -- Result: id: 751d90b5... | asset_uuid: 384b0ff44aaaa1f14cb2cd63b3fea966 | rule: rule1 | status: QUEUED id: 9e5df703... | asset_uuid: 384b0ff44aaaa1f14cb2cd63b3fea966 | rule: rule1 | status: QUEUED ``` **问题**: 2 个 Rule 1 Job 处于 QUEUED 状态,未被 Job Runner 执行 ### Processor Results 表 ```sql SELECT job_id, processor_type, status FROM dev.processor_results WHERE job_id IS NOT NULL; -- Result: job_id: 21 | processor_type: NULL | status: failed job_id: 20 | processor_type: NULL | status: completed ``` **问题**: processor_type 为 NULL,无法判断哪些处理器完成 --- ## 问题诊断 ### 问题 1: Job Worker 未启动 **检查**: ```bash ps aux | grep momentry | grep worker ``` **可能原因**: - Job Worker 进程未运行 - 仅运行 processor worker,未运行 job worker ### 问题 2: Processor Results 缺少类型信息 **影响**: - `completed_processors` 无法正确构建 - Rule 1 前提条件判断失败 **解决方案**: 修复 processor 执行时写入 processor_type: ```rust // src/worker/processor.rs:300 // 确保写入 processor_type 到 processor_results ``` ### 问题 3: 重复 Job **现象**: 同一 asset_uuid 有 2 个 QUEUED job **原因**: Job 创建逻辑未检查现有 Job --- ## 启动流程完整图 ```mermaid graph TD A[Video Registered] --> B[Job Created] B --> C{Job Type?} C -->|Processor Job| D[Processor Worker] C -->|Rule Job| E[Job Runner] D --> F[Execute Processor] F --> G[Update processor_results] G --> H[check_and_complete_job] H --> I{Check Prerequisites} I -->|has_asr && has_asrx| J[Trigger Rule 1] I -->|has_cut && has_asr| K[Trigger Rule 3] E --> L[Poll QUEUED Jobs] L --> M{rule == 'rule1'?} M -->|Yes| N[execute_rule1] J --> O[Rule 1 Ingestion] N --> O O --> P[Create Chunks] P --> Q[Store in chunks table] ``` --- ## 启动参数 | 参数 | 来源 | 说明 | |------|------|------| | **file_uuid** | asset_uuid | Video UUID | | **fps** | videos.fps | 从 video record 获取 | | **db** | PostgresDb | Database connection | --- ## 配置检查 ### Job Worker 配置 ```bash # 检查 Job Worker 是否运行 ps aux | grep "momentry worker" # 检查 Processor Worker ps aux | grep "momentry" | grep "worker" | grep "max-concurrent" ``` ### 当前运行的 Worker ```bash # 从之前的检查 accusys 309 ... target/release/momentry worker --max-concurrent 2 ``` **分析**: - Processor Worker 正在运行(max-concurrent 2) - 但这是 Processor Worker,不是 Job Worker - Job Runner (job_runner.rs) 是独立的 worker --- ## 解决方案 ### 方案 1: 启动 Job Runner Worker ```bash # 启动 Job Runner cargo run --release -- worker --type job_runner --poll-interval 10 ``` ### 方案 2: 使用方式 A(推荐) 确保 Processor Worker 正确触发 Rule 1: 1. **修复 processor_type 写入** - processor.rs 执行完成后,正确写入 processor_type - 确保 processor_results 包含类型信息 2. **检查前提条件逻辑** - 确保 ASR + ASRX 都成功完成 - 修复 ASRX chunks_produced = 0 问题 --- ## 相关文件 | 文件 | 功能 | |------|------| | `src/worker/job_worker.rs` | Processor 完成后触发 Rule | | `src/core/worker/job_runner.rs` | Job Worker 轮询执行 | | `src/core/chunk/rule1_ingest.rs` | Rule 1 执行逻辑 | | `src/worker/processor.rs` | Processor 执行 | | `migrations/003_job_worker.sql` | Job/processor_results 表 | --- ## 下一步 1. **检查 Job Runner 是否运行** 2. **修复 processor_type 写入** 3. **清理重复 QUEUED jobs** 4. **重新运行 Rule 1**