[๐Ÿ”ฅTroubleShooting - TicToc๐Ÿ”ฅ] ๊ฒฝ๋งค ๊ด€๋ฆฌ ์ค‘์— ์ž…์ฐฐ์ดโ‰๏ธ ๋™์‹œ์„ฑ ์ถฉ๋Œ์„ ๋ง‰์•„๋ผโ€ผ๏ธโ€ผ๏ธ

._mungยท2025๋…„ 3์›” 8์ผ
0

TicToc

๋ชฉ๋ก ๋ณด๊ธฐ
3/6

๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

ํŒ€์› : ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ
๊ธฐ๊ฐ„ : 2025.01 ~ ์ง„ํ–‰ ์ค‘
๋งํฌ : https://github.com/M-ung/TicToc_Server
์„œ๋น„์Šค ๋‚ด์šฉ : ๋‹น์‹ ์˜ ์‹œ๊ฐ„์— ๊ฐ€์น˜๋ฅผ ๋งค๊ธฐ๋‹ค, ์‹œ๊ฐ„ ๊ฑฐ๋ž˜ ๊ฒฝ๋งค ํ”Œ๋žซํผ


๐Ÿ”ฅTroubleShooting๐Ÿ”ฅ

Problems

๊ฒฝ๋งค(Auction)์˜ ํ˜„์žฌ ์ง„ํ–‰ ์ƒํƒœ๋ฅผ progress ๋ผ๋Š” ์นผ๋Ÿผ์„ ๋‘๊ณ  ๊ด€๋ฆฌ๋ฅผ ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.
์ด ์นผ๋Ÿผ์€ ์•„๋ž˜์™€ ๊ฐ™์€ ์ƒํƒœ๋ฅผ ๊ฐ€์ง„๋‹ค.

โ€ข NOT_STARTED : ์•„์ง ๊ฒฝ๋งค๊ฐ€ ์‹œ์ž‘๋˜์ง€ ์•Š์Œ
โ€ข IN_PROGRESS : ๊ฒฝ๋งค๊ฐ€ ์ง„ํ–‰ ์ค‘
โ€ข BID : ๊ฒฝ๋งค ์ข…๋ฃŒ ๋ฐ ์ž…์ฐฐ ์™„๋ฃŒ
โ€ข NOT_BID : ๊ฒฝ๋งค ์ข…๋ฃŒ ๋ฐ ์ž…์ฐฐ ์‹คํŒจ

๊ฒฝ๋งค ์ˆ˜์ •์™€ ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ๊ณ ๋ฏผ์ด ์ƒ๊ฒผ๋‹ค. ์ด๋ฏธ ๊ฒฝ๋งค๊ฐ€ ์‹œ์ž‘๋œ ์ƒํƒœ์—์„œ ์ˆ˜์ •/์‚ญ์ œ๋ฅผ ํ•˜๋Š” ๊ฑด ์ž˜๋ชป๋œ ๊ธฐํš์ด๋ผ๊ณ  ์ƒ๊ฐํ•ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •/์‚ญ์ œ ์ „์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๊ฒฝ๋งค ์ˆ˜์ •/์‚ญ์ œ ๊ณผ์ •์—์„œ ์ž…์ฐฐ์ด ๋“ค์–ด์˜ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ..?

๊ธฐํš์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ํ–ˆ๋‹ค.

1. ๊ฒฝ๋งค ์ˆ˜์ • ์ค‘ ์ž…์ฐฐ์ด ๋“ค์–ด์˜ค๋ฉด ์ž…์ฐฐ ์„ฑ๊ณต โœ…, ์ˆ˜์ • ์‹คํŒจ โŒ
2. ๊ฒฝ๋งค ์‚ญ์ œ ์ค‘ ์ž…์ฐฐ์ด ๋“ค์–ด์˜ค๋ฉด ์ž…์ฐฐ ์„ฑ๊ณต โœ…, ์ˆ˜์ • ์‹คํŒจ โŒ


How

์œ„ ๊ธฐํš๋Œ€๋กœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ํ•ด๊ฒฐ์ฑ…์„ ๊ณ ๋ คํ–ˆ๋‹ค.

์ˆ˜์ • ๋ฐ ์‚ญ์ œ ๋กœ์ง์ด ๊ฒฝ๋งค(Auction)์˜ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•ด์•ผ ํ•œ๋‹ค.

๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ "๋น„๊ด€์  ๋ฝ"์„ ์ ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.


Process

๋น„๊ด€์  ๋ฝ์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด delete ์™€ update ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ–ˆ๋‹ค.

๐Ÿ“ AuctionCommandService.java - update

๐Ÿ“ AuctionCommandService.java - delete

๊ทธ๋ฆฌ๊ณ  ์œ„ ๋ฉ”์„œ๋“œ ์•ˆ์— ์กด์žฌํ•˜๋Š” findAuctionByIdForUpdate ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋น„๊ด€์  ๋ฝ์„ ์ ์šฉํ•ด์„œ ๊ตฌํ˜„ํ–ˆ๋‹ค.

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT a FROM Auction a WHERE a.id = :auctionId and a.status = :status")
    Optional<Auction> findByIdAndStatusForUpdate(@Param("auctionId") Long auctionId, @Param("status") TicTocStatus status);

๋‚ด๊ฐ€ ์ƒ๊ฐํ•œ ๊ธฐํš ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋งž๊ฒŒ ์•„๋ž˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

๐Ÿ“ ๊ฒฝ๋งค ์ˆ˜์ • ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ

    @Test
    @DisplayName("๊ฒฝ๋งค ์ˆ˜์ • ์‹œ ๋™์‹œ์„ฑ ์ด์Šˆ ๋ฐœ์ƒ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ")
    public void ๊ฒฝ๋งค_์ˆ˜์ •_์‹œ_๋™์‹œ์„ฑ_์ด์Šˆ_๋ฐœ์ƒ์—_๋Œ€ํ•œ_ํ…Œ์ŠคํŠธ() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.submit(() -> {
            try {
                bidCommandUseCase.bid(2L, new BidUseCaseReqDTO.Bid(auction.getId(), BID_PRICE));
            } catch (Exception e) {
                assertThat(e.getMessage()).isEqualTo(ErrorCode.BID_FAIL.getMessage());
            } finally {
                latch.countDown();
            }
        });

        executorService.submit(() -> {
            try {
                AuctionUseCaseReqDTO.Update updateDTO = new AuctionUseCaseReqDTO.Update(
                        "Updated Auction Title",
                        "Updated Auction Description",
                        1200,
                        LocalDateTime.now().plusMinutes(15),
                        LocalDateTime.now().plusHours(1),
                        LocalDateTime.now().plusHours(2),
                        Collections.emptyList(),
                        AuctionType.ONLINE
                );
                auctionCommandUseCase.update(1L, auction.getId(), updateDTO);
            } catch (Exception e) {
                assertThat(e).isInstanceOf(ConflictAuctionUpdateException.class);
            } finally {
                latch.countDown();
            }
        });

        latch.await();
        executorService.shutdown();

        Auction afterAuction = auctionRepositoryPort.findAuctionById(auction.getId());
        assertThat(afterAuction.getTitle()).isEqualTo("Test Auction");
        assertThat(afterAuction.getProgress()).isEqualTo(AuctionProgress.IN_PROGRESS);
        assertThat(afterAuction.getCurrentPrice()).isEqualTo(BID_PRICE);
    }

๐Ÿ“ ๊ฒฝ๋งค ์‚ญ์ œ ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ

    @Test
    @DisplayName("๊ฒฝ๋งค ์‚ญ์ œ ์‹œ ๋™์‹œ์„ฑ ์ด์Šˆ ๋ฐœ์ƒ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ")
    public void ๊ฒฝ๋งค_์‚ญ์ œ_์‹œ_๋™์‹œ์„ฑ_์ด์Šˆ_๋ฐœ์ƒ์—_๋Œ€ํ•œ_ํ…Œ์ŠคํŠธ() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.submit(() -> {
            try {
                bidCommandUseCase.bid(2L, new BidUseCaseReqDTO.Bid(auction.getId(), BID_PRICE));
            } catch (Exception e) {
                assertThat(e.getMessage()).isEqualTo(ErrorCode.BID_FAIL.getMessage());
            } finally {
                latch.countDown();
            }
        });

        executorService.submit(() -> {
            try {
                auctionCommandUseCase.delete(1L, auction.getId());
            } catch (Exception e) {
                assertThat(e).isInstanceOf(ConflictAuctionDeleteException.class);
            } finally {
                latch.countDown();
            }
        });

        latch.await();
        executorService.shutdown();

        Auction deletedAuction = auctionRepositoryPort.findAuctionById(auction.getId());
        assertThat(deletedAuction.getStatus()).isEqualTo(TicTocStatus.ACTIVE);
        assertThat(deletedAuction.getProgress()).isEqualTo(AuctionProgress.IN_PROGRESS);
        assertThat(deletedAuction.getCurrentPrice()).isEqualTo(BID_PRICE);
    }

๊ทธ์น˜๋งŒ... ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ–ˆ๋‹ค..๐Ÿ˜ญ๐Ÿ˜ญ

์œ„ ์ฝ”๋“œ์— ๋Œ€ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์•„๋ž˜ ์‚ฌ์ง„๊ณผ ๊ฐ™๋‹ค.

๋จผ์ € ์ˆ˜์ •/์‚ญ์ œ ์‹œ ํ•ด๋‹น ๊ฒฝ๋งค๋ฅผ ์กฐํšŒํ•˜๊ณ  ์ด๋ฅผ Lock ํ•œ๋‹ค. ๊ทธ ํ›„ ์ž…์ฐฐ์„ ์‹œ๋„ํ•˜๊ฒŒ ๋˜๋ฉด ์ž ๊ฒจ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž…์ฐฐ์— ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค.

๊ทธ๋Ÿผ ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฅธ ๋ฐฉํ–ฅ์œผ๋กœ ์ƒ๊ฐํ•ด ๋ณด์•„์•ผ ํ•œ๋‹ค.

๋น„๊ด€์  ๋ฝ์ด ์•„๋‹Œ, ๋‚™๊ด€์  ๋ฝ์„ ์ ์šฉํ•ด ๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

๐Ÿ“ Auction.java

๋จผ์ € Auction ์—”ํ‹ฐํ‹ฐ์— @Verison ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

๊ทธ ํ›„ update, delete ๋ฉ”์„œ๋“œ์— ๋‚™๊ด€์  ๋ฝ ๋กœ์ง์„ ์ ์šฉํ•œ๋‹ค.

๐Ÿ“ AuctionCommandService.java - update

๐Ÿ“ AuctionCommandService.java - delete

๐Ÿ“ AuctionRepository.java - findByIdAndStatusForUpdate

์œ„ ๊ตฌํ˜„์˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.


Result

์œ„ ๊ตฌํ˜„ ๊ฒฐ๊ณผ, ์œ„์—์„œ ์‹คํŒจํ–ˆ๋˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
์ฆ‰, ๋ฌธ์ œ์—์„œ ์ œ๊ธฐํ•œ ๊ธฐํš๋Œ€๋กœ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค.

1. ๊ฒฝ๋งค ์ˆ˜์ • ์ค‘ ์ž…์ฐฐ์ด ๋“ค์–ด์˜ค๋ฉด ์ž…์ฐฐ ์„ฑ๊ณต โœ…, ์ˆ˜์ • ์‹คํŒจ โŒ
2. ๊ฒฝ๋งค ์‚ญ์ œ ์ค‘ ์ž…์ฐฐ์ด ๋“ค์–ด์˜ค๋ฉด ์ž…์ฐฐ ์„ฑ๊ณต โœ…, ์ˆ˜์ • ์‹คํŒจ โŒ


Thoughts

์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด ๋ฌด์กฐ๊ฑด ๋น„๊ด€์  ๋ฝ์ด ์ข‹๋‹ค! ๋‚™๊ด€์  ๋ฝ์ด ์ข‹๋‹ค! ๋ณด๋‹จ ์–ธ์ œ ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ํ’€์–ด๋‚˜๊ฐ€๋Š” ๊ฒŒ ๋งž๋Š”์ง€๋ฅผ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋‹จ์ˆœํžˆ ๋™์‹œ์„ฑ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ๋น„๊ด€์  ๋ฝ์„ ์ ์šฉํ•ด DB๋ฅผ ์ž ๊ถˆ๋ฒ„๋ฆฌ๋ ค ํ–ˆ์ง€๋งŒ, ์ด๋Š” ์ž…์ฐฐ ์š”์ฒญ์„ ๋ฐฉํ•ดํ•˜๋Š” ์ผ์ด ๋˜์–ด๋ฒ„๋ ธ๋‹ค.

๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋‚™๊ด€์  ๋ฝ, ๋น„๊ด€์  ๋ฝ, ๋ถ„์‚ฐ ๋ฝ, ๋“ฑ ์–ด๋–ค ์„ค๊ณ„๋ฅผ ์ ์šฉํ•˜๋“  ์ž˜ ์•Œ๊ณ  ์ƒํ™ฉ์— ๋งž๊ฒŒ ์“ฐ๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๋‹ค.


profile
๐Ÿ’ป ๐Ÿ’ป ๐Ÿ’ป

0๊ฐœ์˜ ๋Œ“๊ธ€

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด