fix: invalidate agent cache on knowledge changes
This commit is contained in:
@@ -4,15 +4,47 @@ import json
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.knowledge import (
|
||||
Knowledge,
|
||||
KnowledgeCard,
|
||||
KnowledgeChunk,
|
||||
KnowledgeManifest,
|
||||
KnowledgePublishLog,
|
||||
KnowledgeReviewRecord,
|
||||
KnowledgeSection,
|
||||
KnowledgeSourceSnapshot,
|
||||
KnowledgeSyncJob,
|
||||
KnowledgeVersion,
|
||||
UserKnowledgePermission,
|
||||
)
|
||||
from app.services.redis_client import get_sync_redis_client
|
||||
|
||||
|
||||
class KnowledgeCatalogCacheService:
|
||||
"""Central invalidation point for current and future KnowledgeList caches."""
|
||||
_CACHE_INVALIDATION_PENDING = "agent_knowledge_cache_invalidation_pending"
|
||||
_CACHE_RELEVANT_MODELS = (
|
||||
Knowledge,
|
||||
KnowledgeCard,
|
||||
KnowledgeChunk,
|
||||
KnowledgeManifest,
|
||||
KnowledgePublishLog,
|
||||
KnowledgeReviewRecord,
|
||||
KnowledgeSection,
|
||||
KnowledgeSourceSnapshot,
|
||||
KnowledgeSyncJob,
|
||||
KnowledgeVersion,
|
||||
UserKnowledgePermission,
|
||||
)
|
||||
|
||||
VERSION_KEY = "kb:catalog:generation"
|
||||
KEY_PREFIX = "kb:catalog:v1"
|
||||
METRICS_KEY = "kb:catalog:metrics"
|
||||
|
||||
class KnowledgeCatalogCacheService:
|
||||
"""Shared generation for KnowledgeList and all Agent knowledge caches."""
|
||||
|
||||
VERSION_KEY = "agent:knowledge:generation"
|
||||
KEY_PREFIX = "agent:knowledge:catalog:v2"
|
||||
METRICS_KEY = "agent:knowledge:cache:metrics"
|
||||
TTL_SECONDS = 300
|
||||
|
||||
@classmethod
|
||||
@@ -44,12 +76,37 @@ class KnowledgeCatalogCacheService:
|
||||
|
||||
@classmethod
|
||||
def invalidate(cls) -> None:
|
||||
"""Logically expires every Agent knowledge cache in one atomic operation."""
|
||||
client = get_sync_redis_client()
|
||||
if client is None:
|
||||
return
|
||||
try:
|
||||
client.incr(cls.VERSION_KEY)
|
||||
except Exception:
|
||||
# The catalog currently reads MySQL on every request, so Redis failure
|
||||
# must not make a committed metadata change look unsuccessful.
|
||||
# Redis failure must not make a committed database change look unsuccessful;
|
||||
# subsequent reads automatically fall back to MySQL when Redis is unavailable.
|
||||
return
|
||||
|
||||
|
||||
def register_knowledge_cache_invalidation_events() -> None:
|
||||
if event.contains(Session, "before_flush", _mark_knowledge_cache_dirty):
|
||||
return
|
||||
event.listen(Session, "before_flush", _mark_knowledge_cache_dirty)
|
||||
event.listen(Session, "after_commit", _invalidate_knowledge_cache_after_commit)
|
||||
event.listen(Session, "after_rollback", _clear_knowledge_cache_invalidation)
|
||||
|
||||
|
||||
def _mark_knowledge_cache_dirty(session: Session, _flush_context: object, _instances: object) -> None:
|
||||
changed = list(session.new) + list(session.deleted)
|
||||
changed.extend(item for item in session.dirty if session.is_modified(item, include_collections=True))
|
||||
if any(isinstance(item, _CACHE_RELEVANT_MODELS) for item in changed):
|
||||
session.info[_CACHE_INVALIDATION_PENDING] = True
|
||||
|
||||
|
||||
def _invalidate_knowledge_cache_after_commit(session: Session) -> None:
|
||||
if session.info.pop(_CACHE_INVALIDATION_PENDING, False):
|
||||
KnowledgeCatalogCacheService.invalidate()
|
||||
|
||||
|
||||
def _clear_knowledge_cache_invalidation(session: Session) -> None:
|
||||
session.info.pop(_CACHE_INVALIDATION_PENDING, None)
|
||||
|
||||
Reference in New Issue
Block a user