"""네거티브 캐시 CRUD — not_found 결론을 TTL 동안 기억. 같은 상품 재요청이 즉시 재검색(브라우저+API+LLM 비용)하는 것을 막는다. TTL 만료 후에는 다시 검색 허용(입고 가능성). key 는 보통 product_code.""" from sqlalchemy import text from common.database.db_session_manager import DB_SESSION_MNG from common.enums import DBType, DBWRType class NegativeCache: DB = DBType.MAIN.value async def is_negative(self, key: str) -> bool: """key 가 아직 유효한 not_found 로 캐시돼 있으면 True.""" if not key: return False s = await DB_SESSION_MNG.start_session(self.DB, DBWRType.DB_READ.value) try: row = (await s.execute( text("SELECT 1 FROM search_negative WHERE key = :k AND until > now()"), {"k": key}, )).first() return row is not None finally: await DB_SESSION_MNG.end_session(self.DB, DBWRType.DB_READ.value) async def drop(self, key: str): """key 의 not_found 기록을 지운다 — 사용자가 강제 재검색(force)을 요청했을 때. 캐시 키가 product_code 라 상품명·모델을 고쳐 다시 찾는 경우에도 이걸로 풀어줘야 한다.""" if not key: return s = await DB_SESSION_MNG.start_session(self.DB, DBWRType.DB_WRITE.value) try: await s.execute(text("DELETE FROM search_negative WHERE key = :k"), {"k": key}) await s.commit() except Exception: await s.rollback() raise finally: await DB_SESSION_MNG.end_session(self.DB, DBWRType.DB_WRITE.value) async def put(self, key: str, ttl_sec: int = 86400, reason: str = "not_found"): """key 를 ttl_sec 동안 not_found 로 기록(upsert).""" if not key: return sql = text(""" INSERT INTO search_negative (key, until, reason) VALUES (:k, now() + make_interval(secs => :ttl), :r) ON CONFLICT (key) DO UPDATE SET until = EXCLUDED.until, reason = EXCLUDED.reason, created_at = now() """) s = await DB_SESSION_MNG.start_session(self.DB, DBWRType.DB_WRITE.value) try: await s.execute(sql, {"k": key, "ttl": ttl_sec, "r": reason[:200]}) await s.commit() except Exception: await s.rollback() raise finally: await DB_SESSION_MNG.end_session(self.DB, DBWRType.DB_WRITE.value)