카카오 테크 캠퍼스 9주차

boseung·2023년 6월 15일
0

Offset 페이징, Cursor 페이징

Offset 페이징

Offset 페이징은 게시판에서 버튼을 눌러서 웹 페이지를 이동하는 경우 사용되는 방식이다.

SELECT *
FROM table
ORDER BY column
OFFSET 10
LIMIT 5;

OFFSET은 일반적으로 LIMIT과 함께 사용된다.

예를 들어 OFFSET 10는 처음 10개의 행을 건너뛰고, LIMIT 5 은 그 이후 5개의 행을 반환하도록 지정하는 것을 의미한다.

이런 Offset 방식은 처음부터 데이터를 읽은 후 Limit으로 자르는 방식이기 때문에 대용량 시스템에서 이런 방식은 비효율적일 수 있다.

또한 Offset 방식은 데이터 중복의 문제가 생길 수 있다.

Offset 페이징 방식은 데이터베이스에서 최신의 데이터를 가져오게 된다.

만약 실시간으로 새로운 데이터가 추가된다면, 사용자는 다음 페이지에서 기존에 이미 봤던 정보를 다시 보게 되면서 서비스 상의 문제점을 인지하게 될 것이다.

Cursor 페이징

Cursor 페이지는 실시간으로 대용량의 데이터를 다룰 경우 사용하는 방식이다. 유튜브나 페이스북, 트위터 등 에서 무한 스크롤을 지원할 때 사용되고 있다.

SELECT *
FROM table
WHERE column > last_value
ORDER BY column
LIMIT 5;

Cursorlast_value처럼 특정 변수를 기준으로 새로운 데이터들을 탐색하고 Limit으로 그 이후 5개의 행을 반환하도록 지정하는 방식으로 동작한다.

Cursor 페이징 방식은 Cursor를 사용하여 직접 페이지를 탐색하기 때문에 데이터의 크기와 관련없이 일관된 성능을 유지할 수 있다는 장점을 가지고 있다.

아래는 Offset 페이징과 Cursor 페이징의 시간복잡도를 표현한 차트이다.

Transaction(트랜잭션)

Transaction(트랜잭션)이란?

더 이상 나눌 수 없는 작업의 단위.

즉, 한꺼번에 수행되어야만 하는 작업의 단위를 의미한다.

트랜잭션의 성질

  • Atomicity(원자성)
    • 트랜잭션은 원자적 연산을 보장해야 한다.
    • ALL or Nothing
    • MVCC(Multi Version Concurrency Control)
      • MVCC의 궁극적인 목적은 Lock을 사용하지 않음으로써 성능을 향상시키기 위함
      • InnoDB에서는 Undo log(트랜잭션 커밋 시 변경되기 전 데이터를 저장하는 공간)를 이용해서 트랜잭션 롤백 시 사용한다.
  • Consistency(일관성)
    • 트랜잭션이 종료되었을 때 데이터의 무결성이 보장되어야 한다.
    • 유니크 제약, 외래키 제약 등 제약조건을 위반하지 않도록 보장
  • Isolation(독립성)
    • 트랜잭션은 서로 간섭하지 않고 독립적으로 동작한다.
    • 성능과 직접적으로 관련되므로 개발자가 트랜잭션 격리레벨을 설정해서(via MVCC) 제어 가능
  • Durablility(지속성)
    • 완료된 트랜잭션은 유실되지 않아야 한다.
    • WAL(Write Ahead Log)을 통해 구현됨.(InnoDB에서는 Undo log)

스프링에서 Transaction

스프링에서 @Transactional이라는 어노테이션을 사용하면 간단하게 트랜잭션을 구현할 수 있다.

@Transactional
	    public void performTransactionalOperation() {
        User user = new User("John", 21);
        myRepository.save(user);
    }
}

@Transactional을 사용한 메서드에서 예외가 발생하지 않고 정상적으로 수행되면 결과가 commit 되고, 예외가 발생한다면 rollback 된다.

Transaction Isolation Level(트랜잭션 격리 수준)

트랜잭션 격리 수준이란 동시에 여러 트랜잭션이 처리될 때, 트랜잭션들이 서로의 변경된 데이터를 어느 수준까지 볼 수 있는지 결정하는 것을 의미한다.

트랜잭션의 격리 수준은 다음과 네 가지가 사용된다.

  • Read Uncommit
    • 가장 낮은 격리 수준이며, 트랜잭션 A가 아직 커밋되지 않은 다른 트랜잭션 B의 변경 사항을 볼 수 있다.
    • Dirty Read(더티 리드) 문제가 발생할 수 있어 일관성과 격리성을 보장하지 않는다.
    • 대부분의 데이터베이스 시스템에서 이 격리 수준은 지원하지 않거나, 기본적으로 다음 단계인 READ COMMITTED 수준으로 동작한다.
  • READ COMMITTED
    • 트랜잭션 A는 다른 트랜잭션 B의 커밋된 변경 사항만 볼 수 있다.
    • Dirty Read는 방지하지만 Non-Repeatable Read 문제가 발생할 수 있다.
  • REPEATABLE READ
    • 트랜잭션 A는 트랜잭션 B가 시작되기 전에 커밋된 내용에 대해서만 조회할 수 있다.
    • Non-Repeatable Read는 방지하지만 Phantom Read(유령 읽기) 문제가 발생할 수 있다.
  • SERIALIZABLE
    • 가장 높은 격리 수준이며, 트랜잭션 A와 B가 동시에 실행되더라도 마치 순차적으로 실행된 것처럼 동작한다.
    • Dirty Read, Non-Repeatable Read, Phantom Read 문제를 방지하지만 동시성 처리 수준이 낮아 성능이 저하되므로 잘 사용하지 않는다.

이런 트랜잭션의 격리 수준에 따라 문제들에 대해서 자세히 알아보자.

  • Dirty Read
    • 트랜잭션 A에서 아직 커밋되지 않은 다른 트랜잭션 B의 변경사항을 읽을 수 있는 것
    • 커밋되지 않은 다른 트랜잭션의 변경사항을 읽어서 Rollback시 문제 발생
  • Non-Repeatable Read
    • 트랜잭션 A에서 동일한 쿼리를 여러번 실행할 때, 그 결과가 다른 것
    • 트랜잭션 A에서 동일한 쿼리를 여러번 실행하는 동안 다른 트랜잭션 B에서 해당 데이터를 수정하고 커밋한 경우, 이렇게 결과가 다른 현상이 발생할 수 있음
  • Phantom Read
    • Phantom Read는 트랜잭션 A에서 동일한 쿼리를 여러 번 실행할 때, 처음 쿼리에서 없던 다른 결과(Phantom 유령)가 나타나는 것
    • 트랜잭션 A에서 특정 범위의 데이터를 읽은 후, 트랜잭션 B가 해당 범위에 새로운 행을 삽입하고 커밋한 경우, 트랜잭션 A는 동일한 쿼리를 실행했음에도 새로운 결과가 나타날 수 있음
Dirty ReadNon Repeatable ReadPhantom Read
Read UncommittedOOO
Read CommittedXOO
Repeatable ReadXXO
Serializable ReadXXX

Lock(잠금)

락(Lock)은 동시성 제어를 위해 트랜잭션 처리의 순차성을 보장하기 위한 방법이다.

이렇게 락을 통해 동시성을 제어할 때, 락의 범위를 최소화하는 것이 중요하다.

락의 범위가 크면 클수록 락을 획득하기 위해 대기하는 트랜잭션이 늘어나기 때문에 성능에 영향을 줄 수 있기 때문이다.

스프링의 경우 락의 범위가 커질수록 커넥션 풀(Conntection Pool)을 점유하는 시간이 길어지고, 최악의 경우에는 커넥션 풀이 고갈되는 상황으로 이어질 수 있다.

MySQL에서는 트랜잭션의 커밋 혹은 롤백 시점에 잠금이 풀린다.(트랜잭션 = 락의 범위)

MySQL에서 제공하는 락의 종류는 두가지로, 읽기락(Shared Lock), 쓰기락(Exclusive Lock)을 제공한다.

읽기락(Shared Lock)쓰기락(Exclusive Lock)
읽기락(Shared Lock)O대기
쓰기락(Exclusive Lock)대기대기

MySQL에서 읽기락은 SELECT ... FOR SHARE을 통해 획득할 수 있고, 쓰기락은 SELECT ... FOR UPDATE, DELETE등을 통해 획득할 수 있다.

하지만 매번 잠금이 발생할 경우, 성능 저하를 피할 수 없기 때문에 MySQL에서는 일반 SELECT는 nonblocking consistent read로 동작한다.

잠금을 걸 때 주의할 점은 MySQL에서 잠금은 row가 아니라 인덱스를 잠근다는 점이다.

따라서 인덱스가 없는 조건으로 잘못 Locking Read시 불필요한 데이터들이 잠길 수 있으니 주의해야 한다.

예를 들어

SELECT * FROM POST WHERE contents = 'string' FOR UPDATE; #contents columns은 인덱스 x 가정

그리고 다음을 통해 락을 조회해보면 인덱스가 없는 columns이라서 모든 record가 잠긴 것을 확인 할 수 있다.

SELECT * FROM performance_schema.data_locks;

추가로 공부해보기

  • Java에서의 동시성 이슈 제어방법
  • 분산환경에서의 동시성 이슈 제어방법
  • MySQL의 넥스트 키락이 등장한 배경
  • MySQL 외래키로 인한 잠금
  • MySQL 데드락

Relation Rink

왜 오프셋 페이징보다 커서 페이징일까?

낙관적 락(Optimistic Lock)과 비관적 락(Pessimisitc Lock)이란

profile
Dev Log

0개의 댓글

관련 채용 정보