230418_TIL

majungha·2023년 4월 18일
1

TIL

목록 보기
36/68

앞으로의 목표 👍


  1. javascript 능력 및 고난도 알고리즘 풀이 능력
  2. Nest, Graphql등 최신 기술 스택 활용 능력
  3. 기초 미니프로젝트 포트폴리오
  4. 로그인, 결제기반 심화프로젝트 포트폴리오
  5. 배포를 위한 네트워크 및 CI/CD 배포자동화 능력
  6. 120% 백엔드 개발 지식

오늘부터 꾸준히 해야할 일 👍


  • 영타실력 늘리기
  • 단축키 사용 익숙해지기
  • 코드리딩 실력 키우기
  • 데일리 퀴즈
  • 포트폴리오 작성
  • 독스에 친숙해지기
  • MDN 보는 연습하기

오늘의 수업 👍



📝 결제시 발생할 수 있는 트랜잭션 문제점


  • 결제정보는 저장했는데 중간에 에러가 생겨 로직이 끝났다면 결제정보만 저장되고 사용자의 구매 누적금액은 최신화가 되지 않는다.
  • 데이터가 꼬여서 데이터 오염이 발생한 것입니다. 이런 결제 상황에서 발생할 수 있는 문제를 해결하기 위해 ACID 트랜잭션을 사용한다.
  • 서비스에서 가장 큰 문제 => 데이터의 오염
  • 데이터의 오염보단 차라리 실패하는 것이 더 좋다.

📝 Transaction


  • 처리되는 작업의 단위로, 데이터베이스에서의 Transaction 처리는 Business Logic 상 굉장히 중요한 기능이다.
  • 서로 다른 트랜잭션들을 처리하는 도중 하나의 단위 트랜잭션에서 에러가 발생한다면 이전에 성공했던 트랜잭션들을 다시 rollback 시켜 데이터의 Consistency가 깨지지 않도록 해주는 것이다.
  • 모두 성공했을 경우에는 commit을 통해 확정 지어주게 된다.

▷ DB의 Transaction Flow

  1. 서로 다른 Transaction을 부분적으로 처리합니다.

  2. 모든 Transaction이 정상적으로 완료되면 Commit 합니다.

  3. 만약 Transaction중 하나라도 비정상적으로 처리되면 rollback을 수행합니다.

📝 트랜잭션의 속성들(ACID)


  • A(Atomicity) : 원자성 - 모두 성공할 것 아니면 모두 실패하게 만드는 것이다.(DB의 오염을 막기 위함)
  • C(Consistency) : 일관성 - 똑같은 쿼리를 조회할 때마다 동일한 결과값이 나타나야하는 것이다.
  • I(Isolation) : 격리성 - A 사람의 요청을 처리하는 동안 B사람의 요청은 잠시 기다리는 것이다.
  • D(Durability) : 지속성 - 성공하여 commit이 되었으면 서버를 다시 켜도 그 데이터는 그대로 유지가 되어야 되는 것이다.

📝 Isolation-level


▷ Isolation-level의 정의

  • Transaction의 격리 수준이라고 한다.
  • 동시에 여러 트랜잭션이 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것이다.

▷ Isolation의 4단계

아래 단계로 내려갈수록 안전해지는 반면 성능이 느려진다.

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

📝 Isolation-level의 문제


▷ 1단계 : READ UNCOMMITTED

  • commit 되지 않는 데이터들을 조회할 수 있으나, 정합성에 문제가 많은 격리 수준이기 때문에 사용하지 않는 것을 권장한다.

  • Commit이 되지 않는 상태지만 Update된 값을 다른 트랜잭션에서 읽을 수 있다.

- Dirty Read (더러운 읽기)

  • READ UNCOMMITTED는 문제는 DIRTY READ 현상이 발생 되는것 이다.
  • 트랜잭션이 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있게 되는 현상을 의미한다.

▷ 2단계 : READ COMMITTED

  • RDB(관계형 데이터베이스)에서 대부분 기본적으로 사용되고 있는 격리 수준이다.
  • Dirty Read와 같은 현상은 발생하지 않는다.
  • 실제 테이블 값을 가져오는 것이 아니라 Undo 영역에 백업된 레코드에서 값을 가져온다.

- NON REPEATABLE READ (반복 못하는 읽기)

  • 한 트랜잭션 내에서 같은 쿼리를 두번 수행할 때, 그 사이에 다른 트랜잭션이 값을 수정 또는 삭제함으로써 두 쿼리가 상이하게 나타나는 현상이다. 즉 동일한 쿼리를 요청하면 매번 동일한 결과값이 나타나야 한다.

  • 하지만 동일한 결과값이 나타나지 않을 때 나타나는 현상입니다.

  • 트랜잭션-1이 Commit 한 이후 아직 끝나지 않는 트랜잭션-2가 다시 테이블 값을 읽으면 값이 변경됨을 알 수 있다.

  • 하나의 트랜잭션내에서 똑같은 SELECT 쿼리를 실행했을 때는 항상 같은 결과를 가져와야 하는 REPEATABLE READ의 정합성에 어긋나게 된다.

  • 이러한 문제는 주로 입금, 출금 처리가 진행되는 금전적인 처리에서 주로 발생하며 이러한 현상은 데이터의 정합성은 깨지고, 버그는 찾기 어려워 진다.

▷ 3단계 : REPEATABLE READ

  • RDB(관계형 데이터베이스)에서 대부분 기본적으로 사용되고 있는 격리 수준이다.
  • MySQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 된다.
  • Undo 공간에 백업해두고 실제 레코드 값을 변경한다.
    • 백업된 데이터는 불필요하다고 판단하는 시점에 주기적으로 삭제한다.
    • Undo에 백업된 레코드가 많아지면 MySQL 서버의 처리 성능이 떨어질 수 있다.
      이러한 변경방식은 MVCC(Multi Version Concurrency Control)라고 부른다.

- PHANTOM READ (귀신 읽기)

  • REPEATABLE READ에 종종 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안 보였다가 하는 현상이 발생한다.

  • MySQL에서 자동으로 제거해 주기에 추가적으로 적용할 부분은 없다.

  • 다음과 같은 PHANTOM READ 가 발생하지 않기 위해서 SERIALIZABLE 를 사용합니다.

  • 성능 측면에서는 동시 처리성능이 가장 낮으며 가장 단순한 격리 수준이지만 가장 엄격하다.

  • 데이터베이스에서는 거의 사용되지 않는다.

▷ 4단계 : SERIALIZABLE

  • 데이터를 update 완료 하기 전에 다른 넘겨주면, 데이터의 오염이 일어나게 된다.
  • 그렇기 때문에 데이터가 update 완료될 때 까지는 다른 데이터는 접근할 수 없도록 lock을 걸어 주셔야 합니다.
  • PHANTOM READ 가 발생하지 않기 위해서 lock 걸 수 있는 SERIALIZABLE 가 있다.

- row-lock과 table-lock

▶ table-lock

// 조회시 락을 걸고 조회함으로써, 다른 쿼리에서 조회 못하게 막음(대기시킴) => Select ~ For Update
const payment = await queryRunner.manager.find(Payment, {
       lock: { mode: 'pessimistic_write' }, // write_or_fail: 잠겼으면 관두기, partial_write: 잠긴것 패스하고 나머지 수행, for~~: postgres 전용
});

▶ row-lock

// 조회시 락을 걸고 조회함으로써, 다른 쿼리에서 조회 못하게 막음(대기시킴) => Select ~ For Update
const payment = await queryRunner.manager.find(Payment, {
       lock: { mode: 'pessimistic_write' }, // write_or_fail: 잠겼으면 관두기, partial_write: 잠긴것 패스하고 나머지 수행, for~~: postgres 전용
       where: { id: '0edb1d43-68d5-4225-a992-8b24b3c06972' }, // row-lock
});

▶ join

  • join된 테이블의 해당하는 row에 lock이 걸린다

▷ pessimistic-read / pessimistic-write 의 잘못된 사용과 데드락

  • API-1에서는 User 테이블에 lock을 건 뒤, Board 테이블에 lock을 걸어주고 모든 것이 완료되면 commit이 되는 상황이다.

  • API-2에서는 Board 테이블에 먼저 lock을 걸고, Board가 끝나면 User 테이블에 lock을 걸어주고 모든것이 commit이 되는 상황이다.

  • 이렇게 두 개의 비즈니스 로직이 존재하는데, 만약 두 비즈니스 로직이 동시에 실행된다면 어떻게 될까?

  • API-1에서 User 테이블에서 lock이 걸어지면서 처리를 완료하였다.

  • 이와 동시에 API-2에서는 Board 테이블에서 lock이 걸어지면서 처리를 완료하였다.

  • 그러면 비스니스-1에서는 User의 lock이 걸려서 잡혀있는 상태이며, 비즈니스-2에서는 Board의 lock이 걸려서 잡혀있는 상태가 된다.

  • 즉, 비즈니스-1에서는 Board로 넘어갈 수 없으며, 비즈니스-2에서는 User로 넘어갈 수 없는 상황이 발생하는 것이다.

  • 이러한 상황을 lock이 죽었다고 표현하여 Dead-Lock 이라고 부른다.

  • Dead-Lock 이 발생하지 않게 API 설계를 해야한다.

  • 따라서, lock을 사용할 때는 모든 비즈니스 로직에 동일한 API 순서로 작성하는 것이 일반적인 원칙이다.

▷ update와 lock

  • 트랜잭션 내에서 업데이트하면, 명시적으로 락을 걸지 않아도 row-lock이 걸린다.
  • 락이 걸리지 않으면 데이터가 꼬일 수 있기 때문에 수정 단계에서는 자동적으로 해당 트랜잭션 내에서 row-lock이 걸린다.
  • 그래서 명시적으로 lock을 걸어주지 않아도 데드락이 걸릴 수 있다.

▷ 커밋과 롤백

  • 커밋을 안했으면 롤백이라도 해야한다.
  • 왜냐하면 락이 걸려있으면 해제해야하고, 기록된 내용들을 삭제해야 디스크 용량 확보가 가능하다.

오늘의 마무리 👍



  • 복습
  • github 공부
  • 블로그 포스팅
  • 데일리 퀴즈
  • 알고리즘 문제 풀기

항상 겸손한 자세로 배우면서 성장하자, 할 수 있다!! 💪


출처 : 코드캠프

profile
개발자 블로그 / 항상 겸손한 자세로 배우면서 성장하자 할 수 있다!

0개의 댓글