Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Vercel Edge와 Upstash Redis로 Next.js 속도 제한(Rate Limiting) 구현하기

Vercel Edge 미들웨어와 @upstash/ratelimit 라이브러리를 활용해 웹 애플리케이션에 속도 제한(rate limiting)을 구현하는 과정을 단계별로 소개합니다. @upstash/ratelimit는 내부적으로 Redis를 사용해 요청 제한 데이터를 저장하고 관리합니다.

Vercel Edge를 활용하면 좋은 점

Vercel Edge는 사용자와 가장 가까운 위치에서 연산을 수행하는 컴퓨팅 플랫폼입니다. 이 글에서는 요청이 백엔드에 도달하기 전에 가로채는 Vercel Edge 미들웨어를 사용하는데, 속도 제한 구현에 적합한 이유는 다음과 같습니다.

  • 백엔드와 분리(decoupled)되어 있어, 트래픽이 백엔드에 닿기 전에 엣지 단계에서 차단할 수 있습니다.
  • 응답 속도가 빠르고 콜드 스타트(cold start) 문제가 없어 오버헤드가 거의 없습니다.
  • 서버리스 함수보다 비용 효율적입니다.

@upstash/ratelimit를 선택한 이유

  • @upstash/ratelimit는 엣지 함수 환경을 위해 설계·검증된 전용 속도 제한 라이브러리입니다.
  • 멀티 리전(multi-region) Redis를 지원해 엣지 위치에서도 최적의 지연 시간을 제공합니다.
  • REST API 덕분에 엣지 함수에서 접근할 수 있는 사실상 유일한 관리형 Redis가 바로 Upstash Redis입니다.

그럼 본격적으로 구현해 보겠습니다.

1단계: Redis 설정

Upstash Console 또는 Upstash CLI에서 Redis 데이터베이스를 생성합니다. 이후 단계에서 UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN 값이 필요합니다.

Vercel Edge와 Upstash Redis로 Next.js 속도 제한(Rate Limiting) 구현하기

2단계: Next.js 설정

먼저 Next.js 애플리케이션을 생성합니다. (다른 프레임워크는 해당 공식 문서를 참고하세요.)

npx create-next-app@latest --typescript

@upstash/ratelimit 패키지를 설치합니다.

npm i @upstash/ratelimit

프로젝트 폴더 최상위에 middleware.ts 파일을 생성합니다.

import { NextFetchEvent, NextRequest, NextResponse } from "next/server";

import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const redis = new Redis({
 url: "https://us1-merry-snake-32728.upstash.io",
 token: "AX_sAdsdfsgODM5ZjExZGEtMmmVjNmE345445kGVmZTk5MzQ=",
});

const ratelimit = new Ratelimit({
 redis: redis,
 limiter: Ratelimit.slidingWindow(5, "10 s"),
});

export default async function middleware(
 request: NextRequest,
 event: NextFetchEvent,
): Promise<Response | undefined> {
 const ip = request.ip ?? "127.0.0.1";
 const { success, pending, limit, reset, remaining } =
 await ratelimit.limit(ip);
 return success
 ? NextResponse.next()
 : NextResponse.redirect(new URL("/blocked", request.url));
}

export const config = {
 matcher: "/",
};

Redis URL과 토큰은 반드시 본인의 값으로 교체해야 합니다. 위 코드는 slidingWindow 알고리즘을 사용하며, 동일한 IP에서 10초 동안 최대 5개의 요청만 허용합니다. 사용자 고유 식별자(userId, 이메일 등)가 있다면 IP 대신 해당 값을 사용할 수도 있습니다. 요청이 제한되면 미들웨어는 /blocked 페이지로 리디렉션합니다.

이제 pages/blocked.tsx 파일을 만듭니다.

import styles from "@/styles/Home.module.css";

export default function Blocked() {
 return (
 <div>
 <main className={styles.main}>
 <h3>Access blocked.</h3>
 </main>
 </div>
 );
}

이것으로 끝입니다! 이제 앱을 Vercel에 배포하세요.

vercel deploy

배포 후 페이지를 반복해서 새로고침하면, 일정 횟수를 초과했을 때 /blocked 페이지로 이동하는 것을 확인할 수 있습니다.

캐싱으로 원격 호출 줄이기

요청마다 매번 원격 호출을 수행하는 것은 비효율적입니다. @upstash/ratelimit 패키지의 유용한 기능 중 하나는 엣지 함수가 '활성(hot)' 상태인 동안 데이터를 캐시한다는 점입니다. 즉, 함수가 '비활성(cold)' 상태일 때만 Redis에서 데이터를 가져오므로 원격 호출 횟수가 크게 줄어듭니다. 캐싱은 ratelimit 객체를 핸들러 함수 middleware 외부에 선언하기만 하면 자동으로 적용됩니다.

다양한 속도 제한 알고리즘

앞선 예제에서는 슬라이딩 윈도우(sliding window) 알고리즘을 사용했습니다. 서비스 요구 사항에 따라 고정 윈도우(fixed window), 누출 버킷(leaky bucket) 등 다른 알고리즘을 선택할 수도 있습니다. 각 알고리즘의 특징은 공식 문서를 참고하세요.

멀티 리전 Redis

엣지 함수의 성능을 더욱 향상시키려면 여러 리전에 Redis 데이터베이스를 분산 배치하는 것이 좋습니다. 멀티 리전 Redis 구성 방법 역시 관련 문서에서 자세히 설명하고 있습니다.

참고 자료

  • Next.js(논엣지 환경)에서 속도 제한 구현하기
  • Vercel Edge 미들웨어 공식 문서
  • Ratelimiting SDK 문서