o2o-negosium-original/lps/crud/negative_cache.py
민헌 d54dee4f24 feat(lps): 재시도/못찾음 완결 로직 — 재정제 루프 + not_found + 네거티브 캐시
'못 찾음'을 유한하게 종료. 두 재시도 축을 분리(기술=큐 attempts/백오프,
검색어=refine 라운드). not_found 는 정상 종료(DONE)지 dead-letter 아님.

- ai/keyword: LLM 검색어 생성(정밀/광역) — 원본 0매칭 시에만 지연 호출(비용 절약)
- handler: 한정 재정제 루프(원본→정밀→광역, max_rounds=3) + 명시적 outcome(found/not_found)
  · 0매칭+소스정상 → 다음 라운드, 0매칭+기술실패 → raise(큐 재시도)
  · 라운드 소진 → not_found + 네거티브 캐시 기록
- negative_cache: search_negative 테이블 + TTL(24h) upsert — 같은 상품 재요청 재검색 차단
- worker_main: OPENAI 있으면 judge+keyword_gen ON, neg_cache 상시
- tests: 재정제/not_found/캐시히트/기술실패/캐시CRUD 11건 → 전체 39/39
- 라이브: 없는상품 3라운드→not_found(30s), 재요청 캐시히트(0.00s)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:12:47 +09:00

48 lines
1.8 KiB
Python

"""네거티브 캐시 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 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)