[5분순삭] 데이터베이스 01 동시성제어
동시성 제어( Concurrency Control )
다중 사용자 환경을 지원하는 데이터베이스 시스템에서 여러 트랜잭션들이 동시에 성공적으로 실행될 수 있도록 지원하는 기능
병행 수행 시 같은 데이터에 접근하여 연산을 실행해도 문제가 발생하지 않고 정확한 수행 결과를 얻을 수 있도록 트랜잭션의 수행을 제어하는 것을 의미
트랜잭션의 직렬화 수행 보장
목적
동시성 제어가 보장되지 않는 경우 문제점
트랜잭션이 사용하는 자원에 대하여 상호 배제 기능을 제공하는 기법
공유 lock은 읽기에 대한 동시성을 제공하고, 전용 lock(배타적 lock)은 쓰기에 대한 동시성을 제어
평소엔 공유 lock으로 모든 트랜잭션이 읽기가 가능하다가 한 트랜잭션이 쓰기 연산을 수행하면 해당 트랜잭션은 전용 lock을 얻고 나머지 트랜잭션은 읽기, 쓰기 등 그 데이터에 대한 모든 권한 불가능
Locking 단위로는 데이터베이스 자체, 릴레이션(파일), 투플(레코드), 속성(필드)가 될 수 있음
Locking 단위가 클 수록 병행성 수준이 낮아지고 기법이 간단해짐
모든 트랜잭션들이 Lock과 Unlock 연산을 확장단계와 수축단계로 구분하여 수행
시스템에서 생성하는 Time stamp를 고유한 트랜잭션의 식별자로 부여하여 실행 순서를 미리 선택하는 기법
실행 전 실행 여부에 대한 검증을 수행하지 않고, 일단 트랜잭션을 수행하고, 트랜잭션 종료시 검증을 수행하는 기법
장기 트랜잭션 철회시 자원 낭비 가능성이 낮거나 동시 사용 빈도가 낮은 시스템 사용
과정
1. Read Phase(검증 없이 트랜잭션을 실행)
2. Validation Phase(트랜잭션 종료 시 동시성 검증 실행)
3. Execution Phase(문제 없을 경우 반영, 문제 발생 시 철회)
Start(T): 트랜잭션 T가 판독 단계에 들어가면서 실행을 시작한 시간
Validation(T): 트랜잭션 T가 판독 단계를 끝내고 확인을 시작한 시간
Finish(T): 트랜잭션 T가 최종 기록 단계를 완료한 시간
데이터 변경 시 변경 내용 UNDO 영역 저장
읽는 시점 이후에 변경되었거나 완료된 값을 발견 시, CR Copy 생성(일관성있는 버전)
ex) 10023 시점에 시작 된 쿼리가 10023 시점 이후에 변경된 데이터 블록을 만나는 경우, Rollback 세그먼트에 저장된 정보를 이용해 이전 시점으로 되돌리고서 값을 읽음
Undo data를 활용하여 높은 수준의 동시성과 읽기 일관성을 유지할 수 있지만 snapshot too old 에러가 발생
→ Undo 영역의 크기 조정, 불필요한 커밋 최소화, 분할된 단계적 쿼리 실행 등이 필요
하나의 로직을 처리하는 SQL 질의들을 집합으로 묶어 도중에 예외가 발생할 경우 Rollback 처리, 모두 성공할 경우 Commit 처리
TypeORM에서는 connection.createQueryRunner() 메서드를 통해 QueryRunner 인스턴스를 만든 후 트랜잭션을 이용할 수 있음
// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async createUserWithTransaction(userData: any): Promise<User> {
// QueryRunner 인스턴스 생성
const queryRunner = this.userRepository.manager.connection.createQueryRunner();
await queryRunner.connect(); // 커넥션 연결
await queryRunner.startTransaction(); // 트랜잭션 시작
try {
// 여기서 원하는 데이터베이스 작업을 수행합니다.
const newUser = await this.userRepository.save(userData);
// 트랜잭션 성공 시 commit
await queryRunner.commitTransaction();
return newUser;
} catch (error) {
// 에러가 발생하면 롤백
await queryRunner.rollbackTransaction();
throw error;
} finally {
// queryRunner를 닫아줍니다.
await queryRunner.release();
}
}
}
TypeORM에서는 엔티티에 버전 관리를 위한 @VersionColumn
데코레이터를 사용 가능
이를 통해 동시성 충돌을 방지
// users.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, VersionColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@VersionColumn()
version: number;
}
위의 예시에서 version
필드가 엔티티의 버전
이 필드는 엔티티가 수정될 때마다 자동으로 증가
만약 동시에 여러 사용자가 같은 엔티티를 수정하려고 시도하면, 버전이 일치하지 않아 충돌이 발생
이러한 방식으로 트랜잭션과 버전 관리를 활용하여 TypeORM에서 동시성을 제어
역시 DB는 어렵네요.. 후속글도 부탁드립니다 👍