SELECT 중 UPDATE 발생 시 처리 메커니즘

LeeYulhee·2023년 11월 7일
0

👉 궁금한 부분 : SELECT 중에 계속 UPDATE가 발생해서 생기는 문제나 해결 방안


  • 게시글에 조회수 컬럼을 추가하려다가 생긴 궁금증
    • 조회(SELECT) 중에 다른 사람이 같은 글을 조회하게 되어서 조회수 UPDATE가 일어나게 되는 경우 어떻게 처리되는지
    • 조회수 등을 UPDATE 과정에서 다른 사람이 해당 글을 조회(SELECT)하면 어떻게 되는지
  • ⇒ 이 궁금증을 해결하려면 아래의 개념들이 필요



👉 트랜잭션


  • 일련의 데이터베이스 작업을 논리적으로 묶은 것으로, ACID(Atomicity, Consistency, Isolation, Durability) 속성을 가짐
    • 원자성(Atomicity) : 작업들이 모두 성공하거나 모두 실패함을 보장
    • 일관성(Consistency) : 트랜잭션이 성공하면 데이터베이스는 항상 일관된 상태를 유지해야 함(데이터베이스의 모든 규칙을 준수해야 함)
    • 격리성(Isolation) : 동시에 실행되는 트랜잭션이 서로에게 영향을 주지 않도록 함
    • 지속성(Durability) : 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 저장



👉 트랜잭션의 격리


  • 📌 트랜잭션 격리성
    • 트랜잭션의 네 가지 특성 중 격리성에 해당하는 개념
    • 격리성은 여러 트랜잭션이 동시에 데이터베이스에 접근할 때 서로 간섭하지 않도록 분리하는 능력
    • 격리성을 완벽하게 보장하면 동시성(시스템이 많은 작업을 동시에 처리하는 능력)이 저하될 수 있기 때문에, 다양한 '격리 수준(Isolation Levels)'을 통해 필요에 따라 동시성과 격리성 사이의 균형을 조절할 수 있음

  • 📌 트랜잭션의 격리 수준 설명
    • 트랜잭션 격리 수준은 격리성을 어느 정도까지 보장할지를 결정함
    • 격리 수준을 낮추면 동시성이 향상되지만, 여러 문제들(더티 리드, 논리적 판독, 팬텀 리드 등)이 발생할 수 있음
    • MySQL/InnoDB에서 기본 격리 수준은 REPEATABLE READ

  • 📌 트랜잭션의 격리 수준 종류
    • READ UNCOMMITTED (읽기 미확정)
      • 가장 낮은 격리 수준으로, 다른 트랜잭션에서 아직 확정되지 않은 변경 내용을 다른 트랜잭션이 읽을 수 있음
      • 이로 인해 더티 리드(Dirty Read)가 발생할 수 있음
        • 다른 트랜잭션에 의해 수정되었지만 아직 완료되지 않은 데이터를 읽을 수 있음
    • READ COMMITTED (읽기 확정)
      • 대부분의 데이터베이스 시스템의 기본 격리 수준
      • 한 트랜잭션이 커밋을 완료하여 확정된 데이터만 다른 트랜잭션이 읽을 수 있음
      • 논리적 판독(Non-Repeatable Read)은 여전히 문제가 될 수 있음
        • 즉, 한 트랜잭션 내에서 같은 데이터를 두 번 조회했을 때 다른 결과를 볼 수 있음
    • REPEATABLE READ (반복 읽기 가능)
      • 트랜잭션이 실행되는 동안 해당 트랜잭션에서 참조하는 데이터베이스 레코드에 대해 다른 트랜잭션이 변경(업데이트, 삭제)을 할 수 없게 하는 격리 수준
      • 트랜잭션이 시작되어 처음 읽은 데이터를 트랜잭션이 끝날 때까지 계속 동일하게 읽을 수 있음
      • 그러나 '팬텀 리드(Phantom Read)'가 발생할 수 있음
    • SERIALIZABLE (직렬화 가능)
      • 가장 높은 격리 수준으로, 트랜잭션들이 순서대로 실행되는 것처럼 보장하여 모든 읽기 및 쓰기 문제를 방지
      • 동시성이 가장 많이 저하될 수 있음



👉 MVCC(Multi-Version Concurrency Control)


  • 설명
    • 동시성 제어를 위한 한 방법
    • 여러 사용자가 동시에 데이터베이스에 액세스할 때 발생할 수 있는 문제들을 관리
      • 한 사용자의 작업이 다른 사용자의 작업에 어떤 영향을 끼칠 수 있는지 고려하고 각 트랜잭션들이 서로 간섭 없이 데이터를 읽고 쓸 수 있게
    • MVCC는 이를 위해 데이터의 여러 버전을 만들어서 관리
      • 간단히 말해서, 사용자가 데이터를 읽을 때, 그들은 다른 사용자가 동시에 같은 데이터를 변경하고 있을 때에도 데이터의 일관된 스냅샷을 볼 수 있음
        • 일관된 스냅샷(Consistent Snapshot)
          • 데이터베이스에서 특정 시점의 데이터 상태를 가리키는 용어
          • = 데이터베이스 트랜잭션이 시작될 때의 데이터 모습
    • 각 트랜잭션은 데이터베이스의 특정 시점의 스냅샷을 보게 되며, 이는 일관된 읽기를 가능하게 하여 트랜잭션이 실행되는 동안 다른 트랜잭션에 의한 변경이 결과에 영향을 미치지 않음
  • 예시
    • 도서관의 책
      • 도서관에서 여러 사람들이 같은 책을 참조할 수 있도록, 도서관은 그 책의 여러 복사본을 가지고 있다고 가정
      • 한 사람이 책의 내용을 변경하고 있더라도 (예: 필기를 하고 있더라도), 다른 사람은 원본 책을 참조할 수 있음
      • 즉, 한 사람의 작업이 다른 사람에게 방해가 되지 않음
    • 시간 여행
      • MVCC를 시간 여행에 비유해 볼 수도 있음
      • 만약 과거로 돌아가 사진을 찍는다면, 현재에 있는 다른 사람들은 내가 과거에서 무엇을 하든 상관없이 현재의 모습을 계속 볼 것
      • 여러분이 과거에서 무언가를 바꾸기 전까지 현재는 바뀌지 않음
      • 데이터베이스에서는 이런 식으로 각 트랜잭션은 자신만의 '시간대'를 가지고 작업을 진행할 수 있음



👉 트랜잭션 처리 흐름 예시(REPEATABLE READ인 경우)


  • 📌 SELECT(조회) 중 UPDATE가 발생하는 경우
    • 상황
      • 사용자 A가 게시글의 조회수를 SELECT로 읽고 있을 때, 만약 다른 사용자 B가 동시에 그 게시글을 조회하고, 이로 인해 조회수에 대한 UPDATE가 발생하는 경우
    • 흐름
      • 사용자 A가 트랜잭션을 시작하고 게시글의 조회수를 읽음
      • 동시에 사용자 B가 해당 게시글을 조회하여, 조회수를 증가시키는 UPDATE 쿼리를 실행
      • 사용자 A의 트랜잭션이 REPEATABLE READ 격리 수준에서 실행 중이라면, A는 여전히 B가 UPDATE 하기 전의 데이터를 볼 것
        • 조회수 증가가 A의 트랜잭션에는 반영되지 않음
      • 사용자 A가 트랜잭션을 커밋하거나 롤백할 때까지, A는 조회수에 대한 변경 사항을 볼 수 없음
        • A의 트랜잭션이 종료된 후 새로운 SELECT 쿼리 실행 시, B가 증가시킨 조회수를 확인할 수 있음
        • = 사용자 A가 트랜잭션을 커밋한 후에 새 트랜잭션을 시작하지 않으면, REPEATABLE READ에서는 여전히 기존의 조회수를 볼 것
    • 설명
      • REPEATABLE READ에서는 하나의 트랜잭션 내에서의 SELECT 쿼리 결과는 일관성을 유지하지만, 다른 트랜잭션에서의 변경 사항은 새로운 트랜잭션을 시작할 때까지 반영되지 않음
      • 이는 동시에 여러 트랜잭션이 데이터를 읽을 때 발생하는 데이터 불일치 문제를 방지하도록 설계된 것

  • 📌 UPDATE 중 SELECT(조회)가 발생하는 경우
    • 상황
      • 누군가가 게시글의 조회수를 UPDATE 하는 중에 다른 사용자가 동시에 해당 게시글을 조회하려 하는 경우
    • 흐름
      • 사용자 A가 게시글의 조회수를 증가시키기 위한 UPDATE 트랜잭션을 시작
      • InnoDB는 해당 행에 대해 X-Lock (배타적 락)을 걸게 됨
        • 이는 다른 트랜잭션이 해당 행을 수정하거나 읽지 못하게 함
      • 사용자 B가 동시에 해당 게시글을 조회하려고 SELECT 쿼리를 실행하면, REPEATABLE READ 격리 수준에서는 다음과 같은 상황이 발생할 수 있음
        • MVCC를 통해 사용자 B는 UPDATE 이전의 데이터, 즉 조회수가 증가되기 이전의 값을 볼 수 있음
          • 이는 B의 트랜잭션이 UPDATE 쿼리의 결과를 기다리지 않고, 즉시 스냅샷 데이터를 제공받기 때문
        • 만약 격리 수준이 SERIALIZABLE이라면, 사용자 B는 사용자 A의 트랜잭션이 완료될 때까지 기다려야 할 수 있음
          • 왜냐하면 이 격리 수준에서는 SELECT 쿼리도 S-Lock (공유 락)을 걸기 때문에 UPDATE 작업이 불가해서 기다려야 함
      • 사용자 A의 트랜잭션이 커밋되면, 그 변경 사항은 데이터베이스에 반영되고, 해당 락이 해제
      • 이후 사용자 B가 새로운 트랜잭션을 시작하면 변경된 조회수를 볼 수 있게 됨



👉 발생할 수 있는 성능 문제


  • Locking Overhead
    • 많은 사용자가 동시에 같은 글을 조회할 때, 조회수를 업데이트하기 위한 락이 자주 발생하게 되고, 이는 성능 저하로 이어질 수 있음
  • I/O Overhead
    • 조회수를 업데이트할 때 마다 디스크 I/O가 발생하게 되며, 이는 데이터베이스의 응답 시간을 늦출 수 있음
  • Replication Lag
    • 조회수 업데이트가 복제 환경에서 슬레이브에 복제될 때, 복제 지연(replication lag)이 발생할 수 있음



👉 조치 방법


  • Buffering Updates
    • 애플리케이션 레벨에서 조회수 업데이트를 버퍼링할 수 있음
    • 예를 들어, 레디스(Redis)와 같은 인-메모리 데이터 스토어에 카운터를 임시로 저장하고, 주기적으로 배치로 데이터베이스에 업데이트를 할 수 있음
  • Asynchronous Updates
    • 조회수를 비동기적으로 업데이트하는 방식을 고려할 수 있음
    • 사용자의 조회 요청 처리와 별개로, 메시지 큐 등을 통해 조회수 업데이트를 요청하고, 백그라운드에서 이를 처리할 수 있음
  • Optimistic Locking
    • 낙관적 락을 사용하여, 실제 업데이트가 일어날 때만 락을 거는 방식으로 충돌을 최소화할 수 있음
  • Counter Table
    • 조회수 전용의 별도 테이블을 만들고, 이 테이블만 빈번히 업데이트하여 메인 테이블의 락 경합을 줄일 수 있음
profile
끝없이 성장하고자 하는 백엔드 개발자입니다.

0개의 댓글

관련 채용 정보