락 전략 with Laravel

2024. 10. 21. 11:53PHP

1. 낙관적 락 (Optimistic Lock)

개요

낙관적 락은 데이터 충돌이 거의 발생하지 않을 것이라는 가정하에 동작합니다. 실제로 데이터를 수정할 때만 충돌을 확인합니다.

사용 상황

  • 읽기 작업이 쓰기 작업보다 훨씬 많은 경우
  • 동시 수정 가능성이 낮은 경우
  • 높은 동시성이 요구되는 경우

라라벨에서의 구현

라라벨에는 version 컬럼을 사용하여 낙관적 락을 구현합니다.

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use \Illuminate\Database\Eloquent\Concerns\HasAttributes;

    protected $fillable = ['name', 'price', 'version'];
}

// 사용 예시
$product = Product::find(1);
$product->price = 100;
$product->save();

if ($product->wasChanged()) {
    // 성공적으로 업데이트됨
} else {
    // 다른 프로세스가 이미 수정함
}

장점

  1. 높은 동시성: 락을 획득하기 위해 대기하지 않음
  2. 데드락 위험이 없음
  3. 데이터베이스 리소스 사용이 적음

단점

  1. 충돌 발생 시 애플리케이션에서 재시도 로직을 구현해야 함
  2. 높은 충돌 가능성이 있는 환경에서는 성능이 저하될 수 있음

 

1. 비관적 락 (Pessimistic Lock)

개요

비관적 락은 데이터 충돌이 자주 발생할 것이라고 가정하고, 데이터를 읽는 시점에 락을 겁니다.

사용 상황

  • 동시 수정 가능성이 높은 경우
  • 데이터 일관성이 매우 중요한 경우
  • 트랜잭션이 짧고 빨리 처리되는 경우

라라벨에서의 구현

라라벨에서는 lockForUpdate() 메소드를 사용하여 비관적 락을 구현합니다.

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $product = Product::lockForUpdate()->find(1);
    $product->price = 100;
    $product->save();
});

 

 

장점

  1. 데이터 일관성 보장
  2. 충돌 처리 로직이 단순함

단점

  1. 동시성 저하: 다른 트랜잭션이 락이 해제될 때까지 대기
  2. 데드락 발생 가능성
  3. 데이터베이스 리소스 사용량 증가

 

3. 명시적 락 (Explicit Locking)

개요

명시적 락은 개발자가 직접 락을 제어하는 방식입니다. 라라벨에서는 Cache나 Redis를 사용하여 구현할 수 있습니다.

사용 상황

  • 세밀한 락 제어가 필요한 경우
  • 분산 환경에서의 동기화가 필요한 경우
  • 데이터베이스 외부의 리소스에 대한 락이 필요한 경우

라라벨에서의 구현

라라벨의 Cache 파사드를 사용하여 구현할 수 있습니다.

use Illuminate\Support\Facades\Cache;

if (Cache::lock('product-1-lock')->get()) {
    // 락 획득 성공, 작업 수행
    $product = Product::find(1);
    $product->price = 100;
    $product->save();

    Cache::lock('product-1-lock')->release();
} else {
    // 락 획득 실패
}

장점

  1. 세밀한 제어 가능
  2. 데이터베이스에 의존하지 않는 락 구현 가능
  3. 분산 환경에서 효과적

단점

  1. 구현 복잡도 증가
  2. 락 해제 실패 시 문제 발생 가능
  3. 성능 오버헤드 발생 가능

 

데드락 (Deadlock) 해결 방법

데드락은 두 개 이상의 트랜잭션이 서로가 점유한 자원을 요구하며 무한정 대기하는 상황을 말합니다.

1. 타임아웃 설정

DB::transaction(function () {
    // 트랜잭션 내용
}, 5); // 5초 타임아웃

2. 락 범위 최소화

필요한 최소한의 데이터에만 락을 적용합니다.

4. 재시도 로직 구현

use Illuminate\Database\QueryException;

$maxAttempts = 5;
$attempts = 0;

while ($attempts < $maxAttempts) {
    try {
        DB::transaction(function () {
            // 트랜잭션 내용
        });
        break; // 성공 시 루프 탈출
    } catch (QueryException $e) {
        if ($e->getCode() == 40001) { // 데드락 에러 코드
            $attempts++;
            sleep(1); // 잠시 대기 후 재시도
        } else {
            throw $e; // 다른 에러는 그대로 던짐
        }
    }
}

if ($attempts == $maxAttempts) {
    // 최대 시도 횟수 초과
}