본문 바로가기
Category/개발일지

데이터베이스 동시성 제어 문제_ 트랜잭션 전략 변경하기

by developer__Y 2024. 10. 11.
원인 파악

 

전자정부프레임워크 + Tomcat 을 통해 운영중인 웹사이트에서 특정 이벤트 발생시마다 서버가 무한로딩되는 문제가 자주 발생하였다.

증상은 웹 페이지 이동시 DB 조회를 통해 관련 정보를 출력하는데 결과 데이터를 받아오지 못해 무한 로딩이 걸리는 현상이었고,

원인을 파악하기 어려웠던 이유는 해당 증상이 발생하는데에도 서버 로그에 어떠한 에러가 발생하지 않는다는 것이었다.

해당 증상이 발생했던 날의 모든 로그를 살펴본결과  공통적으로 증상 발생 이전 특정 이벤트 작업이 발생하는것을 확인했다.

 

특정 이벤트 작업은 서버에 부하를 많이 주는 작업이었기에 처음에 의심했던 원인은

OOM(Out Of Memory) 문제일까 싶었지만,

해당 증상이 발생할때 Server에서 서버 리소스를 확인해보니 Memory 사용량이 다소 증가하긴 하였지만

OOM 에러가 발생하진 않았다.

 

내부 소스코드 로직에서 해당 특정 이벤트 DB작업 코드를 확인해보니 Spring Framework의 DataSourceTransactionManager을 사용하여 프로그래밍적 트랜잭션 작업이 걸려있었다.

해당 이벤트는 TransactionManager의 getTransaction()이 시작된이후 I/O 작업이 길게 이어진뒤, DB 작업후 Commit&Rollback을 마친다.

그리하여 문제가 발생하게된 원인에 대한 추측을 해본결과

 

해당 트랜잭션이 시작된 이후 Commit & Rollback이 실행되기 전에 다른 클라이언트 요청이 DB 작업을 처리하지못해 무한 로딩이 발생한다면?

 

 

위 가설이 맞는지 테스트해보기위해 운영중인 서버에서는 불가능하므로 로컬서버에서 비슷한 환경을 구현하여 해당 증상이 나타나는지 실험해보았다.

 

 

context-transaction.xml

 

우선 위와 관련된 문제는 데이터베이스 동시성과 관련된 내용으로 우선 

운영중인 어플리케이션의 트랜잭션 전략에 대해 파악해보자.

 

전자정부프레임워크의 프로젝트에서 트랜잭션은 spring/context-transaction.xml 파일에서 관리한다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="*" rollback-for="Exception"/>
		</tx:attributes>
	</tx:advice>

	<aop:config>
		<aop:pointcut id="requiredTx" expression="execution(*..impl.*Impl.*(..))"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="requiredTx" />
	</aop:config>

</beans>

 

 

위 설정에서 SpringFramework의 DataSourceTransactionManager을 Bean으로 정의한다.

해당 객체는 DataSource를 참조하여 운영중인 데이터베이스의 트랜잭션을 관리할수있다.

txAdvice 트랜잭션 어드바이스는 모든 메소드에 대해 트랜잭션을 시작하며, Exception발생시 rollback 하도록 되어있다.

 

따라서 Spring AOP를 통해 impl,Impl로 끝나는 모든 클래스의 메소드에 대하여 트랜잭션이 감싸져있는 상태이고,

해당 DataSource의 DB의 격리수준은 Read Committed 이다.

 

@Controller
public class Controller {
   
    /** transactionManager */
    @Autowired
    private DataSourceTransactionManager transactionManager;
    
     @RequestMapping(value = "/event")
    public String AMethod() throws Exception {
        
        DefaultTransactionDefinition transaction = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus transactionStatus = transactionManager.getTransaction(transaction);
        
      	// DB 작업 1
        AService.insert();
        
        /*추가 I/O 작업
        .
        .
        .
        */
        // DB 작업 2
        int result = BService.update();
        
       if(result > 0){
       transactionManager.commit(transactionStatus);
       }else{
       transactionManager.rollback(transactionStatus);
        
        
        return "view";
    }

 

 

또한, 코드내에서 트랜잭션을 묶어 처리하기위해 위와 같이 구성된 메소드에서는

 DB 작업 1을 수행한뒤, 또다른 I/O 작업을 수행하고, DB 작업 2를 수행한뒤,

두 결과값에 따라 Rollback&Commit한다.

 

즉, 위의 DB 작업 1과 DB 작업 2는 하나의 트랜잭션으로 관리되므로 

 

멀티스레딩 환경인 tomcat에서 A 클라이언트가 해당 요청작업을 수행중일때, B 클라이언트가 DB 작업 1과 관련된 테이블을 조회하려 할 경우 A클라이언트의 트랜잭션이 끝날때까지 대기할 것이다.

 

위 문제를 해결하기 위해 크게 2가지 방법을 생각할수있다.

 

1. 트랜잭션 작업범위 최소화

2. I/O 작업을 Nonblock I/O 방식으로 변경하여 트랜잭션 작업속도 향상

 

 

데이터베이스의 일관성을 유지하면서 동시성을 유지하려면 Trade Off 관계로 데이터베이스의 일관성과 동시성 사이에서

적절한 타협점을 찾아야 한다는 것을 느꼈다.

 

만약 해당 트랜잭션 작업범위를 줄인다면, 작업속도는 빨라져 다른 트랜잭션을 동시에 처리할수있는 동시성이 향상되겠지만 데이터베이스의 일관성을 저하시킬것이다.

따라서 가장 좋은 방법은  트랜잭션을 지연시키는 원인인 해당 I/O 작업은 현재 block I/O이기때문에, 비동기 처리하여

트랜잭션의 작업속도를 높이는것이 가장 좋지만 그러자니 해당 I/O작업 라이브러리를 통째로 들어내야하는 상황이 발생할것이다.

 

결국 고민하다가 트랜잭션 작업범위를 최소화하고 트랜잭션내 각각의 DB작업이 전체 결과의 일관성을 해치지않도록 타협하여 해결해야 할것같다.

 

 

 

 

 

 

 

 

 

'Category > 개발일지' 카테고리의 다른 글

DB 데드락(deadlock) 해결사례  (0) 2024.12.02
static 메소드와 스프링 IoC Singleton  (0) 2024.08.14