refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조

에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게
[공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류.
문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인).

- Topic_Programming → Domain_Programming (내부 구조 보존)
- Topic_Graphic → Domain_Design
- Topic_Business → Domain_Product
- Topic_General → Domain_General
- _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning),
  Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing)
- 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서)
- 빈 폴더 정리 (memory/procedures)
- 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Antigravity Agent
2026-07-11 11:05:56 +09:00
parent 6549ead309
commit c24165b8bc
6193 changed files with 1717 additions and 31 deletions
@@ -0,0 +1,114 @@
---
id: backend-rate-limiting
title: Rate Limiting — Token Bucket vs Sliding Window
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [backend, rate-limit, throttling, vibe-coding]
tech_stack: { language: "TypeScript / Redis", applicable_to: ["Backend", "API gateway"] }
applied_in: []
aliases: [throttle, leaky bucket, sliding window, fixed window]
---
# Rate Limiting
> 입구 차단으로 시스템 보호. **per-user / per-IP / per-API key** 다중 차원. 알고리즘 4종 — Fixed window / Sliding window / Token bucket / Leaky bucket. 분산 환경은 Redis 가 기본.
## 📖 핵심 개념
- **Fixed window**: "1분당 100회". 단순. 경계에서 burst 두 배.
- **Sliding window**: 시간 가중. 경계 burst 없음. 계산 복잡.
- **Token bucket**: 토큰 N개, 초당 R 회복. burst 허용. 현실적 선택.
- **Leaky bucket**: 큐 + 일정 속도 처리. 평탄화.
## 💻 코드 패턴
### Token bucket — Redis Lua atomic
```lua
-- KEYS[1] = bucket key, ARGV[1] = capacity, ARGV[2] = refillPerSec, ARGV[3] = now
local data = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(data[1]) or tonumber(ARGV[1])
local ts = tonumber(data[2]) or tonumber(ARGV[3])
local elapsed = math.max(0, tonumber(ARGV[3]) - ts)
tokens = math.min(tonumber(ARGV[1]), tokens + elapsed * tonumber(ARGV[2]))
local allowed = 0
if tokens >= 1 then tokens = tokens - 1; allowed = 1 end
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', ARGV[3])
redis.call('EXPIRE', KEYS[1], 3600)
return allowed
```
```ts
// Express middleware
async function rateLimit(req, res, next) {
const key = `rl:${req.ip}:${req.user?.id ?? 'anon'}`;
const allowed = await redis.eval(LUA, 1, key, /*cap*/100, /*refill*/10, Date.now()/1000);
if (!allowed) {
res.setHeader('Retry-After', '1');
return res.status(429).json({ error: 'rate_limited' });
}
next();
}
```
### Sliding window — Redis sorted set
```ts
async function slidingAllow(key: string, limit: number, windowSec: number): Promise<boolean> {
const now = Date.now();
const windowStart = now - windowSec * 1000;
const tx = redis.multi();
tx.zremrangebyscore(key, 0, windowStart);
tx.zadd(key, now, `${now}-${Math.random()}`);
tx.zcard(key);
tx.expire(key, windowSec);
const [, , count] = await tx.exec();
return (count as number) <= limit;
}
```
### Layered limits
```ts
const limits = [
{ key: `rl:ip:${req.ip}`, limit: 100, windowSec: 60 }, // IP 기준
{ key: `rl:user:${userId}`, limit: 1000, windowSec: 3600 }, // 사용자 시간당
{ key: `rl:plan:${userId}`, limit: 50000, windowSec: 86400 }, // 일일
];
for (const l of limits) {
if (!await slidingAllow(l.key, l.limit, l.windowSec)) return res.status(429).end();
}
```
## 🤔 의사결정 기준
| 차원 | 키 |
|---|---|
| Per-IP | `rl:ip:${req.ip}` |
| Per-user | `rl:user:${userId}` |
| Per-API key | `rl:apikey:${key}` |
| Per-endpoint | `rl:endpoint:${path}:${userId}` |
| 비용 큰 작업 (PDF 생성) | per-user 더 엄격 |
| 알고리즘 | 권장 |
|---|---|
| 단순 / 정확성 보통 | Fixed window |
| 정확한 sliding 필요 | Sliding window log (sorted set) |
| burst 허용 + 평균 통제 | Token bucket |
| 평탄한 처리 (DB 보호) | Leaky bucket |
## ❌ 안티패턴
- **메모리 in-process counter**: 다중 인스턴스에서 의미 없음. Redis 또는 외부.
- **`X-Forwarded-For` 검증 없이 IP 사용**: 클라이언트가 위조 가능. 신뢰 가능한 proxy 만.
- **429 응답에 Retry-After 누락**: 클라이언트가 언제 재시도할지 모름.
- **인증 endpoint 에 너무 헐거운 limit**: brute force 무방비.
- **per-IP 만**: NAT 뒤 다수 사용자 = 한 IP. user 기준 병행.
- **rate limit 자체에 타임아웃 누락**: Redis 응답 늦으면 모든 요청 block.
- **fail-open vs fail-closed 결정 안 함**: Redis 다운 시 다 통과 (fail-open) 또는 다 차단 (fail-closed). 명시.
## 🤖 LLM 활용 힌트
- 다층 limit (IP + user + plan) + Redis sorted set 권장.
- 429 + Retry-After + JSON error code 표준.
## 🔗 관련 문서
- [[Backpressure_Patterns]]
- [[Backend_Retry_Strategy]]