InfoGrab DocsInfoGrab Docs

트랜잭션 가이드라인

요약

이 문서는 애플리케이션 코드에서 데이터베이스 트랜잭션을 사용하는 몇 가지 예시를 제공합니다. 추가 참고 자료로 PostgreSQL 문서의 트랜잭션 항목을 확인하세요. Tenant Scale 그룹은 GitLab 주 데이터베이스를 분리하고 일부 데이터베이스 테이블을 다른 데이터베이스 서버로 이동할 계획입니다.

이 문서는 애플리케이션 코드에서 데이터베이스 트랜잭션을 사용하는 몇 가지 예시를 제공합니다.

추가 참고 자료로 PostgreSQL 문서의 트랜잭션 항목을 확인하세요.

데이터베이스 분해 및 샤딩#

Tenant Scale 그룹은 GitLab 주 데이터베이스를 분리하고 일부 데이터베이스 테이블을 다른 데이터베이스 서버로 이동할 계획입니다.

먼저 ci_* 관련 데이터베이스 테이블부터 분해를 시작합니다. 현재의 애플리케이션 개발 경험을 유지하기 위해 코드베이스에 툴링과 정적 분석기를 추가하여 올바른 데이터 접근 및 데이터 수정 방법을 보장합니다. 데이터베이스 트랜잭션을 정의하는 올바른 형식을 사용함으로써, 향후 상당한 리팩토링 작업을 줄일 수 있습니다.

트랜잭션 블록#

ActiveRecord 라이브러리는 데이터베이스 구문을 트랜잭션으로 묶는 편리한 방법을 제공합니다:

issue = Issue.find(10)
project = issue.project

ApplicationRecord.transaction do
  issue.update!(title: 'updated title')
  project.update!(last_update_at: Time.now)
end

이 트랜잭션은 두 개의 데이터베이스 테이블을 포함합니다. 오류가 발생하면 각 UPDATE 구문은 이전의 일관된 상태로 롤백됩니다.

ActiveRecord::Base 클래스 참조를 피하고 대신 ApplicationRecord를 사용하세요.

트랜잭션과 데이터베이스 잠금#

트랜잭션 블록이 열리면 데이터베이스는 리소스에 필요한 잠금을 획득하려 시도합니다. 잠금의 유형은 실제 데이터베이스 구문에 따라 달라집니다.

다음 코드가 두 개의 서로 다른 프로세스에서 동시에 실행되는 동시 업데이트 시나리오를 고려해 보세요:

issue = Issue.find(10)
project = issue.project

ApplicationRecord.transaction do
  issue.update!(title: 'updated title')
  project.update!(last_update_at: Time.now)
end

데이터베이스는 참조된 issueproject 레코드에 대해 FOR UPDATE 잠금을 획득하려 시도합니다. 이 경우, 두 개의 경쟁 트랜잭션이 이 잠금을 다투며 그 중 하나만 성공적으로 잠금을 획득합니다. 다른 트랜잭션은 첫 번째 트랜잭션이 완료될 때까지 잠금 대기열에서 기다려야 합니다. 두 번째 트랜잭션의 실행은 이 시점에서 차단됩니다.

트랜잭션 속도#

잠금 경합을 방지하고 안정적인 애플리케이션 성능을 유지하려면 트랜잭션 블록이 가능한 한 빨리 완료되어야 합니다. 트랜잭션이 잠금을 획득하면 트랜잭션이 완료될 때까지 잠금을 유지합니다.

애플리케이션 성능 외에도, 장시간 실행되는 트랜잭션은 데이터베이스 마이그레이션을 차단하여 애플리케이션 업그레이드 프로세스에도 영향을 미칠 수 있습니다.

위험한 예시: 서드파티 API 호출#

다음 예시를 고려해 보세요:

member = Member.find(5)

Member.transaction do
  member.update!(notification_email_sent: true)

  member.send_notification_email
end

여기서는 send_notification_email 메서드가 성공할 때만 notification_email_sent 칼럼이 업데이트되도록 보장합니다. send_notification_email 메서드는 이메일 발송 서비스에 네트워크 요청을 실행합니다. 기반 인프라에 타임아웃이 지정되어 있지 않거나 네트워크 호출이 너무 오래 걸리면 데이터베이스 트랜잭션이 열린 상태로 유지됩니다.

이상적으로는 트랜잭션에 데이터베이스 구문만 포함되어야 합니다.

transaction 블록에서 다음 작업은 피하세요:

  • 외부 네트워크 요청 (예:

Sidekiq job 트리거하기.

  • 이메일 발송.

  • HTTP API 호출.

  • 다른 연결을 사용하여 데이터베이스 구문 실행.

  • 파일 시스템 작업.

  • 길고 CPU 집약적인 연산.

  • sleep(n) 호출.

명시적 모델 참조#

트랜잭션이 동일한 데이터베이스 테이블의 레코드를 수정하는 경우, Model.transaction 블록을 사용하는 것을 권장합니다:

build_1 = Ci::Build.find(1)
build_2 = Ci::Build.find(2)

Ci::Build.transaction do
  build_1.touch
  build_2.touch
end

위 트랜잭션은 transaction 블록의 모델과 동일한 데이터베이스 연결을 사용합니다. 멀티 데이터베이스 환경에서 다음 예시는 위험합니다:

# `ci_builds` table is located on another database
class Ci::Build < CiDatabase
end

build_1 = Ci::Build.find(1)
build_2 = Ci::Build.find(2)

ApplicationRecord.transaction do
  build_1.touch
  build_2.touch
end

ApplicationRecord 클래스는 Ci::Build 레코드와 다른 데이터베이스 연결을 사용합니다. 트랜잭션 블록의 두 구문은 트랜잭션의 일부가 아니며 문제가 발생해도 롤백되지 않습니다. 이들은 서드파티 호출처럼 동작합니다.

트랜잭션 가이드라인

GitLab v19.1
원문 보기
요약

이 문서는 애플리케이션 코드에서 데이터베이스 트랜잭션을 사용하는 몇 가지 예시를 제공합니다. 추가 참고 자료로 PostgreSQL 문서의 트랜잭션 항목을 확인하세요. Tenant Scale 그룹은 GitLab 주 데이터베이스를 분리하고 일부 데이터베이스 테이블을 다른 데이터베이스 서버로 이동할 계획입니다.

이 문서는 애플리케이션 코드에서 데이터베이스 트랜잭션을 사용하는 몇 가지 예시를 제공합니다.

추가 참고 자료로 PostgreSQL 문서의 트랜잭션 항목을 확인하세요.

데이터베이스 분해 및 샤딩#

Tenant Scale 그룹은 GitLab 주 데이터베이스를 분리하고 일부 데이터베이스 테이블을 다른 데이터베이스 서버로 이동할 계획입니다.

먼저 ci_* 관련 데이터베이스 테이블부터 분해를 시작합니다. 현재의 애플리케이션 개발 경험을 유지하기 위해 코드베이스에 툴링과 정적 분석기를 추가하여 올바른 데이터 접근 및 데이터 수정 방법을 보장합니다. 데이터베이스 트랜잭션을 정의하는 올바른 형식을 사용함으로써, 향후 상당한 리팩토링 작업을 줄일 수 있습니다.

트랜잭션 블록#

ActiveRecord 라이브러리는 데이터베이스 구문을 트랜잭션으로 묶는 편리한 방법을 제공합니다:

issue = Issue.find(10)
project = issue.project

ApplicationRecord.transaction do
  issue.update!(title: 'updated title')
  project.update!(last_update_at: Time.now)
end

이 트랜잭션은 두 개의 데이터베이스 테이블을 포함합니다. 오류가 발생하면 각 UPDATE 구문은 이전의 일관된 상태로 롤백됩니다.

ActiveRecord::Base 클래스 참조를 피하고 대신 ApplicationRecord를 사용하세요.

트랜잭션과 데이터베이스 잠금#

트랜잭션 블록이 열리면 데이터베이스는 리소스에 필요한 잠금을 획득하려 시도합니다. 잠금의 유형은 실제 데이터베이스 구문에 따라 달라집니다.

다음 코드가 두 개의 서로 다른 프로세스에서 동시에 실행되는 동시 업데이트 시나리오를 고려해 보세요:

issue = Issue.find(10)
project = issue.project

ApplicationRecord.transaction do
  issue.update!(title: 'updated title')
  project.update!(last_update_at: Time.now)
end

데이터베이스는 참조된 issueproject 레코드에 대해 FOR UPDATE 잠금을 획득하려 시도합니다. 이 경우, 두 개의 경쟁 트랜잭션이 이 잠금을 다투며 그 중 하나만 성공적으로 잠금을 획득합니다. 다른 트랜잭션은 첫 번째 트랜잭션이 완료될 때까지 잠금 대기열에서 기다려야 합니다. 두 번째 트랜잭션의 실행은 이 시점에서 차단됩니다.

트랜잭션 속도#

잠금 경합을 방지하고 안정적인 애플리케이션 성능을 유지하려면 트랜잭션 블록이 가능한 한 빨리 완료되어야 합니다. 트랜잭션이 잠금을 획득하면 트랜잭션이 완료될 때까지 잠금을 유지합니다.

애플리케이션 성능 외에도, 장시간 실행되는 트랜잭션은 데이터베이스 마이그레이션을 차단하여 애플리케이션 업그레이드 프로세스에도 영향을 미칠 수 있습니다.

위험한 예시: 서드파티 API 호출#

다음 예시를 고려해 보세요:

member = Member.find(5)

Member.transaction do
  member.update!(notification_email_sent: true)

  member.send_notification_email
end

여기서는 send_notification_email 메서드가 성공할 때만 notification_email_sent 칼럼이 업데이트되도록 보장합니다. send_notification_email 메서드는 이메일 발송 서비스에 네트워크 요청을 실행합니다. 기반 인프라에 타임아웃이 지정되어 있지 않거나 네트워크 호출이 너무 오래 걸리면 데이터베이스 트랜잭션이 열린 상태로 유지됩니다.

이상적으로는 트랜잭션에 데이터베이스 구문만 포함되어야 합니다.

transaction 블록에서 다음 작업은 피하세요:

  • 외부 네트워크 요청 (예:

Sidekiq job 트리거하기.

  • 이메일 발송.

  • HTTP API 호출.

  • 다른 연결을 사용하여 데이터베이스 구문 실행.

  • 파일 시스템 작업.

  • 길고 CPU 집약적인 연산.

  • sleep(n) 호출.

명시적 모델 참조#

트랜잭션이 동일한 데이터베이스 테이블의 레코드를 수정하는 경우, Model.transaction 블록을 사용하는 것을 권장합니다:

build_1 = Ci::Build.find(1)
build_2 = Ci::Build.find(2)

Ci::Build.transaction do
  build_1.touch
  build_2.touch
end

위 트랜잭션은 transaction 블록의 모델과 동일한 데이터베이스 연결을 사용합니다. 멀티 데이터베이스 환경에서 다음 예시는 위험합니다:

# `ci_builds` table is located on another database
class Ci::Build < CiDatabase
end

build_1 = Ci::Build.find(1)
build_2 = Ci::Build.find(2)

ApplicationRecord.transaction do
  build_1.touch
  build_2.touch
end

ApplicationRecord 클래스는 Ci::Build 레코드와 다른 데이터베이스 연결을 사용합니다. 트랜잭션 블록의 두 구문은 트랜잭션의 일부가 아니며 문제가 발생해도 롤백되지 않습니다. 이들은 서드파티 호출처럼 동작합니다.