Neon 컴퓨팅 헤드룸 유지를 위한 TVView 자동 새로고침 최적화
요약
Neon 데이터베이스의 scale-to-zero 기능을 방해하던 대시보드의 과도한 폴링 주기를 최적화하여 컴퓨팅 비용을 절감하고 시스템 안정성을 확보했습니다. 클라이언트 측의 새로고침 간격을 30초에서 최대 15분으로 단계적으로 늘려 불필요한 쿼리 부하를 제거했습니다.
핵심 포인트
- 과도한 폴링이 Neon의 scale-to-zero를 방해하여 비용 상승 유발
- react-query 캐싱만으로는 데이터베이스 쿼리 트리거 문제를 해결할 수 없음
- 새로고침 간격 조정을 통해 컴퓨팅 헤드룸 확보 및 비용 최적화 달성
- 사용자 경험(UX)과 인프라 비용 사이의 적절한 균형점 도출
Neon 컴퓨팅 헤드룸 유지를 위한 TVView 자동 새로고침 최적화
요약 (TL;DR):
최근 발생한 장애 상황에서 scale-to-zero(제로 스케일링) 실패를 방지하기 위해, /tv 대시보드의 새로고침 간격을 30초에서 5분으로, 그리고 5분에서 15분으로 늘려 불필요한 Neon 컴퓨팅 사용을 줄였습니다. 이 변경 사항은 src/features/tv/TVDashboard.tsx에서 상수 하나를 교체하는 작업이었지만, 비용과 신뢰성 측면에서 측정 가능한 영향을 미쳤습니다.
문제 상황
최근 Neon 장애 발생 중 /tv 대시보드가 30초마다 데이터베이스를 폴링(polling)하고 있었습니다. Neon의 오토스케일링(autoscaling) 정책은 지속적인 높은 쿼리율을 데이터베이스가 계속 활성화 상태(warm)를 유지해야 한다는 신호로 해석하여, scale-to-zero(제로 스케일링)를 방지합니다. 이로 인해 다음과 같은 문제가 발생했습니다:
- 더 높은 컴퓨팅 비용: Neon이 필요한 것보다 더 오래 컴퓨팅 인스턴스를 활성화 상태로 유지했습니다.
- 잠재적인 스로틀링 (throttling): 데이터베이스가 초당 요청 할당량(quota)에 도달했습니다.
- 불필요한 지연 시간 (latency): 클라이언트가 필요 이상으로 자주 오래된 데이터를 받게 되었습니다.
증상은 Neon의 과금 급증과 로그에 나타난 “높은 활동으로 인해 scale-to-zero가 방지됨”이라는 경고였습니다.
처음 시도했던 방법들
처음에는 react-query의 stale-while-revalidate 전략을 사용하여 클라이언트 측에서 **결과를 캐싱(cache)**하려고 시도했습니다. staleTime을 5분으로 추가하고 refetchInterval을 30초로 설정했습니다. 하지만 react-query는 캐시 상태와 관계없이 자동으로 다시 가져오기(refetch)를 수행하기 때문에 여전히 30초마다 데이터베이스 쿼리가 트리거되었습니다. 로그에는 여전히 높은 쿼리 수가 표시되었습니다.
다음으로, 자동 새로고침을 완전히 비활성화하고 사용자가 "새로고침" 버튼을 클릭하도록 하는 방법을 시도했습니다. 이 방법은 백그라운드 부하를 제거했지만, 대시보드가 오래된 데이터처럼 보여 사용자들이 불만을 제기하는 등 사용자 경험(UX)이 좋지 않았습니다.
마지막으로, 새로운 데이터를 캐시 저장소에 쓰는 서버 측 크론 잡(cron job)으로 폴링 로직을 이동하는 것을 고려했습니다. 이는 새로운 인프라 구성 요소가 필요하고 복잡성을 증가시키기 때문에, 클라이언트 측 폴링을 유지하되 그 빈도를 줄이기로 결정했습니다.
구현 사항
핵심 변경 사항은 src/features/tv/TVDashboard.tsx에 정의된 단일 상수였습니다. 원래 파일에는 다음과 같이 정의되어 있었습니다:
// src/features/tv/TVDashboard.tsx
const REFRESH_MS = 30_000; // 30초 — 데이터는 수동 동기화 시에만 변경되므로, 더 빠르게 폴링(polling)할 필요 없음
저는 이를 두 번 업데이트했습니다:
- 1차 상향 (30초 → 5분) – Neon이 zero-scale(스케일링 투 제로)로 전환되는 것을 방해하던 공격적인 폴링을 중단하기 위해:
// src/features/tv/TVDashboard.tsx
const REFRESH_MS = 300_000; // 5분 — 데이터는 수동 동기화 시에만 변경되므로, 더 빠르게 폴링(polling)할 필요 없음
- 2차 상향 (5분 → 15분) – 장애 발생 이후 Neon에 더 많은 헤드룸(headroom)을 제공하기 위해:
// src/features/tv/TVDashboard.tsx
const REFRESH_MS = 900_000; // 15분 — 데이터
useClock 훅과 인터벌(interval)을 설정하는 useEffect는 이미 REFRESH_MS를 사용하도록 연결되어 있었습니다:
useEffect(() => {
const interval = setInterval(() => {
setClock(Date.now());
...
이 훅은 로컬 시계(local clock)만 업데이트하기 때문에, 대시보드는 매 REFRESH_MS마다 리렌더링(re-render)되지만, 사용자가 수동 동기화를 시작하거나 데이터가 만료(stale)되지 않는 한 새로운 API 호출을 트리거하지 않습니다.
전체 Diff 하이라이트
Commit f5c490c4 — perf: bump /tv refresh to 15min (from 5min)
[modified] src/features/tv/TVDashboard.tsx (+1/-1)
@@ -42,7 +42,7 @@ interface DashboardData {
...
파일의 나머지 부분은 수정되지 않았습니다. 추가적인 의존성(dependencies)은 추가되지 않았으며, 이 변경 사항은 완전히 하위 호환성(backward-compatible)을 유지합니다.
핵심 요점 (Key Takeaway)
데이터베이스의 오토스케일링(autoscaling) 정책이 쿼리 빈도에 민감할 때, 클라이언트 측 폴링(polling) 간격의 단일 상수 변경만으로도 컴퓨팅 비용을 극적으로 줄이고 신뢰성을 향상시킬 수 있습니다.
복잡한 캐싱(caching)이나 서버 측 작업(server-side jobs)을 추가하는 대신, 데이터의 자연스러운 갱신 주기(refresh cadence)를 평가하고 이를 데이터베이스의 스케일링 동작과 일치시키십시오. 수동 동기화 시에만 변경되는 대부분의 대시보드에는 5분 간격만으로도 충분할 수 있습니다.
다음 단계 (What's Next)
- 시각적 표시기 추가 (Add a visual indicator): 사용자가 데이터가 최신 상태임을 알 수 있도록 마지막 새로고침 시간을 표시합니다.
- 지수 백오프 (Exponential backoff) 구현: 데이터베이스에 부하가 많이 걸려 있을 때 Neon에 과도한 요청을 보내는 것을 방지하기 위해, 수동 동기화 실패 시 지수 백오프를 적용합니다.
- 텔레메트리 (Telemetry) 자동화: 시간당 새로고침 횟수를 모니터링 대시보드에 기록하여, 성능 저하(regressions)를 조기에 발견할 수 있도록 합니다.
이러한 단계들은 대시보드의 응답성을 유지하고, 비용 효율적이며, 향후 발생할 수 있는 스케일링 문제에 대해 견고하게 만들어 줄 것입니다.
vibecoding #buildinpublic #nextjs #neon #reactquery #typescript #performance
제 Build in Public 시리즈의 일부입니다 — 멕시코 플라야 델 카르멘(Playa del Carmen)에서 SaaS 프로젝트를 구축하는 실제 과정을 공유합니다.
Repo: zaerohell/tvview · 2026-08-03
#playadev #buildinpublic
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기