problem_solving/MarketRadar

6. 아무튼 내 잘못은 아닌 거 같은데

점심뭐먹지 2026. 4. 19. 00:27

 

0. Intro

가족이 내가 만든 페이지를 그럭저럭 쓰는데, 어느 날 숫자가 안나온다는 이야길 했더랬다.

1. 무슨 상황일까

지금의 내 페이즈는 야후 API를 못 받아오면 아무 로직이 동작할 수 없는 구조라, 빠르게 확인을 했었다.

확인해보니 API를 못받아오는 게 아니라, API를 받아올 때 58초정도나 기다려야 했던 것.

2. 뭐가 문제였을까

문제는 간단했다. 야후 API 서버 상태가 안좋아서, 전반적으로 응답 속도가 느렸던 것.

 

3 days ago가 당시에 다운되었던 상황.

 

이 날 부랴부랴 확인해보니, 다운디텍터에서는 이 날 모든 야후 서비스에서 장애징후를 표시하고 있었다.

 

3. 그러면 왜 58초나?

이건 더 간단했다.

 

이전 코드

    return Promise.all(items.map(([ticker, range, interval]) => {
      const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(ticker)}?interval=${interval}&range=${range}`;
      return fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } })
        .then(upstream => {
          if (!upstream.ok) return { ticker, range, error: upstream.status };
          return upstream.json().then(json => {
            const result = json.chart.result[0];
            return {
              ticker,
              range,
              data: {
                quote: extractQuote(result),
                history: extractHistory(result),
              }
            };
          });
        })
        .catch(e => {
          console.error(`[yahoo] batch error for ${ticker}:`, e.message);
          return { ticker, range, error: 'Upstream Error' };
        });
    }))

 

겉으로 보면 문제는 단순했다.
야후 API 서버 상태가 좋지 않아 응답 속도가 전체적으로 느려진 것.

 

하지만 진짜 문제는 따로 있었다.

나는 58개의 요청을 Promise.all로 한 번에 보내고 있었고,
모든 요청이 끝날 때까지 하나의 응답을 기다리는 구조였다.

 

이 구조에서는

  • 하나라도 느린 요청이 있으면
  • 전체 응답이 같이 지연된다

즉, 평소에는 문제가 없지만
외부 API가 느려지는 순간, 지연이 그대로 증폭되는 구조였던 것.

그래서 단순한 API 지연이
58초짜리 응답 대기로 이어졌다.

 

 

4. 그래서 어떻게 고쳤나

새 코드

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function fetchYahooChart(ticker, range, interval) {
  const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(ticker)}?interval=${interval}&range=${range}`;
  let lastError;

  for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS; attempt += 1) {
    try {
      const upstream = await fetch(url, { headers: { 'User-Agent': YAHOO_USER_AGENT } });
      if (!upstream.ok) {
        lastError = upstream.status;
      } else {
        const json = await upstream.json();
        const result = json.chart?.result?.[0];
        if (!result) {
          lastError = 'No chart result';
        } else {
          return {
            ticker,
            range,
            data: {
              quote: extractQuote(result),
              history: extractHistory(result),
            },
          };
        }
      }
    } catch (e) {
      lastError = e.message;
    }

    if (attempt < MAX_FETCH_ATTEMPTS) {
      await wait(150 * attempt);
    }
  }

  console.error(`[yahoo] fetch failed for ${ticker} ${range}/${interval}:`, lastError);
  return { ticker, range, error: lastError ?? 'Upstream Error' };
}

async function mapWithConcurrency(items, limit, worker) {
  const results = new Array(items.length);
  let nextIndex = 0;

  async function runWorker() {
    while (nextIndex < items.length) {
      const currentIndex = nextIndex;
      nextIndex += 1;
      results[currentIndex] = await worker(items[currentIndex]);
    }
  }

  const workerCount = Math.min(limit, items.length);
  await Promise.all(Array.from({ length: workerCount }, runWorker));
  return results;
}

return mapWithConcurrency(items, BATCH_CONCURRENCY, ([ticker, range, interval]) =>
      fetchYahooChart(ticker, range, interval)
    )

 

 

항목 기존 코드 새 코드
평균 속도 빠를 수 있음 약간 느림
안정성 낮음 매우 높음
실패율 높음 낮음
대량 요청 대응 취약 안정적

 

해결 방법은 단순히 “코드를 고쳤다”가 아니라, 요청 처리 방식을 바꾼 것이다.

기존에는 모든 요청을 한 번에 보내고 전체가 끝날 때까지 기다리는 구조였다면,

 

이제는:

  • 동시 요청 수를 제한하고 (concurrency control)
  • 실패 요청은 재시도하고 (retry)
  • 각 요청을 독립적으로 처리하도록 분리했다
return mapWithConcurrency(items, BATCH_CONCURRENCY, ([ticker, range, interval]) =>
  fetchYahooChart(ticker, range, interval)
)
 
 

이 변화로 인해 달라진 점은 명확하다:

  • 일부 요청이 느려도 전체가 멈추지 않음
  • 일시적인 실패는 자동으로 복구
  • 외부 API 상태에 덜 민감해짐

결과적으로

“빠를 때는 빠르고, 느릴 때는 무너지는 구조”에서 “느려도 버티는 구조”로 바뀌었다.

 

5. 예전에 일하다 있었던 이야기

한 5년 전쯤, 갑자기 전새계에서 로그인이 먹통이 되는 사고가 터진 적이 있었다.

페이스북 인증서버에 문제가 생겨서 소셜 로그인이 안되었던 사건인데, '왜 게임 로그인이 안되요? 라며 문의가 빗발쳤던 기억이 난다. 기다려달라는 양해를 구하는 공지를 올려서 겨우겨우 여론을 진화했었더랬다.

 

서비스를 유지하다보면 보통 만나게 되는 건 이런 장애와 관련된 일이 되곤 한다.

내 문제인가? 하며 찾아봐도 문제가 없는 경우도 가끔 있다.

이번 이슈같은 경우에는 이런 일로 코드 개선이 되었으니 그나마 좀 전화위복인가 싶어졌다.