API 요청 최적화

성능 병목은 API 호출에서 시작된다

대부분의 웹 애플리케이션은 API 기반으로 움직인다.

컴포넌트가 많아지고 상태가 복잡해질수록, 같은 데이터를 여러 컴포넌트가

동시에 요청하는 일이 발생한다.

예를 들어,

A 컴포넌트가 유저 프로필 요청,

B 컴포넌트가 유저 알림 요청,

C 컴포넌트가 프로필 하위 정보 요청

이때 프로필 정보가 서로 다른 위치에서 중복 호출되면

다음과 같은 문제가 발생한다.

  • 서버 트래픽 낭비
  • 응답 지연
  • 사용자 체감 성능 저하

그래서 API 요청 최적화는 성능 튜닝의 시작점이 된다.

중복 요청 방지(Deduplication)

React의 라이프사이클 특성상, 여러 컴포넌트가 같은 시점에 같은 API를 호출할 수 있다.

이런 상황을 해결하려면 다음과 같은 방법이 있다.

  • SWR / React Query의 요청 Deduplication

    동일한 키로 요청하면 내부적으로 캐시를 공유, 중복 네트워크 호출을
    차단한다.
    const { data: user } = useSWR('/api/user', fetcher);
    const { data: profile } = useSWR('/api/user', fetcher);
    → 두 컴포넌트가 동시에 호출해도 네트워크는 1회만 발생한다
  • Axios 요청 취소(Cancellation Token)

    요청이 중복되었거나, 컴포넌트가 언마운트된 경우 기존 요청을
    취소한다. 중복 요청 방지는 불필요한 네트워크 소모를 막고 UI적으로 속도 저하를 해소.
    const controller = new AbortController();
    axios.get('/api/data', { signal: controller.signal });
    controller.abort();

캐싱(Caching)

캐싱?

캐시는 한 번 불러온 데이터를 일정 시간 재사용하는 전략,

API 캐싱에는 서버 캐시, CDN 캐시, 클라이언트 캐시가 있다.

프론트엔드에서는 보통 클라이언트 캐시를 다룬다.

캐싱을 통한 전략

  • SWR / React Query의 캐싱

    key 기반으로 데이터 저장 → 같은 요청 시 즉시 반환.

    TTL 설정이 가능하다(staleTime, cacheTitme)


    *SWR(Stale-While-Revalidate)

    캐시된 데이터를 즉시 보여주고, 백그라운드에서 최신화.
    useSWR('/api/posts', fetcher, { staleTime: 10000 });
  • 브라우저 캐시 활용

    Cache-Control, Etag 헤더를 사용하면 서버 응답을 브라우저가
    저장할 수 있음. React-Query는 브라우저 탭 간 캐시 공유도 지원한다.
  • IndexedDB / localStorage 캐시

    오프라인 지원이 필요한 경우, 클라이언트 영구 저장소를 활용.
    SWR + IndexedDB 커스텀 스토리지 패턴으로 자주 사용된다.


배칭(Batching)

배칭 요청

여러 개의 작은 요청을 합쳐 한 번에 전송하면 네트워크 효율이 크게 향상됨.

예를 들어,

  • GraphQL Batching
    GraphQL에서는 여러 쿼리를 한 번의 POST로 묶을 수 있다.
[
  { "query": "{ user { name } }" },
  { "query": "{ posts { title } }" }
]

→ 서버는 여러쿼리를 한 번의 POST로 묶을 수 있음.

  • Custom Batch API
    REST API라도, 백엔드가 /batch 엔드포인트를 제공하면 다음처럼
    묶을 수 있다.
    {
    "requests": [
    { "url": "/api/user", "method": "GET" },
    { "url": "/api/notifications", "method": "GET" }
    ]
    }

  • 브라우저 요청 지연 처리
    일정 시간동안 요청을 모아서 보낸다.
    e.g. 입력 자동완성, 검색 제안 등 → debounce or throttle로 네트워크 호출 감소.

병렬 vs 직렬 요청 최적화

병렬 요청

서로 의존하지 않는 요청은 Promise.all로 병렬 처리, 렌더링 블로킹 시간을 단축할 수 있다.

const [user, posts] = await Promise.all([
  fetch('/api/user'),
  fetch('/api/posts')
]);

직렬 요청

앞 요청 결과가 다음 요청의 파라미터로 필요한 경우 순차 진행

단, React Query에서는 enabled 옵션을 통해 의존적 요청을 깔끔하게 처리 가능.

const { data: user } = useQuery('user', fetchUser);
const { data: posts } = useQuery(['posts', user?.id], fetchPosts, {
  enabled: !!user
});

정리하자면

  • 중복 요청 방지: SWR / React Query deduping
  • 최신성 유지: Stale-While-Revalidate, revalidateOnFocus
  • 응답 속도 향상:캐싱, prefetching
  • 서버 부하 완화: 배치 요청, debounce
  • UX 개선: optimistic update, background sync

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다