First Come First Served

First Come First Served Event

์‹ค์Šต์œผ๋กœ ๋ฐฐ์šฐ๋Š” ์„ ์ฐฉ์ˆœ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ๊ฐ•์˜๋ฅผ ๋“ฃ๊ณ  ์š”์•ฝํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

Intro

์š”๊ตฌ์‚ฌํ•ญ.

  • ์„ ์ฐฉ์ˆœ 100๋ช…์—๊ฒŒ ํ• ์ธ์ฟ ํฐ์„ ์ œ๊ณตํ•˜๋Š” ์ด๋ฒคํŠธ

  • ์„ ์ฐฉ์ˆœ 100๋ช…์—๊ฒŒ๋งŒ ์ง€๊ธ‰๋˜์–ด์•ผํ•œ๋‹ค.

  • 101๊ฐœ ์ด์ƒ์ด ์ง€๊ธ‰๋˜๋ฉด ์•ˆ๋œ๋‹ค.

  • ์ˆœ๊ฐ„์ ์œผ๋กœ ๋ชฐ๋ฆฌ๋Š” ํŠธ๋ž˜ํ”ฝ์„ ๋ฒ„ํ‹ธ ์ˆ˜ ์žˆ์–ด์•ผํ•œ๋‹ค.

์„ ์ฐฉ์ˆœ ์ด๋ฒคํŠธ ์ง„ํ–‰ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ .

  • ์ฟ ํฐ์ด ๊ฐœ์ˆ˜๋ณด๋‹ค ๋” ๋งŽ์ด ๋ฐœ๊ธ‰๋˜์—ˆ์„ ๊ฒฝ์šฐ

  • ์ด๋ฒคํŠธ ํŽ˜์ด์ง€ ์ ‘์†์ด ์•ˆ ๋  ๊ฒฝ์šฐ

  • ์ด๋ฒคํŠธ๋ž‘ ์ƒ๊ด€์—†๋Š” ํŽ˜์ด์ง€๋„ ๋Š๋ ค์งˆ ๊ฒฝ์šฐ

๋ฌธ์ œ ํ•ด๊ฒฐ.

  • ํŠธ๋ž˜ํ”ฝ์ด ๋ชฐ๋ ธ์„ ๋•Œ ๋Œ€์ฒ˜ํ•  ๋ฐฉ๋ฒ•

  • Redis ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ฟ ํฐ ๋ฐœ๊ธ‰ ๊ฐœ์ˆ˜ ๋ณด์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•

  • Kafka ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋“ค์— ๋Œ€ํ•œ ์˜ํ–ฅ๋„๋ฅผ ์ค„์ด๋Š” ๋ฐฉ๋ฒ•

Race Condition

@Service
@RequiredArgsConstructor
public class ApplyService {

    private final CouponRepository couponRepository;

    public void apply(Long userId) {
        final long count = couponRepository.count();

        if (count > 100) {
            return;
        }

        couponRepository.save(new Coupon(userId));
    }
}

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

@Test
void apply_multiple() throws InterruptedException {
    int threadCount = 1000;
    ExecutorService executorService = Executors.newFixedThreadPool(32);
    CountDownLatch latch = new CountDownLatch(threadCount);

    for (int i = 0; i < threadCount; i++) {
        long userId = i;
        executorService.submit(() -> {
            try {
                applyService.apply(userId);
            } finally {
                latch.countDown();
            }
        });
    }

    latch.await();

    final long count = couponRepository.count();

    assertThat(count).isEqualTo(100);
}

Concurrency issues ์—์„œ ๋ฐฐ์šด ๊ฒƒ๊ณผ ๊ฐ™์ด Java Synchronized ๋ฅผ ์ ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ,

์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€๊ฐ€ ๋œ๋‹ค๋ฉด Race Condition ์ด ๋‹ค์‹œ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ์ ์ ˆํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

.

๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ MySQL, Redis ๋ฅผ ํ™œ์šฉํ•œ ๋ฝ์„ ๊ตฌํ˜„ํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์ง€๋งŒ,

์ฟ ํฐ ๊ฐœ์ˆ˜์— ๋Œ€ํ•œ ์ •ํ•ฉ์„ฑ์„ ์›ํ•˜๋Š”๋ฐ ๋ฝ์„ ํ™œ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๋ฉด, ๋ฐœ๊ธ‰๋œ ์ฟ ํฐ์˜ ๊ฐœ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๋ถ€ํ„ฐ ์ฟ ํฐ์„ ์ƒ์„ฑํ•  ๋•Œ๊นŒ์ง€ ๋ฝ์„ ๊ฑธ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋ฝ์„ ๊ฑฐ๋Š” ๊ตฌ๊ฐ„์ด ๊ธธ์–ด์ง€๋‹ค๋ณด๋‹ˆ ์„ฑ๋Šฅ์— ๋ถˆ์ด์ต(๋ฝ์ด ํ’€๋ฆด ๋•Œ๊นŒ์ง€ ์ฟ ํฐ ๋ฐœ๊ธ‰์„ ๊ธฐ๋‹ค๋ ค์•ผ ํ•˜๋Š”)์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Redis

Redis incr ๋ช…๋ น์–ด๋Š” ํ‚ค์— ๋Œ€ํ•œ ๊ฐ’์„ 1์”ฉ ์ฆ๊ฐ€์‹œํ‚ค๋Š” ๋ช…๋ น์–ด์ž…๋‹ˆ๋‹ค.

Redis ๋Š” ์‹ฑ๊ธ€์Šค๋ ˆ๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜์—ฌ ๋ ˆ์ด์Šค ์ปจ๋””์…˜์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„ ๋ฟ ์•„๋‹ˆ๋ผ incr ๋ช…๋ น์–ด๋Š” ์„ฑ๋Šฅ๋„ ๊ต‰์žฅํžˆ ๋น ๋ฅธ ๋ช…๋ น์–ด์ž…๋‹ˆ๋‹ค.

incr ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐœ๊ธ‰๋œ ์ฟ ํฐ ๊ฐœ์ˆ˜๋ฅผ ์ œ์–ดํ•œ๋‹ค๋ฉด ์„ฑ๋Šฅ๋„ ๋น ๋ฅด๋ฉฐ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ๋„ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

> incr coupon_count
(integer) 1

> incr coupon_count
(integer) 2

commit

Problems

1) ์ฟ ํฐ์˜ ๊ฐœ์ˆ˜

  • ๋ฐœ๊ธ‰ํ•˜๋Š” ์ฟ ํฐ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ถ€ํ•˜๋ฅผ ์ฃผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  • ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๋‹ค๋ฅธ ๊ณณ์—์„œ๋„ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค๋ฉด ์„œ๋น„์Šค ์žฅ์• ๊นŒ์ง€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2) ์งง์€ ์‹œ๊ฐ„์— ๋งŽ์€ ์š”์ฒญ

  • ์งง์€ ์‹œ๊ฐ„ ๋‚ด์— ๋งŽ์€ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๊ฒŒ ๋  ๊ฒฝ์šฐ DB ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ๋ถ€ํ•˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  • ์„œ๋น„์Šค ์ง€์—ฐ ํ˜น์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Kafka

๋ถ„์‚ฐ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ ํ”Œ๋žซํผ

  • ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ: ์†Œ์Šค์—์„œ ๋ชฉ์ ์ง€๊นŒ์ง€ ์ด๋ฒคํŠธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ ํ•˜๋Š” ๊ฒƒ

Producer ---> Topic <--- Consumer

Start Kafka

docker-compose.yml

version: '3' # Docker Compose ํŒŒ์ผ ๋ฒ„์ „ ์ง€์ •
services: # ์—ฌ๋Ÿฌ๊ฐœ์˜ Docker ์ปจํ…Œ์ด๋„ˆ ์„œ๋น„์Šค ์ •์˜
  zookeeper: # Zookeeper ์„œ๋น„์Šค ์ •์˜
    image: wurstmeister/zookeeper:3.4.6
    container_name: zookeeper
    ports:
      - "2181:2181" # ํ˜ธ์ŠคํŠธ์˜ 2181 ํฌํŠธ๋ฅผ ์ปจํ…Œ์ด๋„ˆ์˜ 2181 ํฌํŠธ์™€ ๋ฐ”์ธ๋”ฉ
  kafka: # kafka ์„œ๋น„์Šค ์ •์˜
    image: wurstmeister/kafka:2.12-2.5.0
    container_name: kafka
    ports:
      - "9092:9092" # ํ˜ธ์ŠคํŠธ์˜ 9092 ํฌํŠธ๋ฅผ ์ปจํ…Œ์ด๋„ˆ์˜ 9092 ํฌํŠธ์™€ ๋ฐ”์ธ๋”ฉ
    environment: # kafka ์ปจํ…Œ์ด๋„ˆ์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
      KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:29092,OUTSIDE://localhost:9092 # ๋‚ด/์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฆฌ์Šค๋„ˆ ์ฃผ์†Œ ์„ค์ •
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT # ๋ฆฌ์Šค๋„ˆ์˜ ๋ณด์•ˆ ํ”„๋กœํ† ์ฝœ ๋งคํ•‘
      KAFKA_LISTENERS: INSIDE://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 # ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•  ๋ฆฌ์Šค๋„ˆ ์ฃผ์†Œ ์„ค์ •
      KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE # ๋ธŒ๋กœ์ปค ๊ฐ„ ํ†ต์‹ ์— ์‚ฌ์šฉํ•  ๋ฆฌ์Šค๋„ˆ ์ด๋ฆ„
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 # Kafka๊ฐ€ Zookeeper์— ์—ฐ๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์ฃผ์†Œ
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock # Docker ์†Œ์ผ“์„ ์ปจํ…Œ์ด๋„ˆ์™€ ๊ณต์œ ํ•˜์—ฌ Docker ์ด๋ฒคํŠธ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •

example

# ์นดํ”„์นด ์‹คํ–‰
$ docker-compose up -d

# ํ† ํ”ฝ์ƒ์„ฑ
$ docker exec -it kafka kafka-topics.sh --bootstrap-server localhost:9092 --create --topic testTopic
Created topic testTopic.

# ํ”„๋กœ๋“€์„œ ์‹คํ–‰
$ docker exec -it kafka kafka-console-producer.sh --topic testTopic --broker-list 0.0.0.0:9092
>Hello

# ์ปจ์Šˆ๋จธ ์‹คํ–‰
$ docker exec -it kafka kafka-console-consumer.sh --topic testTopic --bootstrap-server localhost:9092
Hello

# ์นดํ”„์นด ์ข…๋ฃŒ
$ docker-compose down

Producer

api/KafkaProducerConfig.java

@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, Long> producerFactory() {
        final Map<String, Object> config = new HashMap<>();

        config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, LongSerializer.class);

        return new DefaultKafkaProducerFactory<>(config);
    }

    @Bean
    public KafkaTemplate<String, Long> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

api/CouponCreateProducer.java

@Component
@RequiredArgsConstructor
public class CouponCreateProducer {

    private final KafkaTemplate<String, Long> kafkaTemplate;

    public void create(Long userId) {
        kafkaTemplate.send("coupon_create", userId);
    }
}

KafkaProducerConfig CouponCreateProducer

Consumer

consumer/KafkaConsumerConfig.java

@Configuration
public class KafkaConsumerConfig {

    @Bean
    public ConsumerFactory<String, Long> consumerFactory() {
        final Map<String, Object> config = new HashMap<>();

        config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        config.put(ConsumerConfig.GROUP_ID_CONFIG, "group_1");
        config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class);

        return new DefaultKafkaConsumerFactory<>(config);
    }

    /**
     * ํ† ํ”ฝ์œผ๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ๋ฐ›๊ธฐ ์œ„ํ•œ kafka-listener ๋ฅผ ๋งŒ๋“œ๋Š” kafka-listener-container-factory ์ƒ์„ฑ
      */
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, Long> kafkaListenerContainerFactory() {
        final ConcurrentKafkaListenerContainerFactory<String, Long> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());

        return factory;
    }
}

consumer/CouponCreatedConsumer.java

@Component
@RequiredArgsConstructor
public class CouponCreatedConsumer {

    private final CouponRepository couponRepository;

    @KafkaListener(topics = "coupon_create", groupId = "group_1")
    public void listener(Long userId) {
        couponRepository.save(new Coupon(userId));
    }
}

using Consumer

Limit the number of coupons

๋ฐœ๊ธ‰ ๊ฐ€๋Šฅํ•œ ์ฟ ํฐ ๊ฐœ์ˆ˜ 1์ธ๋‹น 1๊ฐœ๋กœ ์ œํ•œํ•˜๊ธฐ.

(1) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ๋‹ˆํฌํ‚ค ์‚ฌ์šฉํ•˜๊ธฐ

  • userId, couponType ์— ์œ ๋‹ˆํฌ ํ‚ค๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•.

  • ๋ณดํ†ต ์„œ๋น„์Šค๋Š” ํ•œ ์œ ์ €๊ฐ€ ๊ฐ™์€ ํƒ€์ž…์˜ ์ฟ ํฐ์„ ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์‹ค์šฉ์ ์ธ ๋ฐฉ๋ฒ•์€ ์•„๋‹˜.

(2) ๋ฒ”์œ„๋กœ ๋ฝ์„ ์žก๊ณ  ์ฒ˜์Œ์— ์ฟ ํฐ ๋ฐœ๊ธ‰ ์—ฌ๋ถ€๋ฅผ ๊ฐ€์ ธ์™€์„œ ํŒ๋‹จํ•˜๋Š” ๋ฐฉ์‹

  • ์ฟ ํฐ ๋ฐœ๊ธ‰ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋งŒ ํŒ๋‹จํ•˜๊ณ  ์‹ค์ œ ์ฟ ํฐ ์ƒ์„ฑ์€ ์ปจ์Šˆ๋จธ์—์„œ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์‹œ๊ฐ„์ฐจ๋กœ 2๊ฐœ ์ด์ƒ์˜ ์ฟ ํฐ์ด ๋ฐœ๊ธ‰๋  ์ˆ˜ ์žˆ์Œ.

  • ์ง์ ‘ ์ฟ ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๋”๋ผ๋„ ๋ฝ์˜ ๋ฒ”์œ„๊ฐ€ ๋„“์–ด์„œ ๋‹ค๋ฅธ ์š”์ฒญ๋“ค์€ ๋ฝ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ์ƒํƒœ๊ฐ€ ๋˜๋ฏ€๋กœ ์„ฑ๋Šฅ์ด ์•ˆ ์ข‹์•„์งˆ ์ˆ˜ ์žˆ์Œ.

(3) Rest ์—์„œ Set ํ™œ์šฉํ•˜๊ธฐ

# add set in redis
> sadd test 1
(integer) 1 # ์ถ”๊ฐ€๋œ value ๊ฐœ์ˆ˜

> sadd test 1
(integer) 0 # ์ด๋ฏธ ํ‚ค(test)์— ๊ฐ’(1)์ด ์กด์žฌํ•˜๋ฏ€๋กœ 0์„ ๋ฆฌํ„ด

AppliedUserRepository.java

@Repository
@RequiredArgsConstructor
public class AppliedUserRepository {

    private final RedisTemplate<String, String> redisTemplate;

    public Long add(Long userId) {
        return redisTemplate
                .opsForSet()
                .add("applied_user", userId.toString());
    }
}

...

/* Service */
final Long apply = appliedUserRepository.add(userId);
if (apply != 1) {
    return;
}

commit

Error issuing coupon

(1) ์‹คํŒจ ์ด๋ฒคํŠธ ๊ด€๋ฆฌ ํ…Œ์ด๋ธ” ํ™œ์šฉํ•˜๊ธฐ

  • ์ปจ์Šˆ๋จธ์—์„œ ์ฟ ํฐ ๋ฐœ๊ธ‰ ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ FailedEvent ํ…Œ์ด๋ธ”์— ์‹คํŒจํ•œ ์ด๋ฒคํŠธ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

  • ์ดํ›„ ๋ฐฐ์น˜๋ฅผ ํ†ตํ•ด FailedEvent ํ…Œ์ด๋ธ”์— ์Œ“์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ฝ์–ด์„œ ์ฟ ํฐ์„ ๋ฐœ๊ธ‰ํ•ด ์ค€๋‹ค๋ฉด ๊ฒฐ๊ณผ์ ์œผ๋กœ ํŠน์ • ์ˆ˜๋Ÿ‰์˜ ์ฟ ํฐ์ด ๋ชจ๋‘ ๋ฐœ๊ธ‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

(3) Saga Pattern ์ ์šฉํ•˜๊ธฐ

  • ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋“ค๋ผ๋ฆฌ ์ด๋ฒคํŠธ๋ฅผ ์ฃผ๊ณ  ๋ฐ›์•„ ํŠน์ • ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ์˜ ์ž‘์—…์ด ์‹คํŒจํ•˜๋ฉด ์ด์ „๊นŒ์ง€์˜ ์ž‘์—…์ด ์™„๋ฃŒ๋œ ๋งˆ์ดํฌ์„œ๋น„์Šค๋“ค์—๊ฒŒ ๋ณด์ƒ(complemetary) ์ด๋ฒคํŠธ๋ฅผ ์†Œ์‹ฑํ•จ์œผ๋กœ์จ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ์›์ž์„ฑ(atomicity)์„ ๋ณด์žฅํ•˜๋Š” ํŒจํ„ด

commit

Last updated