낙낙님의 면접 결과

60

총점

3

질문 수

1

좋아요

28

조회수

종합 평가

면접자님은 트랜잭션과 데이터베이스 격리 수준에 대한 깊은 이해를 보여주셨으며, 특히 @Transactional의 동작 원리와 AOP 구현 방식에 대한 설명이 매우 정확했습니다. MySQL의 갭 락과 관련된 데드락 상황에 대한 설명도 실무적 경험이 느껴질 정도로 구체적이었습니다. 다만 트랜잭션 전파 옵션에서 REQUIRED와 REQUIRES_NEW 외 다른 옵션들에 대한 설명이 다소 부족했으며, 실제 프로젝트에서 어떤 상황에서 어떤 격리 수준을 선택했는지에 대한 구체적 사례가 있었다면 더 좋았을 것입니다. 향후 학습 방향으로는 다양한 트랜잭션 전파 옵션들의 실제 활용 사례를 정리하고, 데이터베이스 성능 최적화 관점에서 락 메커니즘과 인덱스 설계를 함께 고려하는 방식에 대해 더 깊이 공부하시면 좋을 것 같습니다.

질문별 상세 분석

커뮤니티 공개
Q1A

@Transactional 의 동작 방식에 대해 설명해주세요.

응답 내용

@Transactional 은 스프링의 AOP를 활용하여 동작합니다. 트랜잭션에서 커밋과 롤백, 그리고 전파옵션이나 격리수준, readonly와 같은 옵션을 서비스 메소드마다 구현이 된다면, 비즈니스 로직 사이에 애플리케이션 로직이 섞이게 됩니다. 이와 같은 횡단 관심사를 비즈니스 코드에서 제거하기 위해 @Transactional 을 붙이면 AOP를 통해 트랜잭션과 관련된 처리를 하게 됩니다. 스프링에서는 프록시를 통해 AOP를 구현하는데, 즉 실제 객체를 프록시 객체로 한번 감싼 뒤 메소드 호출 앞 뒤로 트랜잭션과 관련된 코드를 실행하는 방식으로 동작합니다. 프록시 구현 방식에는 대표적으로 2가지 방식이 있는데, CGLib 은 이는 해당 클래스를 직접 상속하여 프록시 객체를 만드는 방식이고, JDK Dynamic은 인터페이스를 통해 해당 인터페이스를 구현하는 프록시 객체로 실제 구현체를 감싸는 방식으로 동작합니다. 최신 스프링에서는 주로 어노테이션 기반으로 빈들을 관리하기 때문에 CGLib을 이용하여 프록시 객체를 만듭니다. 그러면 컨트롤러가 서비스 빈을 주입 받을 때 실제 서비스 빈 대신 프록시 빈을 주입받고, 이 때 트랜잭션과 관련된 코드들이 함께 실행이 됩니다. 따라서 private 메소드는 상속이 불가능하기 때문에 적용되지 않고, self invocation은 자기 자신을 직접 호출할 때 그 대상이 프록시가 아닌 실제 서비스 객체의 메소드를 호출하는 것이기 때문에 적용되지 않습니다. 이는 자기 자신을 주입받아서 해당 프록시 빈의 메소드를 호출하게 하거나 서비스 클래스를 분리하여 해결할 수 있습니다.

AI 피드백

@Transactional의 AOP 기반 동작 방식과 프록시 패턴을 통한 구현, 그리고 CGLib과 JDK Dynamic Proxy의 차이점까지 매우 체계적으로 설명해주셨습니다. 특히 private 메소드와 self invocation 문제점을 정확히 파악하고 해결방안까지 제시한 부분이 인상적이었습니다. 다만 트랜잭션의 핵심 동작인 실제 커밋/롤백이 언제 어떻게 발생하는지에 대한 설명이 부족했는데, 메소드 정상 완료 시 커밋, 런타임 예외 발생 시 롤백되는 기본 동작 방식을 추가로 설명하면 더욱 완성도 높은 답변이 될 것 같습니다. 또한 트랜잭션 매니저의 역할이나 실제 데이터베이스 연결과의 관계에 대해서도 학습해보시면 도움이 될 것입니다.

Q2A

@Transactional에서 전파 옵션(Propagation)과 격리 수준(Isolation)에 대해 더 자세히 설명해주세요. 각각의 종류와 언제 어떤 옵션을 선택해야 하는지 실무적인 관점에서 설명해주세요.

응답 내용

트랜잭셔널의 전파 옵션은 물리 트랜잭션과 논리 트랜잭션간의 관계를 정의하는 옵션입니다. 가령 기본값인 REQUIRED 는 물리 트랜잭션이 존재 하지 않는다면 새로 열고, 이미 물리 트랜잭션 내부에 있다면 물리 트랜잭션을 새로열지 않고 논리 트랜잭션만 여는 방식으로, 외부 트랜잭션과 내부 트랜잭션이 마치 하나의 트랜잭션처럼 동작하게 하는 값입니다. REQUIRES_NEW는 물리 트랜잭션을 항상 새로 여는 옵션으로 이미 물리 트랜잭션이 있더라도 새로 열기 때문에 실제로 2개의 트랜잭션이 동작하게 됩니다. 예를들어 부분 재시도와 같은 상황이 필요할 때, 내부 트랜잭션만 롤백 시 해당 트랜잭션만 다시 재시도하는 방식으로 사용할 수 있습니다. 이외에도 다양한 옵션이 있으나 하나하나 외우고 있지는 않고, 적절한 상황에서 사용할 수 있을 것이라 생각합니다. 트랜잭션 격리 수준은 트랜잭션간의 격리성을 어느 수준으로 결정할지 정하는 옵션으로 시리얼라이저블, 리피터블 리드, 리드 커미티드, 리드 언커미티드가 있습니다. 현재 서비스에서 MySQL 을 사용하기 때문에 MySQL을 기준으로 설명하겠습니다. 시리얼라이저블은 모든 SELECT 문에서 S락을 사용하여 2PL 방식으로 직렬성을 보장해주는 방식입니다. 이 방식에서는 동시성 문제가 발생하지는 않지만 락간의 경합으로 인한 지연이나 S락 획득 후 X락 획득과 같은 패턴에서 데드락 발생 확률이 더 높기 때문에 성능 이슈로 많이 사용하지는 않는 옵션입니다. 리피터블 리드에서 S 락을 걸면 같은 효과를 줄 수 있어 잘 사용하지 않습니다. 리피터블 리드는 MySQL에서 기본값으로, 트랜잭션 내부에서 SELECT 문이 항상 같은 값을 반환하게 하는 방식입니다. MySQL에서는 언두 로그를 이용해 각 레코드마다 버저닝을 하는 MVCC 를 통해 리피터블 리드를 보장해줍니다. 리피터블 리드에서는 팬텀 리드가 발생할 수 있는데, MySQL에서는 일반적으로 일어나지 않는 문제입니다. 락 없이 SELECT를 하는 경우에는 언두 로그와 트랜잭션 번호를 통해 새로 삽입되는 레코드를 조회하지 않기 때문에 팬텀리드가 발생하지 않고, SELECT를 락과 함께 사용하더라도 리피터블 리드 이상에서 갭 락이 함께 걸리고, 이 갭 락은 레코드 사이에 다른 트랜잭션에 의한 레코드 삽입을 막기 때문에 팬텀 리드가 발생하지 않습니다. 하지만 락 없이 레코드를 조회 한 뒤 다른 트랜잭션이 삽입하고, 락과 함께 레코드를 조회하는 패턴에서는 팬텀 리드가 발생할 수 있습니다. 이는 언두 로그에는 락을 걸 수 없어 언두로그를 보다가 락을 걸면서 최신의 레코드를 보게 되면서 발생하게 되는 문제입니다. 리드 커미티드는 다른 트랜잭션에서 커밋한 레코드를 볼 수 있게 하는 옵션입니다. 같은 트랜잭션 내에서 SELECT 문의 결과가 바뀔 수 있기 때문에 논리피터블 리드 현상이 발생합니다. 하지만 MySQL 기준으로 갭 락이 걸리지 않는다는 장점이 있을 수 있습니다. 팬텀 리드와 논 리피터블 리드 모두 발생할 수 있지만, 갭 락으로 인한 데드락은 발생하지 않기 때문입니다. 리드 언커미티드는 다른 트랜잭션에서 커밋하지 않은 레코드도 볼 수 있게 하는 옵션입니다. 이 경우 트랜잭션 내에서 커밋하지 않은 레코드를 읽은 상황에서 해당 트랜잭션이 롤백 되면 다시 조회할 때 롤백된 레코드를 읽게 되는 더티 리드를 발생시킵니다. 가장 낮은 옵션으로 현업에서는 잘 사용하지 않는 옵션입니다. 일반적으로는 리피터블 리드를 사용하는 것이 좋으며, 만약 갭 락으로 인한 데드락이 문제가 되는 상황이라면 팬텀 리드와 논리피터블 리드를 감수하더라도 리드 커미티드를 사용하는 것이 더 좋다고 판단하면 리드 커미티드를 사용할 수도 있습니다. 하지만 트랜잭션 전파 옵션이나 격리 수준을 건드리는 것은 인지 비용을 발생시키기 때문에 다른 해결책을 사용하는 경우가 많습니다.

AI 피드백

트랜잭션 전파 옵션과 격리 수준에 대해 매우 체계적이고 깊이 있게 설명해주셨습니다. 특히 MySQL의 MVCC 메커니즘과 갭 락의 동작 원리, 그리고 각 격리 수준에서 발생할 수 있는 동시성 문제들을 정확하게 설명하신 점이 인상적이었습니다. 다만 전파 옵션 부분에서 REQUIRED와 REQUIRES_NEW 외에도 SUPPORTS, MANDATORY, NOT_SUPPORTED 등의 다른 옵션들에 대한 간략한 언급이 있었다면 더욱 완성도 높은 답변이 되었을 것 같습니다. 실무적 관점에서 언제 어떤 옵션을 선택해야 하는지에 대한 구체적인 시나리오나 판단 기준을 조금 더 자세히 설명해주시면 더욱 좋겠습니다.

Q3A

MySQL에서 갭 락(Gap Lock)이 어떻게 동작하는지, 그리고 이로 인해 발생할 수 있는 데드락 상황과 해결 방법에 대해 설명해주세요.

응답 내용

MySQL에서 갭 락은 레코드와 레코드 사이에 다른 트랜잭션의 레코드가 삽입되는 것을 막는 락입니다. 리피터블 리드 이상의 격리 수준에서 사용되며, 락이 걸릴 때 레코드 락과 갭 락을 합친 넥스트 키 락이 인덱스의 스캔 범위만큼 걸리게 됩니다. 따라서 WHERE 조건에 따른 적절한 인덱스가 없는 경우 PK 인덱스 전체에 락이 걸리게 됩니다. 갭 락은 팬텀 리드를 방지하기 위한 락이기 때문에 UK 인덱스에서 WHERE 조건이 단 하나의 레코드만 결과로 나오도록 보장한다면 팬텀 리드가 발생할 일이 없기 때문에 갭 락이 발생하지 않습니다. 갭 락은 X 락이더라도 공유가 가능합니다. 이는 MySQL 공식 문서를 보면 갭 락이 단순 INSERT를 막는 용도로만 사용되기 때문이라고 합니다. 이 때문에 데드락이 발생할 수 있습니다. 두 트랜잭션이 같은 갭에 대해 갭 락을 걸면 동시에 갭 락을 걸게 되고, 해당 갭에 INSERT를 하려고 하면 서로의 갭 락으로 인해 대기하는 데드락이 발생하게 됩니다. 일반적으로 인덱스가 있는 상황에서 존재하지 않는 데이터에 대해 S락이나 X락으로 조회하고 해당 데이터를 삽입하려 하는 경우 데드락이 발생하게 됩니다. 데드락을 해결하기 위해서는 트랜잭션 격리 수준을 리피터블 리드로 낮춰 갭 락이 발생하지 않게 하거나, UK를 걸어서 락을 사용하지 않고 중복과 같은 문제를 해결할 수 있습니다.

AI 피드백

갭 락의 동작 원리와 데드락 발생 메커니즘에 대해 매우 정확하고 체계적으로 설명해주셨습니다. 특히 갭 락이 X락이더라도 공유 가능하다는 점과 넥스트 키 락의 개념, 그리고 UK 인덱스에서 단일 레코드 조회 시 갭 락이 발생하지 않는다는 세부사항까지 정확히 알고 계시는 점이 인상적입니다. 다만 데드락 해결 방법 부분에서 "트랜잭션 격리 수준을 리피터블 리드로 낮춰"라고 하셨는데, 이는 리드 커미티드로 낮춘다는 의미로 보이며, 실제로는 리드 커미티드에서 갭 락이 발생하지 않습니다. 추가적으로 애플리케이션 레벨에서의 해결책인 락 순서 통일이나 재시도 로직 구현 등의 실무적 접근법도 함께 언급하시면 더욱 완성도 높은 답변이 될 것 같습니다.

사용자 메모

생각해보니 SKIP LOCKED나 NO WAIT 같은 방식도 가능하겠네요. 마지막에 리피터블 리드로 낮추라는 것은 리드 커미티드로 낮추라는 것을 잘못 적은 내용입니다.

커뮤니티 공유 안내

• 이 결과는 AI가 분석한 내용이며, 학습 목적으로 커뮤니티에 공유됩니다.

• 좋아요를 눌러 유용한 답변에 반응을 남겨보세요.

• 개인정보는 포함되지 않으며, 면접 연습 개선을 위한 참고 자료로 활용됩니다.