메뉴 닫기

Flask-Limiter로 사용자별 요청 제한 구현, 429 Too Many Requests 완벽 가이드

Flask-Limiter로 사용자별 요청 제한 구현, 429 Too Many Requests 완벽 가이드

🚦 클릭 몇 번으로 API 남용을 차단하고 신뢰도 높은 Flask 서비스로 업그레이드하세요

요청 폭주가 한 번만 발생해도 작은 Flask 서비스는 금세 응답 속도가 흔들리고, 결국 정상 사용자까지 피해를 볼 수 있습니다.
비용과 인력 투입 없이도 견고함을 더하는 첫 단추가 바로 사용자별 요청 제한입니다.
이 글에서는 Flask 확장인 Flask-Limiter를 사용해 IP 또는 사용자 ID 기준으로 요청 빈도를 제어하고, 한도를 넘길 때 표준 429 Too Many Requests를 반환하는 실전 패턴을 다룹니다.
코드를 복사해 바로 적용할 수 있는 예제 중심으로 정리하되, 운영 환경에서 꼭 고려해야 할 저장소 선택과 우회 방지 팁까지 함께 담았습니다.

단순히 데코레이터 하나 추가하는 수준을 넘어, 엔드포인트별로 다른 제한을 적용하고 인증·비인증 사용자를 분리하여 제어하는 등 실무형 시나리오를 기준으로 설명합니다.
또한 분산 서버에서 일관된 카운팅을 보장하기 위한 Redis 스토리지 연동, 재시도 대기 시간을 알리는 헤더 처리, 클라이언트 친화적인 오류 메시지 구조까지 체크리스트처럼 따라갈 수 있도록 구성했습니다.
불필요한 이론은 덜고, 실제로 장애를 줄이는 데 바로 도움이 되는 내용을 엄선했습니다.



🔗 Flask-Limiter로 사용자별 요청 제한의 개념과 동작 원리

웹 애플리케이션은 동시에 많은 요청이 몰릴 경우 성능 저하나 서비스 중단으로 이어질 수 있습니다.
특히 API 서버는 인증 여부와 상관없이 누구나 접근할 수 있는 엔드포인트가 많기 때문에 악의적인 공격이나 과도한 사용을 방치하면 정상 사용자 경험이 크게 저하됩니다.
이 문제를 해결하기 위해 가장 널리 쓰이는 방법이 요청 제한 (Rate Limiting)입니다.

Flask-Limiter는 Flask 애플리케이션에 손쉽게 요청 제한 기능을 추가할 수 있는 확장 모듈입니다.
핵심은 클라이언트 식별자(예: IP 주소, 사용자 ID, 토큰)를 기준으로 일정 시간 내 요청 횟수를 카운트하고, 설정한 한도를 초과하면 429 Too Many Requests 상태 코드를 반환하는 것입니다.
이 방식은 서버 자원을 보호하고, 특정 사용자가 과도하게 시스템을 점유하는 것을 방지하는 데 효과적입니다.

🚦 429 Too Many Requests란?

429 상태 코드는 HTTP 표준에서 정의된 응답 코드로, 클라이언트가 일정 시간 동안 지나치게 많은 요청을 보냈음을 의미합니다.
이 응답에는 종종 Retry-After 헤더가 함께 포함되어, 클라이언트가 얼마 후 다시 요청을 시도해야 하는지 알려줄 수 있습니다.
따라서 API 소비자에게 명확한 제한 정책을 전달하는 수단으로도 중요합니다.

⚙️ 동작 방식 요약

  • 🔑key_func를 통해 요청자의 고유 식별자를 추출
  • 📊지정된 기간 동안의 요청 횟수를 추적
  • 🚫한도를 초과하면 429 상태 코드 반환
  • ⏱️필요 시 Retry-After 헤더로 재시도 대기 시간 안내

💡 TIP: 요청 제한은 단순히 보안 기능이 아니라, 모든 사용자에게 공평한 서비스 품질을 제공하기 위한 장치이기도 합니다.

🛠️ Flask-Limiter 설치와 기본 설정 key_func 구성

Flask-Limiter는 pip를 통해 간단히 설치할 수 있으며, Flask 앱과 자연스럽게 통합됩니다.
가장 기본적인 설정은 key_func를 이용해 각 클라이언트를 구분하는 기준을 정하는 것입니다.
기본적으로는 요청의 IP 주소를 사용하지만, 로그인한 사용자의 ID나 API 토큰으로도 식별할 수 있습니다.

📦 설치 방법

CODE BLOCK
pip install flask-limiter

설치가 끝나면 Flask 애플리케이션에 확장을 초기화해야 합니다.
예를 들어, 아래와 같은 코드로 쉽게 적용할 수 있습니다.

CODE BLOCK
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
    get_remote_address,  # 기본 key_func (클라이언트 IP 기준)
    app=app,
    default_limits=["100 per hour"]  # 기본 제한 설정
)

@app.route("/api")
@limiter.limit("5 per minute")  # 엔드포인트별 제한
def my_api():
    return "Hello, Flask-Limiter!"

🔑 key_func 활용

기본 함수인 get_remote_address는 요청자의 IP 주소를 반환합니다.
하지만 실제 운영 환경에서는 IP만으로는 충분하지 않을 수 있습니다.
예를 들어 동일한 공유 네트워크(회사, 학교)에서 여러 사용자가 접속할 때 IP 기준 제한은 오탐을 발생시킬 수 있습니다.

이럴 경우 Flask의 세션, 인증 토큰, 또는 JWT의 사용자 ID를 활용해 key_func를 재정의하면 더 정밀한 제어가 가능합니다.

CODE BLOCK
def user_based_key_func():
    return getattr(getattr(request, "user", None), "id", get_remote_address())

⚠️ 주의: key_func를 사용자 ID 기준으로 설정할 경우, 인증되지 않은 사용자에 대해서는 fallback으로 IP를 반환하도록 구성하는 것이 안전합니다.



⚙️ 사용자 ID 기준 제한 구현과 429 응답 커스터마이징

실제 서비스에서는 단순히 IP 주소가 아니라 사용자 ID를 기준으로 요청 제한을 적용하는 경우가 많습니다.
이렇게 하면 같은 네트워크를 사용하는 여러 명의 사용자가 한 명처럼 제한되는 문제를 피할 수 있고, 인증 기반 서비스의 경우 훨씬 더 공정한 제어가 가능합니다.

👤 사용자 ID 기준 key_func 구현

CODE BLOCK
from flask import request
from flask_login import current_user

def user_id_key():
    if current_user and hasattr(current_user, "id"):
        return str(current_user.id)
    return request.remote_addr

위와 같이 key_func를 정의하면, 로그인된 사용자는 ID를 기준으로 제한되고, 비로그인 사용자는 기본적으로 IP 주소를 기준으로 제한됩니다.

🚦 429 Too Many Requests 응답 커스터마이징

기본적으로 한도를 초과하면 Flask-Limiter는 429 상태 코드와 함께 간단한 오류 메시지를 반환합니다.
그러나 실제 서비스에서는 API 사용자나 프런트엔드가 처리하기 쉽게 JSON 구조로 응답을 커스터마이징하는 것이 일반적입니다.

CODE BLOCK
from flask import jsonify
from flask_limiter.errors import RateLimitExceeded

@app.errorhandler(RateLimitExceeded)
def ratelimit_handler(e):
    return jsonify({
        "error": "too_many_requests",
        "message": "요청 한도를 초과했습니다. 잠시 후 다시 시도하세요.",
        "retry_after": e.retry_after
    }), 429

이처럼 응답을 세밀하게 정의하면 API 클라이언트는 재시도 대기 시간을 파악하고, 사용자에게 보다 친절한 안내를 제공할 수 있습니다.

💎 핵심 포인트:
429 응답은 단순 차단이 아니라, 재시도 가능 시점을 알려주는 신호로 활용하는 것이 사용자 경험 개선에 도움이 됩니다.

🔌 Redis 스토리지 연동과 분산 환경 고려사항

단일 서버 환경에서는 메모리에 요청 카운트를 저장해도 문제가 없지만, 여러 대의 서버가 동시에 운영되는 분산 환경에서는 요청 제한이 서버마다 따로 계산될 수 있습니다.
이 경우 특정 서버에서는 제한을 초과하지 않았다고 판단되지만, 전체적으로는 과도한 요청이 발생할 수 있습니다.
이를 방지하기 위해 Flask-Limiter는 Redis 같은 중앙 집중형 저장소를 지원합니다.

🗄️ Redis 스토리지 연동

CODE BLOCK
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from redis import Redis

app = Flask(__name__)
redis_client = Redis(host="localhost", port=6379, db=0)

limiter = Limiter(
    key_func=get_remote_address,
    storage_uri="redis://localhost:6379",
    app=app
)

위 코드처럼 storage_uri에 Redis 주소를 지정하면, 요청 카운트가 모든 서버에서 동일하게 공유됩니다.
따라서 로드밸런싱된 환경에서도 일관된 제한 정책이 유지됩니다.

⚠️ 분산 환경에서 고려해야 할 점

  • 🌐로드밸런서 뒤에 있을 경우, 올바른 클라이언트 IP 추출을 위해 X-Forwarded-For 헤더 처리가 필요
  • 🔄Redis 장애 시 요청 제한 로직도 영향을 받을 수 있으므로 fallback 전략 준비
  • 🛡️Redis 자체에 인증/방화벽을 설정하여 보안 강화

⚠️ 주의: 분산 환경에서 Redis 없이 메모리만 사용하면 요청 제한이 서버별로 따로 계산되어 무력화될 수 있습니다.



💡 엔드포인트별 제한 전략과 우회 방지 팁

모든 API 요청에 동일한 제한을 적용하는 것은 실무에서 적절하지 않을 수 있습니다.
예를 들어 로그인 시도는 엄격히 제한해야 하지만, 공지사항 조회 같은 공개 API는 비교적 여유로운 제한을 설정할 수 있습니다.
따라서 엔드포인트별로 서로 다른 제한 정책을 두는 것이 효과적입니다.

🔐 엔드포인트별 제한 예시

CODE BLOCK
@app.route("/login")
@limiter.limit("3 per minute")
def login():
    return "로그인 시도"

@app.route("/public")
@limiter.limit("100 per minute")
def public_data():
    return "공개 데이터"

이처럼 중요한 API는 낮은 한도로 보호하고, 상대적으로 부하가 적은 엔드포인트에는 더 높은 한도를 적용하는 방식이 바람직합니다.

🛡️ 요청 제한 우회 방지 팁

일부 사용자는 요청 제한을 우회하기 위해 VPN, 프록시, 봇 등을 활용할 수 있습니다.
이를 완전히 차단하기는 어렵지만, 몇 가지 보완 방법으로 효과를 높일 수 있습니다.

  • 🧩CAPTCHA를 로그인, 회원가입 등 중요 엔드포인트에 적용
  • 📊비정상적인 트래픽 패턴 모니터링 및 자동 차단 규칙 설정
  • 🔍User-Agent 및 헤더 기반 필터링으로 단순 봇 탐지
  • 🕵️과도한 요청 발생 시 IP 블랙리스트 또는 Rate 제한 강화

💡 TIP: 단일 Rate Limiting만으로는 모든 남용을 막을 수 없습니다. 모니터링, 로깅, 보안 규칙을 함께 운영해야 효과가 극대화됩니다.

자주 묻는 질문 FAQ

Flask-Limiter는 무료로 사용할 수 있나요?
오픈소스 라이브러리이므로 무료로 사용할 수 있으며, 프로젝트 요구사항에 맞게 자유롭게 커스터마이징이 가능합니다.
429 오류가 발생하면 반드시 Retry-After 헤더를 설정해야 하나요?
필수는 아니지만, 클라이언트가 언제 다시 요청해야 하는지 알 수 있도록 설정하는 것이 권장됩니다.
Redis 없이도 분산 서버에서 요청 제한을 적용할 수 있나요?
이론적으로는 각 서버에 별도 제한을 둘 수 있지만, 일관성을 위해 Redis 같은 중앙 저장소를 사용하는 것이 가장 안전합니다.
로그인하지 않은 사용자는 어떻게 구분하나요?
로그인하지 않은 사용자는 기본적으로 IP 주소를 기반으로 제한이 적용되며, 필요 시 세션 ID 등을 함께 활용할 수 있습니다.
Flask-Limiter와 함께 사용할 수 있는 대체 라이브러리는 무엇이 있나요?
Python 환경에서는 django-ratelimit, slowapi 등 유사한 라이브러리를 사용할 수 있으며, 프레임워크 특성에 맞춰 선택하면 됩니다.
요청 제한 정책을 실시간으로 변경할 수 있나요?
코드에서 제한 값을 동적으로 지정하거나 환경 변수를 통해 관리하면 재배포 없이도 정책을 변경할 수 있습니다.
엔드포인트별 요청 제한과 전체 요청 제한을 동시에 적용할 수 있나요?
가능합니다. 기본 제한(default_limits)을 설정하고 엔드포인트별로 추가 제한을 지정하면 다층적인 제어가 가능합니다.
429 응답을 HTML 페이지로 반환할 수도 있나요?
네, JSON뿐만 아니라 Flask의 템플릿 렌더링 기능을 사용해 사용자 친화적인 HTML 페이지로도 응답을 커스터마이징할 수 있습니다.

📝 Flask-Limiter로 안정적인 API 서비스를 구축하는 방법

Flask-Limiter는 간단한 설정만으로도 사용자별 요청 제한을 구현할 수 있는 강력한 도구입니다.
429 Too Many Requests 응답을 통해 클라이언트에게 명확히 알리고, Redis 같은 중앙 저장소를 연동하면 분산 환경에서도 일관된 제한이 가능합니다.
또한 엔드포인트별 맞춤 전략과 보안 보완책을 적용하면 서비스 남용을 최소화하면서도 사용자 경험을 해치지 않는 균형을 유지할 수 있습니다.

결국 중요한 것은 단순한 차단이 아니라 안정적인 서비스 품질을 보장하는 것입니다.
Flask-Limiter는 개발자가 적은 노력으로 이 목표를 달성할 수 있게 해주는 실전형 도구이자, 꼭 도입해야 할 Flask 확장 중 하나라 할 수 있습니다.


🏷️ 관련 태그 : Flask, FlaskLimiter, 파이썬웹개발, API보안, 요청제한, 429에러, Redis연동, 웹프로그래밍, 서버안정화, 백엔드개발