docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
---
|
||||
id: cs-backpressure-deep
|
||||
title: Backpressure 깊이 — 큐 / Reactive / Token Bucket
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [cs, backpressure, queue, vibe-coding]
|
||||
tech_stack: { language: "TS", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [backpressure, flow control, drop, bounded queue, reactive streams]
|
||||
---
|
||||
|
||||
# Backpressure Deep
|
||||
|
||||
> 빠른 producer + 느린 consumer = 메모리 폭발 / latency. **Bounded queue + drop / block / shed**. Reactive streams 가 표준 framework.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- 큐: producer-consumer buffer.
|
||||
- Bounded: 한도 초과 시 drop / block.
|
||||
- Shed: 우선순위 낮은 거 먼저 drop.
|
||||
- Reactive Streams: Pull-based + demand signal.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### Unbounded queue (위험)
|
||||
```ts
|
||||
// ❌ 메모리 폭발
|
||||
const queue: Job[] = [];
|
||||
producer.on('event', (job) => queue.push(job));
|
||||
// consumer 가 느리면 queue 무한 자라남
|
||||
```
|
||||
|
||||
### Bounded — drop
|
||||
```ts
|
||||
class BoundedDropQueue<T> {
|
||||
private q: T[] = [];
|
||||
constructor(private max: number) {}
|
||||
|
||||
push(item: T): boolean {
|
||||
if (this.q.length >= this.max) {
|
||||
// Drop oldest 또는 newest
|
||||
this.q.shift(); // drop oldest
|
||||
}
|
||||
this.q.push(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bounded — block
|
||||
```ts
|
||||
class BoundedBlockingQueue<T> {
|
||||
private q: T[] = [];
|
||||
private waiters: ((v: T) => void)[] = [];
|
||||
|
||||
constructor(private max: number) {}
|
||||
|
||||
async push(item: T): Promise<void> {
|
||||
while (this.q.length >= this.max) {
|
||||
await new Promise(r => setTimeout(r, 10)); // backoff
|
||||
}
|
||||
if (this.waiters.length > 0) {
|
||||
this.waiters.shift()!(item);
|
||||
} else {
|
||||
this.q.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
async pop(): Promise<T> {
|
||||
if (this.q.length > 0) return this.q.shift()!;
|
||||
return new Promise<T>(r => this.waiters.push(r));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Backoff producer (자체 throttle)
|
||||
```ts
|
||||
async function producerLoop() {
|
||||
while (true) {
|
||||
if (queue.length > THRESHOLD) {
|
||||
const wait = Math.min(100 * (queue.length / THRESHOLD), 5000);
|
||||
await sleep(wait);
|
||||
continue;
|
||||
}
|
||||
const job = await fetchJob();
|
||||
queue.push(job);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Reactive Streams (Pull-based demand)
|
||||
```ts
|
||||
// RxJS / Most.js
|
||||
import { from, interval } from 'rxjs';
|
||||
import { mergeMap, map } from 'rxjs/operators';
|
||||
|
||||
interval(10) // producer
|
||||
.pipe(
|
||||
mergeMap(i => slowAsync(i), 5), // 동시 5개만 — backpressure
|
||||
map(x => x * 2),
|
||||
)
|
||||
.subscribe(console.log);
|
||||
```
|
||||
|
||||
→ mergeMap concurrency = backpressure.
|
||||
|
||||
### Node Streams (자동)
|
||||
```ts
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
|
||||
await pipeline(
|
||||
source,
|
||||
transform,
|
||||
destination,
|
||||
);
|
||||
// 각 stream 의 highWaterMark 가 backpressure
|
||||
// readable 가 빠르고 writable 가 느리면 — readable pause
|
||||
```
|
||||
|
||||
```ts
|
||||
const writable = new Writable({
|
||||
highWaterMark: 16384, // 16KB buffer
|
||||
write(chunk, _, cb) {
|
||||
slowProcess(chunk).then(() => cb());
|
||||
},
|
||||
});
|
||||
|
||||
source.pipe(writable);
|
||||
// pipe 가 자동 backpressure
|
||||
```
|
||||
|
||||
### Drop priority (load shedding)
|
||||
```ts
|
||||
class PriorityDropQueue {
|
||||
private q: Job[] = [];
|
||||
|
||||
push(job: Job) {
|
||||
if (this.q.length >= MAX) {
|
||||
// 낮은 priority drop
|
||||
const lowest = this.q.findIndex(j => j.priority < job.priority);
|
||||
if (lowest >= 0) {
|
||||
this.q.splice(lowest, 1);
|
||||
this.q.push(job);
|
||||
}
|
||||
// Job 의 priority 도 낮으면 — drop new job
|
||||
} else {
|
||||
this.q.push(job);
|
||||
}
|
||||
this.q.sort((a, b) => b.priority - a.priority);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ 결제 = high, log = low. Overload 시 log drop.
|
||||
|
||||
### Adaptive concurrency
|
||||
```ts
|
||||
// 응답 시간 따라 동시 처리 수 조정
|
||||
class AdaptiveExecutor {
|
||||
private concurrency = 10;
|
||||
private latencies: number[] = [];
|
||||
|
||||
async execute(task: () => Promise<void>) {
|
||||
while (this.active >= this.concurrency) await sleep(10);
|
||||
|
||||
const t = Date.now();
|
||||
await task();
|
||||
const ms = Date.now() - t;
|
||||
|
||||
this.latencies.push(ms);
|
||||
if (this.latencies.length > 100) this.latencies.shift();
|
||||
|
||||
const p95 = percentile(this.latencies, 0.95);
|
||||
if (p95 > 500 && this.concurrency > 1) this.concurrency--;
|
||||
if (p95 < 100) this.concurrency++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ Latency 가 늘면 concurrency 줄이기.
|
||||
|
||||
### Token bucket (위 rate limit 문서)
|
||||
```ts
|
||||
class TokenBucket {
|
||||
private tokens: number;
|
||||
|
||||
consume(n = 1): boolean {
|
||||
this.refill();
|
||||
if (this.tokens < n) return false;
|
||||
this.tokens -= n;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Producer
|
||||
if (bucket.consume()) {
|
||||
await emit(event);
|
||||
} else {
|
||||
drop();
|
||||
}
|
||||
```
|
||||
|
||||
### Circuit breaker + backpressure
|
||||
```ts
|
||||
// 다운스트림 fail 시 circuit open → 더 이상 보내지 X
|
||||
if (circuit.isOpen()) {
|
||||
drop();
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
→ [[Backend_Circuit_Breaker]].
|
||||
|
||||
### Kafka — partition + lag
|
||||
```
|
||||
Producer 가 빠르면 Kafka partition 의 disk usage 증가.
|
||||
Consumer lag 모니터링 + scale up consumer.
|
||||
```
|
||||
|
||||
```sql
|
||||
-- Lag 모니터링
|
||||
SELECT topic, partition, current_offset, log_end_offset, lag
|
||||
FROM kafka_consumer_groups WHERE group_id = 'my-group';
|
||||
```
|
||||
|
||||
### Bounded memory 안 큐 정책
|
||||
```
|
||||
1. Drop newest (FIFO loss)
|
||||
2. Drop oldest (LIFO loss)
|
||||
3. Drop priority-based
|
||||
4. Block producer
|
||||
5. Spill to disk (overflow)
|
||||
6. Reject (return error)
|
||||
```
|
||||
|
||||
→ Use case 별 다름.
|
||||
|
||||
### Feedback loop (사용자에 알리기)
|
||||
```ts
|
||||
app.post('/api/job', async (req, res) => {
|
||||
if (queue.length > MAX) {
|
||||
return res.status(503).set('Retry-After', '30').json({
|
||||
error: 'Service overloaded, please retry',
|
||||
});
|
||||
}
|
||||
await queue.push(req.body);
|
||||
res.status(202).json({ status: 'queued' });
|
||||
});
|
||||
```
|
||||
|
||||
→ Client 가 알고 backoff.
|
||||
|
||||
### Server health-based load shed
|
||||
```ts
|
||||
app.use((req, res, next) => {
|
||||
const cpu = await getCPU();
|
||||
if (cpu > 90 && Math.random() < 0.1) {
|
||||
return res.status(503).end();
|
||||
}
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
→ 10% 확률 drop — 급격한 cliff 방지.
|
||||
|
||||
### Queue depth limit (별 service)
|
||||
```
|
||||
Worker 수 N + queue depth M 면 — N + M 가 max in-flight.
|
||||
M 너무 큼 = latency 증가 (큐 안 오래 wait).
|
||||
M 너무 적음 = drop / reject 자주.
|
||||
|
||||
Little's Law: L = λ × W
|
||||
- L = 평균 큐 안 작업
|
||||
- λ = 도착률
|
||||
- W = 평균 처리 시간
|
||||
```
|
||||
|
||||
→ p99 latency 목표 고려해서 M 결정.
|
||||
|
||||
### Async iterator + bounded
|
||||
```ts
|
||||
async function* boundedProducer<T>(source: AsyncIterable<T>, limit: number) {
|
||||
let inflight = 0;
|
||||
for await (const item of source) {
|
||||
while (inflight >= limit) await sleep(10);
|
||||
inflight++;
|
||||
yield Promise.resolve(item).finally(() => inflight--);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 정책 |
|
||||
|---|---|
|
||||
| Latency critical | Bounded + drop |
|
||||
| Strong consistency | Bounded + block |
|
||||
| Bursty traffic | Token bucket |
|
||||
| Different priority | Priority drop |
|
||||
| 분산 | Kafka + lag monitoring |
|
||||
| Reactive | RxJS / Reactive Streams |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Unbounded queue**: OOM.
|
||||
- **Blocking 무 timeout**: deadlock 가능.
|
||||
- **Drop 정책 없음**: 무엇이 잃었는지 모름.
|
||||
- **Producer 만 throttle — consumer 안 scale**: 큐 늘어남.
|
||||
- **Latency 모니터링 X**: 점진적 죽음.
|
||||
- **Block producer + caller blocking**: 상위 시스템 다운.
|
||||
- **`fast retry` after drop**: thundering herd.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- Bounded queue + drop / block 정책 명시.
|
||||
- Circuit breaker + 503 + Retry-After.
|
||||
- 큰 처리량 = Kafka + lag monitor.
|
||||
- Adaptive concurrency 가 modern.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Backpressure_Patterns]]
|
||||
- [[Backend_Circuit_Breaker]]
|
||||
- [[Backend_Rate_Limiting]]
|
||||
Reference in New Issue
Block a user