背景服務與排程(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:61、drive_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 |
無專屬 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_at,is_online
由門檻推算 |
| 多人同時填問卷 | client emit editing / update |
同 room 廣播 lock / updated(見 問卷填寫頁) |
3. 權限矩陣
N/A(無 UI、無使用者權限)。背景 job 以系統 session
執行:無 UserContextDTO 時 session_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):
| 狀態 | 意義 | 轉移 |
|---|---|---|
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_batch 用
SELECT FOR UPDATE SKIP LOCKED,多 worker 可並行不搶同
job;max_instances=1 + coalesce=True 避免 tick
重疊;handler 拋例外 → fail_or_retry:retry_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 心跳被動接收,各自觸發/入列邊界見下圖。
背景服務狀態的唯一可見面是: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.py(init_scheduler /
shutdown_scheduler),core/app_factory.py:168-171
啟動 |
| Drive 同步 worker | app/cloud_integration/service/drive_sync_worker.py(run_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_manager(renew_all),via
cloud_integration_container |
| 佇列 domain / repo | domain/cloud_integration/(drive_sync_job_entity
/ enums/sync_job_status /
repository/drive_sync_job_repository:claim_batch
/ fail_or_retry) |
| AI 分類容器 | app/evidence_classification/service/evidence_classification_service.py(trigger_classify
→ threading.Thread → _run_classify_worker →
ClassifierContainerRunner) |
| Agent 心跳 | app/remote_agent/service/agent_enrollment_service.py(is_online
/ heartbeat 更新 last_seen_at) |
| SocketIO handlers | app/notification/handler/notification_socketio_handler.py、app/task_survey/handler/fill_survey_socketio_handler.py(api/notification/__init__.py:28、api/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/notification:on_connect /
on_join(join_room)/ on_reload(emit
reload 給
room)——流程討論重載推播。/socket/fill-survey:on_connect/join/leave/editing/update/edit/endedit——問卷協作(presence、lock/unlock、updated
廣播) |
| 外部服務 | Google Drive API(同步 / webhook
channel);Docker(cmmc-classifier
容器,AI 分類);mTLS(遠端 Agent 撥入,見 memory
feedback_fr039_all_agent_callers_reuse_mtls_auth) |
| jedi-* 套件 | jedi_common.session.database.db(session_scope);Drive
/ OSCAL 相關 domain 走各自套件 |
| 系統參數 | worker batch_size=10、poll 5s、renew 6h(距到期
24h)、parse_job TTL 7d、Agent heartbeat_interval_sec /
offline_threshold_sec(agent_enrollment_service) |
12. 邊界情況與已知坑
- worker 用系統 session 繞 RLS:無 user context 時
session_scope設is_super_admin='t',跨租戶處理 job。改 worker 邏輯要意識到它看得到全租戶資料(RLS 不擋)。 - claim 與 handler 分 scope
是刻意設計:
IN_PROGRESSclaim 先 commit,handler 崩潰只影響該 job、不回滾 claim、不污染同批其他 job。別把 claim 和 handler 併回同一 transaction。 - 5s tick 會洗 log:apscheduler 內部 per-tick INFO
已降 WARNING;drive worker 的成功 tick 走
logger.debug。查背景問題看log/app.log的 WARNING/ERROR,別期待每 tick 有 INFO。 - eventlet 相容性:
main_socketio.py用 eventlet monkey-patch,APSchedulerBackgroundScheduler仍可用(core/scheduler.py檔頭註記若遇問題可考慮 gevent scheduler)。 - AI 分類是 thread 非排程:manager 手動觸發起
threading.Thread,非 APScheduler job;容器 exit≠0 → runfailed(見證據管理群)。同 project 一次一個 job(無並行)。 - Agent 心跳伺服器被動:
last_seen_at由 Agent 主動撥入更新,伺服器無主動 poll;is_online是「距今 < 門檻」的即時推算,非持久欄位。 - framework_parse_job_cleanup 只軟刪:每日清 >7d
的 parse job(含
awaiting_review/failed),是軟刪不是硬刪;暫存 parse 資料 7 天內可查。 - 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。 - 看背景 log:
tail -f log/app.log(BE log 路徑);grepDriveSyncWorker/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 建立;分類需 Dockercmmc-classifierimage;Agent 需 FR-039 enrollment。 - 相關 FR / 文件:Drive 同步座標 memory
project_drive_sync_test_data;FR-039 Agentproject_fr039_distributed_file_agent;證據分類 證據管理群;問卷協作 問卷群。