개발 slecs

레이트 리밋과 블랙리스트 자동 차단으로 운영 안정성 강화

목차

완료/에러 키워드 감지 regex를 통합하고 내부 클래스 쪽 로직을 손봤다. 수정 범위만 보면 작은 변경인데, 이게 레이트 리밋 및 블랙리스트 자동 차단 흐름과 맞물려 있어서 생각보다 신경 쓸 게 많았음.

감지 로직이 제대로 동작하지 않으면, 공격성 요청을 잡아내지 못하고 그냥 흘려보내거나, 반대로 정상 요청을 에러로 분류해서 엉뚱하게 블랙리스트에 넣는 문제가 생긴다. 이번 수정의 핵심도 거기에 있었음. regex 패턴이 분산되어 있으면 케이스 하나 놓치기 쉽고, 통합해두면 유지보수도 편해진다.

버그 재현에서 배포까지

이번 수정 루트는 재현 → 원인 파악 → 최소 범위 수정 → 배포 순서였다. 버그를 발견했을 때 가장 먼저 하는 건 재현 가능한 케이스를 확보하는 것. 재현이 안 되면 원인이 맞아도 수정 후 검증을 못 함.

원인이 내부 클래스 안에 있었던 만큼, 외부 인터페이스는 건드리지 않고 내부 동작만 고쳤다. 이런 상황에서 변경 범위를 넓히면 리스크만 커진다. 특히 운영 중인 시스템이면 더.

수정하다 보면 자주 마주치는 패턴들이 있음:

패턴 증상
null 체크 순서 오류 NPE, 이후 로직 전부 스킵
경로 예외 처리 누락 필터에서 정상 요청 차단
YAML 공백 혼용 설정값 파싱 실패, 기본값으로 폴백
import 누락 컴파일 에러, 배포 실패
regex 패턴 분산 케이스 누락, 감지 오탐

이번 케이스는 regex 관련이었고, 감지 로직이 여러 클래스에 흩어져 있던 게 문제였음. 로그 보면서 원인 찾고 수정하는 과정이 루틴처럼 굳어가고 있다는 건 좋은 신호다. 반복되는 패턴에 익숙해지면 진단 속도가 눈에 띄게 빨라짐.

레이트 리밋, 어디에 맞춰야 하는가

레이트 리밋을 설계할 때 "얼마나 많이 막을 것인가"에만 집중하면 틀린다. 진짜 질문은 "정상 사용자를 막지 않으면서 공격을 차단할 수 있는가"임.

임계값을 너무 타이트하게 잡으면 정상 트래픽도 잘려나간다. 특히 배치 작업이나 API 클라이언트가 짧은 구간에 여러 요청을 몰아서 보내는 경우, 보호 대상을 오히려 막는 상황이 생김. 반대로 너무 느슨하게 잡으면 DDoS성 요청 앞에서 아무 의미가 없다.

Redis 기반 슬라이딩 윈도우 방식을 쓰는 이유 중 하나가 고정 윈도우 방식에서 생기는 경계 구간 버스팅 문제를 피할 수 있어서다. 고정 윈도우는 윈도우가 넘어가는 순간 카운터가 리셋되기 때문에, 경계 직전 + 직후로 요청을 나눠서 보내면 실제로는 두 배 가까운 요청이 통과되는 케이스가 생긴다.

# 슬라이딩 윈도우 핵심 아이디어 (Redis + sorted set 기반)
import time

def is_rate_limited(redis_client, key, limit, window_sec):
    now = time.time()
    window_start = now - window_sec

    pipe = redis_client.pipeline()
    pipe.zremrangebyscore(key, 0, window_start)   # 만료된 항목 제거
    pipe.zadd(key, {str(now): now})               # 현재 요청 기록
    pipe.zcard(key)                               # 현재 윈도우 내 요청 수
    pipe.expire(key, window_sec)
    results = pipe.execute()

    return results[2] > limit

단, Redis가 단일 장애점이 되지 않도록 연결 풀 크기와 타임아웃은 반드시 같이 잡아야 한다. Redis가 느려지거나 다운됐을 때 레이트 리밋 로직 자체가 막히면 전체 서비스에 영향이 간다. 연결 타임아웃을 짧게 잡고, Redis 장애 시 폴백 동작(허용으로 처리할지, 차단으로 처리할지)을 정책으로 정해두는 게 맞음.

블랙리스트 자동 등록 구조

공격성 요청을 감지한 뒤 수동으로 대응하면 속도가 너무 느리다. 봇이나 스캐너는 초 단위로 요청을 쏴대는데, 담당자가 슬랙 알림 보고 콘솔 접속해서 차단하는 동안 이미 수천 건이 지나간다.

DB에 자동 등록하고 다음 요청부터 즉시 차단하는 구조를 쓰면 실시간 대응이 가능하다. 흐름 자체는 단순함:

  • 요청 들어옴
  • 레이트 리밋 초과 또는 감지 로직에서 공격성 패턴 판정
  • DB에 IP/토큰/식별자 자동 등록
  • 이후 요청은 필터 단에서 즉시 차단, 서비스 레이어 도달 전에 반환

자동 등록 구조에서 신경 써야 할 점들:

  • 오탐 처리: 로드 테스트, 크롤러, 사내 배치가 봇으로 오해받으면 곤란하다. 화이트리스트 IP 대역이나 특정 User-Agent를 예외로 빼두는 게 기본임.
  • TTL 설정: 영구 차단은 오탐 복구가 어렵다. TTL을 넣어두면 잘못 등록된 경우 일정 시간 후 자동으로 풀린다. 심각도에 따라 TTL을 다르게 가져가는 방식도 있음.
  • 알림 연동: 자동 차단이 발생할 때마다 슬랙이나 디스코드로 알림을 보내야 한다. 자동화했다고 방치하면 안 됨. 알림이 쌓이는 패턴을 보면서 임계값을 조정하거나 오탐 원인을 찾는 데 써야 함.

레이트 리밋 설정값과 블랙리스트 TTL은 한 번 잡아놓고 끝나는 게 아니라, 트래픽 패턴이 바뀔 때마다 같이 다듬어야 한다. 이번 감지 로직 수정도 그 흐름의 일부였음. 작은 수정처럼 보여도 감지 정확도가 올라가면 오탐이 줄고, 오탐이 줄면 자동 차단 구조가 훨씬 믿을 만해진다.

운영 안정성은 큰 기능 하나로 확 올라가는 게 아니라, 이런 작은 수정들이 쌓여서 만들어진다.

댓글 0

첫 댓글 달아줘.