Skip to content

READY 필수 필드를 못 채운 plain 추출을 헤드리스로 에스컬레이트 - #24

Merged
m-a-king merged 3 commits into
mainfrom
feat/incomplete-snapshot-escalation
Aug 9, 2026
Merged

READY 필수 필드를 못 채운 plain 추출을 헤드리스로 에스컬레이트#24
m-a-king merged 3 commits into
mainfrom
feat/incomplete-snapshot-escalation

Conversation

@m-a-king

@m-a-king m-a-king commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Situation

  • dev 톡딜 위시 등록 실패를 조사하다가, store.kakao.com 상품 중 서버 HTML 에 가격이 아예 없는 상품이 실측으로 확인됐다 (JSON-LD 미포함, OG 메타는 이름·이미지만, 가격은 JS 렌더 후에만 DOM 에 존재). 같은 스토어라도 상품에 따라 JSON-LD 유무가 갈린다.
  • 기존 헤드리스 승격 축은 fetch 실패(PageFetchException.escalatable)만 본다. 이런 페이지는 fetch 가 성공하고 LLM 까지 돌지만 문서에 없는 가격은 못 뽑아, 브라우저를 한 번도 안 써 보고 UNTRUSTWORTHY_VALUE 확정 실패로 닫혔다.
  • 그 결과 store.kakao.com 에 HEADLESS_FIRST 정책을 넣는 것이 그 host 의 유일한 성공 경로(정합성 조건)가 됐다. 정책은 느린-실패 낭비를 줄이는 최적화일 뿐이어야 한다는 설계 의도와 어긋난다.

Task

  • plain 경로가 fetch 에는 성공했지만 READY 필수 필드(name·imageUrl·currentPrice)를 못 채운 경우, 확정 실패 전에 헤드리스로 한 번 더 시도하는 승격 축을 추가한다.
  • 기본 체인만으로도 같은 결과에 도달하게 해서 HEADLESS_FIRST 정책을 순수 최적화로 되돌린다.

Action

  • READY 판정 single source: 응답 경계(ExtractionResponse.from)에 있던 필수 필드 검사를 ProductSnapshot.missingReadyField 로 올려, 경계의 성공 게이트와 승격 판정이 한 조건을 공유한다. 두 곳이 어긋나면 "승격 없이 확정 실패" 또는 "무의미한 승격"이 생기기 때문이다.
  • 승격 축 확장: escalateToHeadless 가 category 문자열을 받도록 바꾸고, 두 축이 같은 카운터(outcome, category)에 모인다.
승격 트리거 판정 category
fetch 실패 (기존) PageFetchException.escalatable fetch 실패 코드명 (EMPTY_SHELL 등)
불완전 결과 (신규) plain snapshot 이 READY 필수 필드 미달 INCOMPLETE_SNAPSHOT
  • 재진입 차단: 불완전 승격은 plain 의 try 바깥에서 호출한다. try 안에서 부르면 headless 의 escalatable 실패가 plain 의 catch 로 새어 headless 를 두 번 때린다. 단위 테스트가 이 구조를 고정한다.
  • 재승격 없음: 승격 결과가 여전히 불완전하면 그대로 반환하고, 응답 경계가 기존과 같은 UNTRUSTWORTHY_VALUE 로 닫는다. 확정 실패 판정의 소유는 경계 한 곳에 유지한다.
  • 승격 제외 유지: LLM 의 상품 아님 확정(ProductSnapshotException)은 종전대로 승격하지 않는다. CSR 셸의 no-data 는 plain 전략이 이미 escalatable 로 재분류하므로(파싱 no-data + CSR 셸을 헤드리스 에스컬레이션 대상으로 재분류 #22), 여기까지 온 상품 아님 판정은 브라우저로 다시 봐도 상품이 되지 않는다.
  • 성공 집계 기준 통일: escalation·headless_first 카운터의 outcome=success 판정을 "예외 없이 반환"에서 "완전한 READY snapshot 확보(요청을 살림)"로 바꿨다(outcomeOf 로 단일화). 불완전 반환은 경계에서 확정 실패로 닫힐 결과라 success 로 세면 구제 성공률·직행 성공률이 실제보다 부푼다. INCOMPLETE_SNAPSHOT 은 목적 자체가 구제 성공률 관측이라 이 어긋남이 지표를 직접 오염시키는 것이 계기였고, 같은 결함이 있던 기존 fetch 실패 category 도 함께 정정했다.

Result

  • 부분 SSR SPA(카카오 톡딜 유형)가 HEADLESS_FIRST 정책 없이도 기본 체인으로 성공한다. 정책 행은 plain fetch + LLM 1회(실측 약 1.5초) 낭비를 줄이는 최적화로 남는다.
  • 헤드리스가 꺼진 구성은 zero-diff: 불완전 결과가 종전처럼 경계에서 확정 실패로 닫힌다 (기존 통합 테스트가 이 표면 계약을 그대로 보증).
  • 남는 한계: SSR 에 가격이 있긴 한데 렌더 후 값과 다른(낡은 값) 페이지는 plain 이 완전한 snapshot 을 반환해 승격이 개입할 지점이 없다. 실패가 나지 않는 부류라 관측되면 별도 대응한다.

연관 이슈

Summary by CodeRabbit

  • 개선 사항

    • 상품명, 이미지 URL, 현재 가격이 누락된 불완전한 추출 결과를 자동으로 보완합니다.
    • 보완 추출이 실패하면 원인에 맞게 결과를 구분하고 관련 상태를 기록합니다.
    • 상품 정보가 완전한 경우에는 기존 처리 결과를 그대로 제공합니다.
  • 버그 수정

    • 필수 상품 정보 누락 여부를 일관되게 확인하도록 개선했습니다.
    • 보완된 결과가 중복 처리되지 않도록 방지했습니다.
    • 보완 추출 비활성화 및 실패 상황에서 기존 동작과 오류 처리를 유지합니다.

- 부분 SSR SPA(카카오 톡딜 실측: 이름·이미지 OG 만 SSR 에 있고 가격은 JS 렌더 뒤에만 존재)는 fetch 가 성공해도 문서에 가격이 없어 LLM 도 못 뽑는다. 기존 승격 축(PageFetchException.escalatable)은 fetch 실패만 봐서 이 경우 브라우저를 안 써 보고 UNTRUSTWORTHY_VALUE 확정 실패로 닫혔다
- READY 판정(name·imageUrl·currentPrice)을 ProductSnapshot.missingReadyField 로 올려 응답 경계(ExtractionResponse.from)와 승격 판정이 한 조건을 공유하게 했다
- 불완전 승격은 plain 의 try 바깥에서 호출해 headless 실패가 catch 로 새어 재승격되는 축을 차단하고, 기존 escalation 카운터에 category=INCOMPLETE_SNAPSHOT 으로 집계한다
- 이 승격이 없으면 해당 host 는 HEADLESS_FIRST 정책이 유일한 성공 경로(정합성 조건)가 된다. 정책을 느린-실패 낭비 제거(최적화)로만 남긴다는 설계를 지키는 변경
@m-a-king m-a-king added the feat 외부 가시적 새 기능 label Aug 9, 2026
@m-a-king m-a-king self-assigned this Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@m-a-king, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 40f03a4c-e56e-478a-84a3-5d614ad00d0b

📥 Commits

Reviewing files that changed from the base of the PR and between ec70f01 and 00e7498.

📒 Files selected for processing (2)
  • src/main/java/com/depromeet/piki/extractor/extraction/FallbackProductLinkExtractor.java
  • src/test/java/com/depromeet/piki/extractor/extraction/FallbackProductLinkExtractorTest.java
📝 Walkthrough

Walkthrough

ProductSnapshot에 READY 필드 검증을 추가했습니다. 불완전한 plain 결과는 INCOMPLETE_SNAPSHOT 사유로 headless 추출에 승격됩니다. 응답 매핑과 Fallback 경계 동작에 대한 테스트를 확장했습니다.

Changes

READY 필드 검증과 응답 매핑

Layer / File(s) Summary
READY 필드 검증과 응답 매핑
src/main/java/com/depromeet/piki/extractor/domain/ProductSnapshot.java, src/main/java/com/depromeet/piki/extractor/api/ExtractionResponse.java, src/test/java/com/depromeet/piki/extractor/domain/ProductSnapshotTest.java
name, imageUrl, currentPrice의 누락을 missingReadyField()로 판정합니다. currency는 검증 대상에서 제외합니다. ExtractionResponse는 공통 검증 결과를 사용합니다.

불완전 스냅샷 Headless 승격

Layer / File(s) Summary
불완전 스냅샷 Headless 승격
src/main/java/com/depromeet/piki/extractor/extraction/FallbackProductLinkExtractor.java
READY 필드가 누락된 plain 결과를 headless 추출로 승격합니다. 승격 범주를 INCOMPLETE_SNAPSHOT으로 기록합니다. 승격 메서드는 차단과 불완전 스냅샷 범주를 구분합니다.

Fallback 경계 동작 검증

Layer / File(s) Summary
Fallback 경계 동작 검증
src/test/java/com/depromeet/piki/extractor/extraction/FallbackProductLinkExtractorTest.java
불완전 결과의 승격 및 성공 메트릭을 검증합니다. headless 결과의 재승격 방지, 실패 전파와 실패 메트릭, headless 비활성화, ProductSnapshotException 처리를 검증합니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FallbackProductLinkExtractor
  participant ProductSnapshot
  participant headless_strategy
  participant metrics

  FallbackProductLinkExtractor->>ProductSnapshot: missingReadyField()
  ProductSnapshot-->>FallbackProductLinkExtractor: 누락 여부 반환
  FallbackProductLinkExtractor->>headless_strategy: INCOMPLETE_SNAPSHOT 승격 요청
  headless_strategy-->>FallbackProductLinkExtractor: ProductSnapshot 반환
  FallbackProductLinkExtractor->>metrics: 승격 성공 또는 실패 기록
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 READY 필수 필드가 누락된 plain 추출 결과를 헤드리스 추출로 에스컬레이트하는 핵심 변경을 정확하게 요약합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/depromeet/piki/extractor/extraction/FallbackProductLinkExtractor.java`:
- Line 123: Update escalateToHeadless to immediately enforce that category is
non-null at method entry using Objects.requireNonNull(category, descriptive
message), before any other processing or Micrometer tag use.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4746baf0-7c38-4a72-aefd-a8d8e935516d

📥 Commits

Reviewing files that changed from the base of the PR and between 27c6318 and ec70f01.

📒 Files selected for processing (5)
  • src/main/java/com/depromeet/piki/extractor/api/ExtractionResponse.java
  • src/main/java/com/depromeet/piki/extractor/domain/ProductSnapshot.java
  • src/main/java/com/depromeet/piki/extractor/extraction/FallbackProductLinkExtractor.java
  • src/test/java/com/depromeet/piki/extractor/domain/ProductSnapshotTest.java
  • src/test/java/com/depromeet/piki/extractor/extraction/FallbackProductLinkExtractorTest.java

- outcome=success 가 "예외 없이 snapshot 반환"이라, READY 필드가 빈 결과(경계에서 확정 실패로 닫힘)도 success 로 집계돼 구제 성공률·직행 성공률이 실제보다 부풀었다
- 판정을 outcomeOf(snapshot)로 모아 escalation·headless_first 두 카운터가 같은 규칙(완전한 READY snapshot 확보 = success)을 공유한다
- INCOMPLETE_SNAPSHOT category 는 목적 자체가 구제 성공률 관측이라 이 어긋남이 지표를 직접 오염시키는 것이 계기. 기존 fetch 실패 category 도 같은 결함이 있어 함께 정정
- category 는 Micrometer 태그 값이라 null 이면 계약 위반이 카운터 생성 시점에야 드러난다. 진입 시 Objects.requireNonNull 로 실패 지점을 고정 (CodeRabbit 리뷰 반영)
@m-a-king
m-a-king merged commit 1a3c97e into main Aug 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 외부 가시적 새 기능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant