1. 트랜잭션이란 무엇이고 원자성, 일관성, 고립성, 지속성이란 무엇인지 설명해주실 수 있을까요?
트랜잭션(transaction)은 데이터베이스에서 한 번에 수행되는 일련의 작업을 가리킵니다. 예를 들어, 은행 계좌 간에 돈을 이체하는 경우에는 두 개의 작업이 수행되는데, 한 계좌에서 돈을 빼고 다른 계좌에 돈을 넣습니다. 이 두 작업을 하나의 트랜잭션으로 묶을 수 있습니다.
데이터베이스에서 트랜잭션은 일반적으로 ACID 속성을 따르는데, 이는 원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 그리고 지속성(Durability)을 의미합니다.
- 원자성(Atomicity): 트랜잭션이 데이터베이스에 모두 적용되거나, 아니면 전혀 적용되지 않아야 함을 의미합니다. 즉, 트랜잭션 내의 모든 명령이 성공적으로 수행되어야 하며, 하나라도 실패하면 트랜잭션 전체가 실패합니다.
- 일관성(Consistency): 트랜잭션 수행 전과 후에 데이터베이스의 상태가 일관성을 유지해야 함을 의미합니다. 이는 데이터베이스의 무결성 제약조건이 유지되어야 함을 보장합니다.
- 고립성(Isolation): 동시에 여러 트랜잭션이 실행되더라도, 각 트랜잭션은 서로 독립적으로 실행되는 것처럼 보이게 해야 함을 의미합니다. 즉, 한 트랜잭션의 중간 상태를 다른 트랜잭션에서 볼 수 없습니다.
- 지속성(Durability): 일단 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 데이터베이스에 반영되어야 함을 의미합니다. 시스템 오류나 실패가 발생하더라도, 완료된 트랜잭션의 결과는 보존되어야 합니다.
이 네 가지 속성은 데이터베이스 트랜잭션의 안정성과 신뢰성을 보장하는 핵심적인 요소입니다.
@Transactional 어노테이션은
Spring 프레임워크에서 제공하는 트랜잭션 관리 기능의 핵심입니다. 이 어노테이션은 특정 메소드나 클래스에 붙여 사용하여 해당 영역을 하나의 트랜잭션으로 처리하게 됩니다.
메서드나 클래스에 적용될 수 있으며, 해당 어노테이션이 적용된 메서드가 실행될 때 트랜잭션을 시작하고, 메서드가 정상적으로 실행되면 트랜잭션을 커밋하고, 예외가 발생하면 트랜잭션을 롤백합니다.
* @Transactional 어노테이션의 장점은 다음과 같습니다.
- 트랜잭션 관리를 쉽게 할 수 있습니다.
- 트랜잭션 처리 중 예외 발생 시 롤백 처리를 자동으로 해줍니다.
- 코드의 가독성을 높여줍니다.
* @Transactional 어노테이션을 사용할 때 주의해야 할 점은 다음과 같습니다.
- 하나의 메소드 내에서 여러 개의 데이터베이스 연산이 수행되는 경우, 모든 연산이 하나의 트랜잭션으로 처리되므로, 성능에 영향을 미칠 수 있습니다.
- @Transactional 어노테이션을 적용할 때, 트랜잭션을 시작하는 시점과 롤백하는 시점을 명확하게 설정해주어야 합니다. 이를 위해 propagation, isolation, readOnly, timeout 등의 속성을 설정할 수 있습니다.
- propagation: 현재 트랜잭션 컨텍스트에 대한 전파 방법을 정의합니다. 예를 들어, 기존 트랜잭션에 참여할 것인지, 새로운 트랜잭션을 시작할 것인지, 트랜잭션이 필요 없는 경우 어떻게 할 것인지 등을 결정합니다.
- readOnly: 트랜잭션이 읽기 전용인지를 지정합니다. 이 속성이 true로 설정되면, Persistence Provider는 데이터를 읽기만 할 수 있음을 가정하고 최적화를 할 수 있습니다.
- rollbackFor, noRollbackFor: 특정 예외가 발생했을 때 트랜잭션을 롤백할 것인지를 결정합니다.
- timeout: 트랜잭션의 타임아웃 시간을 초 단위로 지정합니다. 이 시간이 지나면, 트랜잭션 시스템은 트랜잭션을 롤백합니다.
- isolation: 트랜잭션에서 격리 수준을 정의합니다. 이는 동시에 여러 트랜잭션이 실행되는 경우, 한 트랜잭션에서 다른 트랜잭션의 변경 내용이 보이는 시점을 제어합니다.
- 트랜잭션 전파(Propagation) 설정: 트랜잭션 전파 행동이 적절하게 설정되지 않으면 예상치 못한 문제가 발생할 수 있습니다. 특히, @Transactional이 붙은 메소드가 다른 @Transactional 메소드를 호출하는 경우 주의해야 합니다.
- 비공개 메소드에서의 사용: @Transactional 어노테이션이 붙은 메소드가 같은 클래스 내의 다른 메소드를 호출할 경우, 트랜잭션 어노테이션이 적용되지 않습니다. 이는 Spring의 AOP 프록시 기반의 동작 방식 때문입니다. 외부에서 호출되는 public 메소드에만 @Transactional 어노테이션을 적용해야 합니다.
- 읽기 전용 트랜잭션: 'readOnly=true' 설정을 이용하면 성능을 향상시킬 수 있지만, 실제로 데이터를 변경하는 작업이 포함된 경우 문제가 발생할 수 있습니다.
- 롤백 설정: @Transactional 어노테이션의 'rollbackFor' 속성을 이용하여 특정 예외 발생시 롤백이 이루어지도록 설정할 수 있습니다. 반대로 'noRollbackFor' 속성을 이용하면 특정 예외 발생시에도 롤백이 이루어지지 않게 설정할 수 있습니다. 이 설정을 잘못하면 예상치 못한 데이터 불일치 문제가 발생할 수 있습니다.
- 트랜잭션의 범위: @Transactional 어노테이션이 붙은 메소드의 크기와 복잡성에 따라 트랜잭션의 범위가 크게 달라질 수 있습니다. 너무 큰 트랜잭션은 시스템에 부담을 줄 수 있으므로, 가능한 작은 범위에서 트랜잭션을 처리하는 것이 좋습니다.
데이터베이스의 상태를 변화시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 연산들을 의미.데이터베이스의 상태를 변화시키기 위해 수행하는 작업 단위인 것이다. 트랜잭션은 ACID라 하는 네가지 특성을 보장해야한다. 원자성은 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 모두 실패해야 한다. 일관성은 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야한다. 격리성은 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다. 예를 들어 동시에 같은 데이터를 수정하지 못하도록 해야 한다. 격리성은 동시성과 관련된 성능 이슈로 인해 트랜잭션 격리 수준(Isolation level)을 선택할 수 있다. 지속성은 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다. 중간에 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구해야 한다.
트랜잭션이란 여러 개의 작업을 하나로 묶은 실행 유닛입니다. 데이터베이스 트랜잭션은 ACID라는 특성을 가지고 있는데 ACID는 데이터베이스 내에서 일어나는 하나의 트랜잭션의 안전성을 보장하기 위해 필요한 성질입니다. Atomicity는 원자성으로 트랜잭션이 안전성 보장을 위해 가져야 할 성질입니다. Consistency는 일관성으로 데이터베이스의 상태가 일관되어야 한다는 성질입니다. Isolationd는 고립성으로 모든 트랜잭션은 다른 트랜잭션으로부터 독립되어야 한다는 성질입니다. Durability는 지속성으로 하나의 트랜잭션이 성공적으로 수행되었다면, 해당 트랜잭션에 대한 로그가 남아야하는 성질입니다.
2. 트랜잭션의 격리 수준(Isolation Level)에 대해 알고 있나요? 각각에 대해 설명해보세요.
트랜잭션의 격리 수준(Isolation Level)은 데이터베이스에서 동시에 여러 트랜잭션이 처리될 때, 한 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지를 결정하는 것입니다.
트랜잭션의 격리 수준에 따라 아래와 같은 문제들이 발생할 수 있습니다.
- Dirty Read : 어떤 트랜잭션이 아직 커밋하지 않은 데이터를 다른 트랜잭션이 읽는 경우.
- Non-Repeatable Read : 한 트랜잭션 내에서 같은 쿼리를 두 번 수행할 때, 첫 번째 쿼리와 두 번째 쿼리의 결과가 서로 다른 경우를 Non-Repeatable Read라 합니다. 이는 한 트랜잭션 내에서 쿼리가 반복될 때마다 동일한 결과를 가져와야 하는데, 다른 트랜잭션이 그 사이에 값을 수정하거나 삭제하여 그렇지 않은 경우를 말합니다.
- Phantom Read : 한 트랜잭션 내에서 일련의 쿼리를 수행하는 동안, 쿼리의 결과로 반환되는 행(row)의 수가 추가적인 쿼리 결과에서 변경되는 경우를 말합니다. 이는 다른 트랜잭션에서 새로운 행을 삽입하거나 삭제하면 발생합니다.
이러한 문제들을 해결하기 위해 SQL 표준에서는 다음과 같은 네 가지 트랜잭션 격리 수준을 정의하고 있습니다.
- READ UNCOMMITTED : 가장 낮은 격리 수준으로, 트랜잭션이 아직 커밋하지 않은 데이터를 다른 트랜잭션이 읽을 수 있습니다. 이로 인해 Dirty Read, Non-Repeatable Read, Phantom Read 문제가 모두 발생할 수 있습니다.
- READ COMMITTED : 트랜잭션이 커밋한 데이터만 다른 트랜잭션이 읽을 수 있습니다. 이는 Dirty Read 문제는 방지하지만, Non-Repeatable Read와 Phantom Read 문제는 여전히 발생할 수 있습니다.
- REPEATABLE READ : 한 트랜잭션이 시작할 때의 데이터를 일관되게 읽게 합니다. 이로 인해 Dirty Read와 Non-Repeatable Read는 방지하지만, Phantom Read 문제는 여전히 발생할 수 있습니다.
- SERIALIZABLE(직렬화) : 가장 높은 격리 수준으로, 트랜잭션을 순차적으로 실행함으로써 다른 트랜잭션에서의 동시 처리를 완전히 차단합니다. 이로 인해 Dirty Read, Non-Repeatable Read, Phantom Read 문제가 모두 해결됩니다. 하지만 성능 저하의 문제가 있을 수 있습니다.
격리 수준을 올릴수록 데이터의 일관성은 향상되지만, 동시에 처리 성능은 감소할 수 있으므로 적절한 격리 수준을 선택하는 것이 중요합니다.
3. 트랜잭션의 롤백과 커밋에 대해 설명해 주세요.
트랜잭션의 커밋(Commit)과 롤백(Rollback)은 데이터베이스에서 트랜잭션을 처리하는 과정에서 중요한 연산입니다. 이 두 연산은 데이터의 일관성을 보장하는데 있어 중요한 역할을 합니다.
커밋(Commit) :
커밋은 트랜잭션에서 수행한 모든 변경(삽입, 수정, 삭제 등)을 데이터베이스에 영구적으로 반영하는 연산입니다. 트랜잭션이 성공적으로 완료되었을 때, 즉 모든 작업이 에러 없이 수행되었을 때 커밋을 실행합니다. 커밋이 실행되면 해당 트랜잭션에서 수행한 모든 변경이 데이터베이스에 저장되고, 이후에는 이 변경사항을 취소할 수 없습니다.
롤백(Rollback) :
롤백은 트랜잭션에서 수행한 모든 변경을 취소하는 연산입니다. 트랜잭션이 실패하거나 에러가 발생했을 때 롤백을 실행하여 데이터의 일관성을 보장합니다. 롤백이 실행되면 해당 트랜잭션에서 수행한 모든 변경이 취소되고, 트랜잭션 시작 전의 상태로 되돌아갑니다.
이러한 커밋과 롤백의 작동 방식은 ACID 속성 중 원자성(Atomicity)과 일관성(Consistency)을 보장하는데 중요합니다. 원자성은 트랜잭션의 모든 작업이 완전히 수행되거나 아예 수행되지 않아야 함을 의미하며, 일관성은 트랜잭션의 수행이 데이터베이스를 일관된 상태에서 다른 일관된 상태로만 이동시켜야 함을 의미합니다. 롤백과 커밋은 이러한 속성을 보장하는 데 있어 핵심적인 역할을 합니다.
4. 데드락(Deadlock)이란 무엇이고, 어떻게 해결할 수 있나요?
데드락(Deadlock)은 두 개 이상의 프로세스나 스레드가 서로 대기하고 있어, 결과적으로 아무 것도 진행되지 않는 상황을 말합니다. 주로 멀티프로세싱 시스템에서 자원을 요청하고 사용하는 과정에서 발생합니다.
데이터베이스에서 데드락이 일어나는 경우, 일반적으로 두 개 이상의 트랜잭션이 서로의 작업을 마무리하기 위해 다른 트랜잭션의 자원을 기다리고 있는 상황을 말합니다. 예를 들어, 트랜잭션 A가 자원 1을 잠그고 자원 2를 기다리는 동시에 트랜잭션 B가 자원 2를 잠그고 자원 1을 기다리는 상황이 발생하면, 이 두 트랜잭션은 데드락 상태에 빠집니다.
데드락을 해결하거나 피하는 방법은 여러 가지가 있습니다:
- 데드락 예방(Deadlock Prevention): 데드락의 네 가지 필요 조건 (상호배제, 점유와 대기, 비선점, 순환 대기) 중 하나를 무효화함으로써 데드락이 발생하는 것을 미리 막는 방법입니다.
더보기
데드락이 발생하기 위해서는 보통 네 가지 조건이 동시에 만족되어야 합니다. 이를 '데드락의 네 가지 필요 조건'이라고 부르며, 그것들은 다음과 같습니다:
- 상호배제(Mutual Exclusion): 한 번에 한 프로세스(또는 스레드, 트랜잭션)만이 자원을 사용할 수 있습니다. 다른 프로세스가 그 자원을 사용하려면, 그 자원이 현재 사용 중인 프로세스로부터 해방되어야 합니다.
- 점유와 대기(Hold and Wait): 프로세스가 이미 어떤 자원을 점유하고 있는 상태에서, 다른 자원을 기다리고 있어야 합니다. 즉, 어떤 프로세스가 자원을 점유한 상태에서 추가로 필요한 자원을 기다리고 있는 상황입니다.
- 비선점(No Preemption): 이미 점유하고 있는 자원을 프로세스가 자발적으로만 반납할 수 있으며, 다른 프로세스가 강제로 그 자원을 빼앗아 갈 수 없습니다.
- 순환 대기(Circular Wait): 프로세스 집합 {P0, P1, ..., Pn}에서 P0는 P1이 점유하고 있는 자원을 대기하고, P1은 P2가 점유하고 있는 자원을 대기하고, ..., Pn은 P0가 점유하고 있는 자원을 대기하는 상황을 말합니다. 이처럼 프로세스들이 원형으로 자원을 대기하고 있어서 순환 대기라고 합니다.
데드락을 방지하려면 이 네 가지 조건 중에서 적어도 하나를 깨야 합니다. 예를 들어, 프로세스가 자원을 요청할 때 필요한 모든 자원을 한꺼번에 요청하게 함으로써 점유와 대기 조건을 깨뜨릴 수 있습니다
- 데드락 회피(Deadlock Avoidance): 시스템이 안전하지 않은 상태로 진행되는 것을 피하도록 자원 할당을 신중하게 수행하는 방법입니다.
- 데드락 탐지와 복구(Deadlock Detection and Recovery): 시스템에서 주기적으로 데드락을 탐지하고, 발생한 데드락을 복구하는 방법입니다. 복구 방법 중 하나는 데드락에 연루된 프로세스 중 하나를 종료하거나, 해당 프로세스의 자원 할당을 취소하는 것입니다.
- 데드락 무시(Deadlock Ignorance): 실제로는 대부분의 시스템에서 데드락은 드물게 발생하므로, 데드락이 발생하지 않는다고 가정하고 아무런 조치를 취하지 않는 방법입니다. 이 방법은 데드락 처리에 대한 오버헤드를 줄이는 효과가 있지만, 데드락이 발생하면 심각한 문제를 야기할 수 있습니다.
데이터베이스 시스템에서는 일반적으로 데드락 탐지와 복구 방법을 주로 사용합니다. 데이터베이스 관리 시스템(DBMS)은 주기적으로 또는 요청 시 데드락을 탐지하고, 이를 복구하기 위해 데드락에 연루된 트랜잭션 중 하나를 선택하여 롤백합니다. 롤백된 트랜잭션은 다시 시작되거나 전체 시스템에 실패를 보고합니다.
데드락 예방은 애플리케이션의 설계 단계에서 고려할 수 있습니다. 예를 들어, 모든 트랜잭션에서 자원을 순차적으로 요청하도록 설계하는 것은 데드락의 "순환 대기" 조건을 제거하므로 데드락을 예방할 수 있습니다.
그러나 데드락을 완전히 예방하거나 회피하는 것은 쉽지 않으며, 종종 높은 오버헤드를 발생시킵니다. 따라서 실제 시스템에서는 종종 데드락을 감지하고 복구하는 방법을 사용하며, 그리고 필요에 따라 데드락 예방 기법을 선택적으로 사용하는 것이 일반적입니다.
데드락은 마치 교차로에서 자동차들이 서로 앞을 비켜주지 않아서 교통이 막히는 상황에 비유할 수 있습니다. 각각의 자동차(데이터베이스에서는 '트랜잭션'이라고 합니다)가 자신의 목적지로 가기 위해 필요한 길(이 경우 '자원'입니다)를 차지하고 있지만, 다른 차량이 그 길을 비켜주지 않아서 결국 아무도 움직일 수 없는 상태가 되는 것입니다.
이 문제를 해결하는 방법은 여러 가지가 있습니다.
- 예방: 자동차들이 동시에 교차로에 들어가지 않도록 교통 신호 등을 이용해서 문제를 미리 막는 방법입니다. 데이터베이스에서는 트랜잭션이 필요한 자원을 순서대로 요청하도록 설계하여 데드락이 발생하지 않도록 하는 방법이 있습니다.
- 회피: 이미 교차로에 들어간 자동차들에게 순서를 정해서 나가게 하는 방법입니다. 데이터베이스에서는 시스템이 트랜잭션에게 자원을 어떻게 할당할지를 조심스럽게 결정하여 데드락이 발생하지 않도록 하는 방법이 있습니다.
- 탐지와 복구: 교차로에서 교통이 막혔다면, 경찰이나 구조대가 나와서 자동차들을 하나씩 움직여서 문제를 해결하는 방법입니다. 데이터베이스에서는 DBMS가 주기적으로 데드락을 확인하고, 발생한 데드락을 해결하기 위해 일부 트랜잭션을 취소(롤백)하는 방법이 있습니다.
5. 옵티미스틱(Optimistic) 락과 페시미스틱(Pessimistic) 락에 대해 설명해주세요.
페시미스틱 락 (Pessimistic Locking) :
페시미스틱 락은 이름에서 알 수 있듯이 '비관적'입니다. 이는 데이터에 대한 동시 수정이 이루어질 것이라는 가정하에, 데이터를 읽기 전에 먼저 락을 걸어 데이터를 보호하는 전략입니다. 즉, 다른 트랜잭션들이 동시에 해당 데이터에 접근하는 것을 방지합니다. 이러한 방법은 동시 접근이 자주 발생하는 환경에서는 유용하며, 데이터의 일관성을 유지하는 데 필요한 방법입니다. 그러나 대기 시간이 길어질 수 있으며, 데드락이 발생할 가능성이 있습니다.
옵티미스틱 락 (Optimistic Locking) :
옵티미스틱 락은 '낙관적'입니다. 이는 동시에 동일한 데이터를 수정하는 트랜잭션 충돌이 드물게 발생할 것이라고 가정하고, 트랜잭션을 진행하며 최종적으로 데이터를 업데이트 할 때 충돌을 검사하는 방식입니다. 만약 충돌이 감지되면, 트랜잭션은 중단되고 (보통 롤백되며), 다시 시도해야 합니다. 이러한 방법은 동시 접근이 비교적 드물게 발생하는 환경에서 유용하며, 락으로 인한 대기 시간을 크게 줄일 수 있습니다. 그러나 충돌이 빈번하게 발생하면, 롤백 및 재시도로 인한 오버헤드가 증가할 수 있습니다.
페시미스틱 락의 예시
은행에서 계좌 이체를 생각해봅시다. A 계좌에서 B 계좌로 돈을 이체한다고 하면, 먼저 A 계좌의 잔액을 확인하고, 충분한 금액이 있다면 B 계좌에 이체 금액을 입금하고, 마지막으로 A 계좌에서 이체 금액을 차감해야 합니다.
이 과정에서 페시미스틱 락을 사용한다면, A 계좌의 잔액을 확인하는 순간부터 락을 걸어 다른 트랜잭션(예를 들어, 다른 이체 작업 또는 출금 작업)이 A 계좌를 수정하는 것을 방지합니다. 이렇게 함으로써 A 계좌의 잔액이 이체 작업 도중에 변경되는 것을 막을 수 있습니다.
옵티미스틱 락의 예시
웹사이트에서 사용자 프로필을 수정하는 경우를 생각해봅시다. 사용자가 자신의 프로필 정보를 읽어서 화면에 보여주고, 사용자가 그 정보를 수정한 후 '저장' 버튼을 눌러 변경 사항을 데이터베이스에 반영하게 됩니다.
이 경우 옵티미스틱 락을 사용하면, 사용자가 프로필 정보를 읽어올 때는 락을 걸지 않습니다. 사용자가 '저장' 버튼을 누를 때, 데이터베이스는 사용자가 처음 정보를 읽어온 시점의 버전과 현재 데이터베이스에 저장된 버전을 비교합니다. 만약 이 두 버전이 다르다면(즉, 다른 사용자가 동시에 동일한 프로필을 수정하려고 했다면), 데이터베이스는 '저장' 작업을 거부하고 사용자에게 충돌이 발생했다는 메시지를 보여줍니다. 그러면 사용자는 다시 프로필 정보를 읽어와서 변경 사항을 다시 적용해야 합니다.
6. Spring에서 @Transactional 어노테이션을 어떻게 사용하나요? 그리고 그 장단점은 무엇인가요?
@Transactional은 Spring에서 제공하는 어노테이션으로, 트랜잭션의 경계를 정의하는 데 사용됩니다. 이 어노테이션을 사용하면 특정 메서드의 실행을 하나의 데이터베이스 트랜잭션으로 처리할 수 있습니다.
이 어노테이션은 메서드나 클래스에 적용할 수 있으며, 해당 어노테이션이 붙은 메서드를 호출할 때마다 새로운 트랜잭션이 시작됩니다. 메서드가 성공적으로 완료되면 트랜잭션은 커밋되고, 예외가 발생하면 롤백됩니다.
장점
- 코드의 가독성이 향상됩니다. 트랜잭션 관리 코드를 작성하지 않아도 Spring이 알아서 처리해주기 때문입니다.
- 선언적 트랜잭션 관리를 지원합니다. 이는 트랜잭션 관리를 비즈니스 로직에서 분리하므로, 개발자가 비즈니스 로직에 집중할 수 있게 해줍니다.
단점
- 런타임에 예외가 발생하면 롤백이 자동으로 수행되므로, 예기치 않은 롤백이 발생할 수 있습니다. 따라서 롤백 규칙을 정확히 이해하고 사용해야 합니다.
- @Transactional 어노테이션을 가진 메서드에서 다른 @Transactional 메서드를 호출하면, 기본적으로 두 메서드는 동일한 트랜잭션을 공유합니다. 이러한 동작을 원하지 않는 경우에는 별도로 설정을 변경해야 합니다.
- 프록시 기반 AOP를 사용하므로, 메서드의 자기 호출(self-invocation)에는 @Transactional이 적용되지 않습니다. 이는 Spring이 프록시를 통해 트랜잭션을 관리하기 때문에, 같은 객체 내부에서 @Transactional 메서드를 직접 호출하면 프록시를 통하지 않으므로 트랜잭션 처리가 이루어지지 않습니다.
이처럼, @Transactional의 사용은 코드를 깔끔하게 유지하고 개발자가 트랜잭션 관리에 신경 쓰지 않도록 도와주지만, 사용 시에는 롤백 규칙과 같은 세부 사항을 이해하고 있어야 하며, 특정 상황에서는 예상치 못한 동작을 할 수 있으므로 주의해야 합니다.
7. 트랜잭션의 전파 설정(Propagation)에 대해 설명해 주세요.
트랜잭션 전파 설정은 트랜잭션의 범위를 제어하는 방법으로, Spring에서 @Transactional 어노테이션과 함께 사용합니다. 트랜잭션 전파 설정은 Propagation 열거형을 사용하여 설정할 수 있습니다.
- REQUIRED: 이 옵션은 메서드에 트랜잭션이 필요하다는 것을 나타냅니다. 만약 이미 존재하는 트랜잭션이 있다면 해당 트랜잭션을 사용하고, 없다면 새로운 트랜잭션을 시작합니다. 이는 기본 설정값입니다.
- REQUIRED: 예를 들어, 은행에서 계좌 이체를 진행할 때, 두 개의 작업(돈을 보내는 계좌에서 돈 빼기, 받는 계좌에 돈 넣기)이 하나의 트랜잭션으로 처리되어야 합니다. 이 경우, 두 작업 메서드 모두에 REQUIRED를 설정하면, 두 메서드는 동일한 트랜잭션을 공유하게 됩니다.
- REQUIRES_NEW: 이 옵션은 항상 새로운 트랜잭션을 시작하게 합니다. 이미 존재하는 트랜잭션이 있다면 해당 트랜잭션을 일시 중단하고, 새로운 트랜잭션을 시작합니다.
- REQUIRES_NEW: 사용자의 주문을 처리하는 시스템에서, 주문을 처리하는 메서드와 로깅을 하는 메서드가 있을 때, 로깅은 새로운 트랜잭션에서 독립적으로 실행되어야 할 수 있습니다. 이 경우, 로깅 메서드에 REQUIRES_NEW를 설정하면, 주문 처리 트랜잭션과 별개의 트랜잭션에서 실행됩니다.
- SUPPORTS: 이 옵션은 이미 존재하는 트랜잭션이 있다면 해당 트랜잭션을 사용하고, 없다면 트랜잭션 없이 실행하게 합니다.
- SUPPORTS: 특정 메서드가 트랜잭션 내에서 실행되긴 하지만, 필수적이지 않다면, SUPPORTS를 사용할 수 있습니다. 즉, 호출하는 측에서 이미 트랜잭션이 있으면 그 트랜잭션을 이어받아 사용하고, 없으면 트랜잭션 없이 수행하게 됩니다.
- NOT_SUPPORTED: 이 옵션은 트랜잭션을 사용하지 않도록 합니다. 만약 이미 존재하는 트랜잭션이 있다면 해당 트랜잭션을 일시 중단하고, 트랜잭션 없이 실행하게 합니다.
- NOT_SUPPORTED: 특정 연산이 트랜잭션의 범위에서 제외되어야 할 때 사용합니다. 예를 들어, 읽기 전용 작업을 수행하는 메서드에 이 옵션을 설정할 수 있습니다.
- NEVER: 이 옵션은 트랜잭션을 사용하지 않도록 합니다. 만약 이미 존재하는 트랜잭션이 있다면 예외를 발생시킵니다.
- NEVER: 트랜잭션이 절대로 존재해서는 안될 때 사용합니다. 만약 트랜잭션 내에서 이 옵션을 가진 메서드를 호출하면 예외가 발생합니다.
- MANDATORY: 이 옵션은 반드시 이미 존재하는 트랜잭션을 사용하도록 합니다. 만약 이미 존재하는 트랜잭션이 없다면 예외를 발생시킵니다.
- NESTED: 이 옵션은 현재 트랜잭션이 있다면 중첩 트랜잭션을 시작하게 합니다. 만약 현재 트랜잭션이 없다면 REQUIRED와 동일하게 동작합니다.
- NESTED: 새로운 트랜잭션을 시작하되, 부모 트랜잭션이 롤백되면 함께 롤백되고, 부모 트랜잭션이 커밋되면 독립적으로 커밋 또는 롤백될 수 있습니다.
8. 트랜잭션의 일관성을 위해 사용되는 락(Lock)에 대해 설명해 주세요.
데이터베이스에서 락(lock)은 주로 데이터를 보호하고, 동시에 여러 트랜잭션이 수행될 때 데이터의 일관성을 유지하는 데 사용됩니다. 다음은 락에 대한 기본적인 개념들입니다:
- 공유 락(Shared Lock): 여러 트랜잭션이 동시에 같은 데이터를 읽을 수 있도록 하는 락입니다. 하지만 공유 락이 설정된 데이터는 다른 트랜잭션에 의해 수정될 수 없습니다. 이 락은 데이터의 일관성을 유지하는 데 중요합니다.
- 배타적 락(Exclusive Lock): 트랜잭션이 데이터를 수정할 때 사용하는 락입니다. 배타적 락이 설정된 데이터는 다른 트랜잭션에 의해 읽거나 쓰일 수 없습니다. 이 락은 동시에 같은 데이터를 두 트랜잭션에서 수정하는 것을 방지합니다.
- 옵티미스틱 락(Optimistic Lock): 동시성이 높은 환경에서 성능 향상을 위해 사용되는 락입니다. 옵티미스틱 락은 실제로 데이터를 락하지 않고, 대신 데이터의 버전을 체크하여 충돌이 발생했는지를 확인합니다. 만약 충돌이 발생하면 예외를 발생시키며, 충돌을 해결하는 책임은 애플리케이션에게 넘깁니다.
- 페시미스틱 락(Pessimistic Lock): 동시성이 낮은 환경에서 사용하는 락입니다. 페시미스틱 락은 데이터를 실제로 락하여 다른 트랜잭션에서 해당 데이터를 수정하지 못하게 합니다. 이 락은 데이터의 일관성을 보장하지만, 대기 시간이 증가하고, 데드락의 가능성이 있어 성능에 악영향을 미칠 수 있습니다.
이러한 락 메커니즘은 데이터베이스 시스템의 ACID 특성 중 일관성(Consistency)과 고립성(Isolation)을 보장하는 데 도움이 됩니다. 각 락 메커니즘이 어떻게 작동하는지 이해하고 적절한 상황에 적절한 락을 사용하는 것이 중요합니다.
9. 비관적 락(Pessimistic Locking)과 낙관적 락(Optimistic Locking)의 차이점에 대해 설명해 주세요.
비관적 락(Pessimistic Locking)과 낙관적 락(Optimistic Locking)은 동시성 제어를 위한 두 가지 주요 전략입니다. 각각은 다음과 같은 특징을 가지고 있습니다:
비관적 락(Pessimistic Locking):
- 비관적 락은 동시성 제어를 위한 전통적인 방법으로, 데이터의 충돌이 발생할 것이라는 '비관적'인 가정 하에 작동합니다. 이 방식은 동시에 같은 데이터에 접근하는 트랜잭션을 블록(block)하여 충돌을 방지합니다. 즉, 하나의 트랜잭션이 데이터를 읽거나 수정하는 동안 다른 트랜잭션이 해당 데이터에 접근하는 것을 막습니다.
비관적 락은 동시성이 낮은 환경에서 잘 작동하며, 트랜잭션 충돌의 위험이 높은 시스템에서 사용됩니다. 그러나 이러한 락은 대기 시간을 증가시키고 데드락을 일으킬 수 있어, 성능에 부정적인 영향을 미칠 수 있습니다.
낙관적 락(Optimistic Locking):
- 낙관적 락은 동시성 제어의 현대적인 방법으로, 충돌이 발생하지 않을 것이라는 '낙관적'인 가정 하에 작동합니다. 이 방식은 충돌이 발생할 경우 이를 해결하는 방법에 초점을 맞춥니다. 일반적으로 버전 관리 시스템을 통해 구현되며, 트랜잭션이 데이터를 읽을 때 해당 데이터의 버전 정보도 함께 읽습니다. 트랜잭션이 데이터를 변경하려 할 때, 데이터의 현재 버전이 트랜잭션 시작 시 읽은 버전과 다르면 충돌이 발생한 것으로 간주합니다.
낙관적 락은 동시성이 높은 환경에서 잘 작동하며, 충돌의 위험이 낮은 시스템에서 사용됩니다. 이러한 락은 충돌이 발생할 경우 예외를 발생시키며, 이를 처리하는 책임은 애플리케이션에게 있습니다.
10. 클래스 레벨과 메소드 레벨에서의 차이점
클래스 레벨에서의 @Transactional :
클래스 레벨에서 @Transactional을 사용하면 해당 클래스의 모든 public 메소드가 자동으로 @Transactional 어노테이션을 가지게 됩니다. 즉, 해당 클래스의 모든 public 메소드를 호출할 때마다 새로운 트랜잭션이 시작되고, 메소드가 종료될 때 트랜잭션이 커밋 또는 롤백됩니다. 이는 메소드 레벨에서 어노테이션을 하나씩 달지 않고, 한 번에 클래스 전체에 트랜잭션을 적용하고 싶을 때 유용합니다.
메소드 레벨에서의 @Transactional :
메소드 레벨에서 @Transactional을 사용하면 해당 메소드만 트랜잭션 경계를 가집니다. 이는 특정 메소드에만 트랜잭션을 적용하고 싶을 때 사용합니다.
결론적으로, 클래스 레벨에서 @Transactional을 사용하면 그 클래스의 모든 public 메소드가 트랜잭션 범위에 들어가게 되며, 메소드 레벨에서 사용하면 특정 메소드만이 트랜잭션 범위에 들어가게 됩니다.
그러나 주의할 점은 클래스 레벨에서 @Transactional을 선언하고, 메소드 레벨에서 다시 @Transactional을 선언하면 메소드 레벨의 설정이 우선적으로 적용된다는 점입니다. 이는 메소드 레벨에서 트랜잭션의 특성(예를 들어, propagation behavior, isolation level 등)을 재정의하려는 경우 유용합니다.
'Mockterview' 카테고리의 다른 글
TCP(Transmission Control Protocol), UDP(User Datagram Protocol) (0) | 2023.05.27 |
---|---|
데이터베이스 정규화(database normalization) (0) | 2023.05.27 |
사용자 패스워드를 전송하고 보관하는 방법(일반적인 보안 방법) pt.2 (0) | 2023.05.24 |
인덱스(Index), Composite(복합)인덱스 pt.2 (0) | 2023.05.24 |
DI와 IoC, Bean pt.2 (0) | 2023.05.23 |