본문 바로가기
backend

[Redis] Redis를 이용한 동시성 이슈 해결 실습해보기 - SETNX 사용 방법

by BK0625 2025. 2. 20.
반응형

 

동시성 문제란?

여러개의 프로세스나 스레드가 동시에 같은 리소스, 예를 들자면 데이터베이스나 캐시, 파일 등에 접근 할 때 예상 치 못한 데이터 충돌, 중복 실행, 무결성 문제가 발생하는 상황이다.

 

대표적인 예시로는 재고 감소, 중복 결제 방지, 특정 작업의 중복 실행 방지 등이 필요 할 때 동시성 제어가 필요하다.

 

 

Redis를 이용한 동시성 해결 방법

redis를 통해 분산락을 구현해보자.

 

Redis를 활용하여 분산 락(distributed lock)을 사용하면, 하나의 작업이 끝날 때까지 다른 프로세스가 동일한 리소스를 변경하지 못하도록 막을 수 있다.

 

Redis를 이용한 대표적인 동시성 해결 기법은 다음과 같다

  • SETNX (SET if Not Exists) 사용
  • Redlock 알고리즘 (분산 락 강화 버전) 사용

이 두가지 방법을 nodejs, docker 환경에서 실습해보자. 이번 포스팅에서는 SETNX를 이용한 락을 구현해보겠다. nodejs나 docker, 기본적인 redis 기능에 대한 설명은 생략한다.

 

 

기본적인 SETNX를 이용한 락 구현

Redis의 SETNX를 활용하여 한 번에 하나의 프로세스만 특정 리소스를 접근하도록 제한하는 방법이다.

 

동작 방식

  1. 작업 전에 락을 설정 (SETNX + TTL 설정)
  2. 다른 프로세스가 동일한 리소스에 접근하려하면 거부
  3. 작업이 끝나면 락을 해제

예제 코드

1. 프로젝트 폴더 및 파일 구성

mkdir redis-lock-example && cd redis-lock-example
mkdir src
touch docker-compose.yml
touch src/index.js
touch Dockerfile

 

2. docker-compose.yml

version: "3.8"
services:
  redis:
    image: redis:latest
    container_name: my-redis
    ports:
      - "6379:6379"
  app:
    build: .
    container_name: node-app
    depends_on:
      - redis
    environment:
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    volumes:
      - .:/usr/src/app
    command: ["node", "src/index.js"]

 

3. Dockerfile 작성

# Node.js 이미지 사용
FROM node:18

# 작업 디렉터리 설정
WORKDIR /usr/src/app

# package.json 복사 및 패키지 설치
COPY package.json ./
RUN npm install

# 소스 코드 복사
COPY . .

# 실행 명령어
CMD ["node", "src/index.js"]

 

4. package.json 작성

npm init -y
npm install ioredis

 

5. src/index.js

import Redis from "ioredis";

// Redis 연결
const redis = new Redis({
    host: process.env.REDIS_HOST || "localhost",
    port: process.env.REDIS_PORT || 6379,
});

async function acquireLock(key, ttl) {
    const lock = await redis.set(key, "LOCKED", "NX", "PX", ttl);
    return lock === "OK"; // 락을 획득했는지 여부 반환
}

async function releaseLock(key) {
    await redis.del(key);
}

async function processWithLock() {
    const lockKey = "my_resource_lock";
    const lockTTL = 5000; // 5초 동안 락 유지

    const lockAcquired = await acquireLock(lockKey, lockTTL);

    if (!lockAcquired) {
        console.log("🔒 다른 프로세스가 사용 중. 다시 시도하세요.");
        return;
    }

    try {
        console.log("✅ 락 획득! 작업 수행 중...");
        await new Promise((resolve) => setTimeout(resolve, 3000)); // 3초 동안 작업 수행
    } finally {
        await releaseLock(lockKey);
        console.log("🔓 락 해제 완료!");
    }
}

// 테스트 실행
processWithLock();

 

 

그 다음 docker-compose up --build 명령어를 실행한다.

 

 

테스트 방법

  1. 터미널을 2개 열고
  2. 각각의 터미널에서 다음 명령어를 실행한다.
docker-compose run --rm app

 

그렇게 되면 첫 번째 프로세스가 락을 획득하고 실행 중일 때, 두 번째 프로세스는 락을 못 잡고 대기한다.

  • 첫번째 실행
    • ✅ 락 획득! 작업 수행 중... 🔓 락 해제 완료!
  • 두번째 실행
    • 🔒 다른 프로세스가 사용 중. 다시 시도하세요.

 

사용하기도 편하고 직관적이다. 문제는 실 서비스를 운영할 때 redis를 한개만 띄워놓지 않는다는 것이다. 만약 단일 redis 노드가 아니라 여러개가 있다면 어떻게 해야 될까? 다음 포스팅에서 Redlock 알고리즘을 이용한 분산 락을 구현해보겠다.

 

소스 코드는 다음 링크에 있다.

https://github.com/chobkyu/node-redis-distributed-lock

 

공부하면서 정리한 내용입니다. 모든 지적 감사히 받겠습니다:)

반응형

'backend' 카테고리의 다른 글

[Server] Nginx  (0) 2025.03.26
[Redis] Redis를 이용한 동시성 이슈 해결 실습 - Redlock  (0) 2025.02.20