'못 찾음'을 유한하게 종료. 두 재시도 축을 분리(기술=큐 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>
48 lines
1.8 KiB
Python
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)
|