1. ๊ฐ์
์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค์์ ์ฌ์ฉ์๊ฐ ๋์์ ๋์ผํ ๋ฐ์ดํฐ(์: ์๊ฐ๊ถ ์์ฌ ํ์, ์ํ ์ฌ๊ณ , ์ข์ ์์ฝ)๋ฅผ ์์ ํ๋ ค๊ณ ํ ๋, ๊ฐฑ์ ์์ค(Lost Update) ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๋์์ฑ ๋ฌธ์ ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ๋ฒจ์์ ๊ฐ๋ ฅํ๊ฒ ์ ์ดํ๋ ๋ฐฉ๋ฒ ์ค ํ๋๊ฐ ๋ฐ๋ก **๋น๊ด์ ๋ฝ(Pessimistic Lock)**์ ๋๋ค.
2. ๋น๊ด์ ๋ฝ(Pessimistic Lock)์ด๋?
์ด๋ฆ์์ ์ ์ ์๋ฏ์ด, ๋น๊ด์ ๋ฝ์ **โํธ๋์ญ์ ๊ฐ์ ๋ฐ๋์ ์ถฉ๋์ด ๋ฐ์ํ ๊ฒ์ด๋คโ**๋ผ๊ณ ๋น๊ด์ ์ผ๋ก ๊ฐ์ ํ๊ณ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ์๊ฐ๋ถํฐ DB ์์ฒด์ ๋ฝ์ ๊ฑธ์ด๋ฒ๋ฆฌ๋ ๋ฐฉ์์ ๋๋ค.
RDBMS์ SELECT ... FOR UPDATE ์ฟผ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ **๋ฐฐํ ๋ฝ(Exclusive Lock, X-Lock)**์ ํ๋ํฉ๋๋ค. ๋ฝ์ด ๊ฑธ๋ฆฐ ๋ ์ฝ๋๋ ๋ฝ์ ํ๋ํ ํธ๋์ญ์
์ด ์ปค๋ฐ(Commit)๋๊ฑฐ๋ ๋กค๋ฐฑ(Rollback)๋ ๋๊น์ง ๋ค๋ฅธ ํธ๋์ญ์
์ด ์์ ํ ์ ์์ผ๋ฉฐ, ์ฌ์ง์ด ๋ค๋ฅธ ํธ๋์ญ์
์ด ํด๋น ๋ ์ฝ๋์ ๋ํด ๋น๊ด์ ๋ฝ์ ํ๋ํ๋ ค๊ณ ํ๋ฉด ๋ฝ์ด ํ๋ฆด ๋๊น์ง **๋๊ธฐ(Blocking)**ํ๊ฒ ๋ฉ๋๋ค.
2-1. ๋๊ด์ ๋ฝ(Optimistic Lock)๊ณผ์ ์ฐจ์ด
- ๋๊ด์ ๋ฝ: โ์ถฉ๋์ด ๊ฑฐ์ ๋ฐ์ํ์ง ์์ ๊ฒโ์ด๋ผ ๊ฐ์ ํ๊ณ ๋ฝ์ ๊ฑธ์ง ์์ต๋๋ค. ๋์
@Version์ปฌ๋ผ ๋ฑ์ ๋์ด ์ปค๋ฐ ์์ ์ ๋ฒ์ ์ด ์ผ์นํ๋์ง ํ์ธํฉ๋๋ค. ์ถฉ๋์ด ์ฆ์ผ๋ฉด ๋กค๋ฐฑ๊ณผ ์ฌ์๋ ๋น์ฉ์ด ์ปค์ง๋๋ค. - ๋น๊ด์ ๋ฝ: ์ถฉ๋์ด ์ฆ์ ๊ฒ์ผ๋ก ์์๋ ๋ ์ฌ์ฉํฉ๋๋ค. ๋ฌด์กฐ๊ฑด ๋ฝ์ ๊ฑธ๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ์๋ฒฝํ ๋ณด์ฅ๋๋ ๋๊ธฐ ์๊ฐ์ผ๋ก ์ธํด ์ฑ๋ฅ ์ ํ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
3. ๋์ ๋ฐฉ์ ์๋๋ฆฌ์ค
์ฝ์ํธ ํฐ์ผ ์๋งค๋ ์ผํฐ ์ค์ผ์ค ์์ฝ์ ์๊ฐํด๋ณด๊ฒ ์ต๋๋ค. ์์ฌ ์ข์์ด 1๊ฐ ๋จ์์ ๋ ์ฌ์ฉ์ A์ B๊ฐ ๋์์ ์์ฝ์ ์๋ํฉ๋๋ค.
sequenceDiagram participant TxA as ํธ๋์ญ์ A participant TxB as ํธ๋์ญ์ B participant DB as Database (์์ฌ: 1) TxA->>DB: SELECT FOR UPDATE (์์ฌ: 1 ์ฝ์, ๋ฝ ํ๋) TxB->>DB: SELECT FOR UPDATE (TxA๊ฐ ๋ฝ์ ํด์ ํ ๋๊น์ง ๋๊ธฐ โณ) Note over TxA: ์์ฌ = ์์ฌ - 1 (0์ผ๋ก ์ฐ์ฐ) TxA->>DB: UPDATE ์์ฌ = 0 TxA->>DB: COMMIT (๋ฝ ํด์ ) Note over TxB: ๋๊ธฐ ํด์ ๋ฐ ์ต์ ์ํ(์์ฌ: 0) ์ฝ์ TxB->>DB: ๋ฝ ํ๋ Note over TxB: ์์ฌ ์ข์์ด 0์ด๋ฏ๋ก ๋น์ฆ๋์ค ์์ธ ๋ฐ์ (์์ฝ ์คํจ) TxB->>DB: ROLLBACK
๋น๊ด์ ๋ฝ ๋๋ถ์ ๋์์ ์ ๊ทผํ๋๋ผ๋ ์์ฐจ์ ์ผ๋ก(์ง๋ ฌํ๋์ด) ์ฒ๋ฆฌ๋๋ฏ๋ก, ์ด๊ณผ ์์ฝ์ด๋ผ๋ ์น๋ช ์ ์ธ ๋ฒ๊ทธ๋ฅผ ์๋ฒฝํ๊ฒ ์ฐจ๋จํ ์ ์์ต๋๋ค.
4. Spring Data JPA์์์ ํ์ฉ
Spring Data JPA์์๋ @Lock ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ์ฌ ๋งค์ฐ ์ฝ๊ฒ ๋น๊ด์ ๋ฝ์ ์ ์ฉํ ์ ์์ต๋๋ค.
import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
public interface TicketRepository extends JpaRepository<TicketEntity, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT t FROM TicketEntity t WHERE t.uuid = :uuid")
Optional<TicketEntity> findByUuidWithPessimisticLock(@Param("uuid") String uuid);
}PESSIMISTIC_WRITE: ๋ฐฐํ ๋ฝ(Exclusive Lock)์ ๊ฒ๋๋ค. ๋ค๋ฅธ ํธ๋์ญ์ ์์ ์ฝ๊ธฐ/์ฐ๊ธฐ ๋ชจ๋ ๋๊ธฐํฉ๋๋ค. (์ผ๋ฐ์ ์ผ๋ก ๋์์ฑ ์ ์ด์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋จ)PESSIMISTIC_READ: ๊ณต์ ๋ฝ(Shared Lock)์ ๊ฒ๋๋ค. ๋ค๋ฅธ ํธ๋์ญ์ ์์ ์ฝ์ ์๋ ์์ง๋ง ์ธ ์๋ ์์ต๋๋ค. (DB ๋ฐฉ์ธ์ ๋ฐ๋ผ ๋์์ด ์กฐ๊ธ ๋ค๋ฅผ ์ ์์)
์ฃผ์์ฌํญ
๋น๊ด์ ๋ฝ์ ๋ฐ์ดํฐ๋ฅผ ํ์คํ๊ฒ ๋ณดํธํ์ง๋ง, ๋จ์ฉํ๋ฉด ๋ณ๋ชฉ ํ์์ด ์๊ฒจ ์์คํ ์ฒ๋ฆฌ๋(Throughput)์ด ํฌ๊ฒ ๋จ์ด์ง๊ณ **๋ฐ๋๋ฝ(Deadlock)**์ ์์ธ์ด ๋ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋ฝ์ ์ก๋ ํธ๋์ญ์ ์ ํฌ๊ธฐ๋ฅผ ์ต๋ํ ์๊ฒ ์ ์งํ๊ณ , ์ฌ๋ฌ ํ ์ด๋ธ์ ๋ฝ์ ๊ฑธ์ด์ผ ํ ๋๋ ๋ฐ๋์ ์ผ๊ด๋ ๋ฝ ํ๋ ์์๋ฅผ ์ ์งํ์ฌ ๋ฐ๋๋ฝ์ ์๋ฐฉํด์ผ ํฉ๋๋ค.