1cfd3bbb56
Topic_CSS/Topic_HTML/Topic_JavaScript/Topic_Prompt/Topic_Comfyui 등 기존 카테고리 폴더를 정리하고, Topic_Graphic/Dev 등 신규 산출물과 Topics 내부 세션/메모리 기록을 동기화.
8.0 KiB
8.0 KiB
id, title, category, status, verification_status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, created_at, updated_at, review_reason, merge_history, tags, raw_sources, applied_in, github_commit
| id | title | category | status | verification_status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | created_at | updated_at | review_reason | merge_history | tags | raw_sources | applied_in | github_commit | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| concurrency-lock-queue-transaction | 동시성 제어 Lock Queue Transaction | Architecture | draft | applied |
|
A | 0.94 | 2026-06-13 | 2026-06-13 |
|
|
|
동시성 제어 Lock Queue Transaction
🎯 한 줄 통찰 (One-line insight)
단일 스레드 JavaScript 도 await 사이에 다른 작업이 끼어들어 경쟁 상태(race condition) 가 생기며, AstraAI 는 자원별 비동기 락(AsyncLock)·동시성 제한 큐·파일 보상 트랜잭션 세 가지로 "한 번에 하나만·자원을 폭주시키지 않고·실패하면 되돌리는" 동시성 안전을 확보한다 [S1][S2][S3].
🧠 핵심 개념 (Core concepts)
- JS 의 동시성: 단일 스레드라도
await지점에서 제어가 넘어가므로, 같은 파일을 동시에 읽고-수정-쓰면 갱신 손실이 난다. 직렬화가 필요 [S1]. - 비동기 뮤텍스 (AsyncLock): 자원 ID 별로 "이전 작업이 끝나야 다음이 시작"되는 Promise 체인.
acquire가 release 함수를 반환,try/finally로 반드시 해제 [S1]. - 데드락 방지 타임아웃: 락 대기를
Promise.race([previous, timeout])로 감싸 무한 대기 차단 [S1]. - 동시성 제한 큐: 동시에 실행되는 작업 수를
max(2, cpus-1)로 제한해 I/O·메모리 폭주 방지 [S2]. - 보상 트랜잭션: 파일시스템에 트랜잭션이 없으므로, 변경 전 백업→실패 시 복원으로 원자성을 흉내 [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 체인 + 토큰
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].
동시성 제한 큐
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)
// 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 코드 분석 기반 초안 생성.