Files
2nd/10_Wiki/Dev/Architecture/동시성_제어_Lock_Queue_Transaction.md
T
Antigravity Agent 1cfd3bbb56 docs(10_Wiki): 위키 구조 정리 — 언어 튜토리얼 카테고리 폴더 제거 + 신규 자산 동기화
Topic_CSS/Topic_HTML/Topic_JavaScript/Topic_Prompt/Topic_Comfyui 등 기존 카테고리 폴더를 정리하고,
Topic_Graphic/Dev 등 신규 산출물과 Topics 내부 세션/메모리 기록을 동기화.
2026-07-05 00:10:59 +09:00

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
concurrency
AsyncLock
mutex
race condition
동시성
concurrency limit
트랜잭션
A 0.94 2026-06-13 2026-06-13
architecture
concurrency
lock
queue
transaction
astraai
AstraAI/src/core/lock.ts
AstraAI/src/core/queue.ts
AstraAI/src/core/transaction.ts
AstraAI

동시성 제어 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 체인 + 토큰

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();
    });
}

processNextactiveCount < 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.tslockManager 싱글톤. agent 가 파일/세션 작업 직렬화에 사용 [S1].
  • AstraAI/src/core/queue.tsactionQueue 싱글톤. 대량 액션 처리 [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)

📚 출처 (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 코드 분석 기반 초안 생성.