☘️ λ°±μ—”λ“œ: Backend

[Spring] JPA 비관적 락 적용으둜 λ™μ‹œμ„± 문제 ν•΄κ²°ν•˜κΈ°

🐀 쀀콩이 2023. 6. 15. 17:53

πŸ€” 문제 상황

 

진행쀑인 블라썸 ν”„λ‘œμ νŠΈμ—μ„œ 검색 κ²°κ³Ό 쑰회 횟수λ₯Ό μ¦κ°€μ‹œν‚€λŠ” λ‘œμ§μ„ κ΅¬ν˜„ν•˜λŠ” κ³Όμ •μ—μ„œ λ™μ‹œμ„± λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλŠ” 상황을 ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ 검색 결과둜 꽃 A, B, C κ°€ 쑰회되면 A, B, C 의 쑰회 횟수λ₯Ό 각각 1μ”© μ¦κ°€μ‹œμΌœμ•Ό ν–ˆλŠ”λ°, λ§Œμ•½ A 의 ν˜„μž¬ 쑰회 νšŸμˆ˜κ°€ 1일 λ•Œ, λ™μ‹œμ— 3번 μ‘°νšŒλœλ‹€λ©΄ κ²°κ³ΌλŠ” 4νšŒκ°€ λ˜μ–΄μ•Ό ν•˜μ§€λ§Œ, 2νšŒκ°€ λ˜λŠ” μƒν™©μ΄μ—ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό μ›ν•˜λŠ” λ°©μ‹λŒ€λ‘œ λ™μž‘μ‹œν‚€κΈ° μœ„ν•΄ 비관적 락을 μ μš©ν•΄λ³΄κΈ°λ‘œ ν–ˆμŠ΅λ‹ˆλ‹€.

 

 

πŸ‘» JPA λ™μ‹œμ„± μ œμ–΄ 방식

 

JPA λŠ” 엔티티에 λŒ€ν•œ 무결성을 μœ μ§€ν•  수 μžˆλ„λ‘ λ™μ‹œμ„± μ œμ–΄ λ©”μ»€λ‹ˆμ¦˜μ„ μ§€μ›ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

이 λ©”μ»€λ‹ˆμ¦˜μ—λŠ” 낙관적 락과 비관적 락이 ν¬ν•¨λ©λ‹ˆλ‹€.

 

 

⛓️ λ‚™κ΄€μ  락 (Optimistic Lock)

 

낙관적 락은 DB κ°€ μ œκ³΅ν•˜λŠ” 락 κΈ°λŠ₯을 ν™œμš©ν•˜μ§€ μ•Šκ³  JPA κ°€ μ œκ³΅ν•˜λŠ” @Version μ–΄λ…Έν…Œμ΄μ…˜μ„ ν™œμš©ν•΄μ„œ μ—”ν‹°ν‹°μ˜ 버전을 κ΄€λ¦¬ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€.

 

@Entity
public class SearchHit {
	@Id
	private Long id;

	private String name;

	private Integer count; // 쑰회 횟수

	@Version
	private Integer version; // 버전
}

 

λ‹€μŒκ³Ό 같이 버전 κ΄€λ¦¬μš© ν•„λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλŠ”λ°, ν•΄λ‹Ή μ—”ν‹°ν‹°κ°€ 변경될 λ•Œλ§ˆλ‹€ version 값이 μ¦κ°€ν•˜κ²Œ λ©λ‹ˆλ‹€.

그리고 μ—”ν‹°ν‹°λ₯Ό μˆ˜μ •ν•  λ•Œ, μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•œ μ‹œμ μ˜ 버전 κ°’κ³Ό μˆ˜μ •ν•œ μ‹œμ μ˜ 버전 값이 μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ μ˜ˆμ™Έκ°€ λ°œμƒν•˜κ²Œ λ©λ‹ˆλ‹€.

 

낙관적 락을 μ μš©ν•΄μ„œ μœ„μ—μ„œ μ–ΈκΈ‰ν–ˆλ˜ 문제 상황을 ν•΄κ²°ν•  수 μžˆμ„κΉŒ?

 

A 의 쑰회 νšŸμˆ˜κ°€ 1일 λ•Œ 버전이 1이라고 κ°€μ •ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. λ™μ‹œμ— μ„Έλ²ˆμ˜ μš”μ²­μ΄ λ“€μ–΄μ™”κ³  각각의 νŠΈλžœμž­μ…˜μ„ t1, t2, t3 라고 ν•˜λ©΄ λͺ¨λ‘ 쑰회 μ‹œμ μ˜ 버전은 1μž…λ‹ˆλ‹€. 이 λ•Œ t1 이 κ°€μž₯ λ¨Όμ € μ—”ν‹°ν‹°λ₯Ό λ³€κ²½ν•˜κ³  컀밋을 μ™„λ£Œν–ˆλ‹€κ³  ν•˜λ©΄ A 의 쑰회 νšŸμˆ˜λŠ” 2, 버전은 2둜 λ³€κ²½λ©λ‹ˆλ‹€. λ‚˜μ€‘μ— t2 와 t3 λͺ¨λ‘ 쑰회 횟수 2 둜 μ»€λ°‹ν•˜λ € ν•˜μ§€λ§Œ, 버전이 2둜 λ³€κ²½λ˜μ–΄ μ‘°νšŒν•œ μ‹œμ μ˜ 버전 κ°’κ³Ό μΌμΉ˜ν•˜μ§€ μ•Šμ•„ μ˜ˆμ™Έκ°€ λ°œμƒν•©λ‹ˆλ‹€. λ”°λΌμ„œ 졜초 μ»€λ°‹λ§Œ μΈμ •λ˜μ–΄ μ‘°νšŒμˆ˜λŠ” 4κ°€ μ•„λ‹Œ 2둜 μ—…λ°μ΄νŠΈλ˜μ–΄ μ›ν•˜λŠ” λ°©μ‹λŒ€λ‘œ 문제λ₯Ό ν•΄κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

 

 

⛓️ λΉ„관적 락 (Pessimistic Lock)

 

비관적 락은 DB κ°€ μ œκ³΅ν•˜λŠ” 락 κΈ°λŠ₯을 μ‚¬μš©ν•©λ‹ˆλ‹€. @Lock μ–΄λ…Έν…Œμ΄μ…˜μ„ ν™œμš©ν•΄μ„œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

public interface SearchHitRepository extends JpaRepository<SearchHit, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select h from SearchHit h where h.id = :id")
    SearchHit findByIdForUpdate(Long id);
    
}

 

Querydsl 을 ν™œμš©ν•˜κ³  μžˆλ‹€λ©΄ λ‹€μŒκ³Ό 같이 μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

public class SearchHitRepositoryImpl implements SearchHitRepositoryCustom {

	private final JPAQueryFactory queryFactory;

	@Override
	public SearchHit findByIdForUpdate(Long id) {
		return queryFactory
			.selectFrom(searchHit)
			.where(searchHit.id.eq(id))
			.setLockMode(LockModeType.PESSIMISTIC_WRITE)
			.fetchOne();
	}
}

 

μœ„μ™€ 같이 LockModeType 을 PESSIMISTIC_WRITE 둜 μ„€μ •ν•˜κ²Œ 되면 λ°μ΄ν„°λ² μ΄μŠ€μ— select for update 쿼리가 λ‚˜κ°€κ²Œ 되고 μ‘°νšŒν•œ λ ˆμ½”λ“œ μžμ²΄μ— 락을 걸게 λ©λ‹ˆλ‹€.

 

μ‹€μ œ μ½”λ“œμ— μ μš©ν•œ μ˜ˆμ‹œ

 

이제 λ‹€μ‹œ 문제 상황에 λŒ€ν•΄ μƒκ°ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. A 의 쑰회 νšŸμˆ˜κ°€ 1일 λ•Œ μš”μ²­μ΄ λ™μ‹œμ— 3번 λ“€μ–΄μ˜€λ©΄ κ°€μž₯ λ¨Όμ € A λ₯Ό μ‘°νšŒν•œ νŠΈλžœμž­μ…˜ t1 이 A 에 λŒ€ν•œ 락을 νšλ“ν•˜κ²Œ λ©λ‹ˆλ‹€. λ‹€λ₯Έ νŠΈλžœμž­μ…˜ t2, t3 λŠ” t1 이 락을 ν•΄μ œν•  λ•ŒκΉŒμ§€ λŒ€κΈ°ν•˜κ²Œ 되고, t1 이 A 의 쑰회 횟수λ₯Ό 2둜 μ—…λ°μ΄νŠΈ ν•œ 후에 락을 ν•΄μ œν•˜λ©΄ t2 κ°€ 락을 νšλ“ν•˜μ—¬ A 의 쑰회 횟수λ₯Ό 3으둜 μ—…λ°μ΄νŠΈ, λ§ˆμ§€λ§‰μœΌλ‘œ t3 κ°€ 락을 νšλ“ν•˜μ—¬ 쑰회 횟수λ₯Ό 4둜 μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€. μ›ν•˜λŠ” λ°©μ‹λŒ€λ‘œ 문제λ₯Ό ν•΄κ²°ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

 

❗️ κ³ λ €ν•  점

 

비관적 락을 μ μš©ν•΄μ„œ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό 얻을 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 비관적 락을 μ μš©ν•˜κ²Œ 되면 νŠΈλžœμž­μ…˜μ΄ νŠΉμ • 데이터에 λŒ€ν•œ 락을 ν•΄μ œν•˜κΈ° μ „κΉŒμ§€ λ‹€λ₯Έ νŠΈλžœμž­μ…˜λ“€μ€ λŒ€κΈ°ν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— μ„±λŠ₯이 μ €ν•˜λ  수 μžˆλ‹€λŠ” 문제점이 μ‘΄μž¬ν•©λ‹ˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— λ°μ΄ν„°μ˜ μ •ν™•μ„±κ³Ό μ„±λŠ₯ μ €ν•˜λΌλŠ” νŠΈλ ˆμ΄λ“œ μ˜€ν”„λ₯Ό κ³ λ €ν•΄μ„œ 문제 ν•΄κ²° λ°©μ•ˆμ„ 잘 선택해야 ν•©λ‹ˆλ‹€.