TypeScript

Supabase 인증 요청 최적화

해보구 2025. 4. 30. 17:08

Supabase 인증 요청 최적화로 분당 3,000회에서 5회로 줄인 과정

최근 프로젝트에서 Supabase를 사용해 인증 기능을 구현했는데, 갑작스럽게 분당 3,000회의 인증 요청이 발생하면서 429 Too Many Requests 오류가 터졌다. 무료 플랜의 속도 제한 때문에 서비스가 불안정해졌고, 이를 해결하기 위해 인증 로직을 뜯어고쳤다. 결국 요청을 분당 5회로 줄이는 데 성공했는데, 그 과정을 기록으로 남긴다.

문제 발견

처음 문제를 발견한 건 Supabase 대시보드의 로그였다. /auth/v1/token 엔드포인트로 요청이 폭주하고 있었고, 10분 동안 30,000회의 요청이 발생했다는 기록이 남아 있었다. 클라이언트 측에서 supabase.auth.getSession() 호출이 반복적으로 실행되는 게 주요 원인으로 보였다. 특히, Next.js 프로젝트에서 useAuth 훅과 AuthProvider가 인증 상태를 확인하는 과정에서 불필요한 요청을 유발하고 있었다.

코드를 살펴보니 useAuth 훅에서 useEffect 내부의 getSession() 호출과 onAuthStateChange 이벤트가 세션 갱신을 자주 트리거했다. 또한, 서버 측에서도 RootLayoutpage.tsx에서 각각 세션을 조회해 중복 요청이 발생했다. 이 문제를 해결하기 위해 요청을 최적화하는 작업에 착수했다.

해결 과정

1. 서버 측 세션 캐싱

먼저 서버 측에서 세션 조회를 줄였다. RootLayout에서 supabase.auth.getSession()을 호출하고 있었는데, Next.js의 cache 유틸리티를 사용해 세션 조회를 캐싱했다. 이렇게 하면 페이지 로드 시마다 반복 호출되던 요청이 단일화된다. 코드는 다음과 같이 수정했다.

 

// app/layout.tsx
import { cache } from 'react';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { Database } from '../types/supabase';

const getCachedSession = cache(async () => {
  const supabase = createServerComponentClient<Database>({ cookies });
  const { data: { session }, error } = await supabase.auth.getSession();
  if (error) {
    console.error('세션 조회 오류:', error);
    return null;
  }
  return session;
});

page.tsx에서도 세션을 직접 조회하도록 수정해 클라이언트 측 의존성을 줄였다.

 

2. 클라이언트 측 요청 최적화

클라이언트 측에서는 useAuth 훅이 문제의 핵심이었다. useEffect에서 초기 세션 확인을 위해 getSession()을 호출하고, onAuthStateChange가 세션 갱신을 트리거했다. 특히, initialSession이 제공되었음에도 불구하고 추가로 getSession()을 호출하는 로직이 비효율적이었다.

이를 해결하기 위해 initialSession을 우선 사용하고, 세션이 없을 경우에만 getSession()을 호출하도록 수정했다. 또한, 429 오류 발생 시 10초 재시도 간격을 두는 refreshSession 함수를 추가했다. 수정된 useAuth 훅의 핵심 부분은 다음과 같다.

// hooks/useAuth.ts
const refreshSession = useCallback(
  async (retryDelay = 0): Promise<void> => {
    if (retryDelay) {
      await new Promise((resolve) => setTimeout(resolve, retryDelay));
    }
    try {
      const { data: { session }, error } = await supabase.auth.getSession();
      if (error) {
        if (error.status === 429) {
          console.warn('429 Too Many Requests, 10초 후 재시도...');
          return refreshSession(10000);
        }
        throw error;
      }
      setUser(session?.user || null);
    } catch (error: any) {
      console.error('세션 갱신 오류:', error.message);
    }
  },
  [supabase.auth]
);

useEffect(() => {
  if (initialSession?.user) {
    setUser(initialSession.user);
  } else {
    refreshSession();
  }
  // ...
}, [searchParams, initialSession, supabase.auth, refreshSession]);

 

 

3. AuthProvider 최적화

AuthProvider에서도 초기 세션을 받아 사용하도록 수정했다. 이전에는 클라이언트 측에서 추가적인 getSession() 호출을 유발할 가능성이 있었지만, initialSession을 활용해 요청을 줄였다.

// contexts/AuthContext.tsx
export function AuthProvider({
  initialSession,
  children,
}: {
  initialSession: Session | null;
  children: React.ReactNode;
}) {
  const [session, setSession] = useState<Session | null>(initialSession);
  const supabase = createClientComponentClient<Database>();

  useEffect(() => {
    setSession(initialSession);
    const { data: { subscription } } = supabase.auth.onAuthStateChange((event, newSession) => {
      console.log('AuthProvider onAuthStateChange:', event);
      setSession(newSession);
    });
    return () => subscription.unsubscribe();
  }, [supabase.auth, initialSession]);

  return (
    <AuthContext.Provider value={{ session }}>
      {children}
    </AuthContext.Provider>
  );
}

 

 

4. 디버깅과 검증

수정 후 브라우저의 Network 탭과 Supabase 대시보드 로그를 확인했다. /auth/v1/token 요청이 분당 3,000회에서 5회로 급격히 줄었다. onAuthStateChange 이벤트의 로깅을 통해 세션 갱신이 필요한 경우에만 요청이 발생하는 것을 확인했다. 429 오류도 더 이상 발생하지 않았다.

회고

이번 작업은 Supabase의 인증 요청 문제를 해결하면서 성능 최적화의 중요성을 다시금 깨닫게 했다. 처음에는 단순히 클라이언트 측 코드만 문제라고 생각했지만, 서버와 클라이언트 간의 세션 관리 흐름을 전체적으로 점검해야 했다. 특히, cache를 사용한 서버 측 최적화와 429 오류 처리 로직이 큰 효과를 발휘했다. 

아쉬운 점은 초기 디버깅 과정에서 요청의 정확한 원인을 파악하는 데 시간이 걸린 것이다. useAudioAnalysisuseKeywords 훅이 추가 요청을 유발할 가능성을 배제했지만, 처음부터 Network 탭을 꼼꼼히 확인했다면 더 빠르게 문제를 좁힐 수 있었을 것이다. 앞으로는 로그와 모니터링 도구를 적극 활용해 문제를 신속히 진단해야겠다고 생각했다.

 

이번 경험을 통해 Supabase의 속도 제한과 인증 흐름에 대한 이해가 깊어졌다. 무료 플랜의 제약을 고려해 요청을 최소화하는 설계가 중요하다는 점을 배웠고, 캐싱과 재시도 로직이 성능 개선에 얼마나 큰 역할을 하는지도 체감했다. 다음 프로젝트에서는 초기 설계 단계부터 이런 최적화를 염두에 두고 진행할 계획이다.