# ๐Ÿš— ์ฐจ๊ทผ์ฐจ๊ทผ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ (4)

๐Ÿš— ์ฐจ๊ทผ์ฐจ๊ทผ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ (1) - synchronized (opens new window)
๐Ÿš— ์ฐจ๊ทผ์ฐจ๊ทผ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ (2) - Pessimistic Lock (opens new window)
๐Ÿš— ์ฐจ๊ทผ์ฐจ๊ทผ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ (3) - Optimistic Lock (opens new window)
๐Ÿ‘‰ ๐Ÿš— ์ฐจ๊ทผ์ฐจ๊ทผ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ (4) - Named Lock (opens new window)

์ž‘์„ฑ์— ์‚ฌ์šฉ๋œ ์˜ˆ์ œ ์ฝ”๋“œ๋Š” concurrency-named-lock (opens new window)์—์„œ ํ™•์ธํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

# ๋„ค์ž„๋“œ ๋ฝ

๋„ค์ž„๋“œ ๋ฝ์€ GET_LOCK() ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ž„์˜์˜ ๋ฌธ์ž์—ด์— ๋Œ€ํ•œ ์ž ๊ธˆ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹จ์ˆœํžˆ ์‚ฌ์šฉ์ž๊ฐ€ ์ง€์ •ํ•œ ๋ฌธ์ž์—ด์— ๋Œ€ํ•œ ๋ฝ์„ ํš๋“ํ•˜๊ณ  ๋ฐ˜๋‚ฉํ•œ๋‹ค.

# GET_LOCK(str, timeout)

๋ฌธ์ž์—ด(str)์— ํ•ด๋‹นํ•˜๋Š” ๋ฝ์„ ํš๋“ํ•œ๋‹ค.

  • return 1: ๋ฝ ํš๋“์— ์„ฑ๊ณตํ–ˆ๋‹ค.
  • return 0: timeout ๋™์•ˆ ๋ฝ์„ ํš๋“ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.
mysql
>
SELECT GET_LOCK('1', 5);
+------------------+
| GET_LOCK('1', 5) |
+------------------+
|                1 |
+------------------+

๋จผ์ € ํ„ฐ๋ฏธ๋„์— ์ ‘์†ํ•˜์—ฌ ๋‘ ๊ฐœ์˜ ์„ธ์…˜์„ ์ƒ์„ฑํ•œ๋‹ค. ํ„ฐ๋ฏธ๋„(1)์—๋Š” GET_LOCK()์„ ํ†ตํ•ด ๋ฝ์„ ํš๋“ํ•œ๋‹ค. ํ„ฐ๋ฏธ๋„(2)๋„ ๋™์ผํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•œ๋‹ค.

img.png

๊ฒฐ๊ณผ์ ์œผ๋กœ 1์€ ํ˜„์žฌ ๋„ค์ž„๋“œ ๋ฝ์„ ์„ค์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ„ฐ๋ฏธ๋„(2)์—์„œ๋Š” ๋ฝ ํš๋“์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์—ฌ timeout ์ดํ›„ 0์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ฆ‰ ๋ฝ์„ ํš๋“ํ•˜์ง€ ๋ชปํ–ˆ์Œ์„ ์˜๋ฏธํ•œ๋‹ค.

# RELEASE_LOCK(str)

๋ฌธ์ž์—ด(str)์— ํ•ด๋‹นํ•˜๋Š” ๋ฝ์„ ํ•ด์ œํ•œ๋‹ค.

  • return 1: ๋ฝ์„ ํ•ด์ œํ•œ๋‹ค.
  • return null: ํ•ด์ œํ•  ๋ฝ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.

img.png

๋ฝ์„ ์ง„ํ–‰ํ•œ ํ„ฐ๋ฏธ๋„(1)์—์„œ RELEASE_LOCK()์„ ํ†ตํ•ด ํ•ด์ œํ•˜๋ฉด ํ„ฐ๋ฏธ๋„(2)์—์„œ 1 ๋ฝ์„ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฝ ํ•ด์ œ๋Š” ์˜ค์ง ๋ฝ์„ ํš๋“ํ•œ ์„ธ์…˜์—์„œ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์—ผ๋‘ํ•ด๋‘์–ด์•ผ ํ•œ๋‹ค.

# ๋„ค์ž„๋“œ ๋ฝ ์ ์šฉ

๋„ค์ž„๋“œ ๋ฝ์€ ๋ณ„๋„์˜ DataSource๋ฅผ ์—ฐ๋™ํ•˜์—ฌ Lock์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•˜๋‹ค. ๋งŒ์•ฝ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ธด DataSource๋ฅผ ๊ณต์œ ํ•ด์„œ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋ฝ์„ ๊ฑธ๊ธฐ ์œ„ํ•ด ๋ณ„๋„์˜ ์ปค๋„ฅ์…˜์ด ํ•„์š”ํ•˜๋ฉฐ ์ ์œ ํ•˜๋Š” ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง€๋ฉด ํ•œ์ •๋œ ์ž์›์ธ ์ปค๋„ฅ์…˜ ํ’€ ๋ถ€์กฑ์œผ๋กœ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๋จผ์ € ์ผ๋ฐ˜์ ์ธ ๋ฐ์ดํ„ฐ ์ €์žฅ์„ ์œ„ํ•œ DataSource ์„ค์ •๊ณผ ๋ฝ ๊ด€๋ จ DataSource ์ •๋ณด๋ฅผ ๋ช…์‹œํ•œ๋‹ค.

spring:
  # ...
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://127.0.0.1:3306/products
    username: root
    password: root

user-lock:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://127.0.0.1:3307/user_lock
    username: root
    password: root
# ...

spring.datasource๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ •๋ณด๋ฅผ ์„ค์ •ํ•œ๋‹ค. user-lock.datasource์—๋Š” ๋„ค์ž„๋“œ ๋ฝ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ DataSource๋ฅผ ์„ค์ •ํ•œ๋‹ค. ๋ณ„๋„๋กœ ์„ค์ • ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ๊ฐ๊ฐ ์ปค๋„ฅ์…˜ ํ’€์˜ ๊ฐœ์ˆ˜๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ ์„ค์ • ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ DataSource๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.

@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create()
                .build();
    }

    @Bean
    @ConfigurationProperties("user-lock.datasource")
    public DataSource lockDataSource() {
        return DataSourceBuilder.create()
                .build();
    }
}

์ฃผ ์‚ฌ์šฉ๋  DataSource๋Š” @Primary ์• ๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•œ๋‹ค. ์•„๋ž˜๋Š” ๋ฝ ๊ด€๋ จ๋œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ์„ ์œ„ํ•œ lockDataSource์ด๋‹ค.

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

๋จผ์ € ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋‹ด๊ธด ProductService์ด๋‹ค.

@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(final ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Transactional
    public void purchase(final Long id, final Long quantity) {
        var foundProduct = getProduct(id);
        foundProduct.decrease(quantity);
    }

    private Product getProduct(final Long id) {
        return productRepository.findById(id)
                .orElseThrow(NoSuchElementException::new);
    }
}

์ด์ „ ์‹œ๊ฐ„์— ๋‹ค๋ฃฌ Service์™€ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜๋‹ค. @Transactional์€ ์šฐ๋ฆฌ๊ฐ€ @Primary ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ DataSource๋กœ ์„ค์ •ํ•œ ์ปค๋„ฅ์…˜์„ ํš๋“ํ•  ๊ฒƒ์ด๋‹ค. ์ด์ œ ๋ฝ ๊ด€๋ จ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์ž.

UserLockProductFacade๋Š” ๋ฝ ๊ด€๋ จ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ Facade์ด๋‹ค. UserLockTemplate์˜ executeWithLockWithoutResult()๋ฅผ ํ†ตํ•ด ๋ฝ ์„ค์ • ์ •๋ณด์™€ ํ–‰์œ„๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋‹ค. ์ด์ œ executeWithLockWithoutResult() ๋‚ด๋ถ€๋ฅผ ์‚ดํŽด๋ณด์ž.

@Component
public class UserLockTemplate {

    private static final String GET_LOCK = "SELECT GET_LOCK(?, ?)";
    private static final String RELEASE_LOCK = "SELECT RELEASE_LOCK(?, ?)";

    private final DataSource dataSource;

    public UserLockTemplate(@Qualifier("lockDataSource") final DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void executeWithLockWithoutResult(final String userLockName, double timeout, final Executor callback) {
        executeWithLock(userLockName, timeout, () -> {
            callback.execute();
            return null;
        });
    }

    public <T> T executeWithLock(final String userLockName, double timeout, final Supplier<T> supplier) {
        try (var connection = dataSource.getConnection()) {
            return execute(connection, userLockName, timeout, supplier);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private <T> T execute(final Connection connection, final String userLockName, final double timeout,
                          final Supplier<T> supplier) {
        try {
            getLock(connection, userLockName, timeout);
            return supplier.get();
        } finally {
            releaseLock(connection, userLockName);
        }
    }

    private void getLock(final Connection connection, final String userLockName, double timeout) {
        try (var preparedStatement = connection.prepareStatement(GET_LOCK)) {
            preparedStatement.setString(1, userLockName);
            preparedStatement.setDouble(2, timeout);
            preparedStatement.executeQuery();
        } catch (final SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private void releaseLock(final Connection connection, final String userLockName) {
        try (var preparedStatement = connection.prepareStatement(RELEASE_LOCK)) {
            preparedStatement.setString(1, userLockName);
            preparedStatement.executeQuery();
        } catch (final SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

UserLockTemplate์€ ๋ฝ๊ณผ ํ•จ๊ป˜ ํ–‰์œ„๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ์ฒด์ด๋‹ค. TransactionTemplate๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ ํ•ด๋ณด์•˜๋‹ค.

  • @Qualifier("lockDataSource") final DataSource dataSource: @Primary๋กœ ์„ ํƒํ•œ dataSource๊ฐ€ ์•„๋‹Œ lockDataSource๋ฅผ ์ฃผ์ž… ๋ฐ›๋Š”๋‹ค.
  • executeWithLockWithoutResult(): ๋ฐ˜ํ™˜์ด ํ•„์š”ํ•˜์ง€ ์•Š๋Š” ํ–‰์œ„๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์˜ ๋ฉ”์„œ๋“œ์ด๋‹ค.
  • executeWithLock(): ๋ฝ ๊ธฐ๋Šฅ๊ณผ ํ•จ๊ป˜ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ์ด๋‹ค. connection ๊ฐ์ฒด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋‹ซ๊ธฐ ์œ„ํ•œ ์ฑ…์ž„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • execute(): ์ „๋‹ฌ๋œ ํ–‰์œ„๋ฅผ ์‹ค์งˆ์ ์œผ๋กœ ๋ฝ๊ณผ ํ•จ๊ป˜ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ์ด๋‹ค. ํ•ต์‹ฌ์€ finally์— ๋ฝ ๋ฐ˜๋‚ฉ ๋ฉ”์„œ๋“œ๋ฅผ ์œ„์น˜ ์‹œ์ผœ ๋ฌด์กฐ๊ฑด ๋ฐ˜๋‚ฉํ•จ์„ ๋ณด์žฅํ•œ๋‹ค.
  • getLock(): ๋ฝ์„ ํš๋“ํ•œ๋‹ค.
  • releaseLock(): ๋ฝ์„ ๋ฐ˜๋‚ฉํ•œ๋‹ค.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค. ๋ฝ์„ ์ง€์ •ํ•˜๊ณ  ์‹ถ์€ ํ–‰์œ„๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

@Component
public class UserLockProductFacade {

    private final UserLockTemplate userLockTemplate;
    private final ProductService productService;

    public UserLockProductFacade(final UserLockTemplate userLockTemplate, final ProductService productService) {
        this.userLockTemplate = userLockTemplate;
        this.productService = productService;
    }

    public void purchase(final Long id, final Long quantity) {
        userLockTemplate.executeWithLockWithoutResult(generateUserLockName(id), 5, () -> {
            productService.purchase(id, quantity);
        });
    }

    private String userLockNameGenerate(final Long id) {
        return id.toString();
    }
}

๋„ค์ž„๋“œ ๋ฝ์˜ ์ด๋ฆ„์€ id๋ฅผ ํ™œ์šฉ ํ–ˆ๊ณ , timeout์€ 5์ดˆ๋ฅผ ์ง€์ •ํ–ˆ๋‹ค.

์ด์ œ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•ด๋ณด์ž! ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์ด์ „๊ณผ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜๋‹ค.


@SpringBootTest
@DisplayNameGeneration(ReplaceUnderscores.class)
class UserLockProductFacadeTest {

    private final ProductRepository productRepository;
    private final UserLockProductFacade userLockProductFacade;

    @Autowired
    UserLockProductFacadeTest(final ProductRepository productRepository,
                              final UserLockProductFacade userLockProductFacade) {
        this.productRepository = productRepository;
        this.userLockProductFacade = userLockProductFacade;
    }

    @Test
    void ๋™์‹œ์—_100๊ฐœ์˜_์ƒํ’ˆ์„_๊ตฌ๋งคํ•œ๋‹ค() throws InterruptedException {
        var product = productRepository.save(new Product("์น˜ํ‚จ", 100L));

        var executorService = Executors.newFixedThreadPool(10);
        var countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            executorService.submit(() -> process(product, countDownLatch));
        }

        countDownLatch.await();

        var actual = productRepository.findById(product.getId()).orElseThrow();

        assertThat(actual.getQuantity()).isEqualTo(0L);
    }

    private void process(final Product product, final CountDownLatch countDownLatch) {
        try {
            userLockProductFacade.purchase(product.getId(), 1L);
        } finally {
            countDownLatch.countDown();
        }
    }
}

img.png

ํ…Œ์ŠคํŠธ๋Š” ์ •์ƒ์ ์œผ๋กœ ์ˆ˜ํ–‰๋œ๋‹ค!

# ๋‚จ์€ ๋ฌธ์ œ๋“ค

๋‹ค๋งŒ ํ•œ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ๋‚จ์•„์žˆ๋‹ค. ์ง€๊ธˆ์€ ๋ฝ์„ ํš๋“ํ•˜๊ธฐ ์œ„ํ•œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ์ตœ๋Œ€ 5์ดˆ์ด๋‹ค. ๋งŒ์•ฝ 5์ดˆ ์ด์ƒ์ด ๊ฑธ๋ฆฌ๋Š” ๋กœ์ง์ด๋ผ๋ฉด ๊ฐ€์ฐจ ์—†์ด ๋ฝ์„ ํš๋“ํ•˜์ง€ ๋ชปํ•˜๊ณ  ์˜ˆ์™ธ๋ฅผ ๋˜์งˆ ๊ฒƒ์ด๋‹ค.

๊ฐ„๋‹จํ•œ ํ™•์ธ์„ ์œ„ํ•ด ๋ฝ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ 0.1์ดˆ๋กœ ์กฐ์ •ํ•ด๋ณธ ๋’ค ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด๋ณธ๋‹ค.


@Component
public class UserLockProductFacade {
    // ...
    public void purchase(final Long id, final Long quantity) {
        userLockTemplate.executeWithLockWithoutResult(generateUserLockName(id), 0.1, () -> {
            productService.purchase(id, quantity);
        });
    }
    // ...
}

img.png

์•„์‰ฝ๊ฒŒ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•œ๋‹ค. ๋ฝ ํš๋“์„ ์œ„ํ•œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์ถฉ๋ถ„ํžˆ ์„ค์ •ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์•„ ๋ณด์ธ๋‹ค.

# ์ •๋ฆฌ

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

๋‹ค๋งŒ ๋ณ„๋„์˜ DataSource๋กœ ๊ตฌ์„ฑํ•  ๊ฒฝ์šฐ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ์„ค์ •ํ•ด์ค˜์•ผ ํ•˜๋Š” ๋กœ์ง์ด ๋„ˆ๋ฌด ๋งŽ๋‹ค. ๋˜ํ•œ ๋ฝ ํš๋“์— ์‹คํŒจ ํ–ˆ์„ ๋•Œ์— ๋Œ€ํ•œ ๋กœ์ง๋„ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค. ๋˜ ๋‹ค๋ฅธ ๋‹จ์ ์œผ๋กœ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผ์„ ํ•˜๋‹ค๋ณด๋‹ˆ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ์— ๋น„ํ•ด ๋Š๋ฆฐ ์†๋„๋ฅผ ๊ฐ€์งˆ ๊ฒƒ์ด๋‹ค. ๋˜ํ•œ ํŠน์ • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ข…์†์ ์ธ ๊ตฌ์กฐ๊ฐ€ ๋‚˜์˜ฌ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค. ์œ ์—ฐํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ์ถ”์ƒํ™” ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

๋‹จ์ ๋งŒ ๋‚˜์—ดํ•œ ๊ฒƒ ๊ฐ™์ง€๋งŒ MySQL์— ๋Œ€ํ•œ ์ง€์‹์ด ์ถฉ๋ถ„ํ•˜๋‹ค๋ฉด ๊ณ ๋ ค ํ•ด๋ณผ๋งŒ ํ•˜๋‹ค. ๊ฒฐ๊ตญ ๋ถ„์‚ฐ๋ฝ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์ค‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„์— ๋Œ€์‘์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

์ด๋ฒˆ ๊ณผ์ •์„ ํ†ตํ•ด์„œ ์• ๋งคํ•˜๊ฒŒ ์•Œ๊ณ  ์žˆ๋˜ ๋„ค์ž„๋“œ ๋ฝ์— ๋Œ€ํ•œ ์ง€์‹๊ณผ ํ™œ์šฉ ๋ฐฉ๋ฒ•์„ ์Šต๋“ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋‹ค์Œ ์‹œ๊ฐ„์—๋Š” redis๋ฅผ ํ™œ์šฉํ•œ ๋ถ„์‚ฐ๋ฝ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋ ค ํ•œ๋‹ค.

# References.

MySQL์„ ์ด์šฉํ•œ ๋ถ„์‚ฐ๋ฝ์œผ๋กœ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๊ฑธ์นœ ๋™์‹œ์„ฑ ๊ด€๋ฆฌ (opens new window)
์žฌ๊ณ ์‹œ์Šคํ…œ์œผ๋กœ ์•Œ์•„๋ณด๋Š” ๋™์‹œ์„ฑ์ด์Šˆ ํ•ด๊ฒฐ๋ฐฉ๋ฒ• (opens new window)

last updated: 12/14/2022, 5:20:01 PM