fix(realestate): compose INTERNAL_API_KEY(배포 401 방지) + 매칭·알림 공유락(중복 텔레그램 방지)
- docker-compose.yml realestate-lab environment에 INTERNAL_API_KEY/NAVER_PAGE_LIMIT 누락 추가 (verify_internal_key 항상 401 → 워커 계약 전체 마비 방지, sibling image/video/music/insta-lab과 동형) - pipeline_lock.py 신설: cron _run_listing_pipeline과 워커 listings_ingest가 run_listing_matching()+notify_new_listings() 임계구역을 공유 threading.Lock으로 직렬화 (동시 실행 시 동일 매물 중복 텔레그램 발송 방지). 느린 collect는 락 밖 유지. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqCYBhvTcdeCTUDX3RhWx9
This commit is contained in:
@@ -206,6 +206,8 @@ services:
|
||||
- DATA_GO_KR_API_KEY=${DATA_GO_KR_API_KEY:-}
|
||||
- CORS_ALLOW_ORIGINS=${CORS_ALLOW_ORIGINS:-http://localhost:3007,http://localhost:8080}
|
||||
- AGENT_OFFICE_URL=${AGENT_OFFICE_URL:-http://agent-office:8000}
|
||||
- INTERNAL_API_KEY=${INTERNAL_API_KEY:-}
|
||||
- NAVER_PAGE_LIMIT=${NAVER_PAGE_LIMIT:-2}
|
||||
- PYTHONPATH=/app:/shared
|
||||
volumes:
|
||||
- ${RUNTIME_PATH}/data/realestate:/app/data
|
||||
|
||||
@@ -11,6 +11,7 @@ from .lawd_codes import naver_cortar
|
||||
from .listing_collector import _parse_naver_article
|
||||
from .listing_matcher import run_listing_matching
|
||||
from .notifier import notify_new_listings
|
||||
from .pipeline_lock import match_notify_lock
|
||||
|
||||
logger = logging.getLogger("realestate-lab")
|
||||
router = APIRouter()
|
||||
@@ -61,7 +62,8 @@ def listings_ingest(body: ListingsIngest):
|
||||
logger.warning("네이버 ingest 파싱 실패: %s", e)
|
||||
matched = 0
|
||||
if received:
|
||||
run_listing_matching()
|
||||
noti = notify_new_listings()
|
||||
matched = int(noti.get("sent", 0)) if isinstance(noti, dict) else 0
|
||||
with match_notify_lock:
|
||||
run_listing_matching()
|
||||
noti = notify_new_listings()
|
||||
matched = int(noti.get("sent", 0)) if isinstance(noti, dict) else 0
|
||||
return {"received": received, "new": new, "matched": matched}
|
||||
|
||||
@@ -23,6 +23,7 @@ from .matcher import run_matching
|
||||
from .notifier import notify_new_matches, notify_new_listings
|
||||
from .listing_collector import collect_listings
|
||||
from .listing_matcher import run_listing_matching, compute_safety, compute_valuation
|
||||
from .pipeline_lock import match_notify_lock
|
||||
from . import finance_rules
|
||||
from .finance_rules_config import DISCLAIMER
|
||||
from .models import (
|
||||
@@ -73,7 +74,10 @@ def _run_listing_pipeline():
|
||||
if not _listing_collect_lock.acquire(blocking=False):
|
||||
logger.info("매물 수집 이미 진행 중 — 건너뜀"); return
|
||||
try:
|
||||
collect_listings(); run_listing_matching(); notify_new_listings()
|
||||
collect_listings()
|
||||
with match_notify_lock:
|
||||
run_listing_matching()
|
||||
notify_new_listings()
|
||||
finally:
|
||||
_listing_collect_lock.release()
|
||||
|
||||
|
||||
5
realestate-lab/app/pipeline_lock.py
Normal file
5
realestate-lab/app/pipeline_lock.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""매물 매칭+알림 임계구역 직렬화 락 — cron 파이프라인과 워커 ingest가 공유해
|
||||
run_listing_matching()+notify_new_listings() 동시 실행(중복 텔레그램)을 방지한다."""
|
||||
import threading
|
||||
|
||||
match_notify_lock = threading.Lock()
|
||||
@@ -44,3 +44,16 @@ def test_ingest_idempotent(monkeypatch):
|
||||
c.post("/api/internal/realestate/listings-ingest", headers={"X-Internal-Key": "SECRET"}, json=payload)
|
||||
r2 = c.post("/api/internal/realestate/listings-ingest", headers={"X-Internal-Key": "SECRET"}, json=payload)
|
||||
assert r2.json()["new"] == 0 # 재push → 신규 0
|
||||
|
||||
def test_ingest_holds_match_notify_lock(monkeypatch):
|
||||
from app import internal_router as ir
|
||||
from app.pipeline_lock import match_notify_lock
|
||||
c = _client(monkeypatch)
|
||||
holds = {}
|
||||
def fake_notify():
|
||||
holds["locked"] = match_notify_lock.locked()
|
||||
return {"sent": 0}
|
||||
monkeypatch.setattr(ir, "notify_new_listings", fake_notify)
|
||||
c.post("/api/internal/realestate/listings-ingest", headers={"X-Internal-Key": "SECRET"},
|
||||
json={"batches": [{"dong": "신대방동", "articles": [_ARTICLE]}]})
|
||||
assert holds["locked"] is True # 알림 시점에 락 보유(임계구역 직렬화 증명)
|
||||
|
||||
Reference in New Issue
Block a user