fix(incidents): batch decision token lookup
This commit is contained in:
@@ -206,11 +206,14 @@ async def list_incidents(
|
||||
|
||||
responses = []
|
||||
background_tasks = []
|
||||
existing_tokens = await decision_manager._find_existing_tokens_for_incidents(
|
||||
[incident.incident_id for incident in incidents]
|
||||
)
|
||||
|
||||
for incident in incidents:
|
||||
try:
|
||||
# 只查已快取的決策 (不等待 AI,立即返回)
|
||||
existing = await decision_manager._find_existing_token(incident.incident_id)
|
||||
existing = existing_tokens.get(incident.incident_id)
|
||||
if existing:
|
||||
decision_info = DecisionInfo(
|
||||
token=existing.token,
|
||||
|
||||
@@ -2953,6 +2953,52 @@ class DecisionManager:
|
||||
|
||||
return None
|
||||
|
||||
async def _find_existing_tokens_for_incidents(
|
||||
self,
|
||||
incident_ids: list[str],
|
||||
) -> dict[str, DecisionToken]:
|
||||
"""
|
||||
批次查找現有決策令牌。
|
||||
|
||||
2026-05-06 Codex: GET /api/v1/incidents 是前端輪詢路徑,不可對每個
|
||||
incident 都掃描一次 decision:*。這裡只掃一次 Redis keyspace,避免
|
||||
200+ incidents 時形成 O(N×M) 延遲與前端控制台卡死。
|
||||
"""
|
||||
wanted = set(incident_ids)
|
||||
if not wanted:
|
||||
return {}
|
||||
|
||||
import json
|
||||
|
||||
redis_client = get_redis()
|
||||
found: dict[str, DecisionToken] = {}
|
||||
cursor = 0
|
||||
while True:
|
||||
cursor, keys = await redis_client.scan(
|
||||
cursor=cursor,
|
||||
match=f"{DECISION_TOKEN_PREFIX}*",
|
||||
count=500,
|
||||
)
|
||||
|
||||
for key in keys:
|
||||
try:
|
||||
data = await redis_client.get(key)
|
||||
if not data:
|
||||
continue
|
||||
token_data = json.loads(data)
|
||||
incident_id = token_data.get("incident_id")
|
||||
if incident_id in wanted and incident_id not in found:
|
||||
found[incident_id] = DecisionToken.from_dict(token_data)
|
||||
if len(found) == len(wanted):
|
||||
return found
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if cursor == 0:
|
||||
break
|
||||
|
||||
return found
|
||||
|
||||
async def _persist_decision_to_db(
|
||||
self, incident_id: str, proposal_data: dict
|
||||
) -> None:
|
||||
|
||||
@@ -30,8 +30,15 @@ class _IncidentService:
|
||||
class _DecisionManager:
|
||||
def __init__(self) -> None:
|
||||
self.created = 0
|
||||
self.single_token_lookups = 0
|
||||
self.batch_token_lookups = 0
|
||||
|
||||
async def _find_existing_tokens_for_incidents(self, incident_ids: list[str]):
|
||||
self.batch_token_lookups += 1
|
||||
return {}
|
||||
|
||||
async def _find_existing_token(self, incident_id: str):
|
||||
self.single_token_lookups += 1
|
||||
return None
|
||||
|
||||
async def get_or_create_decision(self, *args, **kwargs):
|
||||
@@ -51,3 +58,5 @@ async def test_list_incidents_does_not_trigger_ai_decision_by_default(monkeypatc
|
||||
assert result.incidents[0].incident_id == "INC-20260506-PURE01"
|
||||
assert result.incidents[0].decision is None
|
||||
assert decision_manager.created == 0
|
||||
assert decision_manager.batch_token_lookups == 1
|
||||
assert decision_manager.single_token_lookups == 0
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
- `GET /api/v1/incidents` 新增 `generate_missing_decisions=false` 預設參數。
|
||||
- 預設只讀取既有 decision token;缺少 token 時回傳 `decision=null`,不再背景觸發 Ollama / OpenClaw / Gemini。
|
||||
- 若維運人員明確需要舊行為,可用 `generate_missing_decisions=true` 觸發背景生成;正式修復建議仍應走 `POST /api/v1/incidents/{incident_id}/proposal` 或 AwoooP Operator Run。
|
||||
- `DecisionManager` 新增批次 token 查詢;列表路徑只掃一次 Redis `decision:*`,避免 200+ incidents 時逐筆掃描造成 O(N×M) 延遲。
|
||||
- 新增 regression test,鎖定列表查詢預設不會呼叫 `get_or_create_decision()`。
|
||||
|
||||
**驗證**:
|
||||
|
||||
Reference in New Issue
Block a user