-
Race condition (경쟁 상태)배움/백엔드 2025. 1. 28. 23:59반응형
Race condition (경쟁 상태)은 여러 프로세스나 스레드가 동시에 같은 리소스에 접근하거나 조작할 때 발생하는 문제이다.
각 프로세스의 실행 순서에 따라 결과가 달라질 수 있어서, 의도하지 않은 오류나 버그가 발생할 수 있다.
특히 병렬 처리나 멀티스레드 환경, 그리고 비동기 처리에서 자주 나타나는 문제이다.
Race Condition의 예시
1. 은행 계좌 잔액 문제
한 계좌에서 두 사용자가 동시에 출금을 시도하는 경우를 생각해 보자.
let balance = 1000; function withdraw(amount) { if (balance >= amount) { balance -= amount; // 잔액 차감 } } // 동시에 실행되는 요청 withdraw(700); // 요청 1 withdraw(500); // 요청 2
- 요청 1과 요청 2가 거의 동시에 실행되면 balance 값은 -200이 될 수 있다. 이는 잔액 확인과 차감이 순차적으로 실행되지 않아서 발생하는 문제이다.
Race Condition을 예방하는 방법
1. Lock(잠금) 사용
공유 자원에 접근하기 전에 잠금을 설정해 다른 프로세스나 스레드가 동시에 접근하지 못하도록 한다.
const Mutex = require('async-mutex').Mutex; const mutex = new Mutex(); let balance = 1000; async function withdraw(amount) { const release = await mutex.acquire(); // 잠금 설정 try { if (balance >= amount) { balance -= amount; } } finally { release(); // 잠금 해제 } }
Mutex를 사용하면 한 번에 하나의 요청만 자원에 접근할 수 있으므로 Race Condition을 방지할 수 있다.
2. Atomic Operations(원자성 연산) 사용
원자성 연산은 작업이 분할되지 않고 완전히 수행되도록 보장한다. 예를 들어, Redis의 INCR 명령어는 원자적으로 값을 증가시키거나 감소시킬 수 있다.
redisClient.incrby("balance", -amount, (err, newBalance) => { if (newBalance < 0) { console.error("잔액 부족!"); } });
이 방식은 연산 도중 다른 요청이 끼어들 수 없게 하여 안전하다.
3. 트랜잭션(Transaction) 사용
트랜잭션은 여러 작업을 하나의 단위로 묶어 실행한다. 모든 작업이 성공해야만 적용되며, 실패 시 롤백된다.
BEGIN TRANSACTION; SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 데이터 잠금 IF balance >= 500 THEN UPDATE accounts SET balance = balance - 500 WHERE id = 1; ELSE ROLLBACK; END IF; COMMIT;
FOR UPDATE를 사용하면 특정 데이터 행을 잠글 수 있어 다른 작업이 해당 데이터를 수정하지 못하도록 한다.
4. Optimistic Locking(낙관적 잠금)
낙관적 잠금은 데이터 수정 전에 버전 번호를 확인하여 충돌 여부를 판단한다. 충돌이 발생하면 작업을 다시 시도하도록 한다.
UPDATE accounts SET balance = balance - 500, version = version + 1 WHERE id = 1 AND version = 1;
- version 값이 변경되지 않았을 때만 업데이트가 이루어지므로 Race Condition을 방지할 수 있다.
5. 비동기 처리 제어
JavaScript에서 비동기 작업을 다룰 때는 Promise나 async/await를 사용해 작업 순서를 제어할 수 있다.
const mutex = new Mutex(); async function withdraw(amount) { const release = await mutex.acquire(); try { if (balance >= amount) { balance -= amount; } } finally { release(); } }
비동기 작업에서도 Mutex를 사용하면 안전하게 동작을 제어할 수 있다.
Race Condition이 발생하는 환경
- 병렬 처리: 여러 스레드나 프로세스가 동시에 실행되는 환경
- 비동기 작업: JavaScript의 Promise나 async/await 같은 비동기 코드
- 공유 자원 사용: 메모리, 데이터베이스, 파일 등 여러 작업이 동시에 접근하는 자원
정리
Race Condition은 동시성 문제로 인해 발생하며, 잘못된 동작이나 데이터 손상을 초래할 수 있다.
이를 예방하려면 Lock, Atomic Operation, 트랜잭션 같은 기법을 사용해야 한다.
프로그램 설계 단계에서부터 동시성 문제를 고려하면 안전하고 신뢰성 높은 시스템을 구축할 수 있다.
반응형'배움 > 백엔드' 카테고리의 다른 글
MongoDB 데이터를 백업하거나 덮어씌우는 방법 (dump, restore) (0) 2024.01.24 MongoDB aggregation: $lookup, $unwind, $project (0) 2022.09.29 JavaScript에서의 객체 순서 보장법 (1) 2022.09.26 Param, Query, Body (0) 2022.08.17 Kafka - Acks 옵션 (0) 2022.07.26