手冊首頁 / 系統管理 / 背景服務與排程

背景服務與排程(Background Services & Scheduling)

功能群:系統管理本檔為非頁面 spec(無 UI route),盤點全系統背景 job / 排程 / socket / 心跳的觸發、頻率、寫入表、故障行為,供後端工程師查「這件事背景是誰在做、多久一次、壞了會怎樣」。

13 節模板適用的節照寫、不適用的(UI / 前端 / 使用者權限)標「N/A(無 UI)」。

事實基準:2026-07-05 從 core/scheduler.py / core/app_factory.py / main_socketio.py / app/cloud_integration/ / app/evidence_classification/ / app/remote_agent/ + db_schema.json 掃出。

1. 功能描述

Guidant AI 的背景工作分三類承載:APScheduler 定時排程core/scheduler.py,由 core/app_factory.py:171 於 app 啟動時 init_scheduler)、SocketIO 即時事件main_socketio.py,eventlet)、以及按需背景執行緒 / 外部撥入(AI 分類容器、遠端 Agent 心跳)。這些服務多以系統 session(無 user context → app.is_super_admin='t',繞過 RLS)執行,故無使用者權限概念(見 §3)。

啟動差異python main_socketio.py(含 SocketIO via eventlet,即時協作 / 通知靠這支);python main_app.py(無 SocketIO)。兩支都會 init_scheduler(排程 job 與 SocketIO 無關)。

1.1 背景服務總覽

# 服務 類型 觸發 / 頻率 寫入 出處
1 Drive 同步 worker APScheduler interval 每 5 秒 poll,batch 10 drive_sync_jobs + Drive/DB(各 handler) core/scheduler.py:61drive_sync_worker.py
2 Webhook 頻道續期 APScheduler interval 每 6 小時(距到期 24h 內續期,Drive TTL 7d) tenant_drive_integrations(webhook channel) core/scheduler.py:95
3 框架解析 job TTL 清理 APScheduler cron 每日 01:00 UTC 軟刪 framework_parse_jobs(>7 天) core/scheduler.py:128
4 通知即時推播 SocketIO namespace client 連線 / join / reload 事件 無(純推播 room) /socket/notification
5 問卷協作編輯 SocketIO namespace client connect/join/editing/update/edit/endedit 答案更新走 service(見問卷群) /socket/fill-survey
6 AI 證據分類容器 threading.Thread → Docker manager 手動觸發(非排程) evidence_classification_runs + Drive evidence_classification_service.py:229
7 遠端檔案 Agent 心跳 外部撥入(mTLS) agent 主動 heartbeat(伺服器被動) remote_agents.last_seen_at agent_enrollment_service.py
bg_services_map cluster_sched APScheduler(core/scheduler.py,app_factory 啟動) cluster_socket SocketIO(main_socketio.py,eventlet) cluster_other 其他背景執行 j1 drive_sync_worker interval 5s dsj drive_sync_jobs (webhook 驅動佇列) j1->dsj 處理佇列 j2 webhook_channel_renewer interval 6h chan Drive webhook channels (tenant_drive_integrations) j2->chan 續期 j3 framework_parse_job_cleanup cron 每日 01:00 UTC fpj framework_parse_jobs j3->fpj 軟刪 >7d s1 /socket/notification 流程討論 reload 推播 s2 /socket/fill-survey 問卷協作編輯(lock/presence) c1 AI 分類容器 threading.Thread → Docker cmmc-classifier (manager 觸發,非排程) ecr evidence_classification_runs + Drive c1->ecr 寫結果 c2 遠端檔案 Agent 心跳 (agent 主動 mTLS 撥入, 伺服器被動更新 last_seen_at) ra remote_agents.last_seen_at c2->ra 更新
背景服務全景:APScheduler 三 job + SocketIO 兩 namespace + AI 分類容器執行緒 + Agent 心跳被動接收,各自寫入的表

無專屬 session 清理排程:JWT 無狀態,未見伺服器端 session-cleanup 排程 job(handoff 列的「session 維護」實際不存在為排程)。過期 user_roles 亦無自動 archive job(設計文件 §9.6 建議未實作)。

2. Use Case(觸發情境)

背景服務無使用者互動 UC,改列「事件 → 背景反應」:

情境 觸發 背景反應
使用者在 Drive 改檔 Google Drive webhook → POST /webhooks/google-drive/<tenant_id> 入列 job 下一個 5s tick 由 worker 處理(見 §4 狀態機)
Manager 觸發證據分類 分類 API → 起背景 thread 容器逐檔分類 → 寫 run(見 證據管理群 §2)
Agent 上線 Agent mTLS 撥入 heartbeat 更新 last_seen_atis_online 由門檻推算
多人同時填問卷 client emit editing / update 同 room 廣播 lock / updated(見 問卷填寫頁

3. 權限矩陣

N/A(無 UI、無使用者權限)。背景 job 以系統 session 執行:無 UserContextDTOsession_scope()app.is_super_admin='t',RLS 不擋 worker 跨租戶處理(core/scheduler.py 註解)。SocketIO / Agent 心跳的認證見 §11(socket 連線靠前端帶 token;Agent 靠 mTLS + enrollment token,不帶 user JWT)。

4. 狀態機與前置條件

drive_sync_jobs 佇列狀態機domain/cloud_integration/enums/sync_job_status.py):

drive_sync_lifecycle webhook Drive webhook / 系統操作 入列 drive_sync_jobs pending PENDING (next_run_at <= now) webhook->pending claim claim_batch FOR UPDATE SKIP LOCKED (batch_size=10) pending->claim prog IN_PROGRESS (claim 於獨立 scope 先 commit) claim->prog disp 有對應 handler? prog->disp handle handler.handle(job) (每 job 獨立 session_scope) disp->handle skip SKIPPED (無 handler) disp->skip ok SUCCESS finished_at handle->ok 回傳成功 retryq 還可重試? (retry_at 有值) handle->retryq 拋例外 backoff 回 PENDING next_run_at=retry_at retry_count+1(指數退避) retryq->backoff failed FAILED last_error / finished_at retryq->failed 否(達上限/永久錯) backoff->pending 下一 tick
Drive 同步 job 生命週期:入列 PENDING→claim(SKIP LOCKED) IN_PROGRESS→handler→SUCCESS / 退避重試回 PENDING / FAILED / 無 handler SKIPPED
狀態 意義 轉移
PENDING 待處理(next_run_at <= now 才會被 claim) → IN_PROGRESS
IN_PROGRESS 已被 claim(claim 在獨立 scope 先 commit,handler 崩潰不回滾 claim) → SUCCESS / FAILED / PENDING
SUCCESS handler 回傳成功,寫 finished_at 終點
FAILED 無可重試(達 max_retries 或永久錯),寫 last_error / finished_at 終點
SKIPPED 無對應 handler 的 job_type 終點

前置 / 併發保證claim_batchSELECT FOR UPDATE SKIP LOCKED,多 worker 可並行不搶同 job;max_instances=1 + coalesce=True 避免 tick 重疊;handler 拋例外 → fail_or_retryretry_at 有值則回 PENDING + next_run_at=retry_at + retry_count+1(指數退避),否則 FAILED

5. UI 設計

N/A(無 UI)。架構拓撲:APScheduler 3 job(Drive 同步 / webhook 續期 / 框架解析 job 清理)+ SocketIO 2 namespace(通知 / 問卷協作)+ AI 分類容器執行緒 + Agent 心跳被動接收,各自觸發/入列邊界見下圖。

背景服務全景:APScheduler 3 job + SocketIO 2 namespace + 分類容器執行緒 + Agent 心跳(無 UI,系統 session 執行) core/scheduler.py ・ main_socketio.py ・ evidence_classification_service.py ・ agent_enrollment_service.py APScheduler(BackgroundScheduler, UTC, max_workers=4) Drive 同步 worker — 每 5 秒 poll,batch 10 Webhook 頻道續期 — 每 6 小時(距到期 24h 內續) 框架解析 job TTL 清理 — 每日 01:00 UTC(軟刪 >7d) SocketIO(eventlet,main_socketio.py) /socket/notification — connect/join/reload 純推播 room,無寫入表 /socket/fill-survey — connect/join/editing/update/endedit 問卷協作:presence、lock/unlock、broadcast 開啟:背景服務 §6 API 規格(Drive webhook 入列 endpoint) drive_sync_jobs 佇列(狀態機):PENDING → claim(SKIP LOCKED) IN_PROGRESS → handler → SUCCESS/退避回 PENDING/FAILED/SKIPPED POST /api/1.0/webhooks/google-drive/<tenant_id> (Google 推播 → 入列 PROCESS_DRIVE_CHANGES) POST /api/1.0/integrations/google-drive/tenant/rebuild-all (手動觸發跨租戶重建,入列 job) → #6-api-規格(入列 / 觸發邊界 endpoint,點我開啟) 開啟:Use Case(事件→背景反應)§2 AI 證據分類容器(threading.Thread → Docker) manager 手動觸發(非排程),同 project 一次一個 job 寫入 evidence_classification_runs(容器 exit≠0 → failed) → #2-use-case觸發情境(點我開啟) 開啟:Use Case(事件→背景反應)§2 遠端檔案 Agent 心跳(外部撥入,mTLS) Agent 主動 heartbeat,伺服器被動接收(無主動 poll) 更新 remote_agents.last_seen_at;is_online 即時推算 → #2-use-case觸發情境(點我開啟) 唯一可見面(本服務本身無畫面):雲端整合頁(Drive 狀態)/檔案 Agent 管理頁(上線狀態)/證據分類覆核頁(run 進度) 背景 job 皆以系統 session 執行(無 user context → is_super_admin='t',繞過 RLS) 彩色=可點跳轉 §6 API/§2 UC 的觸發或入列邊界|灰=純結構描述(排程器/namespace/消費頁)|虛線=非同步佇列或被動接收層 API 僅列代表性 2~3 條,完整規格見 §6;多數排程 job 無 HTTP endpoint,由 APScheduler 直接呼叫函式
背景服務架構拓撲:APScheduler 3 job+SocketIO 2 namespace(灰)、Drive 佇列狀態機/AI 分類容器/Agent 心跳(彩色,連 §2 UC/§6 API)

背景服務狀態的唯一可見面是:Drive 同步狀態顯示在 雲端整合頁、Agent 上線狀態在 檔案 Agent 管理頁、分類 run 進度在 證據分類覆核頁。本服務本身無獨立畫面。

6. API 規格

多數背景 job 由排程自跑、無對外 endpoint。相關 endpoint 僅「入列 / 觸發」邊界:

分類 Method + Path 說明
Drive webhook 入列 POST /api/1.0/webhooks/google-drive/<int:tenant_id> Google 推 change → 入列 drive_sync_jobs(PROCESS_DRIVE_CHANGES)
Drive 全租戶重建 POST /api/1.0/integrations/google-drive/tenant/rebuild-all 手動觸發跨租戶重建(入列 job)
SocketIO(非 REST) namespace /socket/notification/socket/fill-survey 事件見 §11

排程 job(drive_sync_worker / webhook_channel_renewer / framework_parse_job_cleanup)無 HTTP endpoint,由 APScheduler 直接呼叫函式。endpoint 存在性對 routes.json 核實。

7. 前端檔案地圖

N/A(無前端)。前端只在雲端整合 / Agent / 分類 / 問卷等頁消費背景結果,不直接驅動排程(socket client 連線見各消費頁)。

8. 後端檔案地圖

服務 關鍵檔案
排程 bootstrap core/scheduler.pyinit_scheduler / shutdown_scheduler),core/app_factory.py:168-171 啟動
Drive 同步 worker app/cloud_integration/service/drive_sync_worker.pyrun_once / _process_one
Drive handlers(8 種 job_type) app/cloud_integration/service/handlers/archive_drive_file / create_folder / import_drive_file / init_project_folders / process_drive_changes / reconcile_task_folder / rename_folder / soft_delete_evidence(+ base_job_handler
Webhook 續期 webhook_channel_managerrenew_all),via cloud_integration_container
佇列 domain / repo domain/cloud_integration/drive_sync_job_entity / enums/sync_job_status / repository/drive_sync_job_repositoryclaim_batch / fail_or_retry
AI 分類容器 app/evidence_classification/service/evidence_classification_service.pytrigger_classifythreading.Thread_run_classify_workerClassifierContainerRunner
Agent 心跳 app/remote_agent/service/agent_enrollment_service.pyis_online / heartbeat 更新 last_seen_at
SocketIO handlers app/notification/handler/notification_socketio_handler.pyapp/task_survey/handler/fill_survey_socketio_handler.pyapi/notification/__init__.py:28api/task_survey/__init__.py:48 註冊 namespace)

DDD 提醒:worker 自管 per-step session_scope()(claim 一個 scope、每 job 各自 scope),不套 app service 的 @transaction——這是背景 worker 的正當例外(隔離 handler 崩潰),別比照一般 app service。

9. DB

9.1 資料表總清單

讀寫 說明 詳述
public.drive_sync_jobs 讀 / 寫(狀態機) Drive 同步佇列(webhook 驅動、非同步) §9.3
public.tenant_drive_integrations 讀 / 寫(channel 續期) 租戶 Drive OAuth + webhook channel 證據管理 §6
compliance.framework_parse_jobs 寫(軟刪 >7d) 框架解析暫存 job(TTL 清理目標)
compliance.evidence_classification_runs 寫(分類結果) AI 分類 run(容器寫) 證據管理 §6
compliance.remote_agents 寫(last_seen_at FR-039 Agent registry(心跳更新) 檔案 Agent 頁

9.2 ER 圖

各表關係見所屬功能群 spec(Drive / 分類 / Agent 皆在 證據管理群 §6)。本檔聚焦「誰在背景寫它們」。

9.3 核心表欄位卡

public.drive_sync_jobs(來源 db_schema.json):id / uid(uuid) / tenant_id / job_type(40 字,對應 handler registry)/ payload(jsonb) / status(20 字,見 §4)/ priority / retry_count / max_retries / next_run_at(退避排程)/ last_error / started_at / finished_at / created_at

10. 頁面邏輯與資料對應

N/A(無 UI)。job 生命週期見 §4 狀態機;分類 run / Agent 心跳的資料對應見各消費頁。

11. 背景行為與外部依賴

類型 內容
排程 APScheduler BackgroundScheduler(timezone UTC,ThreadPoolExecutor max_workers=4):3 job(§1.1)。per-tick INFO log 被降為 WARNING(5s job 會洗 log 表)
Socket /socket/notificationon_connect / on_join(join_room)/ on_reload(emit reload 給 room)——流程討論重載推播。/socket/fill-surveyon_connect/join/leave/editing/update/edit/endedit——問卷協作(presence、lock/unlock、updated 廣播)
外部服務 Google Drive API(同步 / webhook channel);Dockercmmc-classifier 容器,AI 分類);mTLS(遠端 Agent 撥入,見 memory feedback_fr039_all_agent_callers_reuse_mtls_auth
jedi-* 套件 jedi_common.session.database.dbsession_scope);Drive / OSCAL 相關 domain 走各自套件
系統參數 worker batch_size=10、poll 5s、renew 6h(距到期 24h)、parse_job TTL 7d、Agent heartbeat_interval_sec / offline_threshold_secagent_enrollment_service

12. 邊界情況與已知坑

  1. worker 用系統 session 繞 RLS:無 user context 時 session_scopeis_super_admin='t',跨租戶處理 job。改 worker 邏輯要意識到它看得到全租戶資料(RLS 不擋)。
  2. claim 與 handler 分 scope 是刻意設計IN_PROGRESS claim 先 commit,handler 崩潰只影響該 job、不回滾 claim、不污染同批其他 job。別把 claim 和 handler 併回同一 transaction。
  3. 5s tick 會洗 log:apscheduler 內部 per-tick INFO 已降 WARNING;drive worker 的成功 tick 走 logger.debug。查背景問題看 log/app.log 的 WARNING/ERROR,別期待每 tick 有 INFO。
  4. eventlet 相容性main_socketio.py 用 eventlet monkey-patch,APScheduler BackgroundScheduler 仍可用(core/scheduler.py 檔頭註記若遇問題可考慮 gevent scheduler)。
  5. AI 分類是 thread 非排程:manager 手動觸發起 threading.Thread,非 APScheduler job;容器 exit≠0 → run failed(見證據管理群)。同 project 一次一個 job(無並行)。
  6. Agent 心跳伺服器被動last_seen_at 由 Agent 主動撥入更新,伺服器無主動 poll;is_online 是「距今 < 門檻」的即時推算,非持久欄位。
  7. framework_parse_job_cleanup 只軟刪:每日清 >7d 的 parse job(含 awaiting_review / failed),是軟刪不是硬刪;暫存 parse 資料 7 天內可查。
  8. webhook 續期是保險非唯一:每 6h 跑、距到期 24h 內才續(Drive channel TTL 7d);跑太疏或 Drive outage 可能錯過窗(故意設 6h 遠小於 24h 窗留裕度)。

13. 開發與驗證

  • 跑起來python main_socketio.py(含 SocketIO,即時協作 / 通知需這支)或 python main_app.py(無 SocketIO);兩者都 init_scheduler 起 3 個排程 job。port 8000。
  • 看背景 logtail -f log/app.log(BE log 路徑);grep DriveSyncWorker / WebhookChannelRenewer / APScheduler started / Traceback
  • 驗排程有起:啟動 log 應見「APScheduler started — drive_sync_worker (5s), webhook_channel_renewer (6h), framework_parse_job_cleanup (daily 01:00 UTC)」。
  • 驗 Drive 佇列SELECT status, count(*) FROM public.drive_sync_jobs GROUP BY status;(DEV DB 見 CLAUDE.md DB 環境清單;憑證見 .env)。
  • 前置:Drive 同步需租戶 Drive CONNECTED + webhook channel 建立;分類需 Docker cmmc-classifier image;Agent 需 FR-039 enrollment。
  • 相關 FR / 文件:Drive 同步座標 memory project_drive_sync_test_data;FR-039 Agent project_fr039_distributed_file_agent;證據分類 證據管理群;問卷協作 問卷群