Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

개발자공부일기

Prisma Transaction 본문

TIL(Today I Learned)

Prisma Transaction

JavaCPP 2024. 11. 27. 20:50

Prisma에서 트랜잭션을 사용하는 방법과 작동 방식을 더 자세히 설명하겠습니다. 트랜잭션의 개념, Prisma에서의 구현 방식, 코드 동작 원리, 유용한 기능, 그리고 실제 사용 사례까지 구체적으로 다룹니다.

 

트랜잭션의 기본 개념

트랜잭션은 데이터베이스에서 여러 작업을 하나의 단위로 묶어서 실행하는 것입니다.
모든 작업이 성공하면 커밋(Commit)되고, 하나라도 실패하면 롤백(Rollback)되어 데이터베이스가 원래 상태로 돌아갑니다.

트랜잭션은 데이터베이스의 ACID 속성을 보장합니다:

  1. Atomicity(원자성): 작업은 모두 성공하거나 모두 실패해야 합니다.
  2. Consistency(일관성): 트랜잭션 후에도 데이터는 유효한 상태를 유지해야 합니다.
  3. Isolation(격리성): 한 트랜잭션의 작업이 다른 트랜잭션에 영향을 미치지 않습니다.
  4. Durability(지속성): 커밋된 작업은 영구적으로 저장됩니다.

 

Prisma에서의 트랜잭션 종류

Prisma는 두 가지 트랜잭션 방식을 지원합니다:

  1. 인터랙티브 트랜잭션 (prisma.$transaction)
    여러 작업을 순차적으로 실행하며, 동기적으로 실행 흐름을 제어할 수 있습니다.
  2. 배치 트랜잭션 (Batch Transactions)
    독립적인 쿼리를 묶어 실행하며, 쿼리 간 순서 의존성이 없을 때 적합합니다.

 

1. 인터랙티브 트랜잭션

인터랙티브 트랜잭션은 순차적인 논리 흐름이 필요할 때 사용합니다.
예를 들어, 계좌 이체처럼 하나의 작업 결과가 다음 작업에 영향을 미치는 경우 유용합니다.

사용법

await prisma.$transaction(async (prisma) => {
  const sender = await prisma.account.update({
    where: { id: 1 },
    data: { balance: { decrement: 100 } },
  });

  if (sender.balance < 0) {
    throw new Error("잔액이 부족합니다."); // 오류 발생 시 롤백
  }

  await prisma.account.update({
    where: { id: 2 },
    data: { balance: { increment: 100 } },
  });

  return sender; // 성공 시 결과 반환
});

실행 원리

  1. prisma.$transaction 내부의 모든 작업은 하나의 트랜잭션으로 처리됩니다.
  2. prisma 인스턴스가 제공되며, 이를 사용해 쿼리를 실행합니다.
  3. throw 키워드로 에러를 발생시키면, 트랜잭션 내 모든 작업이 롤백됩니다.
  4. 트랜잭션이 성공하면 commit되고, 반환 값을 호출자에게 전달합니다.

 

예제: 복합 작업 처리

await prisma.$transaction(async (prisma) => {
  const newUser = await prisma.user.create({
    data: { username: "john_doe", email: "john@example.com" },
  });

  await prisma.profile.create({
    data: { bio: "Hello, world!", userId: newUser.id },
  });

  const post = await prisma.post.create({
    data: { title: "My First Post", content: "This is my first post.", userId: newUser.id },
  });

  return post;
});

이 예제에서는 새로운 사용자, 프로필, 게시물을 생성합니다. 중간에 하나라도 실패하면 모든 작업이 롤백됩니다.

 

2. 배치 트랜잭션

배치 트랜잭션은 독립적인 쿼리를 묶어 병렬로 실행합니다.
쿼리 간 순서나 종속성이 없을 때 성능 향상에 유리합니다.

사용법

await prisma.$transaction([
  prisma.user.create({
    data: { username: "user1", email: "user1@example.com" },
  }),
  prisma.user.create({
    data: { username: "user2", email: "user2@example.com" },
  }),
  prisma.post.create({
    data: { title: "Hello World", content: "First post", userId: 1 },
  }),
]);

 

실행 원리

  1. 배열에 전달된 모든 쿼리를 동시에 실행합니다.
  2. 모든 쿼리가 성공해야만 트랜잭션이 커밋됩니다.
  3. 하나라도 실패하면 모든 작업이 롤백됩니다.

 

트랜잭션 옵션

prisma.$transaction은 트랜잭션 실행 방식을 제어하는 여러 옵션을 지원합니다.

1.타임아웃 (Timeout)
트랜잭션의 최대 실행 시간을 설정합니다.

await prisma.$transaction([query1, query2], { timeout: 5000 }); // 5초

2.데드락 재시도 (Deadlock Retry)
데드락(교착 상태)이 발생하면 Prisma가 트랜잭션을 자동으로 재시도합니다.

await prisma.$transaction([query1, query2], { maxWait: 2000 }); // 최대 대기 시간

 

트랜잭션 활용 시 주의점

  1. 작업 간 의존성 관리
    트랜잭션 내 작업은 서로 연관성을 가질 수 있습니다. 따라서 논리적 순서를 잘 설계해야 합니다.
  2. 오류 처리와 롤백
    트랜잭션 내에서 throw를 사용하면 모든 작업이 롤백됩니다.
    그러나 일부 데이터베이스는 특정 작업을 롤백할 수 없으므로, 테스트 환경에서 충분히 검증해야 합니다.
  3. 트랜잭션 범위 최소화
    트랜잭션은 데이터베이스 리소스를 점유하므로, 가능한 한 짧게 유지해야 합니다.
    예를 들어, 불필요한 네트워크 요청이나 비동기 작업을 포함시키지 않도록 주의하세요.

 

Prisma 트랜잭션 디버깅

1.쿼리 로그 활성화
schema.prisma 파일에 로그를 설정하여 쿼리를 디버깅합니다.

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["interactiveTransactions"]
  log = ["query", "info", "warn", "error"]
}

2.Try-Catch 블록 사용
오류 발생 시 트랜잭션 상태를 로그로 출력합니다.

try {
  await prisma.$transaction([...]);
} catch (error) {
  console.error("Transaction Error:", error);
}

종합 예제: 사용자와 주문 관리

사용자가 상품을 구매하면, 재고를 감소시키고 주문 기록을 저장하는 트랜잭션 예제입니다.

await prisma.$transaction(async (prisma) => {
  const product = await prisma.product.update({
    where: { id: 1 },
    data: { stock: { decrement: 1 } },
  });

  if (product.stock < 0) {
    throw new Error("재고가 부족합니다.");
  }

  const order = await prisma.order.create({
    data: { userId: 1, productId: 1, quantity: 1 },
  });

  return order;
});

 

그리고 prisma transaction에서 격리 수준 이라는게 있다.

 

트랜잭션의 격리 수준(Isolation Levels)은 트랜잭션이 서로 간섭하지 않도록 보장하는 방식입니다. 격리 수준은 트랜잭션의 동시성(성능)과 데이터 일관성(안전성) 간의 균형을 결정합니다.

각 격리 수준은 특정 동시성 문제(Dirty Read, Non-Repeatable Read, Phantom Read)의 발생 여부를 정의합니다.

대표적인 동시성 문제

  1. Dirty Read (더러운 읽기)
    한 트랜잭션이 다른 트랜잭션의 커밋되지 않은 데이터를 읽는 경우.
    예: 다른 사용자가 임시 변경한 데이터가 갑자기 롤백되어 읽은 데이터가 유효하지 않음.
  2. Non-Repeatable Read (반복 불가능한 읽기)
    같은 트랜잭션 내에서 두 번 조회 시 결과가 다른 경우.
    예: 한 트랜잭션에서 데이터를 읽는 동안, 다른 트랜잭션이 데이터를 수정하거나 삭제함.
  3. Phantom Read (유령 읽기)
    한 트랜잭션에서 특정 조건으로 데이터를 조회할 때, 다른 트랜잭션이 조건에 맞는 데이터를 삽입함.
    예: 처음 조회할 때는 없던 데이터가 다시 조회할 때 나타남.

 

Prisma에서의 격리 수준

Prisma는 기본적으로 데이터베이스의 격리 수준을 따릅니다. 하지만, 인터랙티브 트랜잭션을 사용할 때 명시적으로 격리 수준을 설정할 수 있습니다.

사용법

await prisma.$transaction(async (prisma) => {
  // 트랜잭션 내부 작업
}, {
  isolationLevel: 'Serializable', // 격리 수준 설정
});

지원되는 격리 수준

Prisma는 데이터베이스의 설정을 기반으로 다음과 같은 격리 수준을 지원합니다:

  • 'ReadUncommitted'
  • 'ReadCommitted'
  • 'RepeatableRead'
  • 'Serializable'

 

격리 수준의 종류

  1. Read Uncommitted (읽기 비커밋)
    • 특징: 커밋되지 않은 데이터를 읽을 수 있음.
    • 장점: 가장 높은 동시성.
    • 단점: Dirty Read가 발생할 수 있음.
    • 보장되는 사항: 없음.
    • 사용 사례: 데이터 정확성보다 성능이 더 중요한 경우.
      성능이 중요하고,데이터가 약간의 불일치를 견딜 수 있는 애플리케이션에 사용한다.
      예: 실시간 대시보드에서 통계 데이터.
  2. Read Committed (읽기 커밋)
    • 특징: 커밋된 데이터만 읽을 수 있음.
    • 장점: Dirty Read 방지.
    • 단점: Non-Repeatable Read 발생 가능.
    • 보장되는 사항: Dirty Read 방지.
    • 사용 사례: 대부분의 애플리케이션에서 기본 격리 수준.
      Dirty Read를 방지해야 하지만, 완벽한 데이터 일관성이 필요하지 않은 경우에 사용한다.
      예: 은행 거래 로그 조회.
  3. Repeatable Read (반복 가능 읽기)
    • 특징: 트랜잭션이 시작된 이후 수정된 데이터는 읽을 수 없음.
    • 장점: Dirty Read, Non-Repeatable Read 방지.
    • 단점: Phantom Read 발생 가능.
    • 보장되는 사항: Dirty Read, Non-Repeatable Read 방지.
    • 사용 사례: 데이터가 수정되는 중에도 읽기 작업의 일관성을 보장하고자 할 때.
      데이터 읽기와 수정 작업이 충돌하지 않아야 할 경우에 사용한다.
      예: 제품 재고 관리 시스템.
  4. Serializable (직렬화)
    • 특징: 트랜잭션이 직렬화된 것처럼 처리됨.
    • 장점: Dirty Read, Non-Repeatable Read, Phantom Read 모두 방지.
    • 단점: 가장 낮은 동시성.
    • 보장되는 사항: Dirty Read, Non-Repeatable Read, Phantom Read 방지.
    • 사용 사례: 모든 데이터 읽기와 수정이 완벽히 일치해야 할 경우.
      예: 금융 기관의 결제 처리 시스템.

 

격리 수준 비교

 

격리 수준 Dirty Read Non-Repeatable Read Phantom Read
Read Uncommitted 가능 가능 가능
Read Committed 방지 가능 가능
Repeatable Read 방지 방지 가능
Serializable 방지 방지 방지

 

격리 수준 선택 시 고려사항

  1. 데이터 정확성
    데이터의 일관성과 무결성이 중요하다면 높은 격리 수준을 선택합니다.
  2. 성능
    높은 격리 수준은 동시성을 제한하므로, 트랜잭션 간의 경쟁이 많다면 낮은 격리 수준이 적합합니다.
  3. 애플리케이션 요구사항
    동시성 문제를 허용할 수 있는지, 데이터 정확성이 어느 정도 중요한지를 평가합니다.

'TIL(Today I Learned)' 카테고리의 다른 글

DOM의 개념과 메서드들  (0) 2024.11.29
아이탬시뮬레이터 트러블 슈팅  (0) 2024.11.28
OSI 7계층  (0) 2024.11.26
express-session  (0) 2024.11.26
Prisma  (0) 2024.11.25