Skip to content

fix: 경기 일정 크론 미업데이트 수정 — 크롤 병렬화 + HTML 파서 재작성#19

Merged
umsungjun merged 1 commit into
mainfrom
fix/schedule-cron-parallelization
May 10, 2026
Merged

fix: 경기 일정 크론 미업데이트 수정 — 크롤 병렬화 + HTML 파서 재작성#19
umsungjun merged 1 commit into
mainfrom
fix/schedule-cron-parallelization

Conversation

@umsungjun
Copy link
Copy Markdown
Owner

@umsungjun umsungjun commented May 10, 2026

원인

  1. 크론 4단계가 순차 실행되어 60s maxDuration 초과 → 스케줄(4단계)만 타임아웃
  2. UFC CloudFront CDN API 양쪽 엔드포인트 모두 404 반환
  3. HTML 폴백 파서가 <time datetime> 을 탐색했으나 실제 구조는 data-main-card-timestamp (Unix 초) 사용으로 변경됨

수정 내용

  • cron/crawl: 3단계 병렬 실행 구조로 재작성
    • Phase 1: stats/rankings/schedule 크롤 병렬 (의존성 없음)
    • Phase 2: DB 저장 + Gemini AI 예측 병렬 (Gemini 태스크 내부는 순차)
    • Phase 3: AI 결과 저장
    • 예상 실행 시간 ~65s → ~43s로 단축
    • serializeError 헬퍼로 PostgrestError 직렬화 버그 수정
    • /rankings 캐시 워밍 누락 추가
  • schedule-crawler: fetchFromHtml() 현재 HTML 구조로 완전 재작성
    • 날짜: data-main-card-timestamp (Unix 초) 파싱
    • 파이터 풀네임: 이미지 파일명에서 추출 (ALLEN_ARNOLD → Arnold Allen)
    • 파이터 이미지: HTML에서 직접 수집 (enrichFighterImages 불필요)
    • TBD 파이터 imageUrl 미설정으로 깨진 이미지 표시 방지
  • schedule/page.tsx: enrichFighterImages 이중 호출 제거

Summary by CodeRabbit

릴리즈 노트

  • 버그 수정

    • 스케줄 데이터 로딩 로직 개선으로 더 빠른 처리 실현
    • 이벤트 선수 정보 추출 정확도 향상
  • 성능 개선

    • 크론 작업 병렬 처리로 전체 실행 시간 단축
    • 스케줄 크롤러 정보 수집 프로세스 최적화

Review Change Stack

**원인**
1. 크론 4단계가 순차 실행되어 60s maxDuration 초과 → 스케줄(4단계)만 타임아웃
2. UFC CloudFront CDN API 양쪽 엔드포인트 모두 404 반환
3. HTML 폴백 파서가 `<time datetime>` 을 탐색했으나 실제 구조는
   `data-main-card-timestamp` (Unix 초) 사용으로 변경됨

**수정 내용**
- cron/crawl: 3단계 병렬 실행 구조로 재작성
  - Phase 1: stats/rankings/schedule 크롤 병렬 (의존성 없음)
  - Phase 2: DB 저장 + Gemini AI 예측 병렬 (Gemini 태스크 내부는 순차)
  - Phase 3: AI 결과 저장
  - 예상 실행 시간 ~65s → ~43s로 단축
  - serializeError 헬퍼로 PostgrestError 직렬화 버그 수정
  - /rankings 캐시 워밍 누락 추가
- schedule-crawler: fetchFromHtml() 현재 HTML 구조로 완전 재작성
  - 날짜: data-main-card-timestamp (Unix 초) 파싱
  - 파이터 풀네임: 이미지 파일명에서 추출 (ALLEN_ARNOLD → Arnold Allen)
  - 파이터 이미지: HTML에서 직접 수집 (enrichFighterImages 불필요)
  - TBD 파이터 imageUrl 미설정으로 깨진 이미지 표시 방지
- schedule/page.tsx: enrichFighterImages 이중 호출 제거

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 10, 2026 13:12
@vercel
Copy link
Copy Markdown

vercel Bot commented May 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lets-ko Building Building Preview, Comment May 10, 2026 1:12pm

@umsungjun umsungjun merged commit e092609 into main May 10, 2026
2 of 4 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2da8e1d6-1793-44da-879a-4ba8334167eb

📥 Commits

Reviewing files that changed from the base of the PR and between 19f2695 and d80124c.

📒 Files selected for processing (3)
  • src/app/[locale]/schedule/page.tsx
  • src/app/api/cron/crawl/route.ts
  • src/lib/crawl/schedule-crawler.ts

📝 Walkthrough

개요

UFC 일정 크롤링 및 예측 파이프라인을 재설계합니다. 일정 HTML 파싱 로직을 새로운 선택기 및 속성 기반 데이터 추출로 업데이트하고, 크론 크롤 루트를 순차 실행에서 세 가지 병렬 페이즈로 리팩토링하며, 페이지 컴포넌트에서 이미지 보강 의존성을 제거합니다.

변경 사항

일정 크롤링 및 예측 통합

레이어 / 파일 요약
일정 HTML 파싱 헬퍼
src/lib/crawl/schedule-crawler.ts
extractNameFromImageUrl 헬퍼를 추가하고 fetchFromHtml을 재작성하여 UFC 카드 선택기(article.c-card-event--result)를 사용하며, 중복 제거, 타임스탬프 속성에서의 날짜 파싱, 이미지 URL 기반 선수명 도출, 구조화된 주소 및 장지 정보 추출을 포함합니다.
크론 루트 에러 처리
src/app/api/cron/crawl/route.ts
새로운 serializeError(reason: unknown) 헬퍼는 거부된 값을 응답 페이로드 보고를 위한 일관된 문자열 오류 메시지로 변환합니다.
페이즈 1: 병렬 크롤 준비
src/app/api/cron/crawl/route.ts
Promise.allSettled를 사용하여 통계, 순위, 일정 이벤트 크롤링을 동시에 실행하며, 기존 일정 예측을 재사용하기 위해 최신 ufc_schedule 행을 Supabase에서 읽습니다.
페이즈 2: 병렬 저장 및 예측 생성
src/app/api/cron/crawl/route.ts
통계 및 순위를 Supabase에 저장하면서 동시에 상대 예측을 생성하고, 일정 데이터가 있을 때 일정 예측을 생성합니다; 거부된 작업별 오류를 기록합니다.
페이즈 3: 조건부 결과 저장
src/app/api/cron/crawl/route.ts
이전 작업의 이행 상태에 따라 AI 결과를 Supabase에 조건부로 삽입하며, 필요한 입력이 없을 때 오류가 발생합니다.
응답 구성
src/app/api/cron/crawl/route.ts
results.stats, results.rankings, results.predictions, results.schedule을 페이즈 결과의 이행/거부 상태에서 유도하며, 오류 사례에 serializeError(...)를 사용합니다.
사이트 워밍업 확장
src/app/api/cron/crawl/route.ts
NEXT_PUBLIC_SITE_URL이 있을 때 페칭할 locale 접두사가 붙은 페이지 목록을 정의합니다.
페이지 컴포넌트 통합
src/app/[locale]/schedule/page.tsx
enrichFighterImages import을 제거하고 getSchedule()이 Supabase 성공 경로와 캐시 폴백 경로 모두에서 보강 없이 일정을 직접 반환하도록 합니다.

시퀀스 다이어그램

sequenceDiagram
    participant Handler as Cron Handler
    participant P1 as Phase 1 Crawl
    participant P2 as Phase 2 Predict
    participant P3 as Phase 3 Persist
    participant DB as Supabase
    
    Handler->>P1: Start all crawl tasks
    P1->>DB: Read existing schedule
    P1->>P1: Crawl from UFC HTML
    P1-->>Handler: Status
    
    Handler->>P2: Save and generate predictions
    P2->>DB: Insert stats, rankings
    P2->>P2: Generate opponent predictions
    P2->>P2: Generate schedule predictions
    P2-->>Handler: Status
    
    Handler->>P3: Conditionally persist results
    P3->>DB: Update predictions, schedule
    P3-->>Handler: Status
    
    Handler->>Handler: Build final results
    Handler->>DB: Revalidate paths
    Handler->>Handler: Warm up pages
Loading

예상 코드 리뷰 노력

🎯 4 (Complex) | ⏱️ ~75분

관련 가능성 있는 PR

  • umsungjun/lets-ko#13: 두 PR 모두 크론 크롤 루트를 수정하여 revalidatePath 후 사이트 URL을 페칭하는 페이지 워밍업을 추가합니다.
  • umsungjun/lets-ko#16: 직접 관련됨: 두 PR 모두 src/app/[locale]/schedule/page.tsx를 수정하며, 주요 차이점은 getSchedule()enrichFighterImages를 호출하는지 여부입니다(이 PR에서는 제거됨).

토끼가 춤을 추며, 🐰
병렬 페이즈가 흘러가고,
이미지 체인을 끊어내니,
일정은 신선하게 온다!
UFC 데이터는 빠르고 밝게! ⚡

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/schedule-cron-parallelization

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 and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes UFC schedule cron updates timing out and restores schedule crawling by parallelizing cron phases and rewriting the HTML fallback parser to match the current ufc.com DOM.

Changes:

  • Parallelized cron execution into 3 phases (crawl → DB/AI → DB) and improved error serialization + cache warming.
  • Rewrote fetchFromHtml() to parse data-main-card-timestamp, extract fighter names from image URLs, and collect fighter image URLs directly.
  • Removed enrichFighterImages usage from the schedule page to avoid redundant enrichment.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/lib/crawl/schedule-crawler.ts Rewrites HTML fallback parsing and adds image-URL-based fighter name extraction.
src/app/api/cron/crawl/route.ts Parallelizes crawl/save/predict phases, adds error serialization, and warms more pages.
src/app/[locale]/schedule/page.tsx Removes schedule-time image enrichment and returns stored/cached schedule as-is.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +285 to +295
* UFC 이미지 파일명 규칙: {LAST}_{FIRST}_{MM-YY}.png 또는 {LAST}_{FIRST}.png
* 예: ALLEN_ARNOLD_01-24.png → "Arnold Allen"
*/
function extractNameFromImageUrl(
url: string | undefined,
fallback: string
): string {
if (!url) return fallback;
const match = url.match(/\/([A-Z]+)_([A-Z]+)(?:_[\d-]+)?\.png/);
if (!match) return fallback;
const toTitle = (s: string) => s.charAt(0) + s.slice(1).toLowerCase();
Comment on lines 193 to 197
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
if (siteUrl) {
const pagesToWarm = [
"/",
"/en",
Comment on lines 71 to 75
if (data?.data) {
const schedule = data.data as UfcSchedule;
if (schedule.events?.length > 0) {
// 이미지 없는 파이터 보완 (크론 실패 또는 구형 데이터 대비)
const enriched = await enrichFighterImages(schedule.events);
return { ...schedule, events: enriched };
return schedule;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants