Spring 프레임워크에서 Transaction과 Async를 사용할 때 고려사항

2012-10-21 16:56

SLiPP.net에 facebook 알림 기능을 구현하려고 이러 저리 삽질하다가 알게된 내용을 공유한다. 어찌 보면 당연한 결과이지만 왜 이 같은 현상이 발생하는지 몰라 해결책을 찾는 개발자에게 도움이 될까하고 공유한다.

처음 구현은 다음과 같다.

먼저 질문에 답변을 다는 경우 답변을 저장한 후 NotificationService.java를 활용해 페이스북에 알림을 하는 기능이다.

@Service("qnaService")
@Transactional
public class QnaService {
	public void createAnswer(SocialUser loginUser, Long questionId, Answer answer) {
		Question question = questionRepository.findOne(questionId);
		answer.writedBy(loginUser);
		answer.answerTo(question);
		answerRepository.save(answer);
		notificationService.notifyToFacebook(loginUser, question);
	}
}
@Service
public class NotificationService {
	public void notifyToFacebook(SocialUser loginUser, Question question) {
		Set<SocialUser> notifierUsers = question.findNotificationUser(loginUser);
		if (notifierUsers.isEmpty()) {
			return;
		}


		// 페이스북에 알림을 보낸다.
	}
}

최초 구현을 위와 같이 구현했더니 정상적으로 동작했다. 그런데 알림을 보내야 하는 사용자가 많아지면 많아질 수록 답변 글을 쓴 사용자에 대한 응답은 느려진다. 이 같은 단점을 보완하기 위해 알림 기능을 Spring의 Async 기능을 활용해 비동기 처리했다. 비동기 처리는 설정 하나 추가하고 notifyToFacebook에 @Async 어느테이션을 추가하면 쉽게 구현할 수 있었다.

그런데 문제는 다음에 방생했다. 답변 글을 달면 데이터베이스 Transaction을 완료하지 못한다는 에러 메시지가 발생하는 것이다. 원인은 question.findNotificationUser(loginUser); 메소드에 있었다. Async 어노테이션을 사용하는 순간 서로 다른 Thread에서 동작하기 때문에 앞에서 생성한 Transaction이 연장되지 않는다. 즉, Question 객체 내에서 데이터베이스와의 연동 작업을 할 수 없다는 것이다. 그런데 question.findNotificationUser(loginUser); 메소드에서는 데이터베이스와의 연동 작업을 진행하고 있었다. 이 같은 원인을 파악하고 QnaService에서 모든 데이터베이스 작업을 완료한 후 알림을 요청하는 구조로 변경했다. 최종 결과는 다음과 같다.

@Service("qnaService")
@Transactional
public class QnaService {
	public void createAnswer(SocialUser loginUser, Long questionId, Answer answer) {
		Question question = questionRepository.findOne(questionId);
		answer.writedBy(loginUser);
		answer.answerTo(question);
		answerRepository.save(answer);
		notificationService.notifyToFacebook(loginUser, question, question.findNotificationUser(loginUser));
	}
}
@Service
public class NotificationService {
	@Async
	public void notifyToFacebook(SocialUser loginUser, Question question, Set<SocialUser> notifierUsers) {		
		if (notifierUsers.isEmpty()) {
			return;
		}


		// 
	}
}

앞으로 @Async를 사용할 경우 무작정 사용할 것이 아니라 각각의 실행이 Thread가 분리된 상태에서 실행되기 때문에 이로 인해 발생할 수 있는 이슈가 있는지 검토한 후에 적용해야겠다. 그리 오랜 시간 삽질을 하지는 않았지만 아무 생각 없이 적용하려하다가 큰 코 다칠 뻔 했다.

0개의 의견 from SLiPP

의견 추가하기

연관태그

← 목록으로