problem_solving/MarketRadar

4. RSS를 깎자

점심뭐먹지 2026. 4. 2. 03:30

RSS를 깎자 - Vercel 타임아웃부터 제미나이 프롬프트 튜닝까지

Market Radar의 증시 분석 퀄리티를 높이기 위해, 기존 5개였던 미국 위주의 RSS 피드에 CNBC, BBC, ECB, 야후 파이낸스(JP) 등 글로벌 매체를 더해 총 9개로 확장하는 작업을 진행했다. (NHK world backstory는 이번에 삭제.)

 

단순한 배열 추가 작업인 줄 알았으나, 데이터 소스의 규모가 2배 가까이 커지면서 기존 아키텍처(파서, 서버리스 타임아웃, AI 프롬프트 전처리) 전반의 스케일업이 필요해짐. 백엔드 데이터 수집부터 프롬프트 주입까지, 확장 과정에서 만난 6가지 병목 현상과 해결 과정을 정리했다.

 

Phase 1. 데이터 수집의 늪 (API & 파싱 이슈)

가장 먼저 맞닥뜨린 문제는 새로 추가한 글로벌 피드들이 화면에 아예 뜨지 않거나 에러를 뱉는 현상.

1. 등잔 밑이 어둡다: "왜 배포가 안 됐지?"

로컬에서는 배열에 10개의 피드가 정상적으로 잡히는데, 프로덕션에서는 여전히 5개만 돌고 있었어서 더 이상했다. 원인은 허무하게도 커밋을 안하고 기다리고 있었음.

Lesson: "왜 안 되지?" 할 때는 제일 먼저 git status부터 쳐보자.

2. 가짜 RSS 링크 (ECB)

ECB(유럽중앙은행) 피드가 빈 배열([])만 반환하고 있었다. 확인해 보니 넣은 주소(.../rss.en.html)는 XML 피드가 아니라 RSS 목록을 안내하는 일반 웹페이지(HTML). 이것저것 찾아서 긁어오다 미처 확인을 못했다. 그러니 파서가 <item> 태그를 찾지 못해 조용히 돌다 빈 값을 리턴. 주소를 실제 XML 엔드포인트(press.xml)로 교체하여 해결.

3. BBC의 변칙: Atom 스타일 태그 파싱

BBC 뉴스 카드를 클릭하면 원문 기사로 가지 않고 href="# " (데드 링크)가 걸리는 버그가 발생. 기존 정규식은 <link>주소</link> 형태만 찾았는데, BBC는 <link href="주소"/> 형태의 Atom 스타일을 섞어 쓰고 있었음. 파서(lib/parseRSS.js)에 Fallback 정규식을 추가해 속성값을 안전하게 빼오도록 수정.

const linkHrefRegex = /<link[^>]+href=["'](.*?)["']/i;
const link = (lM ? lM[1] : content.match(linkHrefRegex)?.[1] ?? '').trim();

4. 깐깐한 야후와 Vercel의 시간제한

야후 파이낸스는 Mozilla/5.0 User-Agent를 봇으로 간주하고 403이나 리다이렉트 루프를 돌려버렸다. 때문에 헤더에 완전한 크롬 브라우저 정보를 넣어 우회하기로 결정. 거기에, 10개의 피드를 병렬로 당겨오다 보니 가끔 응답이 느린 서버 때문에 Vercel의 기본 함수 실행 제한 시간(10초)을 초과하는 상황도 발생. vercel.json에서 api/news.js의 maxDuration을 30초로 넉넉하게 늘려 타임아웃을 예방.


Phase 2. 조용한 데이터 누락 (프론트엔드 & 프롬프트 이슈)

API를 고쳐서 화면에는 글로벌 뉴스가 뜨기 시작. 그런데, 제미나이(Gemini)가 작성하는 '시황 요약'을 읽어보니 여전히 국내 뉴스만 가지고 분석을 하고 있었다. 그럼 이제 하드코딩해둔 프롬프트의 시간이다.

5. 프론트엔드의 숨은 하드코딩 (Cap 제한)

프롬프트를 조립하는 src/lib/buildDataBlock.ts 파일에 이런 코드가 있었다.

const newsBlock = news.slice(0, 10); // 문제의 코드

초기에 토큰 길이를 아끼겠다고 걸어둔 제한. 피드가 9개로 늘어나 후보군이 45개나 되는데, 최신순 상위 10개만 뚝 잘라 AI에게 보내면 당연히 모두 다 전달이 될리가 없었다. 발행 빈도가 높은 국내 통신사 뉴스가 10칸을 꽉 채워버리니, 서구권 뉴스는 제미나이에게 도달하기도 전에 버려지고 있었던 것. 전송할 양을 slice(0, 20)으로 늘려주는 것으로 일단 대처.

6. 프롬프트 헤더 불일치

마지막으로 템플릿의 조건문 트리거가 불일치하고 있었다. 프롬프트에서는 "[최신 뉴스 헤드라인]이 있으면 요약하라"고 지시해놓고, 정작 데이터를 넘기는 쪽에선 \n\n[Top News]\n라는 헤더를 달아서 보내고 있었으니. AI가 문맥상 눈치껏 처리하긴 했지만, 확실한 조건부 실행을 위해 양쪽의 텍스트를 하나로 통일.


결론 및 회고

단순한 피드 추가 작업이었지만, 이 과정을 통해 모듈 간의 결합도가 높을 때 단일 수정 사항이 전체 파이프라인에 어떤 파급 효과를 일으키는지 생생하게 경험했다.

  1. 데이터 소스가 늘어나면 전체 파이프라인의 Cap과 타임아웃을 재계산할 것. (maxDuration 연장, 배열 slice 범위 스케일업)
  2. 외부 데이터를 다룰 때는 항상 엣지 케이스를 의심할 것. (XML vs HTML, <link> 포맷 차이, 봇 차단 정책 등)
  3. 프롬프트와 데이터 블록의 텍스트는 강타입(Strong Type)처럼 정확히 일치시킬 것.

이제까지의 MarketRadar는 한국 뉴스와 미국 뉴스에 다소 의존하는 경향이 있었는데, 소스의 다양성을 늘렸다.

초기 기획에서 하드코딩(slice(0, 10))해둔 것이 확장을 어떻게 가로막는지 라던가, 시스템 설계 단계부터 유연성을 고려해야 한다는 경험을 얻었다. 이래서 사람들이 나중으로 가면 함수형 프로그래밍으로 가려고 하는 건가.

간단할 줄 알았는데 자기 전에 떠올라서, 막상 수정하다보니 이것저것 손 대게 되어서 이 시간.