읽기 작업에 @Transactional(readOnly = true)를 사용하는 이유 #40
Replies: 3 comments
-
|
저는
그래서 저는 readOnly의 효용을 "어떤 영역에서 어떤 최적화가 일어나는가"로 나눠서 봐야 정확하다고 생각합니다. 1. 먼저, "읽기에 왜 트랜잭션을 거는가" — 트랜잭션 자체의 효용
(a) 일관된 읽기 보장한 메서드 안에서 여러 번 조회할 때, 트랜잭션이 없으면 각 조회가 별개의 커넥션·시점에서 일어납니다. 그 사이에 다른 트랜잭션이 데이터를 바꾸면 같은 메서드 안에서 조회 결과가 일관되지 않을 수 있습니다. public ReservationReport report() {
int totalCount = repo.count(); // T1 시점: 100
List<Reservation> latest = repo.findRecent(); // T2 시점: 다른 트랜잭션이 추가해서 105개에서 골라짐
// → 카운트와 목록이 불일치
}트랜잭션으로 묶고 격리 수준을 (b) 커넥션 재사용이전 트랜잭션 토론에서 정리한 것처럼, (c) 명시적 경계로 의도 표현
2. readOnly = true가 실제로 하는 일 — 영역별로 다르다이제 본 질문입니다. (a) JPA / 하이버네이트 영역 — 가장 큰 효용 (단, JPA 사용 시에만)이 영역의 효과가 가장 두드러집니다. JPA는 영속성 컨텍스트(1차 캐시)에 조회한 엔티티를 보관하면서, 트랜잭션 종료 시점에 변경 사항을 감지(dirty checking)해 자동으로 UPDATE를 날립니다. 이 dirty checking을 위해 엔티티의 스냅샷을 따로 보관합니다.
특히 큰 결과 집합을 조회할 때 효과가 큽니다. 1만 건을 조회하면 스냅샷도 1만 건 만들어지는데, readOnly면 이걸 안 만듭니다. 다만 이 효용은 JPA 특유의 영속성 컨텍스트 메커니즘에서 나오는 것이라, JdbcTemplate처럼 영속성 컨텍스트가 없는 환경에서는 적용되지 않습니다. JdbcTemplate은 조회한 결과를 단순히 (b) Spring 트랜잭션 매니저 영역 — 의도 전달Spring은 connection.setReadOnly(true);이게 커넥션·드라이버·DB에 어떻게 작용할지는 그들이 결정합니다. Spring 입장에서는 의도를 아래로 전달했다는 것이 핵심입니다. 이 동작은 JPA든 JdbcTemplate이든 동일합니다. 영속성 기술과 무관한 Spring 트랜잭션 추상화 레벨의 일이기 때문입니다. (c) JDBC 드라이버 / DB 영역 — DB마다 다르다여기가 가장 헷갈리는 부분입니다.
그래서 "readOnly가 DB 성능을 얼마나 끌어올리는가"는 DB 종류에 따라 다르다가 정확한 답입니다. 그리고 이 영역의 동작도 JPA 여부와 무관하게 커넥션 레벨에서 일어나는 일이라, JdbcTemplate에서도 그대로 받을 수 있습니다. 3. 핵심 효용 — 마스터-슬레이브 라우팅저는 readOnly의 가장 실용적 효용이 읽기 전용 복제본으로의 라우팅이라고 생각합니다. 운영 환경에서 DB 부하가 커지면 보통 마스터-슬레이브 구조를 도입합니다. 쓰기는 마스터 1대로, 읽기는 슬레이브 N대로 분산하는 방식입니다. 이때 "이 쿼리를 어디로 보낼지" 결정하는 라우터(예: Spring의 @Transactional(readOnly = true)
public List<Reservation> findAll() {
// 슬레이브 DB로 라우팅
}
@Transactional
public Long create(...) {
// 마스터 DB로 라우팅
}라우팅의 기준이 되는 이게 운영 단계에서 가장 큰 효용입니다. 단일 DB 환경에서는 readOnly의 효과가 미미할 수 있어도, 트래픽이 커져 복제본을 도입할 때 readOnly가 명시되어 있으면 코드 변경 없이 라우팅이 가능합니다. 저는 이 점이 readOnly를 거는 가장 강한 실무적 이유라고 봅니다. 지금 당장 마스터-슬레이브 구조가 아니더라도, 나중에 도입할 때 readOnly 미명시 메서드들을 전부 찾아 고치는 비용이 큽니다. 4. 안전망 — "쓰기 의도가 없다"의 명시성능 외에도 readOnly가 주는 효용이 하나 더 있습니다 — 안전망 역할입니다. PostgreSQL 같은 DB는 이게 의미하는 바는, readOnly = true가 "이 메서드는 데이터를 안 바꾼다"는 약속을 코드로 박는 것이라는 점입니다. 누군가 이 메서드에 변경 로직을 잘못 끼워 넣으면 런타임에 막힙니다. 이전 토론들에서 강조한 의도를 코드에 박는다는 원칙과 같은 맥락입니다. 단순 주석이 아니라 동작으로 강제되는 의도 선언이고, 이 효용은 영속성 기술과 무관합니다. 5. JPA vs JdbcTemplate — 효용 비교표영역별 효용이 영속성 기술에 따라 어떻게 달라지는지 한눈에 보기 위해 정리하면:
핵심: JdbcTemplate에서는 JPA 영역의 가장 직접적인 성능 효용(dirty checking 생략 등)만 사라지고, 나머지 효용은 그대로 살아 있습니다. 인터넷의 많은 글이 readOnly의 성능 이득을 강조하다 보니 "JdbcTemplate에서는 readOnly가 의미 없다"는 오해가 생기기 쉬운데, 이건 정확하지 않습니다. 6. 그래서 읽기에 트랜잭션을 거는가 — 제 입장저는 읽기 작업에도
이 중 어느 하나만으로는 "꼭 걸어야 한다"고 단정하기 어렵지만, 여섯 가지가 누적되면 안 거는 비용보다 거는 비용이 훨씬 작다고 봅니다. 어노테이션 한 줄로 얻는 것치고는 많습니다. JdbcTemplate에서는 (3)번 효용이 사라져 누적이 약간 줄어들지만, 나머지 다섯 가지는 그대로이고 특히 라우팅 대비와 의도 표현은 환경에 무관하게 가치 있습니다. 그래서 JdbcTemplate에서도 readOnly를 다는 쪽이 안 다는 쪽보다 유리하다고 생각합니다. 7. 반론 검토 — "단일 조회에도 거는 게 과한가?"readOnly를 거는 것에 대한 흔한 반론도 있습니다. 짚어보면: 반론 1. "조회 하나만 하는 메서드인데 트랜잭션이 굳이 필요한가?"저는 이 경우에도 거는 편이 좋다고 봅니다. 이유는:
반론 2. "성능 이득이 크지 않으면 의미 없는 것 아닌가?"성능 이득은 환경에 따라 다르지만(JPA를 쓰는지, DB가 무엇인지), 위에서 정리한 다른 효용(일관성, 안전망, 라우팅 대비, 의도 표현)이 살아 있습니다. 성능만으로 판단하면 readOnly의 진짜 가치를 놓친다고 봅니다. 특히 JdbcTemplate처럼 JPA 영역의 성능 이득이 없는 환경에서는, "성능 이득이 없으니 의미 없다"가 아니라 나머지 효용을 위해 단다가 정확한 시각이라고 생각합니다. 반론 3. "코드가 더 길어진다"
@Service
@Transactional(readOnly = true) // 기본은 읽기 전용
public class ReservationService {
public List<Reservation> findAll() { ... } // readOnly = true 적용
public Reservation findById(Long id) { ... } // readOnly = true 적용
@Transactional // 이 메서드만 변경 가능 트랜잭션
public Long create(...) { ... }
}이 패턴이 코드 가독성과 안전성 양쪽을 챙기는 방법이라고 생각합니다. 8. 미션에서 어떻게 적용할지이번 예약 대기 미션 기준으로 제가 적용하는 방식입니다. 예약/대기 Service클래스 레벨에 @Service
@Transactional(readOnly = true)
public class ReservationService {
public List<MyReservationView> findMine(Long memberId) { ... }
public List<Reservation> findByDate(LocalDate date) { ... }
@Transactional
public Long create(...) { ... }
@Transactional
public void cancel(Long id) {
// 예약 취소 + 첫 대기 승격 → 이 흐름은 반드시 변경 가능 트랜잭션
}
}이번 미션은 JPA가 아닌 JdbcTemplate 기반이라 dirty checking 같은 JPA 영역의 효과는 없습니다. 그래서 readOnly로 얻는 직접 성능 이득은 거의 없다고 봐야 합니다. 다만 읽기 일관성·커넥션 재사용·의도 표현·라우팅 대비·안전망 측면의 효용은 그대로 살아 있습니다. 특히 "내 예약 목록 조회"처럼 예약과 대기를 함께 조회하는 메서드는 두 조회가 같은 시점을 봐야 의미 있는 결과가 나오므로, 트랜잭션으로 묶는 의미가 분명합니다. 이건 JPA든 JdbcTemplate이든 동일하게 적용되는 가치입니다. 9. 정리
핵심 효용 정리:
읽기에 트랜잭션을 거는가에 대한 제 입장:
결국 readOnly는 성능을 위해 거는가가 아니라 데이터를 변경하지 않는다는 의도를 명시할 가치가 있는가의 문제라고 생각합니다. 그 의도를 명시할 가치는 거의 항상 있고, 명시 비용은 어노테이션 한 줄입니다. 그래서 저는 JPA든 JdbcTemplate이든 읽기 작업에 readOnly를 다는 쪽이 안 다는 쪽보다 안전하고 미래에 유리하다고 봅니다. |
Beta Was this translation helpful? Give feedback.
-
읽기 작업에 @transactional(readonly = true)를 사용하는 이유조회 전용 로직인데도 기본적인 트랜잭션을 메서드에 걸게 된다면 다음과 같은 불필요한 비용이 생깁니다.
이렇듯, 조회 전용이라는 의도 선언을 통해서, 성능 최적화와 실수 방지를 위해서 존재한다고 생각합니다. 그래서 다음과 같이 3가지 관점으로 읽기 전용에서 @transactional(readOnly)를 사용하는 이유를 설명하겠습니다.
JPA를 사용하지 않는 현재 저희 미션 레벨에서는 1번은 해당되지 않아서,
2가지의 이유로 사용한다고 생각합니다. |
Beta Was this translation helpful? Give feedback.
-
1.
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Beta Was this translation helpful? Give feedback.
All reactions