정상 상태에서는:
Request
↓
NestJS
↓
DB
↓
Response
가 잘 동작한다.
하지만 갑자기 요청이 폭증하면:
Request 100
↓
DB Query 100
정도가 아니라:
Request 10,000
↓
Connection Pool 대기
↓
DB Latency 증가
↓
API Timeout
↓
Client Retry
↓
요청 더 증가
하는 악순환이 생길 수 있다.
예:
10,000 요청 모두 받기
→ 전체 서비스 장애
보다:
중요 요청 1,000개 처리
불필요 요청 일부 제한
이 더 나을 수 있다.
이게 이번 내용의 핵심이다.
특정 시간 동안 허용하는 요청 수를 제한하는 것이다.
예:
1분 동안
100 requests
이상 들어오면 추가 요청을 제한한다.
흔히:
DDoS
Bot
Abuse
만 생각하지만 실제 운영에서는:
실수로 무한 API 호출
Frontend Retry Loop
Webhook 재전송 폭주
잘못된 자동화
대량 Excel 요청
AI Tool Loop
같은 정상 기능의 Bug도 Rate Limit으로 피해를 줄일 수 있다.
예:
로그인
검색 API
주문 조회
신청 API
Excel Export
알림톡 재발송
AI 분석 API
Webhook Endpoint
관리자 Bulk Action
등이다.
예:
상품 조회
는 초당 많은 요청이 가능해도 괜찮을 수 있다.
반면:
알림톡 재발송
은 1초에 수십 번 실행되면 큰 문제가 된다.
예:
Public Read API
높은 Limit
Authentication
낮은 Limit
Export
매우 낮은 Limit
Notification Resend
매우 낮은 Limit
AI Tool Action
별도 Budget
이다.
보통 다음이 필요하다.
누구를 기준으로 제한할 것인가?
몇 번까지 허용할 것인가?
어떤 시간 단위인가?
초과하면 어떻게 할 것인가?
예:
IP
User ID
Admin ID
API Key
Session
Order ID
Tenant
Webhook Provider
를 사용할 수 있다.
예:
IP A
1분 100회
같은 방식이다.
Public API에서는 유용하다.
회사나 통신망에서 여러 사용자가 같은 공인 IP를 공유할 수 있다.
그러면 정상 사용자끼리 Limit을 공유하게 된다.
예:
user:{userId}
기준으로 요청 횟수를 관리한다.
예:
admin:{id}:export
처럼 한다.
한 관리자가 Excel 버튼을 계속 눌러도 시스템 전체에 영향을 덜 준다.
예:
user:{id}:ip:{ip}
다만 Key가 너무 복잡해지지 않도록 한다.
Rate Limit:
짧은 시간 동안 얼마나 빨리 사용할 수 있는가?
Quota:
전체 기간 동안 얼마나 많이 사용할 수 있는가?
이다.
Rate Limit:
1분
10회
Quota:
하루
1,000회
이다.
예:
분당 10회
하루 500회
월 Token 1M
같이 관리할 수 있다.
돈이 나가지는 않더라도:
CPU
RAM
실행 시간
동시 모델 Run
은 제한된 Resource다.
예:
maxToolCalls = 30
maxFixAttempts = 3
maxDuration = 20m
maxConcurrentRuns = 1
정도로 둘 수 있다.
대표적으로:
Fixed Window
Sliding Window
Token Bucket
Leaky Bucket
등이 있다.
모든 걸 직접 구현할 필요는 없지만 개념은 알아두면 좋다.
예:
18:00:00 ~ 18:00:59
100회
허용한다.
다음 분이 되면 Counter를 초기화한다.
예:
18:00:59
100회
18:01:00
100회
가 들어오면 실제 1초 사이에 200회가 들어올 수 있다.
현재 시점 기준으로 최근 일정 기간 요청을 본다.
예:
지금 기준
최근 60초
100회
이다.
더 정확하지만 구현 비용이 증가한다.
Bucket에 Token이 일정 속도로 충전된다.
요청 하나가 Token 하나를 소비한다.
Bucket Size
100
Refill
초당 10 Token
이면 잠깐 100개 Burst는 허용하면서 장기적으로 초당 10개 수준을 유지할 수 있다.
사용자가 페이지를 열면 동시에:
상품 조회
FAQ
카테고리
추천
배너
등 여러 요청이 나갈 수 있다.
너무 엄격한 초당 Limit은 정상 동작도 막을 수 있다.
예:
평균
10 req/s
Burst
30
정도로 허용한다.
요청이 Bucket에 쌓이고 일정 속도로 처리된다.
즉:
입력 Burst
↓
Queue
↓
일정한 처리 속도
로 만든다.
특히:
알림톡
외부 API
AI 작업
에서 유용한 사고방식이다.
HTTP:
요청 자체를 얼마나 받을 것인가?
Queue:
쌓인 Job을 얼마나 빠르게 처리할 것인가?
이다.
둘 다 필요할 수 있다.
관리자가:
Excel Export 100개
를 연속 클릭했다고 하자.
API 자체에서:
Export Request
분당 5개
를 제한할 수 있다.
Export Worker
concurrency = 2
로 둔다.
즉:
API Rate Limit
+
Worker Concurrency Limit
두 단계가 있다.
예:
알림톡 Provider
초당 N건
제한이 있다면 Worker가 무제한 호출해서는 안 된다.
Provider가:
100 req/s
를 허용하더라도:
80 req/s
정도로 운영할 수 있다.
여유를 남긴다.
HTTP Rate Limit을 초과하면 일반적으로:
429
응답을 사용할 수 있다.
예:
Retry-After
를 제공할 수 있다.
예:
요청이 많습니다.
잠시 후 다시 시도해주세요.
처럼 보여준다.
나쁜 Retry:
429
↓
즉시 Retry
↓
429
↓
즉시 Retry
이다.
그래서:
Backoff
+
Jitter
+
Retry-After
를 함께 사용한다.
Server가 제한해도 Client가 계속 즉시 재시도하면 부하는 줄지 않는다.
예:
한 Request
최대 3회 Retry
정도로 제한한다.
더 강하게는 서비스 전체의 Retry 비율을 제한할 수 있다.
예:
전체 요청의
10% 이상 Retry 금지
같은 사고방식이다.
하류 시스템이 감당할 수 없는 속도로 일이 들어올 때:
상류 시스템에 속도를 줄이라는 신호를 주는 것
이다.
API
초당 1,000 Job 생성
↓
Worker
초당 100 Job 처리
이면 Queue는 계속 쌓인다.
10분이면:
540,000 Job
이 쌓일 수 있다.
Queue는 Burst를 흡수해 주지만 무한 Buffer가 아니다.
Queue Depth 증가
↓
Job Latency 증가
↓
메모리/Redis 증가
↓
Retry 증가
↓
DLQ 증가
한다.
예:
0~100
정상
100~1,000
주의
1,000+
과부하
처럼 볼 수 있다.
정확한 숫자는 실제 처리량에 맞춘다.
Depth가 100이라도:
Oldest Job
30분
이면 심각할 수 있다.
Worker가 초당 5,000개를 처리한다면 큰 문제가 아닐 수 있다.
그래서:
Depth
+
Oldest Job Age
+
Processing Rate
를 같이 본다.
예:
job.createdAt
→
job.startedAt
까지 걸린 시간이다.
예:
Notification
95% 10초 이내
AI Report
30분 이내
처럼 업무별로 다르다.
Queue가 너무 많이 쌓이면 새 Job 생성을 제한한다.
예:
queueDepth > threshold
→ Export 요청 거절
한다.
예:
현재 Export 요청이 많습니다.
잠시 후 다시 시도해주세요.
로 처리한다.
핵심 업무는 다른 방식이 필요하다.
예:
Order
CRITICAL
Notification
HIGH
Export
NORMAL
AI Report
LOW
로 나눈다.
시스템이 과부하일 때 중요도가 낮은 기능부터 의도적으로 거절/중단하는 것이다.
정상 상태:
주문
검색
추천
통계
AI 분석
전부 동작.
과부하:
주문
정상
검색
제한
추천
OFF
AI 분석
OFF
처럼 운영한다.
핵심 기능은 살리고 부가 기능을 줄인다.
일부 기능을 제한함으로써 전체 서비스가 죽는 것을 막는다.
대략:
P0
주문 신청
P0
관리자 주문 처리
P1
고객 안내
P1
상품 조회
P2
Excel Export
P2
통계
P3
AI 분석
P3
추천
처럼 볼 수 있다.
실제 업무 중요도에 맞게 조정한다.
예:
AI Insight
관리자 무거운 Report
대량 Export
추천 기능
이다.
0928에서 만든 Feature Flag를 사용해:
ai_analysis_enabled=false
같이 빠르게 끌 수 있다.
예:
DB CPU > 90%
↓
AI 분석 요청 거절
같은 정책이다.
Metric 순간 Spike 하나로 기능이 계속 켜졌다 꺼질 수 있다.
예:
90% 이상 5분
→ OFF
70% 이하 10분
→ ON
처럼 복구 기준을 다르게 둔다.
자동 Shedding은 나중에 적용한다.
동시에 몇 개 작업까지 실행할지 제한한다.
예:
Export Worker
2
AI Worker
1
Notification Worker
10
이다.
Rate:
초당 몇 건 시작?
Concurrency:
동시에 몇 건 실행?
이다.
한 Job이 30초 걸린다면:
rate = 초당 10건
이어도
concurrency
300
까지 늘어날 수 있다.
현재 로컬 LLM은 모델 하나만 동시에 돌리는 편이 안정적일 수 있다.
예:
AI Worker
concurrency = 1
이다.
예:
Pool Size
20
이면 동시에 DB Connection을 20개까지만 사용한다.
과도한 요청은 Pool Queue에 계속 쌓이면서 Latency가 증가한다.
즉:
Request Limit
↓
Application
↓
DB Pool
이다.
0924와 연결된다.
리소스를 기능별로 분리해 하나의 과부하가 전체를 먹지 않게 한다.
Order Worker
별도 Concurrency
AI Worker
별도 Concurrency
Export Worker
별도 Concurrency
이다.
AI Job 하나가 CPU를 많이 사용해 주문 Job이 늦어질 수 있다.
예:
critical
normal
low
정도다.
현재 규모에서는 너무 많이 나누지 않는다.
하나의 Queue 안에서 Priority를 줄 수도 있다.
예:
Order
priority 1
Export
priority 5
AI Report
priority 10
처럼 한다.
고우선 Job이 계속 들어오면 Low Job이 Starvation될 수 있다.
LOW Job
계속 대기
하는 문제다.
오래 기다린 Job의 Priority를 점차 높이는 전략이 있다.
현재 규모에서는 필요할 때 고려한다.
예:
알림톡
5분 안에 보내야 함
이면 Deadline을 가지고 우선 처리할 수도 있다.
예:
오전 9시 안내
현재 오후 8시
라면 보내지 않는 편이 나을 수 있다.
상태:
EXPIRED
로 처리한다.
예:
if (job.deadlineAt < now) {
return markExpired();
}
같이 한다.
예:
10,000명 알림
하나의 Job으로 만들지 않는다.
500명
×
20 Job
으로 나눈다.
Concurrency 조절 가능
일부 Retry 가능
Progress 확인 가능
Rate Limit 적용 가능
하다.
너무 큼:
한 Job 실패 영향 큼
너무 작음:
Queue Overhead 증가
한다.
예:
동시 Provider 요청
최대 5
이다.
예:
동시 5개
초당 20회
둘 다 적용할 수 있다.
예:
Kakao
20/s
Notion
5/s
Other Provider
10/s
처럼 한다.
Provider별 Bulkhead다.
단일 서버라면:
Memory
로도 가능하다.
예:
Redis
가 흔하다.
예:
Server A
100/min
Server B
100/min
이면 전체 Limit은 200/min이 된다.
예:
admin-export:{adminId}
Counter를 Redis에 둔다.
NestJS Guard 수준으로 시작할 수 있다.
Brute Force 방어에 도움이 된다.
예:
IP 단위
계정 단위
를 함께 볼 수 있다.
IP 단위도 필요하다.
그래서 조합이 필요할 수 있다.
예:
5회 실패
→ Delay 증가
등이다.
공격자가 다른 사람 계정을 일부러 잠글 수 있기 때문이다.
완전 잠금 대신 속도를 제한한다.
검색은 Query 비용이 높을 수 있다.
특히:
LIKE '%keyword%'
같은 Query가 무거우면 Bot이 DB를 압박할 수 있다.
Frontend 검색창에서:
한 글자 입력할 때마다 API 요청
하지 않는다.
300ms
사용자 입력이 멈춘 뒤 요청한다.
불필요한 요청 자체를 만들지 않는다.
TanStack Query가 같은 Key 요청을 공유할 수 있다.
Cache/Rate Limiting과 같이 활용한다.
API에서:
limit=100000
을 허용하면 한 요청만으로도 큰 부하가 발생한다.
예:
limit
max 100
등 상한을 둔다.
사용자가 10만 건을 내려받고 싶다면:
GET /orders?limit=100000
이 아니라:
ExportJob
으로 넘긴다.
큰 요청을 일반 API에서 분리한다.
검색 필터가 지나치게 복잡할 수도 있다.
예:
OR 수십 개
기간 10년
정렬 + Full Text + Join
등이다.
관리자 검색 기본 범위:
최근 30일
로 두고 필요할 때 확장하도록 할 수 있다.
즉:
Interactive Request
와:
Batch Job
을 구분한다.
무거운 요청이 무한히 서버 Resource를 점유하지 않게 한다.
API
10초
External API
3초
처럼 제한한다.
Timeout이 발생해도 DB Query가 계속 돌아가는 경우가 있을 수 있다.
DB Statement Timeout도 고려한다.
너무 오래 걸리는 Query를 제한할 수 있다.
운영 정책에 맞춰 설정한다.
예:
Order Detail
짧게
Report Query
길게
다.
이게 Async Job 구조의 장점이다.
Queue Producer보다 Consumer가 계속 느린 상태다.
외부 API 느림
DB Lock
CPU 부족
잘못된 Concurrency
Rate Limit
Provider 장애
일 수 있다.
예:
Worker 10
→ 100
으로 늘리면 DB/Provider를 더 압박할 수 있다.
CPU-bound?
DB-bound?
External API-bound?
Rate Limit-bound?
를 구분한다.
대략적으로:
Concurrency
≈
Throughput × Latency
관계를 생각할 수 있다.
한 작업이 평균:
2초
걸리고 초당:
10건
처리하려면 대략:
20개
동시 실행이 필요할 수 있다.
공식 숫자로 맹신하지 않고 Capacity Planning 힌트로 사용한다.
Queue Depth가 증가하면 Worker 수를 늘릴 수도 있다.
Auto Scaling까지는 실제 필요가 생긴 뒤 고려한다.
DB와 External API Capacity는 그대로일 수 있다.
Worker만 늘리면 하류가 무너진다.
Producer Capacity
>
Consumer Capacity
상태를 오래 유지하지 않는다.
시스템에 일을 받아들일지 말지를 입구에서 결정한다.
현재:
AI Queue Depth
20
이고 최대:
20
이라면 신규 AI 요청은:
거절
할 수 있다.
예:
현재 분석 작업이 많습니다.
잠시 후 다시 시도해주세요.
또는:
대기열 5번째
처럼 보여줄 수 있다.
장시간 작업은:
PENDING
PROCESSING
COMPLETED
상태를 제공하는 것이 낫다.
Queue가:
maxDepth
를 넘으면 신규 Job 생성을 막는다.
Redis가 100만 Job을 넣을 수 있다고 100만 건을 받아도 되는 것은 아니다.
예:
Worker
분당 100건 처리
업무 Deadline
10분
이면 대략 1,000건 이상 쌓이면 Deadline을 넘길 가능성이 높다.
단순 Memory 기준만 보면 안 된다.
예:
NORMAL
DEGRADED
OVERLOADED
상태를 정의할 수 있다.
모든 기능 정상.
예:
AI Analysis OFF
Export 제한
Recommendation OFF
한다.
핵심 기능만 허용한다.
예:
주문
필수 관리자 처리
만 유지한다.
Input:
DB CPU
Queue Age
Error Rate
Latency
등이다.
자동화는 운영 데이터가 쌓인 뒤 한다.
interface LoadSheddingPolicy {
feature: string;
priority:
| 'CRITICAL'
| 'HIGH'
| 'NORMAL'
| 'LOW';
shedWhenOverloaded: boolean;
}
예:
system_overload_mode=true
일 때 Low Priority 기능을 비활성화한다.
어떤 기능이 영향을 받는지 명확해야 한다.
Server:
503
응답.
Client 100개가 동시에:
1초 후 Retry
하면 다시 100개가 몰린다.
각 Retry 시간을 다르게 한다.
예:
1.0s
1.3s
1.8s
2.4s
등이다.
예:
1s
2s
4s
8s
처럼 Retry 간격을 늘린다.
무한히 증가시키지 않는다.
예:
최대 1분
등 상한을 둔다.
Job 자체 Deadline을 넘으면 Retry하지 않는다.
하지만:
Retry-After
를 존중해야 한다.
400
Invalid Payload
Permission Denied
같은 Error는 Rate Control과 무관하다.
Provider가 느린데 계속 요청하면 Worker가 쌓인다.
Circuit Breaker를 열어:
새 요청 잠시 차단
할 수 있다.
Rate Limiter:
요청이 너무 많음
Circuit Breaker:
하류 시스템이 비정상
이다.
예:
초당 10건 Limit
+
Provider Error 50% 이상
→ Circuit Open
이다.
과부하 보호는:
Rate Limit
Concurrency Limit
Timeout
Circuit Breaker
Backpressure
를 조합한다.
예:
Rate Limit이 있어도 Job 하나가 5분 걸리면 Concurrency가 쌓일 수 있다.
AI Agent가 Bug로:
DB Query
DB Query
DB Query
...
를 반복할 수 있다.
예:
DB Read
20/run
Git command
50/run
External API
10/run
같이 Budget을 둔다.
예:
Production Write
1/run
또는 Human Approval을 요구한다.
API 모델을 사용하는 경우:
maxTokens
maxCost
를 설정한다.
예:
한 Run
최대 30분
이다.
AI_BUDGET_EXCEEDED
로 종료한다.
무한 자동화 방지다.
예:
Admin Export
하루 20건
같이 둘 수 있다.
누군가 매분 Limit 이하로 호출해도 하루 전체 사용량은 매우 클 수 있다.
예:
1분 3건
하루 50건
같이 둘 수 있다.
예:
동시 1
시간당 10
하루 100
처럼 구성할 수 있다.
DAILY
MONTHLY
등 Reset 기준이 필요하다.
예:
Asia/Seoul 기준 자정
인지 UTC 기준인지 명확히 한다.
0929 Scheduler와 연결된다.
예:
최근 24시간
을 기준으로 한다.
더 정확하지만 구현 복잡도가 높다.
정말 필요할 때 Rolling으로 바꾼다.
예:
주문 상태 10,000건 일괄 변경
을 한 Request로 허용하면 위험하다.
예:
한 번에 최대 100건
으로 제한할 수 있다.
BulkOrderUpdateJob
으로 넘긴다.
100건 × 여러 Batch
형태로 처리한다.
대량 변경 전에:
대상 건수
현재 상태
예상 변경
을 보여준다.
예:
알림톡 재발송
1주문당 5분에 1회
같이 한다.
Key:
notification-resend:{orderId}
이다.
특정 업무 Entity 기준 제한이다.
예:
한 주문
알림 재발송
5분 1회
이다.
다른 이름으로:
cooldown
이라고 볼 수 있다.
Idempotency와 같이 사용한다.
Idempotency:
같은 Logical Action 중복 방지
Rate Limit:
서로 다른 Action이라도 빈도 제한
이다.
사용자가 서로 다른 Request ID로 재발송을 10번 눌러도:
Idempotency Key
각각 다름
일 수 있다.
Rate Limit은 2번째부터 막을 수 있다.
실패 Job Retry 버튼을 계속 누르는 것을 막는다.
job:{id}:manual-retry
1분
1회
정도다.
예:
한 번에 최대 50건
으로 제한한다.
1001에서 다룬 Retry Storm과 연결된다.
외부 Provider의 정상 Burst를 잘못 차단하면 Event가 유실될 수 있다.
Webhook은:
짧은 시간에 대량 전송
할 수 있다.
너무 낮은 HTTP Limit을 두지 않는다.
Receive
↓
Validate
↓
Inbox Insert
↓
200 OK
후 Background Processing한다.
Webhook Handler에서 무거운 Business Logic을 직접 하지 않는다.
Inbox가 계속 증가하면 별도 Alert가 필요하다.
429/500 응답을 보내면 Provider가 더 자주 Retry할 수도 있다.
좋은 패턴이다.
같은 Global Limit을 쓰지 않는다.
상품 사이트는 검색엔진/봇 Traffic도 있을 수 있다.
SEO Crawler까지 막으면 검색 노출에 영향을 줄 수 있다.
예:
초당 수백 검색
임의 ID Enumeration
등이다.
Application 이전 계층에서 차단할 수도 있다.
예:
CloudFront/WAF
같은 Edge 계층이다.
Application/DB까지 요청이 내려오기 전에 막을 수 있다.
사용자/업무 ID처럼 Edge가 모르는 Context가 있기 때문이다.
Edge Rate Limit
↓
Application Rate Limit
↓
Concurrency Limit
↓
DB Pool
↓
Provider Rate Limit
같은 여러 층이 있다.
실제 위험도가 높은 Endpoint부터 적용한다.
로그인
검색
Excel Export
알림 재발송
관리자 Bulk Action
AI 실행
이다.
1004와 연결된다.
예:
remaining
resetAt
등이다.
공격자가 Limit을 정밀하게 계산할 수 있다.
필요한 Client 정보만 제공한다.
예:
오늘 Export
17 / 50
이다.
서버 오류
라고 하지 않고:
짧은 시간에 요청이 많습니다.
로 구분한다.
과부하 상황에서 즉시 처리되지 않아도 정상 Queueing일 수 있다.
Processing Rate를 이용해 대략 계산할 수도 있다.
예:
앞에 100건
분당 20건 처리
예상 5분
이다.
외부 API Latency 등으로 달라질 수 있다.
고객/관리자에게:
대기 중
정도만 보여줘도 충분할 수 있다.
모든 요청을 Queue에 넣는 게 좋은 것은 아니다.
Export
AI Report
대량 알림
Background 분석
이다.
검색 요청
페이지 조회
현재 상태 조회
는 Queue보다 빠르게 Reject하는 편이 낫다.
수초 내 Response 필요
대기 가능
이다.
빠르게:
429
503
등으로 실패시키는 것이 낫다.
Queue 또는 Deferred Processing을 사용한다.
429
Client/Identity 기준 요청 과다
503
서비스 Capacity 부족
처럼 구분할 수 있다.
Client가 무작정 즉시 재시도하지 않도록 한다.
예:
DB Connection Pool 95% 사용
이면 Low Priority 요청을 거절할 수 있다.
Application 내부에서 사용할 수 있는 가벼운 Signal을 사용한다.
Queue Depth Cache
Pool Active Count
Local Overload State
등이다.
예:
5초마다
Metric을 보고 상태를 갱신한다.
if overloaded && featureLowPriority
→ reject
이다.
너무 민감하게 반응하면 정상 요청이 불필요하게 거절된다.
초기에는:
관찰
Alert
Manual Control
부터 시작한다.
예:
평소 API RPS
DB p95
Queue Throughput
Worker Concurrency
를 측정한다.
Staging에서 점진적으로 Traffic을 올려본다.
예:
10 RPS
50 RPS
100 RPS
200 RPS
이다.
어디서:
Latency가 급증하는가?
Error가 생기는가?
DB가 포화되는가?
를 찾는 것이다.
Resource가 포화되기 시작하는 지점이다.
100 RPS
정상
150 RPS
p95 200ms
180 RPS
p95 1.5s
200 RPS
Timeout 증가
이면 180~200 부근이 위험할 수 있다.
예:
150 RPS 이하
처럼 안전 Margin을 둔다.
Endpoint별 비용이 다르다.
예:
상품 Detail
cost 1
복잡 검색
cost 5
Export
cost 50
처럼 요청 비용을 다르게 계산할 수 있다.
우선 무거운 Endpoint에 별도 Limit을 두는 것이 단순하다.
대량 Write도 제한이 필요할 수 있다.
예:
Lead Insert
Webhook Insert
Bulk Update
이다.
한 건씩 10,000번 Insert보다 Batch Insert가 더 효율적일 수 있다.
적당한 크기로 나눈다.
무한히 큰 Transaction을 막는다.
큰 Payload도 일종의 Resource 공격이다.
예:
100MB JSON
을 받아 Parser가 CPU/RAM을 쓸 수 있다.
Endpoint별 적절한 크기로 제한한다.
예:
이미지
10MB
등이다.
예:
한 번에
100만 Row
를 허용하지 않는다.
예:
월별
통신사별
50,000 rows/file
등으로 나눈다.
1004 Cache 이전에 Pagination을 먼저 최적화하는 이유다.
과부하 때 Error Log가 초당 수천 건 발생할 수 있다.
Disk IO
Cloud Logging 비용
Network
이 증가한다.
예:
동일 Error
초당 1000회
실제 Log
초당 10회
만 기록하고 Counter는 Metric으로 남길 수 있다.
DATABASE_TIMEOUT
1분 12,420건
처럼 집계한다.
Audit와 Application Log를 구분한다.
예:
label=userId
로 모든 사용자를 Metric Label에 넣으면 Series가 폭증한다.
예:
endpoint
limiterType
result
정도만 사용한다.
Cardinality를 줄인다.
예:
rate_limit_allowed_total
rate_limit_rejected_total
quota_exceeded_total
이다.
queue_depth
queue_oldest_age
queue_processing
queue_failed
queue_throughput
이다.
load_shed_total
system_overload_state
feature_disabled_due_to_load
등이다.
worker_active
worker_capacity
db_pool_active
db_pool_waiting
등이다.
Observability의:
Traffic
Errors
Latency
Saturation
중 Saturation을 보는 단계다.
동시에:
DB CPU
Provider Latency
Error Rate
도 본다.
Export queue oldest age
> 10분
Warning.
> 1분
이면 더 높은 Severity일 수 있다.
> 30분
이어도 업무 영향이 낮을 수 있다.
업무 SLO에 맞춘다.
예:
1. Error Rate 확인
2. Latency 확인
3. DB Pool / CPU 확인
4. Queue Depth / Age 확인
5. External Provider 상태 확인
6. Low Priority 기능 Pause
7. Worker Concurrency 확인
8. Retry Storm 여부 확인
9. Recovery 후 Backlog Controlled Drain
이다.
장애 복구 후 Queue가 많이 쌓여 있다.
Worker Concurrency를 갑자기 최대치로 올리면 다시 장애가 날 수 있다.
예:
concurrency
5
↓
10
↓
20
점진적으로 올린다.
Provider가 복구되자마자 수십만 Retry를 보내면 Provider가 다시 다운될 수 있다.
예:
초당 20건
처럼 제한한다.
Backlog와 신규 요청 중 어떤 것을 먼저 처리할지 결정한다.
알림톡:
30분 전 메시지
vs
현재 주문 완료 메시지
현재 메시지가 더 중요할 수 있다.
Deadline/Expiration 정책을 같이 사용한다.
너무 오래된 Job은:
실제 여전히 필요한가?
확인한다.
0917 Reconciliation 원칙과 같다.
Order 상태
이미 CANCELLED
인데 오래된:
ORDER_COMPLETED 알림
이 Queue에 남아 있으면 발송하면 안 될 수 있다.
특히 오래 대기한 Job에서는 중요하다.
예:
commitSha
가 달라졌다면 기존 AI Job 실행이 의미 없을 수 있다.
baseCommit
promptVersion
policyVersion
을 다시 확인한다.
STALE
로 종료하고 새 Run을 만든다.
정책이 변경된다.
예:
Export
5/min
→
2/min
이다.
예:
ratePolicyVersion = v3
를 기록할 수 있다.
특히:
100/min
→
10000/min
은 큰 위험이다.
예:
min
max
allowed range
를 둔다.
0 = disabled?
인지:
0 = unlimited?
인지 혼동하면 위험하다.
예:
enabled=false
와 limit 숫자를 분리한다.
장애 대응 중 일시적으로 Limit을 낮추거나 높일 수 있다.
예:
1시간 후 자동 원복
한다.
0928 Configuration Drift와 연결된다.
내부 Health Check나 Trusted System은 예외가 필요할 수 있다.
관리자 Bulk 요청이 오히려 큰 부하를 만들 수 있다.
예:
Internal Health Endpoint
특정 Infrastructure Probe
정도다.
외부 Integration이 생기면:
partner-a
1000/day
partner-b
100/day
처럼 나눌 수 있다.
한 고객/지점이 Resource를 전부 독점하지 않게 한다.
현재 여러 지점/통신사 통합으로 확장될 경우 중요해질 수 있다.
예:
LGU
SKT
KT
Job이 한쪽에 몰려도 다른 쪽 Job이 전부 밀리지 않게 할 수 있다.
예:
LGU
5
SKT
5
KT
5
처럼 분리할 수도 있다.
과도한 Partitioning은 피한다.
예:
상담 신청 Form
에 Bot이 수천 건을 넣을 수도 있다.
하지만 정상 사용성을 해치지 않게 한다.
현재 필요할 때 도입한다.
공격 방어와 Resource 보호 모두 담당한다.
Rate Limiter가 Redis에 의존하는데 Redis가 다운됐다.
어떻게 할까?
Rate Limit 검사 실패
→ 요청 허용
이다.
가용성은 높지만 Abuse 방어는 약해진다.
Limiter 실패
→ 요청 차단
이다.
보안은 강하지만 정상 사용자도 막힌다.
예:
상품 조회
Fail Open
위험한 관리자 Bulk Action
Fail Closed
처럼 한다.
단 Rate Limiter 장애 때문에 모든 로그인까지 막을지 신중히 결정한다.
Redis 장애 시 Instance별 임시 Local Limit을 사용할 수도 있다.
그래도 무제한 허용보다 나을 수 있다.
Rate Limit 확인 때문에 Request가 느려지면 안 된다.
예:
rate:user:123:window
Key가 영원히 남지 않게 한다.
전화번호 대신 내부 ID를 사용한다.
subjectType
endpoint
result
정도만 남긴다.
정상 제어 동작일 수 있다.
예:
rate_limit.rejected
Metric을 증가시킨다.
Bot/Client Bug 가능성이 있다.
예:
전체 요청 중
20% Rate Limited
라면 정상 Limit이 너무 낮거나 Traffic 이상일 수 있다.
어디에서 문제가 발생하는지 본다.
버튼을 연속 클릭하는 사용자의 경우:
버튼 Disable
Loading State
만 잘 구현해도 Server Rate Limit에 걸릴 요청이 줄어든다.
예:
신청 버튼
Double Submit 방지
이다.
Frontend Disable만 믿지 않는다.
Button Disable
↓
Idempotency Key
↓
Business Cooldown
↓
Rate Limit
이다.
동일 신청을 두 번 만드는 문제를 막아야 하기 때문이다.
Rate Limit보다 사용자 실수를 먼저 줄인다.
같은 데이터 요청이 동시에 여러 개 들어오면 하나로 합친다.
1004 Single Flight와 같다.
Rate Limit 없이도 하류 요청 수를 줄인다.
Cache Hit
→ DB 요청 감소
Rate Limit
→ Traffic 제한
Backpressure
→ Queue 성장 제한
서로 다른 층이다.
CDN / Cache
↓
Rate Limit
↓
Admission Control
↓
Application
↓
Concurrency Limit
↓
Queue
↓
Worker Rate Limit
↓
DB / External Provider
이다.
현재 필요한 병목부터 해결한다.
로그인 Rate Limit
Search Debounce
Max Pagination Limit
Export Request Limit
AI Concurrency 1
이다.
Notification Resend Cooldown
Bulk Action Size Limit
Queue Depth / Age Monitoring
이다.
Provider Worker Rate Limit
Retry Backoff + Jitter
Backlog Controlled Drain
이다.
Load Shedding Kill Switch
Low Priority Feature Pause
이다.
실제 규모가 커지면:
Distributed Rate Limit
Autoscaling
Fair Queueing
을 고려한다.
복잡한 Global Adaptive Limiter
Multi-region Quota
Dynamic Weighted Fair Queue
AI가 자동으로 Capacity 조정
서비스 Mesh Rate Limit
까지는 필요 없다.
src/
├─ common/
│ ├─ rate-limit/
│ │ ├─ rate-limit.guard.ts
│ │ ├─ rate-limit.policy.ts
│ │ └─ rate-limit.service.ts
│ │
│ └─ overload/
│ ├─ overload.service.ts
│ └─ load-shedding.guard.ts
│
├─ export/
├─ notification/
└─ automation/
예:
interface RateLimitPolicy {
key: string;
limit: number;
windowMs: number;
burst?: number;
}
예:
export const ratePolicies = {
login: {
limit: 10,
windowMs: 60_000,
},
export: {
limit: 3,
windowMs: 60_000,
},
};
정확한 숫자는 실제 Traffic에 맞게 조정한다.
예:
interface CooldownPolicy {
action: string;
resourceKey: string;
cooldownMs: number;
}
이다.
action
RESEND_NOTIFICATION
resourceKey
order:123
cooldown
5m
같이 할 수 있다.
예:
interface QueueCapacityPolicy {
maxDepth?: number;
maxOldestAgeMs?: number;
concurrency: number;
ratePerSecond?: number;
}
이다.
예:
interface FeaturePriority {
feature: string;
priority:
| 'CRITICAL'
| 'HIGH'
| 'NORMAL'
| 'LOW';
disableInDegraded: boolean;
disableInOverload: boolean;
}
type SystemLoadState =
| 'NORMAL'
| 'DEGRADED'
| 'OVERLOADED';
이다.
관리자나 Config에서:
NORMAL
→ DEGRADED
로 전환한다.
먼저 운영 경험을 쌓는다.
현재 NestJS + Prisma + PostgreSQL + 기존 Queue/Worker 구조에
과부하 보호를 위한 최소한의
Rate Limiting / Quota / Backpressure 구조를 추가해줘.
목표는 복잡한 Traffic Management Platform을 만드는 것이 아니라,
- 잘못된 반복 요청
- 관리자 Bulk Action
- Export 폭주
- AI Job 폭주
- 외부 Provider Rate Limit
- Queue Backlog
때문에 전체 서비스가 영향을 받는 것을 막는 것이다.
현재 사용하는 Queue, Redis, Auth Guard,
Frontend 요청 구조를 먼저 분석한다.
실제 Traffic Metric이 없는 곳의 Limit 값을
근거 없이 매우 낮게 설정하지 않는다.
1. 현재 Endpoint를 중요도와 비용 기준으로 분류한다.
예:
CRITICAL
- 주문 신청
- 관리자 주문 처리
HIGH
- 상품 조회
- 고객 알림
NORMAL
- Export
- 통계
LOW
- AI 분석
- 추천
실제 코드 기준으로 수정한다.
2. 다음 Endpoint를 우선 Rate Limit 후보로 검토한다.
- 로그인
- 검색
- Export 요청
- 알림 재발송
- 관리자 Bulk Action
- AI 실행 API
3. 일반 Product Read API 등에
무조건 강한 Rate Limit을 적용하지 않는다.
CloudFront / Cache가 더 적합한지 먼저 검토한다.
4. Rate Limit Key 전략을 만든다.
지원 후보:
- IP
- authenticated userId
- adminId
- API key
- resourceId
Endpoint에 맞는 Key만 사용한다.
5. 내부 ID 대신 전화번호/이메일 등의
PII를 Rate Limit Key로 사용하지 않는다.
6. 현재 단일 Instance라면
과도한 Distributed Rate Limiter를 만들지 않는다.
Multi-instance가 확인되거나 Global Limit이 필요할 때
기존 Redis를 활용할 수 있는 추상화를 만든다.
7. RateLimitService를 Business Logic과 분리한다.
8. Limit 초과 시
HTTP 429를 반환할 수 있게 한다.
가능한 경우 Retry-After를 제공한다.
9. Frontend가 429를 일반 서버 오류와 구분할 수 있게 한다.
10. Export 요청은
Rate Limit과 Queue Concurrency를 둘 다 고려한다.
예:
- 사용자/관리자 요청 빈도 제한
- Export Worker 동시 실행 제한
정확한 숫자는 Config로 조정 가능하게 한다.
11. Export Job이 이미 너무 많이 대기 중이라면
신규 Export 요청을 거절할 수 있는
Admission Control을 추가할 수 있게 한다.
판단에:
- queue depth
- oldest job age
를 고려한다.
12. Queue Capacity는
Redis가 얼마나 많이 저장 가능한지가 아니라
업무 SLO와 Worker Throughput을 기준으로 판단하도록 한다.
13. Queue별로 다음 정보를 확인할 수 있게 한다.
- pending
- active
- failed
- oldest job age
- throughput
14. AI Worker는
현재 하드웨어/모델 구조를 분석해서
기본 concurrency를 보수적으로 설정한다.
Local LLM 작업을 무제한 동시 실행하지 않는다.
15. AI Workflow에 다음 Budget을 지원할 수 있는 구조를 만든다.
- maxAttempts
- maxDuration
- maxToolCalls
- maxConcurrentRuns
16. Budget 초과는
AI_BUDGET_EXCEEDED 같은 명확한 Error Code로 처리한다.
17. 알림 재발송에는
일반 User Rate Limit 외에
Domain Cooldown을 적용할 수 있게 한다.
예:
orderId 단위 cooldown.
18. Cooldown과 Idempotency를 구분한다.
- Idempotency:
동일 Logical Action 중복 방지
- Cooldown:
서로 다른 요청이어도 과도한 반복 방지
19. 관리자 Bulk Action에
최대 선택 건수 제한을 추가할 수 있게 한다.
대량 작업은
Background Bulk Job으로 넘길 수 있게 한다.
20. Pagination API의
page size에 Max Limit을 적용한다.
Client가 과도한 limit 값을 보내도
전체 데이터를 한번에 조회하지 않게 한다.
21. Search UI에 Debounce가 제대로 적용되어 있는지 확인한다.
동일 검색 Query 중복 호출도 줄인다.
22. Worker가 외부 Provider를 호출하는 경우
Provider별 Rate Limit / Concurrency Limit을
설정할 수 있게 한다.
예:
Notification Provider
Notion
기타 External API
23. Provider Rate Limit Error가 발생하면
Retry-After가 있는 경우 이를 우선한다.
24. Retry에는
Exponential Backoff + Jitter를 사용할 수 있게 한다.
25. 무한 Retry를 허용하지 않는다.
maxAttempts / retryDeadline을 지원한다.
26. Queue Backlog 복구 시
Worker Concurrency를 갑자기 최대치로 올려
Recovery Storm이 발생하지 않게 한다.
Controlled Drain이 가능하게 한다.
27. 오래 대기한 Job은
실행 직전 현재 Business State를 다시 확인할 수 있게 한다.
예:
과거 ORDER_COMPLETED 알림 Job인데
현재 주문이 CANCELLED라면
Blind Execution하지 않는다.
28. Deadline이 있는 Job에는
deadlineAt 또는 expiresAt을 지원할 수 있게 한다.
이미 의미가 없는 Job은 EXPIRED 처리한다.
29. Load Shedding의 최소 구조를 설계한다.
SystemLoadState:
- NORMAL
- DEGRADED
- OVERLOADED
초기에는 Metric 기반 자동 전환이 아니라
Manual/Config 기반으로 시작해도 된다.
30. Feature별 Priority를 정의할 수 있게 한다.
예:
CRITICAL
HIGH
NORMAL
LOW
31. DEGRADED 상태에서는
LOW Priority 기능을 제한할 수 있게 한다.
예:
- AI 분석
- 추천
- 무거운 통계
32. OVERLOADED 상태에서는
핵심 주문/관리 기능을 우선 유지하도록 한다.
33. 기존 Feature Flag/Kill Switch 시스템이 있다면
중복 구현하지 않고 연결한다.
34. Rate Limit/Quota 설정에는 Validation을 적용한다.
예:
- limit < 0 금지
- 지나치게 높은 값 Warning
- window 유효성 검사
35. enabled와 limit=0의 의미를 분리한다.
0이 unlimited인지 disabled인지
모호한 구조를 만들지 않는다.
36. 임시 Emergency Override가 필요하면
expiresAt을 지원할 수 있게 한다.
37. Rate Limit 설정 변경을 Audit에 기록한다.
- actor
- before
- after
- reason
- timestamp
38. 다음 Metric을 추가할 수 있게 한다.
- rate_limit_allowed_total
- rate_limit_rejected_total
- quota_exceeded_total
- queue_depth
- queue_oldest_age
- worker_active
- load_shed_total
39. userId/adminId 같은 High Cardinality 값을
Metric Label로 무분별하게 사용하지 않는다.
40. Structured Log는 다음 Context만 최소한으로 남긴다.
- endpoint/action
- limiter policy
- result
- errorCode
PII 원문을 남기지 않는다.
41. Rate Limiter 저장소 장애 시
Endpoint 중요도별 Failure Policy를 정의할 수 있게 한다.
예:
- 일반 상품 조회: Fail Open
- 위험한 Bulk Action: Fail Closed 또는 제한적 처리
42. Redis 기반 Limiter를 사용한다면
짧은 Timeout을 사용한다.
Rate Limit 검사 자체가 API Latency의 병목이 되지 않게 한다.
43. 다음 Reliability Test를 작성한다.
- Limit 미만 요청
- Limit 초과 요청
- Window Reset
- Domain Cooldown
- Export Queue Full
- AI concurrency limit
- Provider 429 + Retry-After
- Backoff + Jitter
- Queue backlog
- Expired Job
- Limiter storage unavailable
- DEGRADED 상태 Low Priority 기능 차단
- NORMAL 복구
44. 기존 Production Queue나 실제 고객 요청을 사용하지 말고
Integration Test 환경에서 재현한다.
45. 기존 프로젝트가 아직 낮은 Traffic이라면
대형 Distributed Rate Limiting 시스템을 만들지 말고,
- Endpoint Guard
- Queue Concurrency
- Retry Backoff
- Domain Cooldown
- Queue Metrics
정도의 최소 구조를 우선 구현해줘.
현재 Queue / Worker 구조를 분석해서
어디에 Backpressure가 필요한지 정리해줘.
코드를 수정하지 않고 분석만 한다.
검토 대상:
- Notification
- Export
- Webhook
- Scheduled Job
- Workflow
- AI Task
각 Queue에 대해:
## Producer
어디에서 Job을 생성하는가?
## Consumer
어떤 Worker가 처리하는가?
## 평균 작업 비용 후보
- DB 중심
- CPU 중심
- 외부 API 중심
- File 중심
실제 Metric이 없으면 처리량을 임의로 숫자로 단정하지 않는다.
## 현재 Concurrency
## Retry 정책
## Deadline 여부
## Queue가 쌓였을 때 업무 영향
## Rate Limit 필요 여부
## Concurrency Limit 필요 여부
## Backlog Limit 필요 여부
## Load Shedding 가능 여부
## Priority
CRITICAL / HIGH / NORMAL / LOW
마지막에 다음 세 가지를 정리한다.
1. 가장 먼저 Queue 폭주 위험을 줄여야 하는 영역
2. Concurrency를 낮추는 것이 좋은 영역
3. Worker를 늘리기 전에 DB/Provider 병목을 확인해야 하는 영역
현재 NestJS API에 대한
안전한 Staging Load Test Plan을 작성해줘.
Production이나 실제 고객 데이터를 사용하지 않는다.
목표는 최대 TPS 기록이 아니라
어느 지점부터 Latency/Error/Saturation이 급증하는지 찾는 것이다.
테스트 대상:
1. Product Read
2. Search
3. Order Admin List
4. Export Request
5. Notification Request
각 대상마다:
- 요청 패턴
- 점진적 RPS 증가 단계
- 테스트 시간
- 예상 병목 후보
- 관찰 Metric
- 중단 조건
- 성공 기준
을 정리한다.
반드시 관찰할 Metric:
- p50 / p95 / p99 latency
- error rate
- DB connections
- DB query latency
- CPU
- memory
- queue depth
- oldest queue age
중단 조건 예:
- 오류율 급증
- DB Connection Pool 포화
- p95 급격한 상승
- 시스템 Health 이상
실제 수치는 현재 Infrastructure Capacity를 확인한 뒤
조정할 수 있게 작성한다.
# Overload Incident
## 1. 증상 확인
- API Error Rate
- p95/p99 Latency
- DB Connection Pool
- DB CPU
- Queue Depth
- Oldest Job Age
## 2. Traffic 확인
- 특정 Endpoint 급증?
- 특정 IP/User?
- Bot?
- Client Retry Loop?
- Webhook Burst?
## 3. Retry Storm 확인
- 429/503 이후 즉시 Retry?
- Worker Retry 증가?
- Provider 장애?
## 4. Low Priority 작업 제한
- AI Worker Pause
- Export 제한
- Heavy Report Pause
- Recommendation OFF
## 5. Queue 확인
- Notification
- Export
- AI
- Workflow
## 6. Provider 확인
- Error Rate
- Latency
- 429
- Timeout
## 7. Controlled Recovery
- Worker Concurrency 점진 증가
- Retry Rate 제한
- Backlog Drain
## 8. Reconciliation
오래 대기한 Job이
현재도 유효한 업무인지 확인.
## 9. 정상화
- Latency 정상
- Queue Lag 정상
- Error Rate 정상
## 10. 복구한 Feature 재활성화
LOW Priority부터 단계적으로 복구.
1004에서는:
Cache
를 이용해 같은 Read가 계속 DB까지 내려가지 않도록 만들었다.
하지만 Cache가 아무리 좋아도:
Traffic 폭증
잘못된 Retry
Queue 폭주
AI Job 동시 실행
대량 Export
이 발생하면 시스템 Capacity를 넘을 수 있다.
1005의 핵심은:
시스템이 감당할 수 없는 일을 전부 받아들이는 것이 좋은 서비스가 아니라는 것
이다.
첫 번째 방어선은:
Rate Limit
이다.
누가
어떤 기능을
얼마나 자주
사용할 수 있는지 제한한다.
그다음:
Quota
를 통해 장기간 사용량을 제한할 수 있다.
예:
분당 3회
하루 50회
처럼 서로 다른 목적을 가진다.
Queue에서는:
Producer
>
Consumer
상태가 계속되면 Backlog가 끝없이 증가한다.
따라서:
Queue Depth
Oldest Job Age
Throughput
을 같이 보면서 Backpressure를 걸어야 한다.
또:
Worker Concurrency
와:
초당 처리 Rate
는 서로 다르다.
장시간 작업에서는 Concurrency를 제한하고, Provider에는 Rate Limit을 적용해야 한다.
과부하가 실제로 시작되면 모든 기능을 동일하게 보호하려고 하지 않는다.
CRITICAL
주문
HIGH
알림
NORMAL
Export
LOW
AI 분석
처럼 Priority를 나누고,
Low Priority 기능부터 제한
하는 것이 Load Shedding이다.
즉:
전체 기능이 같이 죽는 것
보다:
부가 기능 일부 제한
+
핵심 기능 생존
이 훨씬 낫다.
특히 Retry는 과부하 상황에서 매우 위험하다.
Timeout
↓
즉시 Retry
↓
다시 Timeout
↓
더 많은 Retry
가 장애를 증폭하기 때문이다.
그래서:
Backoff
Jitter
Retry Limit
Retry Deadline
이 필요하다.
그리고 Queue 장애가 복구됐다고:
밀린 Job 전체를 한 번에 실행
해서도 안 된다.
Controlled Drain
으로 천천히 처리해야 한다.
현재 프로젝트에서는 거대한 Traffic Platform을 만들기보다 먼저:
로그인 Rate Limit
검색 Debounce
Pagination Max Limit
Export 제한
Notification Cooldown
AI Concurrency Limit
Queue Depth/Age Monitoring
Retry Backoff
정도만 적용해도 효과가 크다.
전체 운영 흐름으로 보면:
Traffic
↓
Cache
↓
Rate Limit
↓
Admission Control
↓
Application
↓
Concurrency Limit
↓
Queue
↓
Backpressure
↓
Worker Rate Limit
↓
DB / Provider
가 된다.
그리고 Capacity를 넘기기 시작하면:
NORMAL
↓
DEGRADED
↓
OVERLOADED
상태를 인식하고 낮은 Priority 기능부터 줄인다.
결국 Rate Limiting·Backpressure의 핵심은
“최대한 많은 요청을 처리하는 것”이 아니라, 시스템이 감당할 수 있는 범위 안에서 중요한 작업을 먼저 처리하고, 과부하가 다른 계층으로 전파되기 전에 의도적으로 속도를 늦추거나 일부 작업을 거절하는 것
이다.