ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SpringBoot] ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ž (Synchronized, MySQL, Redis)
    SpringBoot 2024. 1. 10. 21:23

    ๐Ÿ’ญ ๋“ค์–ด๊ฐ€๋ฉฐ

     

    ์ง„ํ–‰ํ•˜๋˜ ํ”„๋กœ์ ํŠธ์—์„œ ์œ„์™€ ๊ฐ™์ด ์‘์› ํšŸ์ˆ˜๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ํด๋ฆญํ•˜๋ฉด ์ด์— ๋งž๊ฒŒ ํšŸ์ˆ˜๊ฐ€ ์ฆ๊ฐ€๋˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด์•ผ ํ–ˆ๋‹ค.

    ํ•œ ๋ช…์˜ ์‚ฌ์šฉ์ž๋Š” ์—ฌ๋Ÿฌ๋ฒˆ ์‘์› ํšŸ์ˆ˜๋ฅผ ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

    ์ด์™€ ๊ด€๋ จํ•ด ์–ด๋–ป๊ฒŒ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ์‹œ์ž‘ํ–ˆ๋‹ค.

    โœ… ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ž€?

    • ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ์ƒํ™ฉ

    pk๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ์˜ ์‘์› ํšŸ์ˆ˜๊ฐ€ 90์ธ ์ƒํƒœ์—์„œ ์‚ฌ์šฉ์ž A ๊ฐ€ 3๋ฒˆ ์‘์› ํšŸ์ˆ˜๋ฅผ ์ฆ๊ฐ€์‹œํ‚ค๊ณ  ์‚ฌ์šฉ์ž B ๊ฐ€ 10๋ฒˆ ์‘์› ํšŸ์ˆ˜๋ฅผ ์ฆ๊ฐ€์‹œ์ผฐ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

    ์‚ฌ์šฉ์ž A ์‘์› ํšŸ์ˆ˜ ์‚ฌ์šฉ์ž B
    pk๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๋Š”๋‹ค >> ์•„ ์‘์› ํšŸ์ˆ˜๋Š” 90์ด๊ตฌ๋‚˜! 90ํšŒ  
    pk ๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ์˜ ํšŸ์ˆ˜๋ฅผ 3ํšŒ ์ฆ๊ฐ€์‹œํ‚จ๋‹ค. 93ํšŒ  
      93ํšŒ pk๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๋Š”๋‹ค >> ์•„ ์‘์› ํšŸ์ˆ˜๋Š” 93์ด๊ตฌ๋‚˜!
      103ํšŒ pk ๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ์˜ ํšŸ์ˆ˜๋ฅผ 10ํšŒ ์ฆ๊ฐ€์‹œํ‚จ๋‹ค.

    ์œ„์ฒ˜๋Ÿผ ์ •์ƒ์ ์œผ๋กœ ์š”์ฒญ์ด ๋ฐ˜์˜๋˜์–ด ๊ฐ๊ฐ 3ํšŒ, 10ํšŒ๋ฅผ ๋”ํ•œ 103ํšŒ๊ฐ€ ๊ฒฐ๊ณผ๋กœ ๋‚˜์˜ฌ ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ•œ๋‹ค.

     

    • ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ
    ์‚ฌ์šฉ์ž A ์‘์› ํšŸ์ˆ˜ ์‚ฌ์šฉ์ž B
    pk๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๋Š”๋‹ค >> ์•„ ์‘์› ํšŸ์ˆ˜๋Š” 90์ด๊ตฌ๋‚˜! 90ํšŒ  
      90ํšŒ pk๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๋Š”๋‹ค >> ์•„ ์‘์› ํšŸ์ˆ˜๋Š” 90์ด๊ตฌ๋‚˜!
    pk ๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ์˜ ํšŸ์ˆ˜๋ฅผ 3ํšŒ ์ฆ๊ฐ€์‹œํ‚จ๋‹ค. 93ํšŒ  
        pk ๊ฐ€ 1์ธ ์‘์› ๋ฐ์ดํ„ฐ์˜ ํšŸ์ˆ˜๋ฅผ 10ํšŒ ์ฆ๊ฐ€์‹œํ‚จ๋‹ค.
      100ํšŒ  

    ๊ทธ๋Ÿฌ๋‚˜, ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๊ณ ๋ ค๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์œ„์ฒ˜๋Ÿผ ์ผ๋ถ€ ํšŸ์ˆ˜๊ฐ€ ๋ˆ„๋ฝ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•œ๋‹ค.

    โœ… ๋™์‹œ์„ฑ ๋ฌธ์ œ์˜ ์›์ธ - Race Condition

    • Race condition ์ด๋ž€?

    ์ด๋Š” ๋‘๊ฐœ ์ด์ƒ์˜ ์“ฐ๋ ˆ๋“œ๊ฐ€ ๊ณต์œ  ๋ฐ์ดํ„ฐ์— ์•ก์„ธ์Šค๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋™์‹œ์— ๋ณ€๊ฒฝ์„ ํ•˜๊ณ ์ž ํ•˜๋ฉด ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ด๋‹ค.

    Race Condition ์— ์˜ํ•ด์„œ ๋ฐ์ดํ„ฐ์˜ ๊ฒฐ๊ณผ๋Š” ๊ณต์šฉ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ ‘๊ทผ์— ๋Œ€ํ•œ ์ˆœ์„œ์— ์˜ํ•ด ๋‹ฌ๋ผ์ง€๊ฒŒ ๋œ๋‹ค.

     

    ์œ„์˜ ์˜ˆ์‹œ๋กœ ๋‹ค๋ฅด๊ฒŒ ์ด์•ผ๊ธฐํ•˜์ž๋ฉด, ์‚ฌ์šฉ์ž A ์™€ ์‚ฌ์šฉ์ž B ๊ฐ€ ์ˆœ์„œ๋Œ€๋กœ ์ ‘๊ทผํ•˜๊ฒŒ ๋˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ˜์˜๋˜์ง€๋งŒ ๋™์‹œ์— ์ ‘๊ทผํ•˜๊ฒŒ ๋˜๋ฉด ์ž˜๋ชป๋œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋Š” ๊ฒƒ์ด๋‹ค.

    โŒ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ์ฝ”๋“œ

    • request body
    // ๊ธฐ์กด์˜ request body
    {
    	gameTeamId:1,
        cheerCount:101
    }

     

    • Repository ์ฝ”๋“œ
    @Modifying
    @Query("UPDATE GameTeam t SET t.cheerCount = :cheerCount WHERE t.id = :gameTeamId")
    void updateCheerCount(@Param("gameTeamId") Long gameTeamId, @Param("cheerCount") int cheerCount);
    • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
    @Test
    void ๋™์‹œ์—_์‘์›_์š”์ฒญ์„_๋ณด๋‚ผ_๊ฒฝ์šฐ์—๋„_์ •์ƒ์ ์œผ๋กœ_์š”์ฒญ์ด_๋ฐ˜์˜๋œ๋‹ค() throws InterruptedException {
        // given
        ExecutorService executor = Executors.newFixedThreadPool(100);
        CountDownLatch latch = new CountDownLatch(100);
    
        long gameTeamId = 3L;
    
        // when
        for (int i = 0; i < 100; i++) {
            final int j = i;
            executor.execute(() -> {
                try {
                    gameTeamService.updateCheerCount(2L, new GameTeamCheerRequestDto(gameTeamId, j + 1));
                } finally {
                    latch.countDown();
                }
            });
        }
    
        latch.await();
        executor.shutdown();
    
        // then
        assertThat(gameTeamFixtureRepository.findById(gameTeamId))
                .map(GameTeam::getCheerCount)
                .get()
                .isEqualTo(101);
    }

    ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋˜ ์ฝ”๋“œ๋Š” ์œ„์™€ ๊ฐ™์•˜๋‹ค.

    ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๊ธฐ์กด์˜ ํšŸ์ˆ˜์— ์ฆ๊ฐ€์‹œํ‚ฌ ํšŸ์ˆ˜๋ฅผ ๋”ํ•ด์„œ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ์ด๋ฅผ ๋ฐ˜์˜ํ•˜๋„๋ก ํ–ˆ์—ˆ๋‹ค.

    ์ฆ‰, ๊ธฐ์กด ํšŸ์ˆ˜๊ฐ€ 100ํšŒ์ด๊ณ  ์ฆ๊ฐ€์‹œํ‚ฌ ํšŸ์ˆ˜๊ฐ€ 1์ด๋ผ๋ฉด ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ 101์„ ์š”์ฒญ์œผ๋กœ ๋ณด๋ƒˆ๋˜ ๊ฒƒ์ด๋‹ค.

     

    ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

    ๊ธฐ๋Œ€ํ–ˆ๋˜ ํšŸ์ˆ˜๋Š” 1์—์„œ 100๋ฒˆ ์ฆ๊ฐ€์‹œํ‚จ 101 ํšŒ์˜€๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜, ์‹ค์ œ ๋ฐ˜์˜๋œ ๋ฐ์ดํ„ฐ๋Š” 81ํšŒ์ž„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

    ๐Ÿ‘€ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ–ˆ์„๊นŒ

    • request body
    // ๊ธฐ์กด์˜ request body
    {
    	gameTeamId:1,
        cheerCount:101
    } 
    
    // ์ˆ˜์ •๋œ request body
    {
    	gameTeamId:1,
        cheerCount:1
    }

    ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ง์ ‘ ๊ธฐ์กด์˜ ํšŸ์ˆ˜์— ์ƒˆ๋กœ ์ฆ๊ฐ€ํ•  ํšŸ์ˆ˜๋ฅผ ๋”ํ•ด์„œ ์š”์ฒญ์„ ๋ณด๋ƒˆ๋˜ ๊ฒƒ์—์„œ, ์ฆ๊ฐ€ํ•  ํšŸ์ˆ˜๋งŒ์„ ์š”์ฒญ์œผ๋กœ ๋ณด๋‚ด๋„๋ก ์ˆ˜์ •ํ–ˆ๋‹ค.

    ๊ธฐ์กด์˜ request body ์—๋Š” ๊ธฐ์กด์˜ ํšŸ์ˆ˜๊ฐ€ 100ํšŒ์ด๊ณ  ์ฆ๊ฐ€์‹œํ‚ฌ ํšŸ์ˆ˜๊ฐ€ 1์ด๋ผ๋ฉด 101์„ ๋ณด๋ƒˆ์ง€๋งŒ, ์ˆ˜์ •๋œ ๋ฐฉ์‹์—์„œ์˜ request body ์—๋Š” 1๋งŒ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๋‹ค.

    @Modifying
    @Query("UPDATE GameTeam t SET t.cheerCount = t.cheerCount + :cheerCount WHERE t.id = :gameTeamId")
    void updateCheerCount(@Param("gameTeamId") Long gameTeamId, @Param("cheerCount") int cheerCount);

    ์œ„์˜ ์ฟผ๋ฆฌ๋ฅผ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ, ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ ๊ทธ ์‹œ์ ์—์„œ์˜ ์‘์›ํšŸ์ˆ˜์— ์ƒˆ๋กœ์šด cheerCount ๋ฅผ ๋”ํ•œ๋‹ค.

     

    ์ด๋ ‡๊ฒŒ ์ˆ˜์ •ํ•˜๋‹ˆ, ์–ธ์ œ ์“ฐ๋ ˆ๋“œ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์—ˆ๋Š”์ง€์™€๋Š” ๊ด€๊ณ„๊ฐ€ ์—†์–ด์ง€๋ฏ€๋กœ ์ฆ‰, race condition ๋ฌธ์ œ๊ฐ€ ์ž์—ฐ์Šค๋ ˆ ํ•ด๊ฒฐ๋˜์–ด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

    ๐Ÿ‘€ ์•„์‰ฌ์› ๋˜ ์ 

    ์‚ฌ์‹ค ์œ„์˜ ๋ฐฉ์‹์€, ํ•จ๊ป˜ ํ”„๋กœ์ ํŠธ์— ์ฐธ์—ฌํ•˜๊ณ  ์žˆ๋Š” ๋™๋ฃŒ๋ถ„๊ป˜ ์ถ”์ฒœ์„ ๋ฐ›์•„ ๋ฐ˜์˜ํ–ˆ๋˜ ๋ฐฉ๋ฒ•์ด์—ˆ๋‹ค.

    ๋”ฐ๋ผ์„œ ๋™์‹œ์„ฑ ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ๋ฐฉ์•ˆ์ด ์–ด๋–ค ๊ฒƒ๋“ค์ด ๋” ์žˆ์„์ง€์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•˜๊ณ , ํ•™์Šตํ•˜์—ฌ ์ถ”ํ›„์— ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•ด๋ณด๋ฉด ์ข‹์„์ง€์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค.

     

    ๐Ÿ“ ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

    ๋‹ค์Œ ๊ฐ•์˜๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค :)

    https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard

     

    ์žฌ๊ณ ์‹œ์Šคํ…œ์œผ๋กœ ์•Œ์•„๋ณด๋Š” ๋™์‹œ์„ฑ์ด์Šˆ ํ•ด๊ฒฐ๋ฐฉ๋ฒ• ๊ฐ•์˜ - ์ธํ”„๋Ÿฐ

    ๋™์‹œ์„ฑ ์ด์Šˆ๋ž€ ๋ฌด์—‡์ธ์ง€ ์•Œ์•„๋ณด๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค., ๋™์‹œ์„ฑ ์ด์Šˆ ์ฒ˜๋ฆฌ๋„ ์ž์‹ ์žˆ๊ฒŒ! ๊ฐ„๋‹จํ•œ ์žฌ๊ณ  ์‹œ์Šคํ…œ์œผ๋กœ ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐฐ์›Œ๋ณด์„ธ์š”. ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๊ผญ ์•Œ์•„์•ผ ํ•  ๋™์‹œ์„ฑ ์ด์Šˆ

    www.inflearn.com

    ๐Ÿ’ฅ Synchronized

    โœ”๏ธ synchronized๋ž€?

    ์—ฌ๋Ÿฌ ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๋Š” ์Šค๋ ˆ๋“œ ๊ฐ„์˜ ๋™๊ธฐํ™”๊ฐ€ ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

    ๋”ฐ๋ผ์„œ synchronized ๋Š” thread-safe ๋ฅผ ์œ„ํ•ด์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ํ•œ๊ฐœ์˜ ์ž์›์„ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•  ๋•Œ, ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์Šค๋ ˆ๋“œ๋ฅผ ์ œ์™ธํ•˜๊ณ ๋Š” ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋„๋ก ๋ง‰๋Š” ๊ฒƒ์ด๋‹ค.

    ์ด๋Š” ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜์— ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.

    โœ”๏ธ ์‚ฌ์šฉ ์˜ˆ์‹œ

      @Transactional
        public synchronized void decrease(Long id, Long quantity) {
            Stock stock = stockRepository.findById(id).orElseThrow();
            stock.decrease(quantity);
            stockRepository.saveAndFlush(stock);
        }

    โœ”๏ธ ๋‹จ์ 

    1. @Transactional ์–ด๋…ธํ…Œ์ด์…˜์˜ ํ”„๋ก์‹œ ๊ฐ์ฒด

    synchronized ๋Š” ์Šคํ”„๋ง๋ถ€ํŠธ์˜ @Transactional ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์‹ค์ƒ ํšจ๋ ฅ์ด ์—†๋‹ค.

    ์™œ๋ƒํ•˜๋ฉด, @Transactional ์–ด๋…ธํ…Œ์ด์…˜์€ AOP ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ๋งŒ๋“ค์–ด์ง€๋ฉฐ ๋™์ž‘ํ•˜๋Š”๋ฐ ์ด ๋•Œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ๋ฉ”์„œ๋“œ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋“ค๋งŒ ๋ณต์‚ฌํ•ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— synchronized ๋Š” ๋ˆ„๋ฝ๋œ๋‹ค.

     

    2. ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค ์•ˆ์—์„œ๋งŒ ๋ณด์žฅ๋œ๋‹ค.

    ํ•œ ๋Œ€์˜ ์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ๋Š” synchornized ํ‚ค์›Œ๋“œ๊ฐ€ ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜, ์„œ๋ฒ„๊ฐ€ ๋‘๋Œ€ ์ด์ƒ์ธ ๊ฒฝ์šฐ์—๋Š” ํ”„๋กœ์„ธ์Šค ๋‚ด๋ถ€์—์„œ์˜ ์ ‘๊ทผ๋งŒ์„ ๋ง‰๋Š” synchronized ๊ฐ€ ์˜๋ฏธ๊ฐ€ ์—†์–ด์ง€๊ฒŒ ๋œ๋‹ค.

    ๋”ฐ๋ผ์„œ ์ด๋Š” ์‹ค๋ฌด์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค.

    ๐Ÿ’ฅ MySQL ์˜ Pessimistic Lock (๋น„๊ด€์  ๋ฝ)

    ๋น„๊ด€์  ๋ฝ๋„ ๋ฝ์ด๋‹ค์˜น

    โœ”๏ธ Pessimistic Lock ์ด๋ž€?

    ์ด๋Š” ๋ฐ์ดํ„ฐ์— ๋ฝ์„ ๊ฑธ์–ด์„œ ์ •ํ•ฉ์„ฑ์„ ๋งž์ถ”๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

    ์ด๋Š” ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ ์‹œ์— ๋ฝ์„ ๊ฑธ๊ณ  ์‹œ์ž‘ํ•œ๋‹ค.

    ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” ๋ฝ์ด ํ•ด์ œ๋˜๊ธฐ ์ด์ „์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์—†๋‹ค.

    ์ด์™€ ๊ฐ™์ด, ํŠธ๋žœ์žญ์…˜ 1์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ ‘๊ทผํ•˜๊ฒŒ ๋˜๋ฉด ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์€ ๋Œ€๊ธฐํ•ด์•ผ ํ•œ๋‹ค.

    ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์œ„์—์„œ ์„ค๋ช…ํ•œ Race Condition ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฝ์— ์˜ํ•ด ๋™์‹œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณต์œ ๋˜์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

    โœ”๏ธ ์‚ฌ์šฉ ์˜ˆ์‹œ

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select s from Stock s where s.id = :id")
    Stock findByIdWithPessimisticLock(Long id);

    โœ”๏ธ ์ฃผ์˜ํ•  ์ 

    ๋ฐ๋“œ๋ฝ์„ ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.

    ๐Ÿ’ฅ MySQL ์˜ Optimistic Lock (๋‚™๊ด€์  ๋ฝ)

    โœ”๏ธ Optimistic Lock ์ด๋ž€?

    ์‹ค์ œ๋กœ ๋ฝ์„ ๊ฑฐ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ฒ„์ €๋‹์„ ์ด์šฉํ•ด ํ•ด๊ฒฐํ•œ๋‹ค.

    ํŠธ๋žœ์žญ์…˜ 1์ด ์ปค๋ฐ‹ํ•  ๋•Œ์˜ ์ฟผ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ๊ฒƒ์ด๋‹ค.

    update set version = version+1, quantity = quantity-10
    from stock
    where version = 1 and id = 1

    ํŠธ๋žœ์žญ์…˜ 2๊ฐ€ ์ปค๋ฐ‹ํ•  ๋•Œ์˜ ์ฟผ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ๊ฒƒ์ด๋‹ค.

    update set version = version+1, quantity = quantity-5
    from stock
    where version = 1 and id = 1

    ๊ทธ๋Ÿฌ๋‚˜, ์•ž์„  ํŠธ๋žœ์žญ์…˜ 1์˜ ์ปค๋ฐ‹์œผ๋กœ ์ธํ•ด version ์€ 1์—์„œ 2๋กœ ์ฆ๊ฐ€ํ–ˆ๋‹ค.

    ๋”ฐ๋ผ์„œ ํŠธ๋žœ์žญ์…˜ 2๊ฐ€ ์—…๋ฐ์ดํŠธํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฒ„์ „์ด 1์ธ ๋™์‹œ์— pk ๊ฐ€ 1์ธ ๋ฐ์ดํ„ฐ๋Š” ๋” ์ด์ƒ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.

    ์ด ๊ฒฝ์šฐ์— ์กฐํšŒ๋ฅผ ๋‹ค์‹œ ํ•˜๊ณ , ๊ทธ ์ดํ›„์— ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค.

    โœ”๏ธ ์‚ฌ์šฉ ์˜ˆ์‹œ

    @Entity
    public class Stock {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Version
        private Long version;
        
        // ์ค‘๋žต ..
    }
    @Lock(LockModeType.OPTIMISTIC)
    @Query("select s from Stock s where s.id = :id")
    Stock findByIdWithOptimisticLock(Long id);
    @Transactional
    public void decrease(Long id, Long quantity) {
        Stock stock = stockRepository.findByIdWithPessimisticLock(id);
        stock.decrease(quantity);
        stockRepository.save(stock);
    
    }
    @Component
    public class OptimisticLockStockFacade {
    
        public void decrease(Long id, Long quantity) throws InterruptedException {
            // ์—…๋ฐ์ดํŠธ ์‹คํŒจ ์‹œ์˜ ์žฌ์‹œ๋„
            while (true) {
                try {
                    optimisticLockStockService.decrease(id, quantity);
                    break;
                } catch (Exception e) {
                    Thread.sleep(50);
                }
            }
    
        }
    }

     

    ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ํŠธ๋žœ์žญ์…˜ 2์˜ ๊ฒฝ์šฐ์ฒ˜๋Ÿผ, ์กฐํšŒํ–ˆ๋˜ ๋‹น์‹œ์˜ version ๊ณผ ์—…๋ฐ์ดํŠธํ•˜๊ณ ์ž ํ•  ๋•Œ์˜ version ์ด ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๋‹ค์‹œ ์กฐํšŒ๊ฐ€ ์ด๋ค„์ง€๋Š” ๋กœ์ง์ด ํ•„์š”ํ•˜๋‹ค.

    ๋”ฐ๋ผ์„œ OptimisticLockStockFacade ์—์„œ ๊ฐ์†Œ ๋กœ์ง์„ ์‹œ๋„ํ•˜๊ณ , ์ด๊ฐ€ ์„ฑ๊ณตํ•˜๊ธฐ๊นŒ์ง€ ๋ฐ˜๋ณต์ ์œผ๋กœ ์‹œ๋„ํ•œ๋‹ค.

    โœ”๏ธ ์žฅ์ 

    1. ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ๋ฝ์„ ๊ฑธ์ง€ ์•Š๋Š”๋‹ค. 

    ์ปค๋ฐ‹๋˜๋Š” ์‹œ์  (์—…๋ฐ์ดํŠธ ๋˜๋Š” ์‹œ์ )์—๋งŒ ๋ฝ์ด ๊ฑธ๋ฆฌ๊ณ  ์ด์ „๊นŒ์ง€๋Š” ์•„๋ฌด๋Ÿฐ ์กฐ์น˜๋ฅผ ์ทจํ•˜์ง€ ์•Š๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค.

     

    2. ๋น„๊ด€์  ๋ฝ์— ๋น„ํ•ด ์„ฑ๋Šฅ์ƒ์˜ ์ด์ ์„ ์ง€๋‹Œ๋‹ค.

    ๋น„๊ด€์  ๋ฝ์€ ๋‹จ์ˆœํžˆ ์กฐํšŒํ•˜๊ณ ์ž ํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์˜ ์ ‘๊ทผ๋งˆ์ € ๋ง‰์ง€๋งŒ, ๋‚™๊ด€์  ๋ฝ์€ ์ปค๋ฐ‹ ์ด์ „๊นŒ์ง€๋Š” ๋ฝ์„ ๊ฑธ์ง€ ์•Š๊ธฐ์— ๋‹จ์ˆœ ์กฐํšŒํ•˜๊ณ ์ž ํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์˜ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•œ๋‹ค.

    โœ”๏ธ ๋‹จ์ 

    1. ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„์— ์†Œ์š”๋˜๋Š” ์‹œ๊ฐ„์ด ์กด์žฌํ•œ๋‹ค.

    2. ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ ๋กœ์ง์„ ์ง์ ‘ ์ž‘์„ฑํ•ด์ค˜์•ผ ํ•œ๋‹ค. (OptimisticLockStockFacade์˜ decrease)

    ๐Ÿ’ฅ MySQL ์˜ Named Lock

    โœ”๏ธ Named Lock ์ด๋ž€?

    ์ด๋ฆ„์„ ๊ฐ€์ง„ ๋ฝ์ด๋‹ค.

    ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ๋ฐ์ดํ„ฐ์˜ pk ๋ฅผ ๋ฝ์˜ ์ด๋ฆ„์œผ๋กœ ์ง€์ •ํ•œ๋‹ค๋ฉด ํ•ด๋‹น pk ๋ฅผ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๊ณ ์ž ํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์€ ํ•ด๋‹น pk ๋ฅผ ์ด๋ฆ„์œผ๋กœ ํ•˜๋Š” ๋ฝ์ด ๊ฑธ๋ ค์žˆ๋Š”์ง€, ๊ฑธ๋ ค์žˆ์ง€ ์•Š์€์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ  ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

    ์—ฌ๊ธฐ์„œ Lock ์€ entity ๊ฐ€ ์•„๋‹Œ ๋ณ„๋„์˜ ๊ณต๊ฐ„์— ์ €์žฅ๋œ๋‹ค. ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๊ฐ€ ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋™์•ˆ์—๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฝ์„ ๋ณ„๋„์˜ ์ด๋ฆ„์ด๋‚˜ ์‹๋ณ„์ž๋กœ ์„ค์ •ํ•œ๋‹ค.

    โœ”๏ธ ์‚ฌ์šฉ ์˜ˆ์‹œ

    @Component
    public class NamedLockStockFacade {
    
       @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void decrease(Long id, Long quantity) {
            Stock stock = stockRepository.findById(id).orElseThrow();
    
            stock.decrease(quantity);
    
            stockRepository.saveAndFlush(stock);
        }
    
    }
    public interface LockRepository extends JpaRepository<Stock, Long> {
    
        @Query(value = "select get_lock(:key, 30000)", nativeQuery = true)
        void getLock(String key);
    
        @Query(value = "select release_lock(:key, 30000)", nativeQuery = true)
        void releaseLock(String key);
    
    }

    Named Lock ์€ ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ์— ์ž๋™์œผ๋กœ ๋ฝ์ด ํ•ด์ œ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„์˜ ๋ช…๋ น์–ด๋กœ ์ง์ ‘ ํ•ด์ œํ•ด์ค˜์•ผ ํ•œ๋‹ค.

    ๐Ÿ’ก@Transactional(propagation = Propagation.REQUIRES_NEW)

    ์œ„์˜ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด, @Transactional(propagation = Propagation.REQUIRES_NEW) ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ด ๋ณด์ธ๋‹ค.

    ์ด๋Š” ํ•ญ์ƒ decrease ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ ํ•  ๋•Œ๋Š” ์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์ด ์‹คํ–‰๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

    ์ด๋Š” ์ƒ์œ„์˜ (์ด์ „์˜) ํŠธ๋žœ์žญ์…˜๊ณผ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ์˜ ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ฆ‰, ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์ด์—ˆ๋˜ ๊ฒƒ์ด ์„œ๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

    ์™œ ๋‹ค๋ฅธ ๋ฝ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์•˜๋˜ ๋ฝ์ด Named Lock ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋งŒ ํ•„์š”ํ• ๊นŒ?

    • ๋น„๊ด€์  ๋ฝ : ๋น„๊ด€์  ๋ฝ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ ์‹œ์ž‘ํ•  ๋•Œ๋ถ€ํ„ฐ ๋ฌด์กฐ๊ฑด ๋ฝ์„ ๊ฑฐ๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
      • ์ฆ‰, ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ๋™์‹œ์— ์ ‘๊ทผํ•˜๋”๋ผ๋„ ์ด๋ฏธ ๋ฝ์ด ๊ฑธ๋ ค ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒƒ์ด๋‹ค.
    • ๋‚™๊ด€์  ๋ฝ : ๋‚™๊ด€์  ๋ฝ์€ ์—ฌ๋Ÿฌ ํŠธ๋žœ์žญ์…˜์ด ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋”๋ผ๋„, ์ถฉ๋Œ์€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ์ ์—๋งŒ ๋ฐœ์ƒํ•œ๋‹ค.
      • ๋‚™๊ด€์  ๋ฝ์€ ๋™์‹œ์— ์—ฌ๋Ÿฌ ํŠธ๋žœ์žญ์…˜์ด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ๊ฒƒ์„ ๋ง‰์ง€ ์•Š๋Š”๋‹ค.
      • ์ถฉ๋Œ์€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ์ ์—๋งŒ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ๊ตณ์ด ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
    • Named Lock : Named Lock ์€ ์ „์—ญ์ ์œผ๋กœ ๋ฝ์„ ๊ด€๋ฆฌํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์—ฌ๋Ÿฌ ํŠธ๋žœ์žญ์…˜์ด ํ•˜๋‚˜์˜ ๋ฝ์„ ์ฝ๊ณ ์ž ํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ์— ํŠธ๋žœ์žญ์…˜์€ ๋ถ„๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค.

    โœ”๏ธ ์žฅ์ 

    - ํƒ€์ž„์•„์›ƒ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

    -- 'my_named_lock'์ด๋ผ๋Š” ์ด๋ฆ„์˜ ๋ฝ์„ ์–ป๊ธฐ ์œ„ํ•ด ์ตœ๋Œ€ 10์ดˆ๊นŒ์ง€ ๋Œ€๊ธฐ
    SELECT GET_LOCK('my_named_lock', 10);

    ์œ„์™€ ๊ฐ™์ด 10์ดˆ๋ฅผ ์„ค์ •ํ•ด๋‘๋ฉด, ๋ฝ์„ 10์ดˆ๊ฐ„ ์–ป์ง€ ๋ชปํ•˜๋ฉด ํƒ€์ž„์•„์›ƒ์ด ๋˜๋Š” ๊ธฐ๋Šฅ์ด MySQL ์—๋Š” ๋‚ด์žฅ๋˜์–ด ์žˆ๋‹ค.

    โœ”๏ธ ๋‹จ์ 

    - ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ์— ๋ฝ์ด ํ•ด์ œ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„์˜ ๋ช…๋ น์–ด๋กœ ํ•ด์ œํ•ด์ค˜์•ผ ํ•œ๋‹ค.

    - ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•  ์‹œ์—๋Š” ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

    ๐Ÿ’ฅ Redis ์˜ Lettuce

    โœ”๏ธ Redis ์˜ Lettuce ๋ฐฉ์‹์ด๋ž€?

    ์ถœ์ฒ˜ :&nbsp;https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard

    ์œ„์™€ ๊ฐ™์ด, ์“ฐ๋ ˆ๋“œ 1์ด key ๊ฐ€ 1์ธ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด lock ์„ ์–ป๊ณ ์ž ํ•œ๋‹ค๋ฉด ํ˜„์žฌ ๊ฑธ๋ ค ์žˆ๋Š” lock ์ด ์—†๊ธฐ์— ์ด๋ฅผ ํš๋“ํ•˜๊ฒŒ ๋˜๊ณ  ์„ฑ๊ณต์ด ๋ฐ˜ํ™˜๋œ๋‹ค. ์“ฐ๋ ˆ๋“œ 2 ์—ญ์‹œ๋„ key ๊ฐ€ 1์ธ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด lock ์„ ์–ป๊ณ ์ž ํ•œ๋‹ค๋ฉด ์ด๋ฏธ ๊ฑธ๋ ค ์žˆ๋Š” lock ์œผ๋กœ ์ธํ•ด ์‹คํŒจ๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.

    ์ด๋•Œ, ๋ฝ ํš๋“์— ์‹คํŒจํ•œ ์“ฐ๋ ˆ๋“œ์˜ ๊ฒฝ์šฐ์—๋Š” spin lock ๋ฐฉ์‹์œผ๋กœ ๋Œ€๊ธฐํ•œ๋‹ค. ์ด๋Š” ๊ณ„์† ๋Œ๊ณ  ๋Œ๋ฉฐ ๋ฝ์„ ๋Œ€๊ธฐํ•˜๋‹ค๊ฐ€ ํš๋“ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

     

    ๐Ÿ’กsetnx

    redis ์˜ ๋ช…๋ น์–ด ์ค‘ ํ•˜๋‚˜๋กœ, "SET if Not eXists" ์˜ ์•ฝ์ž์ด๋‹ค.

    ์ด๋Š” ์ฃผ์–ด์ง„ ํ‚ค์— ๋Œ€ํ•œ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š”๋ฐ, ๋งŒ์•ฝ ํ•ด๋‹น ํ‚ค๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•œ๋‹ค๋ฉด ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์ง€๋งŒ ํ‚ค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ง€์ •๋œ ๊ฐ’์œผ๋กœ ํ‚ค์™€ ๊ฐ’์„ ์„ค์ •ํ•œ๋‹ค.

     

    redis์˜ cli ๋ฅผ ์ด์šฉํ•ด ํ™•์ธํ•ด๋ณด์ž!

    โฏ docker exec -it 043d5ca6952f redis-cli
    127.0.0.1:6379> setnx 1 value
    (integer) 1 
    127.0.0.1:6379> setnx 1 value
    (integer) 0
    127.0.0.1:6379> del 1
    (integer) 1
    127.0.0.1:6379> setnx 1 value
    (integer) 1
    127.0.0.1:6379>

    - setnx 1 value ๋ฅผ ํ•˜๋‹ˆ, ๊ธฐ์กด์— 1์ด ์—†๊ธฐ์— ์„ฑ๊ณต (1 ๋ฐ˜ํ™˜)

    - ์ดํ›„์— setnx 1 value ๋ฅผ ํ•˜๋‹ˆ, ๊ธฐ์กด์— 1์ด ์žˆ์œผ๋ฏ€๋กœ ์‹คํŒจ (0 ๋ฐ˜ํ™˜)

    - ์ดํ›„์— del 1 (key 1์„ ์‚ญ์ œ) ⇒ ์„ฑ๊ณต (1 ๋ฐ˜ํ™˜)

    - ๋‹ค์‹œ setnx 1 value ๋ฅผ ํ•˜๋‹ˆ, 1 ์ด ์‚ญ์ œ๋œ ์ดํ›„์— ์‹œ๋„ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ ์„ฑ๊ณต (1 ๋ฐ˜ํ™˜)

     

    โœ”๏ธ ์‚ฌ์šฉ ์˜ˆ์‹œ

    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    @Component
    public class RedisLockRepository {
    
        public Boolean lock(Long key) {
            return redisTemplate
                    .opsForValue()
                    // key ๋Š” stock ์˜ id
                    // value ๋Š” "lock" ์ด๋ผ๋Š” ๋ฌธ์ž์—ด
                    .setIfAbsent(generateKey(key), "lock", Duration.ofMillis(3_000));
        }
    
        public Boolean unlock(Long key) {
            return redisTemplate
                    .delete(generateKey(key));
        }
    
        public String generateKey(Long key) {
            return key.toString();
        }
    }
    @Component
    public class LettuceLockStockFacade {
    
        public void decrease(Long id, Long quantity) throws InterruptedException {
    
            // ๋ฝ์ด ํš๋“ํ•  ๋•Œ๊นŒ์ง€ ๊ณ„์†ํ•ด์„œ ์‹œ๋„๋ฅผ ํ•˜๊ณ , ์‹คํŒจํ•˜๋ฉด ์“ฐ๋ ˆ๋“œ๋ฅผ 100 ๋ฐ€๋ฆฌ์„ธ์ปจ๋™์•ˆ ์ž ์žฌ์šด๋‹ค.
            while (!repository.lock(id)) {
                Thread.sleep(100);
            }
    
            try {
                stockService.decrease(id, quantity);
            } finally {
                repository.unlock(id);
            }
        }
    }

    ์•ž์„œ ์„ค๋ช…ํ–ˆ๋˜ ๊ฒƒ๊ณผ ๊ฐ™์ด, Lettuce ๋ฐฉ์‹์—์„œ๋Š” spin lock ๋ฐฉ์‹์œผ๋กœ ๋ฝ์„ ์–ป๊ธฐ๋ฅผ ๋Œ€๊ธฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด์— ๋Œ€ํ•œ ๋กœ์ง์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ž‘์„ฑํ•ด์ค˜์•ผ ํ•œ๋‹ค.

    while ๋ฌธ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ, Lock ์„ ํš๋“ํ•˜๋ฉด StockService์˜ decrease ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ํ•ด๋‹น lock ์„ ํ•ด์ œํ•œ๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜ Lock ์„ ํš๋“ํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์“ฐ๋ ˆ๋“œ๋ฅผ 100 ms ์ •๋„ ์ค‘์ง€ํ–ˆ๋‹ค๊ฐ€, ๊ณ„์†ํ•ด์„œ ์‹œ๋„๋ฅผ ํ•œ๋‹ค.

    โœ”๏ธ ์žฅ์ 

    - ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๋‹ค.

    - named lock ๊ณผ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜์ง€๋งŒ, ์„ธ์…˜ ๊ด€๋ฆฌ๋ฅผ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

    - ๊ธฐ๋ณธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค.

    โœ”๏ธ ๋‹จ์ 

    - spin lock ๋ฐฉ์‹์ด๊ธฐ์— ์“ฐ๋ ˆ๋“œ๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ๋Œ€๊ธฐ ์ค‘์ด๋ผ๋ฉด ๋ ˆ๋””์Šค์— ๋ถ€ํ•˜๊ฐ€ ๊ฐˆ ์ˆ˜๋„ ์žˆ๋‹ค.

    ๐Ÿ’ฅ Redis ์˜ Redisson

    โœ”๏ธ Redis ์˜ Redisson ๋ฐฉ์‹์ด๋ž€?

    ์ถœ์ฒ˜ :&nbsp;https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard

    Redisson ๋ฐฉ์‹์€ pub-sub ๊ตฌ์กฐ์ด๋‹ค. ๋”ฐ๋ผ์„œ ๋Œ€๊ธฐ ์ค‘์ธ ์“ฐ๋ ˆ๋“œ๋“ค์ด ๊ณ„์†ํ•ด์„œ ๋ฝ ํš๋“์„ ์‹œ๋„ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

    ์™œ๋ƒํ•˜๋ฉด, ํš๋“์„ ์‹œ๋„ํ–ˆ๋˜ ๋ฝ์ด ํ•ด์ œ๋˜์—ˆ์„ ๋•Œ ๋ฝ ํš๋“์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

     

    redis-cli ๋กœ ์‚ดํŽด๋ณด์ž.

    127.0.0.1:6379> subscribe ch1
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "ch1"
    3) (integer) 1
    1) "message"
    2) "ch1"
    3) "hello"
    127.0.0.1:6379> publish ch1 hello
    (integer) 1

    ์œ„์™€ ๊ฐ™์ด, ch1 ๋ฅผ subscribe ํ•ด๋‘๊ณ  ๋‹ค๋ฅธ ์ชฝ์—์„œ ch1 ์— publish ๋ฅผ ํ•˜๋ฉด subscribe ๋ฅผ ํ•ด๋‘” ์ชฝ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

    โœ”๏ธ ์‚ฌ์šฉ ์˜ˆ์‹œ

    implementation group: 'org.redisson', name: 'redisson-spring-boot-starter', version: '3.25.2'

     

    @Component
    public class RedissonLockStockFacade {
    
        public void decrease(Long id, Long quantity) {
            RLock lock = redissonClient.getLock(id.toString());
    
            try {
                boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS);
    
                if (!available) {
                    System.out.println("lock ํš๋“ ์‹คํŒจ");
                    return;
                }
    
                stockService.decrease(id, quantity);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    
    }

     

    Redisson ๋ฐฉ์‹์—์„œ๋Š” ๋ฝ ํ•ด์ œ์™€ ํš๋“์„ ์œ„ํ•œ ์ฝ”๋“œ๋ฅผ ๋”ฐ๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ , ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋‚ด์žฅ๋œ ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.

    ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ฝ ํš๋“์„ ์š”์ฒญํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋„ ํ•„์š” ์—†๋‹ค.

    โœ”๏ธ ์žฅ์ 

    - ๋ฝ ํš๋“ / ์žฌ์‹œ๋„๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•œ๋‹ค.

    - pub / sub ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ถ€ํ•˜๊ฐ€ ๋œ ๊ฐ„๋‹ค.

     

    โœ”๏ธ ๋‹จ์ 

    - ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

    - ๋ฝ์„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฐจ์›์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด์— ๋Œ€ํ•œ ๋Ÿฌ๋‹ ์ปค๋ธŒ๊ฐ€ ์กด์žฌํ•œ๋‹ค.


    ์žฌ์‹œ๋„๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฉด lettuce, ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฉด redisson ์„ ์ฃผ๋กœ ์ด์šฉํ•œ๋‹ค.

     

    ๐Ÿ“š ํ˜„์žฌ์˜ ๋ฐฉ์‹๊ณผ ๋น„๊ตํ•ด๋ณด์ž!

    @Modifying
    @Query("UPDATE GameTeam t SET t.cheerCount = t.cheerCount + :cheerCount WHERE t.id = :gameTeamId")
    void updateCheerCount(@Param("gameTeamId") Long gameTeamId, @Param("cheerCount") int cheerCount);

    ํ˜„์žฌ๋Š” ์œ„์™€ ๊ฐ™์ด update ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

    ์ด๋Š”, ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ ํ•œ๋ฒˆ ๋” ์‘์› ํšŸ์ˆ˜๋ฅผ ์กฐํšŒํ•˜๊ณ  update ๋ฅผ ํ•  ๋•Œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•ด๋‹น row ์— ๋ฝ์ด ๊ฑธ๋ฆฌ๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

    ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค๊ณผ ๋น„๊ตํ•ด๋ณด์ž!

     

    1. update ์ฟผ๋ฆฌ vs ๋น„๊ด€์  ๋ฝ

    ๋น„๊ด€์  ๋ฝ์„ ์ด์šฉํ•˜๋ฉด ํ•ด๋‹น row ์ „์ฒด์— ๋ฝ์ด ๊ฑธ๋ฆฌ๊ฒŒ ๋œ๋‹ค.

    ์ด๋กœ ์ธํ•ด ๋‹ค๋ฅธ ์ชฝ์—์„œ ํ•ด๋‹น row ๋ฅผ ๋‹จ์ˆœ ์กฐํšŒ ์ด์™ธ์˜ ์ˆ˜์ •์ด๋‚˜, ๋ฝ์„ ๊ฑธ๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์— ๋Œ€๊ธฐ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค. 

    ๋”ฐ๋ผ์„œ ๋น„๊ต์  ์ข๊ฒŒ update ์‹œ์—๋งŒ ๋ฝ์ด ๊ฑธ๋ฆฌ๋Š” ํ˜„์žฌ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋‚ซ๋‹ค.

     

    2. update ์ฟผ๋ฆฌ vs ๋‚™๊ด€์  ๋ฝ

    ๋‘˜ ๋‹ค update ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๋ฏ€๋กœ ์‚ฌ์‹ค์ƒ ์ˆ˜์ •ํ•˜๋Š” ์‹œ์ ์— ๋ฝ์ด ๊ฑธ๋ฆฌ๋Š” ๊ฒƒ์€ ๊ฐ™๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜, ๋‚™๊ด€์  ๋ฝ์˜ ๊ฒฝ์šฐ์—๋Š” ์‹คํŒจ ์‹œ์— ์žฌ์‹œ๋„ ๋กœ์ง๊ณผ ๋ฒ„์ „์— ๋Œ€ํ•œ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

    ์‹คํŒจ ์‹œ์— ์žฌ์‹œ๋„ ๋กœ์ง์ด ํ•„์š”ํ•˜๋‹ค๋Š” ์ ์—์„œ ๋น„๊ต์  ๋ฝ์ด ์ž์ฃผ ๊ฑธ๋ฆฌ์ง€ ์•Š์„ ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€๋˜๋Š” ๊ฒฝ์šฐ์— ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•œ๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜, ์‘์› ํšŸ์ˆ˜์˜ ๊ฒฝ์šฐ์—๋Š” ๊ฒŒ์ž„ ์ง„ํ–‰ ์ค‘์—๋Š” ๋Š˜ ๋ฝ์ด ๊ฑธ๋ฆด ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•ด์•ผ ํ•˜๊ธฐ์— ํ˜„์žฌ์˜ update ์ฟผ๋ฆฌ๊ฐ€ ๋‚ซ๋‹ค.

     

    3. update ์ฟผ๋ฆฌ vs named lock

    ์šฐ์„ , named lock์„ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ณ„๋„์˜ ๋ฆฌ์†Œ์Šค๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

    ๋˜ํ•œ, ์ด๋Š” ๋ฝ์˜ ์ด๋ฆ„์„ ์ด์šฉํ•ด์„œ ํŠน์ • ์กฐ๊ฑด์— ๋งž๊ฒŒ ์„ธ๋ฐ€ํ•œ ๋ฝ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜, ํ˜„์žฌ๋Š” ์„ธ๋ฐ€ํ•œ ์กฐ์ •์ด ํ•„์š”ํ•˜์ง€๋„ ์•Š์„ ๋ฟ๋”๋Ÿฌ ๋ฝ์˜ ๋ฒ”์œ„๊ฐ€ ์ปค์งˆ ๊ฒฝ์šฐ๋„ ์—†๊ธฐ์— named lock ์„ ๋„์ž…ํ•˜๋Š” ๋งˆ๋•…ํ•œ ์ด์œ ๊ฐ€ ์—†๋‹ค.

     

    4. update ์ฟผ๋ฆฌ vs ๋ ˆ๋””์Šค

    ํ˜„์žฌ ์‘์› ํšŸ์ˆ˜์˜ ๊ฒฝ์šฐ์—๋Š” ์น˜๋ช…์ ์ธ ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์ด ์•„๋‹ˆ๋‹ค.

    ํšŸ์ˆ˜ ๋ช‡ ๋ฒˆ์ด ๋ˆ„๋ฝ๋œ๋‹ค๊ณ  ํ•ด์„œ ์‚ฌ์šฉ์ž์˜ ๊ฒฝํ—˜์ด๋‚˜, ๋ฐ์ดํ„ฐ ์ƒ์˜ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค.

    ๋‹จ์ˆœ ์—”ํ„ฐํ…Œ์ธ ์š”์†Œ์— ๊ฐ€๊น๊ธฐ ๋•Œ๋ฌธ์— ์‘์› ํšŸ์ˆ˜์˜ ๋™์‹œ์„ฑ ์ด์Šˆ๋ฅผ ์œ„ํ•ด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํƒ์„ ๋„์ž…ํ•˜๋Š” ๊ฒƒ์€ ๋ฌด๋ฆฌ๋ผ๊ณ  ํŒ๋‹จ๋œ๋‹ค.

     

    ๐Ÿ’ฌ ์ฐธ๊ณ  ๋งํฌ

    https://seunghyunson.tistory.com/11

     

    [ACID #2] Atomicity๋ž€? ์•„ํ† ๋ฏนํ•œ ํŠธ๋žœ์žญ์…˜์ด๋ž€?

    โœ”๏ธ Atomicity๋ž€? ๐Ÿ’ก Atomicity๋Š” RDBMS๋ฅผ ์ •์˜ํ•˜๋Š” ACID ํŠธ๋žœ์žญ์…˜ ํŠน์„ฑ ์ค‘ A์— ํ•ด๋‹นํ•˜๋Š” ํŠน์„ฑ์ž…๋‹ˆ๋‹ค. ํ•œ๊ธ€๋กœ ์ง์—ญํ•˜๋ฉด ์›์ž์„ฑ์ด๋ผ๋Š” ๋œป์„ ๊ฐ€์ง€๋ฉฐ ์™œ ํŠธ๋žœ์žญ์…˜์—์„œ Atomicity๋ž€ ํŠน์„ฑ์ด ์ค‘์š”ํ•œ์ง€์— ๋Œ€ํ•ด ์•Œ

    seunghyunson.tistory.com

    https://zzang9ha.tistory.com/443

     

    ์ข‹์•„์š” ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์‚ดํŽด๋ณด๋Š” ๋™์‹œ์„ฑ ์ด์Šˆ (synchronized)

    ์•ˆ๋…•ํ•˜์„ธ์š”, ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ๋™์‹œ์„ฑ(Concurrency)์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. (์˜ˆ์ œ ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.) ๋™์‹œ์„ฑ(Concurrency) ๊ฐœ๋… ๋„ค์ด๋ฒ„ ์‚ฌ์ „์— ๊ฒ€์ƒ‰ํ•ด๋ณธ ๋™์‹œ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™

    zzang9ha.tistory.com

    https://sabarada.tistory.com/175

     

    [database] ๋‚™๊ด€์  ๋ฝ(Optimistic Lock)๊ณผ ๋น„๊ด€์  ๋ฝ(Pessimistic Lock)

    ์•ˆ๋…•ํ•˜์„ธ์š”. ์˜ค๋Š˜์€ ๋‚™๊ด€์  ๋ฝ๊ณผ ๋น„๊ด€์  ๋ฝ์˜ ๊ฐœ๋…์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์ ธ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. DB ์ถฉ๋Œ ์ƒํ™ฉ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• database์— ์ ‘๊ทผํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ ๋™์‹œ์— ์ˆ˜์ •์ด

    sabarada.tistory.com

     

Designed by Tistory.