Lock

ClassBinu·2024년 4월 20일

F-lab

목록 보기
11/65

Pessimistic Lock

비관적 락

충돌이 발생할 것을 비관적으로 가정
충돌 방지를 위한 데이터 접근을 엄격하게 제어
데이터를 사용하려는 시점에 즉시 락을 걸어 버림

락이 유지되는 동안 다른 트랜잭션은 대기해야 함.
데드락 발생 위험

비관적 락의 종류

  1. 공유 락
    데이터 읽을 때 사용
    트랜잭션은 동시에 데이터 읽을 수 있지만, 수정과 쓰기는 차단

  2. 배타적 락
    데이터 수정할 때 사용
    해당 트랜잭션만 데이터 읽고 수정할 수 있도록 함.
    다른 트랜잭션은 데이터 읽고 쓸 수 없음.

사례: 은행과 같이 정확성과 일관성이 중요한 경우

// 데이터 업데이트
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getConnection } from 'typeorm';
import { Post } from './entities/post.entity';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private readonly postRepository: Repository<Post>,
  ) {}

  async updatePost(id: number, title: string, content: string): Promise<Post> {
    return await getConnection().transaction(async transactionalEntityManager => {
      const post = await transactionalEntityManager
        .getRepository(Post)
        .createQueryBuilder("post")
        .setLock("pessimistic_write")
        .where("post.id = :id", { id })
        .getOne();

      if (!post) {
        throw new Error('Post not found');
      }

      post.title = title;
      post.content = content;

      return await transactionalEntityManager.save(post);
    });
  }
}

// 데이터 읽기
async getPostForUpdate(id: number): Promise<Post> {
  return await getConnection().transaction(async transactionalEntityManager => {
    const post = await transactionalEntityManager
      .getRepository(Post)
      .createQueryBuilder("post")
      .setLock("pessimistic_read")
      .where("post.id = :id", { id })
      .getOne();

    if (!post) {
      throw new Error('Post not found');
    }

    return post;
  });
}

Optimistic Lock

낙관적 락

충돌 가능성이 적다고 낙관적으로 제한
데이터를 처음부터 제한하지 않고 데이터를 업데이트할 때 충돌이 없었는지 확인
예를 들어 버전 번호나 타임스탬프로 업데이트 시점의 버전과 현재 버전을 비교함.

트랜잭션 동시 가능
데드락 위험 없음

충돌이 발생하면 트랜잭션 롤백하고 다시 시도
충돌 감지 별도 처리 로직 필요

예시: 웹과 같이 데이터를 동시에 읽기만 하고 가끔 수정하는 경우

// Entity
import { Entity, PrimaryGeneratedColumn, Column, VersionColumn } from 'typeorm';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;

  @VersionColumn()
  version: number;
}

// Service
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from './entities/post.entity';
import { UpdatePostDto } from './dto/update-post.dto';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private readonly postRepository: Repository<Post>,
  ) {}

  async update(id: number, updatePostDto: UpdatePostDto): Promise<Post> {
    const post = await this.postRepository.findOne(id);
    if (!post) {
      throw new Error('Post not found');
    }

    // 버전이 일치하는지 검사
    if (post.version !== updatePostDto.version) {
      throw new Error('Update conflict, please reload the latest version and try again.');
    }

    this.postRepository.merge(post, updatePostDto);
    return this.postRepository.save(post);
  }
}

0개의 댓글