사이트 발행 성공과 Google 색인 관측은 별도 상태다. 외부 API 장애로 발행이 실패하거나 재시작 때 추적 정보가 사라지지 않도록 분리. - Google 클라이언트·배치·DB·Teams 알림 모듈 분리 - 기존 스케줄러 연결, 재시도·중복 실행 방지와 선택 설정 추가 - ORM·초기 DDL·마이그레이션·운영 설정 문서 동시 갱신 검증: 관련 59건 통과, compose 설정·diff 검사 통과. 추가 회귀 23건 통과, 기존 발행 검수 실패 1건은 변경 전 코드에서도 재현. 운영 배포·Google/Teams 실호출 미실행.
58 lines
2.5 KiB
Python
58 lines
2.5 KiB
Python
"""발행 DB를 작업 원장으로 사용해 알림 적재 실패/재시작에도 대상을 다시 찾는다."""
|
|
from sqlalchemy import select, or_, text
|
|
from sqlalchemy.dialects.postgresql import insert
|
|
|
|
from common.database.model.models import sites, places, site_versions, site_search_status as Status
|
|
from common.enums import SiteStatus
|
|
|
|
|
|
def published_conditions():
|
|
return (
|
|
sites.deleted.is_(False), places.deleted.is_(False),
|
|
sites.status == SiteStatus.PUBLISHED.value,
|
|
sites.published_at.is_not(None), sites.current_version_id.is_not(None),
|
|
)
|
|
|
|
|
|
async def lock_batch(session) -> bool:
|
|
# 여러 API 프로세스가 같은 크론을 등록해도 외부 호출은 한 곳만 수행한다.
|
|
return bool(await session.scalar(text("SELECT pg_try_advisory_xact_lock(734920151)")))
|
|
|
|
|
|
async def new_publications(session, property_url: str):
|
|
result = await session.execute(
|
|
select(sites, places, site_versions)
|
|
.join(places, places.place_id == sites.place_id)
|
|
.join(site_versions, site_versions.site_version_id == sites.current_version_id)
|
|
.outerjoin(Status, Status.site_id == sites.site_id)
|
|
.where(*published_conditions(), site_versions.deleted.is_(False), or_(
|
|
Status.site_id.is_(None), Status.site_version_id != sites.current_version_id,
|
|
Status.property_url != property_url, Status.published_at != sites.published_at,
|
|
))
|
|
.order_by(sites.published_at).limit(100)
|
|
)
|
|
return result.all()
|
|
|
|
|
|
async def register(session, values: dict):
|
|
reset = dict(values, sitemap_submitted_at=None, inspected_at=None, first_indexed_at=None,
|
|
inspection=None, error_code=None, failures=0, alerted_at=None,
|
|
next_check_at=text("now()"), updated_at=text("now()"), deleted=False)
|
|
await session.execute(insert(Status).values(**values).on_conflict_do_update(
|
|
index_elements=[Status.site_id], set_=reset,
|
|
))
|
|
|
|
|
|
async def due_sites(session, property_url: str):
|
|
result = await session.execute(
|
|
select(Status).join(sites, sites.site_id == Status.site_id)
|
|
.join(places, places.place_id == sites.place_id)
|
|
.where(*published_conditions(), Status.deleted.is_(False),
|
|
Status.site_version_id == sites.current_version_id,
|
|
Status.published_at == sites.published_at, Status.property_url == property_url,
|
|
Status.next_check_at <= text("now()"))
|
|
.order_by(Status.next_check_at).limit(5)
|
|
.with_for_update(of=Status)
|
|
)
|
|
return result.scalars().all()
|