[JPA] 16장 트랜잭션과 락, 2차 캐시

diveintoo·2022년 4월 16일
0

본 글은 김영한님의 <자바 ORM 표준 JPA 프로그래밍>을 읽고 공부한 내용을 정리한 글입니다.

1. 트랜잭션과 락

1.1 트랜잭션과 격리수준

트랜잭션은 ACID를 보장해야 한다.

  • 원자성(Atomicity) : 트랜잭션 내에서 실행된 작업들은 마치 하나의 작업처럼 모두 성공하거나 모두 실패해야 한다.

  • 일관성(Consistency) : 데이터베이스에서 정한 무결성 제약조건을 항상 만족해야 한다.

  • 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다. 예를 들어, 동시에 데이터를 수정하지 못하도록 한다.

  • 지속성(Durability) : 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다.

트랜잭션의 격리수준

트랜잭션은 원자성,일관성,지속성은 보장하지만 격리성은 보장하지 않는다.

아래로 갈수록 격리수준이 높아지고, 동시성과 문제점은 줄어든다.

  • READ UNCOMMITED

    • DIRTY READ : 다른 트랜잭션이 수정 중일 때 해당 데이터를 조회할 수 있다.
  • READ COMMITED : 다른 사용자가 데이터를 변경하는 동안 사용자는 데이터를 읽을 수 없다.

    • NON-REPEATABLE READ : 선행 트랜잭션이 조회하는 중에 후행 트랜잭션이 수정하고 커밋하면 선행 트랜잭션이 다시 조회했을 때 수정된 데이터가 조회된다.
  • REPEATABLE READ : 선행 트랜잭션이 조회한 데이터는 트랜잭션이 종료될때까지 후행 트랜잭션은 수정,삭제가 불가능하다.

    • PHANTOM READ : 반복 조회 시 결과 집합이 달라진다.
  • SERIALIZABLE : 동일한 데이터에 대해서 두 개의 트랜잭션이 수행될 수 없다.

애플리케이션은 동시성 처리가 중요하므로 보통 READ COMMITED를 기본으로 사용한다.

1.2 낙관적 락과 비관적 락

JPA의 영속성 컨텍스트를 적절히 활용하면 데이터베이스의 격리수준이 READ COMMITED어도 애플리케이션 레벨에서 반복 가능한 읽기가 가능하다.
-> 영속 엔티티 동일성 보장

일부 로직에서 더 높은 격리 수준이 필요하면 낙관적 락과 비관적 락 중 하나를 사용해야 한다.

낙관적 락

애플리케이션, JPA가 제공하는 버전 관리 기능을 사용한다. 트랜잭션을 커밋하기 전까지는 트랜잭션의 충돌을 알 수 없다.

비관적 락

트랜잭션의 충돌이 발생한다고 우선 락을 걸고 보는 방법이다. 데이터베이스에서 제공하는 락 기능을 사용한다.

second lost updates problem (두 번의 갱신 분실 문제)

  • 사용자 A,B가 동시에 이름을 수정하는 중에 사용자 A가 먼저 수정완료 버튼을 누르고, 후에 사용자 B가 버튼을 누른다면 A의 수정사항은 사라지고 B의 수정사항만 남게 된다.

  • 데이터베이스 트랜잭션 범위를 넘어서는 문제

    • 마지막 커밋만 인정하기 : 마지막에 커밋한 사용자 B의 내용만 인정
    • 최초 커밋만 인정하기 : 먼저 수정한 사용자 A의 내용이 수정되고 B는 오류가 발생한다.
    • 충돌하는 갱신 내용 병합하기 : A,B의 내용을 병합

JPA에서 권장하는 락 설정

READ COMMITTED 격리 수준 + 낙관적 버전관리

2. 2차 캐시

네트워크를 통해 DB를 접근하는 방식은 서버에서 내부 메모리를 접근하는 것보다 시간비용이 수만에서 수십만 배 이상 높다.

따라서 DB를 조회해서 내부 메모리 캐시에 저장해두면 비용을 아낄 수 있다.

1차 캐시

  • 1차 캐시는 영속성 컨텍스트 내부에 있다. 엔티티 매니저로 조회하거나 변경하는 모든 엔티티는 1차 캐시에 저장된다.

  • 1차 캐시는 같은 엔티티가 있으면 해당 엔티티를 그대로 반환한다. 따라서 1차 캐시는 개체 동일성(a==b)을 보장한다.

  • 1차 캐시는 기본적으로 영속성 컨텍스트 범위의 캐시다(컨테이너 환경에서는 트랜잭션 범위의 캐시, OSIV를 적용하면 요청 범위의 캐시다).

2차 캐시

  • 애플리케이션에서 공유하는 캐시를 JPA는 공유 캐시(shared caches)라 하는데 일반적으로 2차 캐시(second level cache, L2 cache)라 부른다. 2차 캐시는 애플리케이션 범위의 캐시로 애플리케이션을 종료할 때까지 캐시가 유지된다.

  • 2차 캐시는 동시성을 극대화하려고 캐시한 객체를 직접 반환하지 않고 복사본을 만들어서 반환한다.

  • 2차 캐시는 데이터베이스 기본 키를 기준으로 캐시하지만 영속성 컨텍스트가 다르면 객체 동일성(a==b)를 보장하지 않는다.

2차 캐시 사용

엔티티 클래스에 @Cacheable 어노테이션을 사용한다.

캐시 모드는 javax.persistence.SharedCacheMode에 정의되어 있다.

캐시 모드에 따라 캐시를 무시할수도 있고 더 세밀하게 캐시 사용을 설정할 수 있다.

캐시 영역

캐시를 하면 엔티티는 엔티티 캐시 영역, 연관 참조 엔티티는 컬렉션 캐시 영역에 저장된다.

  • 엔티티 캐시 영역: [패키지 명 + 클래스 명]

  • 컬렉션 캐시 영역: [패키지 명 + 클래스 명 + 필드 명]

쿼리 캐시

쿼리 캐시는 쿼리와 파라미터 정보를 키로 활용해서 쿼리 결과를 캐시하는 방법이다.

org.hibernate.cacheable = true로 설정해야 한다.

쿼리/컬렉션 캐시는 대상 엔티티 캐시와 함께 써야한다.
-> 쿼리 캐시는 엔티티 식별자 값만 들어있기 때문에 실제 엔티티 정보는 엔티티 캐시를 통해 하나씩 조회해오기 때문이다.

0개의 댓글