ํ์ : ๊ฐ์ธ ํ๋ก์ ํธ
๊ธฐ๊ฐ : 2025.01 ~ ์งํ ์ค
๋งํฌ : https://github.com/M-ung/TicToc_Server
์๋น์ค ๋ด์ฉ : ๋น์ ์ ์๊ฐ์ ๊ฐ์น๋ฅผ ๋งค๊ธฐ๋ค, ์๊ฐ ๊ฑฐ๋ ๊ฒฝ๋งค ํ๋ซํผ
์ฐ๋ฆฌ ์๋น์ค "TicToc"์ ๊ฐ์ฅ ์ค์ํ ํต์ฌ ๊ธฐ๋ฅ์ ๋ํด์ ๊ณ ๋ฏผํ ์๊ฐ์ด๋ค. ๋ฐ๋ก "์
์ฐฐ ๊ธฐ๋ฅ" ์ด๋ค.
๋จ์ํ Bid ์ํฐํฐ๋ฅผ ์ ์ฅํด์ ์
์ฐฐ์ ํ๋ ๋ฐฉ์์ ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
์ฌ๊ธฐ์ ๋์์ฑ ๋ฌธ์ ๋?
๋์์ฑ ๋ฌธ์ ๋ ์ฌ๋ฌ ๊ฐ์ ํธ๋์ญ์ ์ด ๋์ผํ ๋ฐ์ดํฐ์ ๋์ ์ ๊ทผํ์ฌ ์๊ธฐ์น ์์ ๊ฒฐ๊ณผ๊ฐ ๋ฐ์ํ๋ ๋ฌธ์ ๋ฅผ ์๋ฏธํ๋ค.
์๋ฅผ ๋ค์ด ์ฐ๋ฆฌ TicToc ์๋น์ค์ ์ ์ฐฐ ๊ธฐ๋ฅ์ ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๊ฐ์ ๊ฒฝ๋งค์ ๋์์ ์ ์ฐฐํ ๋, ์ ์ฐฐ ๊ฒฐ๊ณผ๊ฐ ์์๊ณผ ๋ค๋ฅด๊ฒ ๋ฐ์๋๊ฑฐ๋ ๋ฐ์ดํฐ ์ถฉ๋์ด ๋ฐ์ํ ์ ์๋ค.
์ฐ๋ฆฌ ์๋น์ค๋ ๋์ผ๋ก ์ ์ฐฐ์ ํ๊ธฐ ๋๋ฌธ์ ์ ํํ ์ ์ฐฐ ๊ฒฐ๊ณผ๋ฅผ ์๋ ค์ผ ํ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ "์ ์ฐฐ ๊ธฐ๋ฅ"์ ๊ตฌํํ๋ฉด์ ์ค๋ ๊ณ ๋ฏผ์ ํ๊ฒ ๋์๋ค.
๊ถ๊ธ์ ์ผ๋ก ๋ฌธ์ ๋ ์๋์ ๊ฐ๋ค.
1. ๋์ผํ ๋ฐ์ดํฐ์ ๋์ ์ ๊ทผํ๋ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ
2. ์ฌ๋ฌ ์ ๊ทผ ์ค์ ๋ฑ ํ๋์ ์ ๊ทผ๋ง ๊ฐ์ ธ์ค๊ณ ๋๋จธ์ง๋ ๋กค๋ฐฑ
์ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด ์์์ ์ฐ์ฐ๊ณผ ๋๊ด์ ๋ฝ์ ํผํฉํด์ ํ์ฉํด ๋ณด์๋ค.
์ฌ๊ธฐ์ ์์์ ์ฐ์ฐ์ด๋?
์์์ ์ฐ์ฐ์ ์ฌ๋ฌ ๊ฐ์ ์ฐ์ฐ์ด ํ๋์ ๋จ์๋ก ์คํ๋์ด์ ์ค๊ฐ์ ๋ค๋ฅธ ์ฐ์ฐ์ด ๋ผ์ด๋ค ์ ์๋๋ก ๋ณด์ฅํ๋ ๋ฐฉ์์ด๋ค.
์ฆ, ์ ์ฐฐ ์ ๊ฒฝ๋งค ๊ฐ๊ฒฉ๋ณด๋ค ๋์ ๊ฒฝ์ฐ์๋ง ์ ์ฐฐ์ ์งํํ๋๋ก ์์์ ์ธ UPDATE SQL์ ํ์ฉํ์ฌ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๊ณ ํ๋ค.
์ฌ๊ธฐ์ ๋๊ด์ ์ด๋?
๋๊ด์ ๋ฝ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ ๋, ์์์น ๋ชปํ ๋ณ๊ฒฝ์ด ๋ฐ์ํ๋์ง ํ์ธํ๋ ๋ฐฉ์์ด๋ค.
๋ณดํต ์ํฐํฐ์ ๋ฒ์ ํ๋๋ฅผ ์ถ๊ฐํ์ฌ ๋ณ๊ฒฝ์ด ๋ฐ์ํ์ ๊ฒฝ์ฐ ์์ธ๋ฅผ ๋์ง๊ณ , ๋ค์ ์๋ํ๋๋ก ํ๋ค.
์ฐ๋ฆฌ ์๋น์ค์ ๊ฒฝ์ฐ ๋๊ด์ ๋ฝ์ ์ง์ ์ฌ์ฉํ์ง ์๊ณ , ์์์ ์ฐ์ฐ ํ ์์ธ ์ฒ๋ฆฌ ๋ฐฉ์์ผ๋ก ์ ์ฉํ๋ ค๊ณ ํ๋ค.
์๋๋ ์์์ ์ฐ์ฐ์ ์ ์ฉํ updateBidIfHigher
์ฝ๋์ด๋ค. ์ด๋ฅผ ์
์ฐฐํ ๋ ๋ฐ๋ก ์คํํ์ฌ ํ์ฌ currentPrice๊ฐ ์
์ฐฐ ๊ธ์ก๋ณด๋ค ๋์์ง ํ์ธํ๊ณ ๋ง์ฝ ์ด๋ณด๋ค ๋ฎ์ ๋ 0์ ๋ฐํํ๋ ๋ก์ง์ด๋ค.
๐ Bid.java
@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "bid")
public class Bid {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long auctionId;
private Long bidderId;
private Integer bidPrice;
@Enumerated(EnumType.STRING)
private BidStatus status;
}
๐ AuctionRepository.java
public interface AuctionRepository extends JpaRepository<Auction, Long>, AuctionRepositoryCustom {
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE Auction a SET a.currentPrice = :price WHERE a.id = :auctionId AND a.currentPrice < :price")
int updateBidIfHigher(@Param("auctionId") Long auctionId, @Param("price") Integer price);
}
0์ ๋ฐํํ๊ฒ ๋๋ค๋ฉด BidException๊ฐ ๋ฐ์ํ ์ ์๋๋ก ๊ตฌํํ๋ค. ๋ 0์ด ์๋๋ผ๋ฉด ์ ์์ ์ผ๋ก ์ ์ฐฐ ๊ธฐ๋ฅ์ ์ํํ ์ ์๋๋ก ๊ตฌํํ๋ค.
@Service
@Transactional
@RequiredArgsConstructor
public class BidCommandService implements BidCommandUseCase {
private final AuctionRepositoryPort auctionRepositoryPort;
private final BidRepositoryPort bidRepositoryPort;
@Override
public void bid(final Long userId, BidUseCaseReqDTO.Bid requestDTO) {
if(auctionRepositoryPort.updateBidIfHigher(requestDTO) == 0) {
throw new BidException(BID_FAIL);
}
var findAuction = auctionRepositoryPort.findAuctionById(requestDTO.auctionId());
bidRepositoryPort.checkBeforeBid(findAuction);
findAuction.start(userId);
bidRepositoryPort.saveBid(Bid.of(userId, requestDTO));
}
}
์ด์ ์ ์์ ์ผ๋ก ๋์ํ๋์ง ํ์ธํ๊ธฐ ์ํด ํ
์คํธ ์ฝ๋๋ฅผ ๋๋ ค๋ณด๊ฒ ๋ค.
ํ
์คํธ ์ฝ๋์ ์๋๋ฆฌ์ค๋ 5000๋ช
์ด ๋์์ ์
์ฐฐ์ ์๋ํ์ ๋ "1๋ช
๋ง ์
์ฐฐ ์ฑ๊ณต โ
, 4999๋ช
์
์ฐฐ ์คํจ โ" ํด์ผ ํ๋ค.
๐ ์ฒซ ๋ฒ์งธ ํ ์คํธ ์ฝ๋
@Test
@DisplayName("์
์ฐฐ ์ ๋์์ฑ ์ด์ ๋ฐ์์ ๋ํ ํ
์คํธ")
public void ์
์ฐฐ_์_๋์์ฑ_์ด์_๋ฐ์์_๋ํ_ํ
์คํธ() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(NUM_USERS);
ExecutorService executorService = Executors.newFixedThreadPool(NUM_USERS);
AtomicInteger successUsers = new AtomicInteger(0);
AtomicInteger failedUsers = new AtomicInteger(0);
for (int i = 1; i <= NUM_USERS; i++) {
final Long userId = (long) i;
executorService.submit(() -> {
try {
bidCommandService.bid(userId, new BidUseCaseReqDTO.Bid(auction.getId(), BID_PRICE));
successUsers.incrementAndGet();
} catch (BidException e) {
failedUsers.incrementAndGet();
assertThat(e.getMessage()).isEqualTo(ErrorCode.BID_FAIL.getMessage());
} finally {
latch.countDown();
}
});
}
latch.await();
Auction updatedAuction = auctionRepositoryPort.findAuctionById(auction.getId());
assertThat(updatedAuction.getCurrentPrice()).isEqualTo(BID_PRICE);
assertThat(successUsers.get()).isEqualTo(1);
assertThat(failedUsers.get()).isEqualTo(NUM_USERS-1);
executorService.shutdown();
}
ํ ์คํธ๋ฅผ ๋๋ ค๋ณธ ๊ฒฐ๊ณผ ํ ์คํธ๋ฅผ ์ ์์ ์ผ๋ก ํต๊ณผํ์๊ธฐ์ ๋์์ฑ ๋ฌธ์ ๋ฅผ ์ ํด๊ฒฐํ๋ค๊ณ ์๊ฐํ๋ค.
ํ.์ง.๋ง
์ด๋ ์ฐฉ๊ฐ์ด์๋ค... ํ ์คํธ ์ฝ๋๋ฅผ ์ ๋ ๊ฒ ์์ฑํด์ ์ ์์ ์ผ๋ก ์ ์ฐฐ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค๊ณ ์ฐฉ๊ฐํ๋ค.
ํ ์คํธ ์ฝ๋๋ฅผ ์๋์ฒ๋ผ ๋ฐ๊ฟ๋ณด๊ฒ ๋ค.
๐ ๋ ๋ฒ์งธ ํ ์คํธ ์ฝ๋
@Test
@DisplayName("์
์ฐฐ ์ ๋์์ฑ ์ด์ ๋ฐ์์ ๋ํ ํ
์คํธ")
public void ์
์ฐฐ_์_๋์์ฑ_์ด์_๋ฐ์์_๋ํ_ํ
์คํธ() throws InterruptedException {
CountDownLatch readyLatch = new CountDownLatch(NUM_USERS);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(NUM_USERS);
ExecutorService executorService = Executors.newFixedThreadPool(NUM_USERS);
AtomicInteger successUsers = new AtomicInteger(0);
AtomicInteger failedUsers = new AtomicInteger(0);
AtomicReference<Long> successUserId = new AtomicReference<>();
AtomicReference<Integer> successBidPrice = new AtomicReference<>();
for (int i = 1; i <= NUM_USERS; i++) {
final Long userId = (long) i;
executorService.submit(() -> {
try {
readyLatch.countDown();
startLatch.await();
int bidPrice = (int) (BID_PRICE * userId);
bidCommandUseCase.bid(userId, new BidUseCaseReqDTO.Bid(auction.getId(), bidPrice));
successUserId.set(userId);
successBidPrice.set(bidPrice);
successUsers.incrementAndGet();
} catch (Exception e) {
failedUsers.incrementAndGet();
assertThat(e.getMessage()).isEqualTo(ErrorCode.BID_FAIL.getMessage());
} finally {
doneLatch.countDown();
}
});
}
readyLatch.await();
startLatch.countDown();
doneLatch.await();
executorService.shutdown();
Auction findAuction = auctionRepositoryPort.findAuctionById(auction.getId());
Bid findBid = bidRepositoryPort.findBidByAuctionId(findAuction.getId());
assertThat(findAuction.getCurrentPrice()).isEqualTo(successBidPrice.get());
assertThat(findBid.getBidderId()).isEqualTo(successUserId.get());
assertThat(successUsers.get()).isEqualTo(1);
assertThat(failedUsers.get()).isEqualTo(NUM_USERS-1);
executorService.shutdown();
}
์ด๋ ์ ์ฐฐ ๊ธ์ก์ด ์ ์ ์ฆ๊ฐํ๋๋ก ๋ฐ๊พธ์๋ค. ์ด๋ ๊ฒ ์์ฑํ์ ๋ ํ ์คํธ๋ ์ด๋ค ๊ฒฐ๊ณผ๊ฐ ๋์ฌ๊น?
์ด๊ฒ ๋ฌด์จ ์ผ์ผ๊น..?
์ด๋ ๋ด ์์ ํ ์ฐฉ๊ฐ์ด์๋ค. ์์์ ์ฐ์ฐ์ ์ ์ ์ฉํ๋ค๊ณ ์๊ฐํ๋๋ฐ, ํ ์คํธ ์ฝ๋๋ฅผ ์๋ชป ์์ฑํด์ ์ฑ๊ณตํ๋ค๊ณ ์คํดํ๋ค. ๋จ์ํ currentPrice๋ณด๋ค ๋์ง ์๋ค๋ฉด 0์ ๋ฐํํ๊ฒ ์ฟผ๋ฆฌ๋ฌธ์ ์์ฑํ๊ณ , ํ ์คํธ ์ฝ๋ ๋ํ ์ฒ์์ BID_PRICE๋ฅผ ํ๋๋ก ๊ณ ์ ํด ๋จ๊ธฐ์ ํต๊ณผํ ์ ์์๋ ๊ฑฐ๋ค.
๐ ์ฒซ ๋ฒ์งธ ํ ์คํธ ์๋๋ฆฌ์ค
์ฒซ ๋ฒ์งธ ํ
์คํธ ์๋๋ฆฌ์ค
currentPrice = 1000
BID_PRICE = 1500
1์ฐจ Bid : 1000(currentPrice) < 1500(BID_PRICE) -> Bid ์ ์์ ์ผ๋ก ์ํ
currentPrice = 1500
BID_PRICE = 1500
2์ฐจ Bid : 1500(currentPrice) < 1500(BID_PRICE) -> Bid ์คํจ
currentPrice = 1500
BID_PRICE = 1500
3์ฐจ Bid : 1500(currentPrice) < 1500(BID_PRICE) -> Bid ์คํจ
.
.
.
.
์ ๊ฐ์ ์๋๋ฆฌ์ค์ด๊ธฐ ๋๋ฌธ์ ํต๊ณผํ ์ ์์๋ค.
์๋๋ ๋ ๋ฒ์งธ ํ ์คํธ ์ฝ๋ ์๋๋ฆฌ์ค์ด๋ค.
๐ ๋ ๋ฒ์งธ ์๋๋ฆฌ์ค
๋ ๋ฒ์งธ ์๋๋ฆฌ์ค
currentPrice = 1000
BID_PRICE = 1500
1์ฐจ Bid : 1000(currentPrice) < 1500(BID_PRICE) -> Bid ์ ์์ ์ผ๋ก ์ํ
currentPrice = 1500
BID_PRICE = 3000
2์ฐจ Bid : 1500(currentPrice) < 3000(BID_PRICE) -> Bid ์ ์์ ์ผ๋ก ์ํ
currentPrice = 3000
BID_PRICE = 4500
3์ฐจ Bid : 3000(currentPrice) < 4500(BID_PRICE) -> Bid ์ ์์ ์ผ๋ก ์ํ
.
.
.
.
์ ๊ฐ์ ์๋๋ฆฌ์ค์ด๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ์ ์ฐฐ์ด ์ฑ๊ณตํ๊ณ ํ ์คํธ ์ฝ๋๋ ์คํจํ๊ฒ ๋ ๊ฒ์ด๋ค.
๊ทธ๋ผ.. ๋ค์ ์๋กญ๊ฒ ์ค๊ณ๋ฅผ ํด์ผ ํ ๊ฒ ๊ฐ๋ค.
์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ "๋ถ์ฐ ๋ฝ" ์ ํ์ฉํด์ ๋จ ํ๋์ ์ ์ฐฐ๋ง ์ฑ๊ณตํ ์ ์๊ฒ ๊ตฌํํด ๋ณด์๋ค.
๋ ๋จ์ํ ๋ถ์ฐ ๋ฝ๋ง ์ ์ฉํ๋ ๊ฒ์ด ์๋๋ผ ๋ฝ์ ํ๋ํ์ ๋, ์์์ ์ฐ์ฐ์ ํตํด ๋ฐ๋ก currentPrice๋ฅผ DB์ ์
๋ฐ์ดํธํด ์ฃผ์๋ค. ๊ทธ ํ ์
์ฐฐ ๋ก์ง์ ์์ฐจ์ ์ผ๋ก ์งํํ๊ณ , ๋ฐ๋ก ๋ฝ์ ํด์ ํ์ ๋ ๋๊ธฐ ์ค์ด์๋ ์
์ฐฐ์ด ์ฑ๊ณต๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด Thread.sleep(RedisConstants.LOCK_LEASE_TIME * 1000);
๋ฅผ ์ถ๊ฐํ์ฌ ๋๋ ์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค.
์ฝ๋๋ ์๋์ ๊ฐ๋ค.
๐ BidCommandService.java
@Service
@Transactional
@RequiredArgsConstructor
public class BidCommandService implements BidCommandUseCase {
private final AuctionRepositoryPort auctionRepositoryPort;
private final BidRepositoryPort bidRepositoryPort;
private final RedissonClient redissonClient;
@Override
@Transactional
public void bid(final Long userId, BidUseCaseReqDTO.Bid requestDTO) {
String lockKey = RedisConstants.LOCK_PREFIX + requestDTO.auctionId();
RLock lock = redissonClient.getLock(lockKey);
try {
if (!lock.tryLock(Math.round(RedisConstants.LOCK_WAIT_TIME), RedisConstants.LOCK_LEASE_TIME, TimeUnit.SECONDS)) {
throw new BidException(BID_FAIL);
}
var findAuction = auctionRepositoryPort.findAuctionById(requestDTO.auctionId());
bidRepositoryPort.checkBeforeBid(findAuction);
findAuction.startBid(userId);
Integer beforePrice = findAuction.getCurrentPrice();
int updatedRows = auctionRepositoryPort.updateBidIfHigher(requestDTO);
if (updatedRows == 0) {
throw new BidException(BID_FAIL);
}
bidRepositoryPort.saveBid(Bid.of(userId, requestDTO, beforePrice));
Thread.sleep(RedisConstants.LOCK_LEASE_TIME * 1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BidException(BID_FAIL);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
๋ "์
์ฐฐ ๊ธฐ๋ฅ" ์ ์ค์ํ ๊ธฐ๋ฅ์ด๊ธฐ์ Bid ํ
์ด๋ธ์ beforePrice
๋ผ๋ ์
์ฐฐ ์ฑ๊ณต ์ ๊ฐ๊ฒฉ์ ์ ์ฅํ๊ณ , @Table(name = "bid", uniqueConstraints = @UniqueConstraint(columnNames = {"auctionId", "beforePrice"}))
์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑด์ ์ถ๊ฐํ๋ค.
์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑด์ผ๋ก ์ธํด Bid ํ ์ด๋ธ์๋ ํ๋์ auctionId์๋ ์ค๋ณต๋๋ beforePrice๊ฐ ์ฌ ์ ์๊ธฐ์ ๊ฐ์ ๊ฒฝ๋งค๊ฐ์ ์ ์ฐฐ์ ์ฑ๊ณตํ๋ Bid๊ฐ 2๊ฐ ์ด์ ์กด์ฌํ ์ ์๊ฒ ๋๋ค.
์ ์ฝ๋์ ์๋๋ฆฌ์ค๋ ์๋์ ๊ฐ๋ค.
๋ฌธ์ ํด๊ฒฐ๋ก ์๋์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์๋ค.
1. โ์์์ ์ฐ์ฐ + ๋ถ์ฐ ๋ฝ + ๋๋ ์ด + ์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑดโ โ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ.
2. 5,000๊ฑด ๊ธฐ์ค, ๋์ ์
์ฐฐ ์์ฒญ ์ค 1๊ฑด ์ฑ๊ณต, 4,999๊ฑด ์คํจ.
3. ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ 100% ๋ณด์ฅ.
4. ์๋น์ค์ ํต์ฌ ๊ธฐ๋ฅ โ์
์ฐฐโ ์ ๋งค์ฐ ์์ ํ๊ฒ ์ด์ ๊ฐ๋ฅ.
์ต์ข ์ ์ผ๋ก "์์์ ์ฐ์ฐ + ๋ถ์ฐ ๋ฝ + ๋๋ ์ด + ์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑด" ์ ํ์ฉํ์ฌ 5000๋ช ์ด ๋์์ ์ ์ฐฐ ์๋๋ฅผ ํ์ ๋, "1๋ช ๋ง ์ ์ฐฐ ์ฑ๊ณต โ , 4999๋ช ์ ์ฐฐ ์คํจ โ" ํ ์คํธ๋ฅผ ํต๊ณผํ ์ ์์๋ค.
"์ ์ฐฐ ๊ธฐ๋ฅ" ์ ์ฐ๋ฆฌ ์๋น์ค "TicToc" ์์ ๊ฐ์ฅ ์ค์ํ ๊ธฐ๋ฅ์ด๊ณ ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ค๋ ์๊ฐ์ด ๊ฑธ๋ ธ๋ ๊ฒ ๊ฐ๋ค. ํ ์คํธ ์ฝ๋๋ฅผ ์๋ชป ์์ฑํ์ฌ ์ด๋ฐ์ ์ฑ๊ณตํ๋ค๊ณ ์คํด๋ฅผ ๊ฐ๊ฒ ๋์๋ค.
์ด ๊ณผ์ ์ ํตํด ๋จ์ํ ๋น์ฆ๋์ค ๋ก์ง์ ๊ตฌํํ๋ ๊ฒ์ด ์๋๋ผ, ํ ์คํธ ์ฝ๋์ ์์ฑ๋๊ฐ ์์คํ ์ ์ ๋ขฐ์ฑ์ ๊ฒฐ์ ํ๋ค๋ ๊ฒ์ ๋ฐฐ์ธ ์ ์์๋ค.
"์ ์ฐฐ ๊ธฐ๋ฅ" ํ๋๋ฅผ ๊ตฌํํ๋ ๋ฐ์ ๋ง์ ์๊ฐ ๋์ ๋ง์ ๊ณต๋ถ๋ฅผ ํ ์ ์์๋ค. ๋จ์ํ ์ด๋ ธํ ์ด์ ์ ๋ถ์ด ๋์์ฑ ์ ์ด์์ ๋๋ด๋ ๊ฒ์ด ์๋ Redis๋ฅผ ํ์ฉํ์ฌ ๋ถ์ฐ ๋ฝ์ ํ์ฉํ๊ณ , ๋จ์ํ ๋ถ์ฐ ๋ฝ์์ ๋๋ด๋ ๊ฒ์ด ์๋ ๋๋ ์ด ์ ๋ต์ ํ์ฉํด ์์ ์ฑ์ ๋ณด์ฅํ๋ค. ๋ ์ด์์ ๋๋ด๋ ๊ฒ์ด ์๋, ์์์ ์ฐ์ฐ๊ณผ ์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑด์ ํตํด ๋์ฑ ์์ ์ฑ์ ๋ณด๊ฐํ๋ค.
์ด๋ฒ ๊ฒฝํ์ ํตํด ๋์์ฑ ์ ์ด์ ์ค์์ฑ์ ๋ค์ ๊นจ๋ฌ์๊ณ , ํ ์คํธ ์ฝ๋์ ์ ๋ขฐ์ฑ์ด ์๋น์ค ์์ ์ฑ์ ์ง์ ์ ์ธ ์ํฅ์ ๋ฏธ์น๋ค๋ ์ ์ ๋ฐฐ์ธ ์ ์์๋ค. ์์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ๊พธ์คํ ์์ฑํ์ฌ ๋์ฑ ์์ ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํ๋ ๊ฐ๋ฐ์๋ก ์ฑ์ฅํด์ผ๊ฒ ๋ค.