fix(km): retry transient list readbacks
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled

This commit is contained in:
ogt
2026-07-09 18:22:12 +08:00
parent 709724bd89
commit c94e08e995
2 changed files with 101 additions and 32 deletions

View File

@@ -462,6 +462,57 @@ class KnowledgeService:
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
return await repo.delete(entry_id)
async def _list_entries_from_primary(
self,
*,
category: str | None,
entry_type: EntryType | None,
status: EntryStatus | None,
tags: list[str] | None,
q: str | None,
limit: int,
offset: int,
) -> KnowledgeListResponse:
async with get_db_context() as db:
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
items, total = await repo.list_entries(
category=category,
entry_type=entry_type,
status=status,
tags=tags,
q=q,
limit=limit,
offset=offset,
)
categories_raw = await repo.get_categories()
categories = [
CategoryCount(category=cat, count=cnt) for cat, cnt in categories_raw
]
asset_taxonomy_raw = await repo.get_asset_taxonomy_counts()
asset_taxonomy = [
KnowledgeAssetTaxonomyCount(key=key, count=count)
for key, count in asset_taxonomy_raw
]
if total == 0:
return build_knowledge_list_readback_degraded_response(
"knowledge_db_empty",
category=category,
entry_type=entry_type,
status=status,
tags=tags,
q=q,
limit=limit,
offset=offset,
readback_status="source_backed_db_empty",
)
return KnowledgeListResponse(
items=items,
total=total,
categories=categories,
asset_taxonomy=asset_taxonomy,
primary_readback_ready=True,
)
async def list_entries(
self,
category: str | None = None,
@@ -474,29 +525,21 @@ class KnowledgeService:
) -> KnowledgeListResponse:
"""列出知識條目 + 分類統計"""
try:
async with get_db_context() as db:
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
items, total = await repo.list_entries(
category=category,
entry_type=entry_type,
status=status,
tags=tags,
q=q,
limit=limit,
offset=offset,
)
categories_raw = await repo.get_categories()
categories = [
CategoryCount(category=cat, count=cnt) for cat, cnt in categories_raw
]
asset_taxonomy_raw = await repo.get_asset_taxonomy_counts()
asset_taxonomy = [
KnowledgeAssetTaxonomyCount(key=key, count=count)
for key, count in asset_taxonomy_raw
]
if total == 0:
return build_knowledge_list_readback_degraded_response(
"knowledge_db_empty",
return await self._list_entries_from_primary(
category=category,
entry_type=entry_type,
status=status,
tags=tags,
q=q,
limit=limit,
offset=offset,
)
except Exception as exc: # noqa: BLE001 - production readback must fail soft
degraded_reason_code = _classify_knowledge_readback_degraded_reason(str(exc))
if degraded_reason_code == "primary_km_db_timeout_or_pool_exhausted":
try:
await asyncio.sleep(0.25)
retry_response = await self._list_entries_from_primary(
category=category,
entry_type=entry_type,
status=status,
@@ -504,19 +547,22 @@ class KnowledgeService:
q=q,
limit=limit,
offset=offset,
readback_status="source_backed_db_empty",
)
return KnowledgeListResponse(
items=items,
total=total,
categories=categories,
asset_taxonomy=asset_taxonomy,
primary_readback_ready=True,
)
except Exception as exc: # noqa: BLE001 - production readback must fail soft
retry_response.operator_stage = "knowledge_readback_primary_retry_recovered"
return retry_response
except Exception as retry_exc: # noqa: BLE001 - keep source-backed fallback
logger.warning(
"knowledge_list_readback_retry_degraded",
error=str(retry_exc),
degraded_reason_code=_classify_knowledge_readback_degraded_reason(str(retry_exc)),
q=q,
limit=limit,
offset=offset,
)
logger.warning(
"knowledge_list_readback_degraded",
error=str(exc),
degraded_reason_code=degraded_reason_code,
category=category,
entry_type=entry_type.value if entry_type else None,
status=status.value if status else None,