Troubleshooting:Lock Time Out - takeoff-26/logistics-service GitHub Wiki

Postgres Lock Timeout

๋ฌธ์ œ ์ธ์‹

์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์ค‘ DB์— ๋ถ€ํ•˜๊ฐ€ ๋งŽ์•„ ์ฒ˜๋ฆฌ๊ฐ€ ์ง€์—ฐ๋  ๋•Œ timeOut ์—์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ์ ์—์„œ ์ธ์‹


๋ฌธ์ œ ํ…Œ์ŠคํŠธ

ํƒ€์ž„์•„์›ƒ ์ œํ•œ - 1000ms ํŠธ๋žœ์žญ์…˜ ๋‚ด ์˜๋„์ ์œผ๋กœ 3000ms์œผ๋กœ ์„ค์ •ํ•ด ํ™•์ธ

@Lock(LockModeType.PESSIMISTIC_WRITE)
	@QueryHints({
		@QueryHint(name = "jakarta.persistence.lock.timeout", value = "1000"),
		@QueryHint(name = "org.hibernate.dialect.lock.timeout", value = "1000")
	})
	@Query("SELECT s FROM Stock s WHERE s.id = :id AND s.deletedAt is null")
	Optional<Stock> findByIdWithLock(@Param("id") StockId id);
	@Override
	@Transactional
	public void prepareStock(PrepareStockRequestDto requestDto) {
		try {
			getSortedStocks(requestDto.stocks())
				.forEach(stockItem ->
					getStockWithLock(stockItem.stockId()).decreaseStock(stockItem.quantity()));
			sleep(3000);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}
Caused by: org.hibernate.PessimisticLockException: JDBC exception executing SQL [select s1_0.hub_id,s1_0.product_id,s1_0.created_at,s1_0.created_by,s1_0.deleted_at,s1_0.deleted_by,s1_0.quantity,s1_0.updated_at,s1_0.updated_by from p_stock s1_0 where (s1_0.hub_id,s1_0.product_id)=(?,?) and s1_0.deleted_at is null for update] [Timeout trying to lock table "P_STOCK"; SQL statement:
select s1_0.hub_id,s1_0.product_id,s1_0.created_at,s1_0.created_by,s1_0.deleted_at,s1_0.deleted_by,s1_0.quantity,s1_0.updated_at,s1_0.updated_by from p_stock s1_0 where (s1_0.hub_id,s1_0.product_id)=(?,?) and s1_0.deleted_at is null for update [50200-232]] [n/a]
	at org.hibernate.dialect.H2Dialect.lambda$buildSQLExceptionConversionDelegate$3(H2Dialect.java:771) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:58) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]

image

ํƒ€์ž„ ์•„์›ƒ ์˜ˆ์™ธ์‚ฌ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜๊ณ  ํ…Œ์ŠคํŠธ ์‹œ๊ฐ„์ด 30์ดˆ๊ฐ€ ๊ฑธ๋ฆฐ ๊ฒƒ์„ ํ™•์ธ.


๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•œ ์ ‘๊ทผ

(https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#locking-jpa-query-hints)

javax.persistence.lock.timeout์ด๋‚˜ jakarta.persistence.lock.timeout ์„ค์ •์„ ํ–ˆ๋”๋ผ๋„, ์‚ฌ์šฉ ์ค‘์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ JDBC ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์ด ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฉด ์„ค์ •์ด ์ ์šฉ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, MySQL, Oracle ๋“ฑ์˜ ๋“œ๋ผ์ด๋ฒ„๋Š” ์ด ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜์ง€๋งŒ, ์ผ๋ถ€ ๋‹ค๋ฅธ ๋“œ๋ผ์ด๋ฒ„๋Š” ์ง€์›ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.


ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

๋„ค์ดํ‹ฐ๋ธŒ ์ฟผ๋ฆฌ๋ฅผ aop๋กœ ์ ์šฉํ•ด ํ•ด๊ฒฐ

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LockTimeout {
	int timeout() default 3000;
}

1. ์–ด๋…ธํ…Œ์ด์…˜ ์ •์˜ ํ›„ acvice ์ •์˜

public interface LockManager {

	void setLockTimeout(int timeout);
}

2.ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์ธ h2์™€ ์‹ค์ œ db์ธ postgresql์€ ๋ฌธ๋ฒ•์ด ๋‹ฌ๋ผ interface๋กœ ์ •์˜

h2 ๊ตฌํ˜„์ฒด
@Slf4j
@Profile("Test")
@Component
@RequiredArgsConstructor
public class H2LockManager implements LockManager {

	@Override
	public void setLockTimeout(int timeout) {
		log.info("ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฝ ํƒ€์ž„์•„์›ƒ ์„ค์ •์ด ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค: {}ms", timeout);
	}
}
postgres ๊ตฌํ˜„์ฒด
@Profile("!Test")
@Component
@RequiredArgsConstructor
public class PostgresLockManager implements LockManager {

	private final EntityManager em;

	public void setLockTimeout(int timeout) {
		Query query = em.createNativeQuery("SET LOCAL lock_timeout = '" + timeout + "ms'");
		query.executeUpdate();
	}

}

3.Aspect ์ •์˜

@Aspect 
@Component
@RequiredArgsConstructor
public class LockTimeoutAspect {

	private final LockManager lockManager;

	@Before("@annotation(takeoff.logistics_service.msa.product.stock.infrastructure.persistence"
		+ ".aspect.LockTimeout)")
	public void beforeLockTimeout(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		LockTimeout annotation = signature.getMethod().getAnnotation(LockTimeout.class);
		lockManager.setLockTimeout(annotation.timeout());
	}
}

4.์‹คํ–‰ํ•  ๋ฉ”์„œ๋“œ์— ์–ด๋…ธํ…Œ์ด์…˜ ์ถ”๊ฐ€

	@LockTimeout(timeout = 1000)
	@Lock(LockModeType.PESSIMISTIC_WRITE)
	@Query("SELECT s FROM Stock s WHERE s.id = :id AND s.deletedAt is null")
	Optional<Stock> findByIdWithLock(@Param("id") StockId id);

5. ํ•ด๊ฒฐ ๊ฒฐ๊ณผ

findByIdWithLock ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด, @LockTimeout ์–ด๋…ธํ…Œ์ด์…˜ ๋•Œ๋ฌธ์— AOP๊ฐ€ ๋™์ž‘ํ•จ
AOP๋Š” LockTimeoutAspect์˜ beforeLockTimeout ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•จ
์ด ๋ฉ”์„œ๋“œ๋Š” LockManager.setLockTimeout()์„ ํ˜ธ์ถœํ•จ
PostgreSQL ํ™˜๊ฒฝ์—์„œ๋Š” PostgresLockManager์˜ ๊ตฌํ˜„์ด ์‚ฌ์šฉ๋˜๊ณ ,
์ด ๊ตฌํ˜„์€ em.createNativeQuery("SET LOCAL lock_timeout = '1000ms'")๋ฅผ ์‹คํ–‰ํ•จ
๊ทธ ํ›„์— ์‹ค์ œ ์ฟผ๋ฆฌ(@Query("SELECT s FROM Stock s WHERE s.id = :id AND s.deletedAt is null"))๊ฐ€ ์‹คํ–‰๋˜๋Š”๋ฐ, ์ด ์ฟผ๋ฆฌ๋Š” @Lock(LockModeType.PESSIMISTIC_WRITE) ๋•Œ๋ฌธ์— for update ๋˜๋Š” for no key update ๊ตฌ๋ฌธ์ด ์ถ”๊ฐ€๋จ

โš ๏ธ **GitHub.com Fallback** โš ๏ธ