개발일기
[채용 정보 스크랩] 동시 요청 100개 스크랩 해결 방법 본문
Jmeter를 사용해서 똑같은 유저 정보로 동시에 100개 요청을 날리면 어떻게 되는지 테스트 해봤습니다.
100개를 동일하게 요청을 날릴 때,
Jmeter 해당 모습 Error 가 발생하였다. 에러가 발생하는건 사실상 문제는 아니다. 이유는 1초에 요청을 날리고 그 요청을 날리 때, 기준으로 디비에 데이터가 아직 insert되어있지 않기에 여러개의 중복데이터가 저장되어버리고 이에따라 문제가 발생하게 되었다.
대참사는 흠 중복저장 되어버리는 것이 문제였다.
어떻게 해결해야 하는가?
비관적인 락을 걸었다. 사실 Synchorized를 사용할까도 고민이였다. 하지만 Synchorized를 사용해도 똑같은 문제가 발생하였다.
이유는 Transactional 때문이였다.
Spring AOP 트랙션 관리 기능이 적용되는데 이 과정에서 프록시 객체가 만들어지면서 충돌하게 된다.
JobInfoServiceProxy 예제 클래스이다.
public class JobInfoServiceProxy extends JobInfoServiceImpl {
private final JobInfoServiceImpl target;
public JobInfoServiceProxy(JobInfoServiceImpl target) {
this.target = target;
}
@Override
public String jobInfoScrap(JobInfoScrapRequest request) {
// ..
}
}
이렇게 되어있다. 그러면서 덕분에 Synchorized는 메서드 이름/ 파라미터 타입과 개수가 아니기에 때문에 상속되지 않는다. 따라서 프록시 객체의 jobInfoScrap 메서드는 여러 스레드가 사용할 수 있게 되어버린다...
흠 어떻게 해야할까? Transcational은 꼭 걸어줘야 하는데, 그래서 고민하게 된 것이 비관적인락을 걸자였다. 조회하는 부분에 비관적인락을 걸게 된다면 괜찮을 것이라 판단하였다.
QueryDSL에서 비관적인 락을 걸어주었다.
@Override
public Optional<JobInfoScrap> findByJobInfoId(Long id, String email) {
JobInfoScrap scrap = queryFactory.selectFrom(jobInfoScrapEntity)
.where(
(jobInfoScrapEntity.jobInfo.id.eq(id))
.and(jobInfoScrapEntity.user.email.eq(email))
)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
return Optional.ofNullable(scrap);
}
코드를 이렇게 작성해주었다. 결론적으로 말하지만 똑같은 문제가 발생해버렸다. 왜 발생한거지?? 이유는 알게되었다.
조회가할 조직이 없다면 즉, JobInfoScrap에 조회된 데이터가 없다면 비관적락이 발생하기 때문에 동일한 문제가 계속해서 동일하게 발생하였다.
그러다가 UniqueKey를 걸어주는 것이였다.
package darkoverload.itzip.feature.job.domain;
import darkoverload.itzip.feature.job.entity.JobInfoEntity;
import darkoverload.itzip.feature.user.domain.User;
import darkoverload.itzip.feature.user.entity.UserEntity;
import darkoverload.itzip.global.entity.AuditingFields;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@ToString
@Entity
@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor(access= AccessLevel.PROTECTED)
@Builder
@Table(
name="job_info_scraps",
uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "job_id"})
)
@EntityListeners(AuditingEntityListener.class)
@EqualsAndHashCode(callSuper = false)
public class JobInfoScrap extends AuditingFields {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="user_id")
private UserEntity user;
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
@JoinColumn(name="job_id")
private JobInfoEntity jobInfo;
public static JobInfoScrap createScrap(UserEntity user, JobInfoEntity jobinfo) {
return JobInfoScrap.builder()
.user(user)
.jobInfo(jobinfo)
.build();
}
}
테이블에 UniqueConstraint 제약조건을 걸어주었다. 덕분에 최종적으로 중복저장되지 않도록 막을 수 있었다.
다음글은 도메인 테스트 방법과 스크랩에 캐싱을 적용하는 글로 찾아뵈려고 한다.
'취준생 프로젝트' 카테고리의 다른 글
🛠️ [1편 Spring] Redis Caching 사용하여 스크랩 성능 개선 (1) | 2024.12.18 |
---|---|
2부 itzip 프로젝트에 단위테스트 적용하기 (0) | 2024.12.06 |
Jmeter 사용한 성능 분석 여행기 (맥북 설치 포함) (1) | 2024.11.27 |
1부 itzip 프로젝트 이력서에 테스트 코드 TDD 적용기 (3) | 2024.11.27 |
ElasticSearch을 사용한 학교 정보 검색어 자동완성 (2) | 2024.08.28 |