docs(10_Wiki): 위키 구조 정리 — 언어 튜토리얼 카테고리 폴더 제거 + 신규 자산 동기화
Topic_CSS/Topic_HTML/Topic_JavaScript/Topic_Prompt/Topic_Comfyui 등 기존 카테고리 폴더를 정리하고, Topic_Graphic/Dev 등 신규 산출물과 Topics 내부 세션/메모리 기록을 동기화.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
---
|
||||
id: concurrency-lock-queue-transaction
|
||||
title: "동시성 제어 Lock Queue Transaction"
|
||||
category: "Architecture"
|
||||
status: "draft"
|
||||
verification_status: "applied"
|
||||
canonical_id: ""
|
||||
aliases: ["concurrency", "AsyncLock", "mutex", "race condition", "동시성", "큐", "concurrency limit", "트랜잭션"]
|
||||
duplicate_of: ""
|
||||
source_trust_level: "A"
|
||||
confidence_score: 0.94
|
||||
created_at: 2026-06-13
|
||||
updated_at: 2026-06-13
|
||||
review_reason: ""
|
||||
merge_history: []
|
||||
tags: ["architecture", "concurrency", "lock", "queue", "transaction", "astraai"]
|
||||
raw_sources: ["AstraAI/src/core/lock.ts", "AstraAI/src/core/queue.ts", "AstraAI/src/core/transaction.ts"]
|
||||
applied_in: ["AstraAI"]
|
||||
github_commit: ""
|
||||
---
|
||||
|
||||
# [[동시성 제어 Lock Queue Transaction]]
|
||||
|
||||
## 🎯 한 줄 통찰 (One-line insight)
|
||||
단일 스레드 JavaScript 도 `await` 사이에 다른 작업이 끼어들어 *경쟁 상태(race condition)* 가 생기며, AstraAI 는 **자원별 비동기 락(AsyncLock)·동시성 제한 큐·파일 보상 트랜잭션** 세 가지로 "한 번에 하나만·자원을 폭주시키지 않고·실패하면 되돌리는" 동시성 안전을 확보한다 [S1][S2][S3].
|
||||
|
||||
## 🧠 핵심 개념 (Core concepts)
|
||||
1. **JS 의 동시성:** 단일 스레드라도 `await` 지점에서 제어가 넘어가므로, 같은 파일을 동시에 읽고-수정-쓰면 갱신 손실이 난다. 직렬화가 필요 [S1].
|
||||
2. **비동기 뮤텍스 (AsyncLock):** 자원 ID 별로 "이전 작업이 끝나야 다음이 시작"되는 Promise 체인. `acquire` 가 release 함수를 반환, `try/finally` 로 반드시 해제 [S1].
|
||||
3. **데드락 방지 타임아웃:** 락 대기를 `Promise.race([previous, timeout])` 로 감싸 무한 대기 차단 [S1].
|
||||
4. **동시성 제한 큐:** 동시에 실행되는 작업 수를 `max(2, cpus-1)` 로 제한해 I/O·메모리 폭주 방지 [S2].
|
||||
5. **보상 트랜잭션:** 파일시스템에 트랜잭션이 없으므로, 변경 전 백업→실패 시 복원으로 원자성을 흉내 [S3].
|
||||
|
||||
## 🧩 추출된 패턴 (Extracted patterns)
|
||||
- **고유 토큰으로 안전한 정리:** 각 락 entry 에 `Symbol` 토큰을 부여하고, cleanup/release 시 "내 토큰이 아직 Map 의 최신인지" 확인 후에만 삭제 — 연쇄 호출 시 다른 작업 entry 를 지우는 race 방지 [S1].
|
||||
- **release 는 반드시 try/finally:** `const release = await lock.acquire(id); try { ... } finally { release(); }` — 예외가 나도 락이 풀리게 [S1].
|
||||
- **enqueue 가 Promise 반환:** `enqueue<T>(task)` 가 작업 결과 Promise 를 돌려주되 실행은 슬롯이 빌 때 — 호출부는 평소처럼 await [S2].
|
||||
- **micro-delay 로 숨통:** 무거운 I/O 사이 `await sleep(10)` 로 시스템에 여유 [S2].
|
||||
- **begin/record/commit/rollback:** 변경 전 `record(파일)` 로 백업, 성공 `commit`(백업 폐기), 실패 `rollback`(원복) [S3].
|
||||
|
||||
## 📖 세부 내용 (Details)
|
||||
### AsyncLock 의 핵심 — Promise 체인 + 토큰
|
||||
```typescript
|
||||
const previousPromise = this.locks.get(resourceId)?.promise ?? Promise.resolve();
|
||||
const token = Symbol(`lock:${resourceId}`);
|
||||
let release!: () => void;
|
||||
const newPromise = new Promise<void>((resolve) => { release = resolve; });
|
||||
this.locks.set(resourceId, { promise: previousPromise.then(() => newPromise), token });
|
||||
await Promise.race([previousPromise, timeoutPromise]); // 앞 작업 끝날 때까지(타임아웃 보호)
|
||||
return () => { release(); if (this.locks.get(resourceId)?.token === token) this.locks.delete(resourceId); };
|
||||
```
|
||||
**버그 사후기록(주석)**: 옛 구현은 `.then(...)` 이 매번 새 Promise 를 반환해 동일성 비교가 *항상 false* → cleanup 실패. 또 release 시 무조건 `delete` 해서 연쇄 호출 시 다른 작업 entry 를 지우는 race. → 각 entry 에 고유 symbol 을 부여하고 "내 토큰이 최신일 때만" 정리하도록 수정 [S1].
|
||||
|
||||
### 동시성 제한 큐
|
||||
```typescript
|
||||
function defaultConcurrencyLimit() { return Math.max(2, (os.cpus()?.length ?? 4) - 1); } // UI 스레드 여유
|
||||
public async enqueue<T>(task: () => Promise<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.queue.push(async () => { try { resolve(await task()); } catch (e) { reject(e); } });
|
||||
this.processNext();
|
||||
});
|
||||
}
|
||||
```
|
||||
`processNext` 는 `activeCount < limit` 일 때만 다음 작업을 꺼내 실행하고, `finally` 에서 `activeCount--` 후 재귀로 다음을 당긴다. 무거운 LLM 호출은 큐가 아니라 `missionId` 락으로 직렬화하므로 큐는 단순하게 유지 [S2].
|
||||
|
||||
### 보상 트랜잭션 (파일 원자성)
|
||||
`isTransactionActive` 가드로 중복 begin 방지. `record` 는 같은 파일을 한 번만 백업(이미 있으면 skip). rollback 은 `created` 파일은 삭제, `modified` 는 원본 내용으로 복원. 외부 API 호출 성공 여부도 `recordExternalAction` 으로 추적해 `isFullyVerified()` 로 전체 검증 [S3].
|
||||
|
||||
## ⚖️ 비교 및 선택 기준 (Comparison & decision criteria)
|
||||
|
||||
| 도구 | 막는 문제 | 사용 시점 |
|
||||
|---|---|---|
|
||||
| AsyncLock (자원별) | 같은 자원 동시 수정(갱신 손실) | 파일/세션 등 공유 자원 read-modify-write |
|
||||
| 동시성 제한 큐 | 자원 폭주(메모리/IO/소켓) | 대량 작업을 일정 동시성으로 처리 |
|
||||
| 보상 트랜잭션 | 부분 실패로 인한 불일치 | 여러 파일을 한 단위로 변경 |
|
||||
| 외부 abort signal | 취소 불가 | 사용자 Stop / 타임아웃 ([[비동기 프로그래밍 Promise async await]]) |
|
||||
|
||||
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
|
||||
- **락 타임아웃의 부작용:** 타임아웃으로 깨어난 작업은 자원을 못 얻은 채 throw 한다. 호출부는 이 실패를 재시도/사용자 안내로 처리해야 한다(조용히 진행 금지).
|
||||
- **큐의 static 동시성:** 동적 조정이 없어 부하 급변에 둔감하지만, 무거운 작업이 락으로 직렬화되므로 큐는 단순함을 택했다 — 의도적 단순성.
|
||||
- **메모리 내 트랜잭션:** 백업이 메모리(Map)에 있어 프로세스가 죽으면 롤백이 불가능하다. 진짜 내구성이 필요하면 WAL/DB 가 필요.
|
||||
|
||||
## 🛠️ 적용 사례 (Applied in summary)
|
||||
- `AstraAI/src/core/lock.ts` — `lockManager` 싱글톤. agent 가 파일/세션 작업 직렬화에 사용 [S1].
|
||||
- `AstraAI/src/core/queue.ts` — `actionQueue` 싱글톤. 대량 액션 처리 [S2].
|
||||
- `AstraAI/src/core/transaction.ts` — AgentExecutor 가 파일 다중 변경을 묶을 때 [S3].
|
||||
|
||||
## 💻 코드 패턴 (Code patterns)
|
||||
```typescript
|
||||
// 1) 락 — 반드시 try/finally 로 해제 (src/core/lock.ts)
|
||||
const release = await lockManager.acquire(filePath);
|
||||
try {
|
||||
const cur = fs.readFileSync(filePath, 'utf-8');
|
||||
fs.writeFileSync(filePath, transform(cur)); // read-modify-write 직렬화
|
||||
} finally {
|
||||
release(); // 예외가 나도 반드시 해제
|
||||
}
|
||||
|
||||
// 2) 동시성 제한 큐 (src/core/queue.ts)
|
||||
const result = await actionQueue.enqueue(() => heavyTask(item)); // 슬롯 빌 때 실행
|
||||
|
||||
// 3) 보상 트랜잭션 (src/core/transaction.ts)
|
||||
tx.begin();
|
||||
try {
|
||||
await tx.record(fileA); fs.writeFileSync(fileA, nextA);
|
||||
await tx.record(fileB); fs.writeFileSync(fileB, nextB);
|
||||
tx.commit();
|
||||
} catch (e) { tx.rollback(); throw e; }
|
||||
```
|
||||
|
||||
## ✅ 검증 상태 및 신뢰도
|
||||
- **상태:** draft
|
||||
- **검증 단계:** applied
|
||||
- **출처 신뢰도:** A
|
||||
- **신뢰 점수:** 0.94
|
||||
- **중복 검사 결과:** 신규 생성 (New discovery)
|
||||
|
||||
## 🔗 지식 그래프 (Knowledge Graph)
|
||||
- **상위/루트:** [[AstraAI 아키텍처 개요]]
|
||||
- **관련 개념:** [[비동기 프로그래밍 Promise async await]], [[에러 처리와 커스텀 에러]], [[이벤트 소싱 스토어 패턴]]
|
||||
- **참조 맥락:** 로컬 LLM 이 공유 자원·대량 작업·다중 파일 변경의 동시성 안전 코드를 작성할 때 참조.
|
||||
|
||||
## 📚 출처 (Sources)
|
||||
- [S1] AstraAI/src/core/lock.ts — AsyncLockManager(토큰 기반), race 타임아웃, 버그 사후기록
|
||||
- [S2] AstraAI/src/core/queue.ts — ActionQueueManager 동시성 제한
|
||||
- [S3] AstraAI/src/core/transaction.ts — 보상 트랜잭션(begin/record/commit/rollback)
|
||||
|
||||
## 📝 변경 이력 (Change history)
|
||||
- 2026-06-13: AstraAI 코드 분석 기반 초안 생성.
|
||||
Reference in New Issue
Block a user