최근(2026-04-07 ~ 2026-04-09)에는 Market Radar를 프로토타입에서 운영 가능한 서비스로 만드는 작업이 대부분이었다.
3/28~3/29에 했던 작업이 보안/성능/기능 추가(RSS, RateLimit, DOMPurify, CORS, 프롬프트 최적화 등)였다면, 이번 구간은
- 외부 의존성(RSS/Yahoo/Gemini) 때문에 생기는 데이터 계약(contract) 문제
- React + Chart.js 경계에서 생기는 UI stale/리렌더링 문제
- “내일 시장 주가”라는 제품 컨셉 때문에 생기는 시간대/거래일 계산 문제
- i18n 확장으로 인해 생기는 상태 꼬임 / stale 결과 문제
- CORS / RateLimit 같은 보안 정책을 실제 트래픽/환경에 맞게 튜닝하는 문제
패치노트 기반으로 정리하되, 실제로는 장애 대응 로그에 가까운 경험이었다.
1. 주로 확인했던 사항들 – 신뢰성, 보안, UX
1-1. 신뢰성(Reliability): 외부 데이터는 ‘항상 정상’이라는 가정이 깨진다
(1) RSS pubDate가 없을 때 “0으로 박아넣는” 게 더 큰 버그를 만든다
문제: RSS 뉴스 수신을 붙여놓고 보니, 모든 피드가 pubDate를 정상적으로 주는 게 아니었다. 파싱이 실패하거나 아예 없는 경우도 있었다.
처음에는 “없으면 0으로 두자” 같은 식으로 임시처리를 하게 되는데, 이게 나중에 UI/정렬 로직에서 오히려 더 큰 버그가 된다.
- 0은 “값이 없는 상태”가 아니라 “유효한 숫자”처럼 취급된다
- 정렬/표시에 개입하면서 “시간이 1970년으로 표시”되거나
- “최신 뉴스” 정렬이 깨질 수도 있다
해결: pubDate를 억지로 0으로 채우는 걸 제거하고, “없는 값은 없는 값(옵셔널)”로 Contract를 명확히 했다.
UI는 “시간 모르면 표시하지 않는다”로 확정.
(2) “내일”은 날짜가 아니라 ‘다음 거래일(next trading day)’이었다
문제: 이 프로젝트는 '내일의 주가를 예상한다'가 포인트였는데, 새벽 시간대에 접속하면 접근하면 AI가 모레를 분석하는등, 날짜 컨텍스트가 꼬일 수 있었다.
원인은 단순했다.
- today + 1 day는 캘린더 상 내일이지만,
- “시장” 관점의 내일은 다음 거래일이다.
- 주말/휴장/시간대까지 섞이면, 단순한 날짜 계산은 제품 컨셉을 깨뜨린다.
해결: 거래일 컨텍스트를 명시적으로 계산하는 로직을 추가했고, Gemini 프롬프트에서도 “tomorrow”를 실질적으로는 nextTradingDay 라는 의미로 바꿨다.
이건 AI 정확도 문제처럼 보이지만, 사실은 도메인 정의(내일 = 거래일) 문제였고, 제품 컨셉을 코드에 정확히 반영한 케이스였다.
무엇보다도 도메인 지식이 명확히 없어서 생긴 문제.
이 문제는 무엇보다도 문제를 정의하며 데이터 처리할 때의 타임존 기준을 KST로 맞추는 계기가 되었다.
어차피 한국 시장 관련 웹사이트라, 한국 시장의 시간에 맞추는 게 더 타당하기 때문이었다.
(3) API/AI가 깨졌을 때를 대비한 “계약 기반 테스트”를 넣기 시작했다
문제: Yahoo / RSS / Gemini는 언제든 깨질 수 있다. 응답이 느려질 수도 있고, 일부 필드가 빠질 수도 있고, 사용자가 탭을 바꿔서 요청이 중간에 취소(abort)될 수도 있다.
운영 관점에서 진짜 중요한 건 “성공 케이스가 잘 되냐”가 아니라:
- 실패했을 때 UI 상태가 꼬이지 않는지
- 부분 실패가 전체 실패로 번지지 않는지
- 한 번 고친 계약이 다음 리팩터링에서 다시 깨지지 않는지
해결: 뉴스/야후/Gemini 경계에서 계약 테스트를 추가하고, abort 이후 상태 잔류를 줄이는 식으로 신뢰성 작업을 계속했다.
1-2. UX(Frontend): 차트/상태는 “가끔”이 제일 잡기 어렵다
(1) ticker/기간 전환 시 차트가 stale(이전 캔버스)로 남는 문제
문제: 대시보드에서 ticker나 기간을 바꾸면, Chart.js 캔버스가 가끔 이전 상태를 유지하는 현상이 있었다.
state는 바뀌었는데, Chart.js 내부 인스턴스가 재사용되면서 “정확히 다시 그려지지 않는” 상황.
이런 버그가 무서운 이유는:
- 100% 재현이 아니라 “가끔” 발생한다 -> 재현 불가 이슈. 어쩌다 한번 본 게 운이 좋은 것.
- 사용자는 “데이터가 틀렸다”고 느끼게 된다 (신뢰도 하락)
해결: 선택된 ticker/period 조합이 바뀔 때 차트 컴포넌트를 확실히 remount되게 만들고, 회귀 테스트도 추가했다.
state는 '다시 꺼내 쓰기 위해서는' 좋지만, 그 state가 잘 업데이트 되었는지도 체크해야
1-3. 보안/정책 튜닝(Security): CORS/RateLimit은 ‘정답’이 아니라 ‘운영에 맞춘 정책’이었다
(1) CORS 중앙화 이후에 생긴 “너무 엄격해서 정상적인 요청도 막는” 문제
문제: 3/29에 CORS allowlist를 손봤지만, 시간이 지나면서 엔드포인트가 늘고 정책이 복잡해지니 CORS 처리도 공통화가 필요해졌다.
다만 공통화는 편해지지만, “정책 튜닝 실패”의 영향 범위가 커진다.
- ALLOWED_ORIGIN이 빠져있으면 어디까지 막아야 하는가?
- Origin 헤더가 없는 요청은 무조건 막아야 하는가?
- strict 정책을 모든 API에 적용하면, 정상 트래픽까지 같이 죽지 않는가?
해결 흐름:
- CORS를 helper 코드를 작성해서 중앙화하고, 에러 응답도 sanitize
- 설정 누락 시 fail-closed 도입
- 어차피 뉴스나 API 정보는 서버에서 요청중. 때문에 범위를 Gemini 요청으로만 제한
- Origin 없는 same-origin 스타일 트래픽 허용
한 줄로 요약하자면:
“보안 정책은 한 번 커밋하고 끝나는 게 아니라, 실제 요청 패턴과 충돌하면서 계속 다듬게 된다.”
(2) RateLimiter(KV)가 죽으면 fail-open이냐 fail-closed냐
문제: RateLimit은 “있다/없다”보다 “장애 시 어떻게 행동하냐”가 중요했다.
KV가 일시적으로 실패했을 때 fail-open으로 두면, 그 순간부터 API 비용이 그대로 터질 수 있다.
vercel 에서 제공하는 redis의 콜은 꽤 넉넉한 양이라 사실 어지간해서는 죽을 일이 없지만, 그게 죽을 정도로 과도한 요청이 '만약' 들어온다면, fail-open 해뒀다간 제미나이 요금폭탄으로 이어질 수도 있을 것이다.
해결: 운영 관점에서 비용/남용 리스크가 더 크다고 판단해서, RateLimiter가 불안정하면 요청을 막는 방향(fail-closed)으로 선회했다.
2. (추가) 기능 확장: i18n을 붙이면서 상태 관리가 더 어려워졌다
2-1. i18n(KO/EN/JP) 기능 추가
Added: 다국어(ko/en/ja) 기능 추가
- UI 문구 사전화
- 언어 스위처 + 로컬스토리지 저장
- Gemini 프롬프트도 선택 언어로 생성 (RSS 원문은 그대로 유지)
2-2. 언어 변경 시 Gemini 분석 결과가 남아있는(stale) 문제
문제: 언어를 바꾸면 UI만 바뀌는 게 아니라 Gemini 프롬프트 언어 자체가 바뀐다.
근데 그 순간에도 기존 요청이 진행중이면:
- 이전 언어 응답이 늦게 도착해서 UI를 덮어쓰거나
(일본어로 요청 날렸더니 4개 분석중 1개가 한국어로 되어있다던가) - loading/error 상태가 꼬이면서 UX가 무너질 수 있다
해결: 언어 변경 시 in-flight 요청 abort + 기존 결과/에러/로딩 초기화로 “언어 변경은 분석 결과 무효화”를 명시적으로 만들었다.
이 부분은 기능 추가가 아니라 state를 정리하는 작업에 가까웠다.
3. 회고
배포는 끝이 아니라 시작이라는 말이 흔한데, 이번에는 그 말이 좀 더 현실적으로 다가왔다.
- 외부 데이터는 형식이 뒤죽박죽이다. “이걸 어떻게 안전하게 실패할지”가 곧 품질이다.
- 보안은 기능이 아니라 정책이다. 정책은 운영 환경에서 계속 튜닝된다.
- “가끔 발생하는 UI 버그”는 기능 결함이 아니라 신뢰도 결함이다.
- i18n은 번역 문제가 아니라 상태/캐시/요청 무효화 문제까지 같이 온다.
혼자 개발하다보니 좀 엉성하기도 하고, 아무래도 프로토타이핑을 해가며 이것저것 추가하다보니 전체적으로 기획을 조망하기가 쉽지가 않았다. 금융이나 경제쪽 도메인 지식도 적었던 편이라, 아무래도 거기서 생기는 이슈도 있었고.
중간중간 생산해낸 문서들도 최신화 하고, 이슈 트래킹 해나가며 기능추가나 개선, 버그 수정을 하고 있다.
CLI로 개발할 때는 눈 앞에 있는 문제들을 바로바로 해치우기엔 좋지만, 간트차트 같은 게 아무래도 전체적으로 스프린트를 조망하기에는 더 좋은 느낌이라는 인상을 받고 있다.
다국어 기능은 일본쪽에 글을 하나 썼더니 '아무래도 외국 사람들이 와서 보는 건 좀 불편할 수도 있겠다' 싶어서 급하게 집어넣은 것이었다. Thymeleaf 환경에서 다국어화를 해본적도 있는데, 프론트에서 처리하는 건 좀 더 신경쓸 게 많다는 느낌이라, 이것저것 확인하고 수정하는 데에 오래 걸렸다.
개인적인 감상으로는 초단기 기획 이후 구현, 배포, 운영 순으로 이어지고 있는데,
배포 이전까지의 단계가 너무 짧았어서 (거의 이틀도 안되었으니) '이래도 되는 건가?' 싶을 정도였다.
프론트쪽 기능은 일단 안정화가 되었으니, 이젠 서버 붙여서 Vercel 걷어내고 클라우드 배포로 가야지.
'problem_solving > MarketRadar' 카테고리의 다른 글
| 6. 아무튼 내 잘못은 아닌 거 같은데 (0) | 2026.04.19 |
|---|---|
| 4. RSS를 깎자 (0) | 2026.04.02 |
| 3. 프론트엔드 고도화 (0) | 2026.04.01 |
| 2. 배포 이후 수행했던 것들 (0) | 2026.03.30 |
| 1. 바닐라 JS로 지수 분석 페이지 만들다 생겼던 문제들 (0) | 2026.03.29 |