๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
ํ”„๋กœ์ ํŠธ ํšŒ๊ณ /ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

๋น„๋™๊ธฐ ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜(Saga)์˜ ๋ฉฑ๋“ฑ์„ฑ, ๋ฉ”์‹œ์ง€ ์ˆœ์„œ ์—ญ์ „(Out-of-Order) ํ•ด๊ฒฐ๊ธฐ

by ์ ผ์ ผ39 2026. 5. 25.
  • ๋ถ„์•ผ: MSA, Event-Driven Architecture, Saga Pattern, Idempotency, Kafka Partition Key Routing
  • ๊ด€๋ จ ํŒŒ์ผ: OrderCommandService.java , OrderEventConsumer.java

1. ๋ฐœ๋‹จ: ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ ๋ฐœ๊ฒฌ๋œ '์‹œ๊ฐ„์ฐจ ์—ฃ์ง€ ์ผ€์ด์Šค'

์ฃผ๋ฌธ ์ƒ์„ฑ ํŠธ๋žœ์žญ์…˜์„ ๋น„๋™๊ธฐ Saga ํŒจํ„ด(Kafka)์œผ๋กœ ๋ถ„๋ฆฌํ•œ ํ›„, ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์˜ˆ์™ธ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•จ. ๊ทธ์ค‘ "์œ ์ €์˜ ๊ธฐ์Šต ์ทจ์†Œ" ์ƒํ™ฉ์—์„œ ์—ฃ์ง€ ์ผ€์ด์Šค๋ฅผ ๋ฐœ๊ฒฌํ•จ

[์‹œ๋‚˜๋ฆฌ์˜ค ํƒ€์ž„๋ผ์ธ]

  1. ์œ ์ €๊ฐ€ ์ฃผ๋ฌธ์„ ์š”์ฒญํ•˜์—ฌ ์ƒํƒœ๊ฐ€ PENDING(๋Œ€๊ธฐ)์ด ๋จ.
  2. ์œ ์ €๊ฐ€ ๋ณ€์‹ฌํ•˜์—ฌ ์ฆ‰์‹œ ์ฃผ๋ฌธ ์ทจ์†Œ(cancelOrder)๋ฅผ ์š”์ฒญํ•จ. ์ƒํƒœ๋Š” CANCELED๊ฐ€ ๋˜๊ณ , ๊นŽ์˜€์„์ง€ ๋ชจ๋ฅผ ์žฌ๊ณ ๋ฅผ ์›๋ณตํ•˜๊ธฐ ์œ„ํ•ด STOCK_RESTORE(์žฌ๊ณ  ๋ณต๊ตฌ) ์ด๋ฒคํŠธ๋ฅผ Kafka๋กœ ๋ฐœํ–‰ํ•จ. (๋ณต๊ตฌ ์ด๋ฒคํŠธ #1)
  3. [๊ฒฝํ•ฉ ๋ฐœ์ƒ] ๊ทธ๋Ÿฐ๋ฐ ์ฐฐ๋‚˜์˜ ์‹œ๊ฐ„์ฐจ๋กœ, ์ธ๋ฒคํ† ๋ฆฌ ์„œ๋น„์Šค๋Š” ์œ ์ €๊ฐ€ ์ทจ์†Œํ•˜๊ธฐ ์ง์ „์— ์ด๋ฏธ ์žฌ๊ณ  ์ฐจ๊ฐ์„ ์„ฑ๊ณตํ•˜๊ณ  ORDER_APPROVED(์Šน์ธ) ๋ฉ”์‹œ์ง€๋ฅผ ์˜ค๋” ์„œ๋น„์Šค๋กœ ๋‚ ๋ ค๋‘” ์ƒํƒœ์˜€๋‹ค๋ฉด?
  4. ์˜ค๋” ์„œ๋น„์Šค๊ฐ€ ๋’ค๋Šฆ๊ฒŒ ์Šน์ธ ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ (approveOrder)ํ•จ. ์ด๋ฏธ ์ทจ์†Œ๋œ ์ฃผ๋ฌธ์ด๋ฏ€๋กœ ์ƒํƒœ ๋ณ€๊ฒฝ์€ ๋ฌด์‹œํ•˜์ง€๋งŒ, ์ธ๋ฒคํ† ๋ฆฌ์˜ ์žฌ๊ณ ๊ฐ€ ๊นŽ์˜€๋‹ค๋Š” ๋œป์ด๋ฏ€๋กœ ๋กค๋ฐฑ์„ ์œ„ํ•ด STOCK_RESTORE๋ฅผ ๋˜ ๋ฐœํ–‰ํ•จ. (๋ณต๊ตฌ ์ด๋ฒคํŠธ #2)

2. ๋ฌธ์ œ ์ธ์‹: "์ด๋ฒคํŠธ๊ฐ€ 2๋ฒˆ ๋‚ ์•„๊ฐ€๋ฉด ์žฌ๊ณ ๊ฐ€ 2๋ฐฐ๋กœ ๋Š˜์–ด๋‚˜๋Š” ๊ฒƒ ์•„๋‹Œ๊ฐ€?" (๋ฉฑ๋“ฑ์„ฑ ๊ฒฐํ•จ)

์‚ฌ์‹ค ์ด์ „์—๋„ CodeRabbit ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ†ตํ•ด "์ค‘๋ณต ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜๋‹ˆ ์ธ๋ฒคํ† ๋ฆฌ ์„œ๋น„์Šค์˜ ๋ฉฑ๋“ฑ์„ฑ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์ˆ˜์ ์ด๋‹ค"๋ผ๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•˜์Œ. ๊ทธ๋• ์ธ๋ฒคํ† ๋ฆฌ์— ์ด๋ฏธ ProcessedEvent ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ™œ์šฉํ•œ ๋ฉฑ๋“ฑ์„ฑ ๋ฐฉ์–ด ๋กœ์ง์ด ์žˆ์–ด์„œ ์ž˜๋ชป๋œ ํ”ผ๋“œ๋ฐฑ์ธ ์ค„ ์•Œ์•˜์œผ๋‚˜, ๋‹ค์‹œ ๋ณด๋‹ˆ ์˜ค๋” ์„œ๋น„์Šค์˜ ์ด๋ฒคํŠธ ๋ฐœํ–‰๋ถ€์— ๊ฒฐํ•จ์ด ์žˆ์—ˆ์Œ.

// [๊ธฐ์กด ์ฝ”๋“œ - OrderCommandService.java]
orderOutboxHelper.append("ORDER", orderId, "STOCK_RESTORE",
    // ๋ฌธ์ œ์ : ์ด๋ฒคํŠธ ID๋ฅผ ๋ฌด์กฐ๊ฑด ๋žœ๋ค UUID๋กœ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Œ!
    new StockRestoreEventPayload(UUID.randomUUID(), item.getOptionId(), item.getQuantity())
);

์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ทจ์†Œํ•  ๋•Œ์—๋„ ๋žœ๋ค UUID๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์Šน์ธ๋ฐ›์€ ํ›„ ๋กค๋ฐฑํ•  ๋•Œ์—๋„ ๋˜ ๋‹ค๋ฅธ ๋žœ๋ค UUID๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์ธ๋ฒคํ† ๋ฆฌ ์ž…์žฅ์—์„œ๋Š” "์„œ๋กœ ๋‹ค๋ฅธ 2๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ ๋ณต๊ตฌ ์š”์ฒญ"์œผ๋กœ ์ธ์‹ํ•˜์—ฌ ์žฌ๊ณ ๋ฅผ 2๋ฐฐ๋กœ ๋Š˜๋ ค๋ฒ„๋ฆฌ๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•จ.

3. 1์ฐจ ํ•ด๊ฒฐ: ๊ฒฐ์ •์ (Deterministic) UUID๋ฅผ ํ†ตํ•œ ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ

์ด ๋ฌธ์ œ๋ฅผ ๊ทผ๋ณธ์ ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, ๊ฐ™์€ ๋น„์ฆˆ๋‹ˆ์Šค ํ–‰์œ„(ํŠน์ • ์ฃผ๋ฌธ์˜ ํŠน์ • ์˜ต์…˜ ๋ณต๊ตฌ)์— ๋Œ€ํ•ด์„œ๋Š” ํ•ญ์ƒ ๋™์ผํ•œ ์ด๋ฒคํŠธ ID๊ฐ€ ์ƒ์„ฑ๋˜๋„๋ก ๊ฒฐ์ •์ (Deterministic) UUID ๋ถ€์—ฌ ๋ฐฉ์‹์œผ๋กœ ๋ฆฌํŒฉํ„ฐ๋งํ–ˆ์Œ.

// [๊ฐœ์„ ๋œ ์ฝ”๋“œ - OrderCommandService.java]
// ํŠน์ • ์ฃผ๋ฌธ๊ณผ ์˜ต์…˜์— ๋Œ€ํ•ด ํ•ญ์ƒ ๋™์ผํ•œ ์ด๋ฒคํŠธ ID๋ฅผ ๋ณด์žฅํ•˜๋Š” ์‹๋ณ„์ž ์ƒ์„ฑ
String uniqueKey = "RESTORE_" + order.getId().toString() + "_" + item.getOptionId().toString();
UUID deterministicEventId = UUID.nameUUIDFromBytes(uniqueKey.getBytes());

orderOutboxHelper.append(
    "ORDER", order.getId().toString(), "STOCK_RESTORE",
    new StockRestoreEventPayload(deterministicEventId, item.getOptionId(), item.getQuantity())
);

๐Ÿ’ก ๊ฒ€์ฆ ๋ฐ ์„ฑ๊ณผ (์‹ค์ œ ์šด์˜ ๋กœ๊ทธ)

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ, ์˜ค๋” ์„œ๋น„์Šค๋Š” ์™„๋ฒฝํžˆ ๋™์ผํ•œ UUID(7500eaee...)๋ฅผ ๊ฐ€์ง„ ์ด๋ฒคํŠธ๋ฅผ 2๋ฒˆ ๋ฐœํ–‰ํ–ˆ๊ณ , ์ธ๋ฒคํ† ๋ฆฌ ์„œ๋น„์Šค๋Š” ๋‘ ๋ฒˆ์งธ ์ด๋ฒคํŠธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐฉ์–ดํ–ˆ์Œ.

// [1] ์˜ค๋” ์„œ๋น„์Šค: ์—ฃ์ง€ ์ผ€์ด์Šค ๊ฐ์ง€ ๋ฐ 2๋ฒˆ์งธ ๋กค๋ฐฑ ์ด๋ฒคํŠธ ๋ฐœํ–‰
WARN [Order Saga Edge-Case] ์ด๋ฏธ ์œ ์ €๊ฐ€ ์ทจ์†Œํ•œ ์ฃผ๋ฌธ์— ๋Œ€ํ•ด ์Šน์ธ์ด ๋„์ฐฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ธ๋ฒคํ† ๋ฆฌ ๋กค๋ฐฑ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค: 68578038...

// [2] ์˜ค๋” ์„œ๋น„์Šค DB (p_order_outbox): ๊ฒฐ์ •์  UUID ์ƒ์„ฑ ํ™•์ธ
{"eventId": "7500eaee-df88-3cb9-a9b5-b97dc7d82d84", "optionId": "1a2e7d36...", "quantity": 1}
{"eventId": "7500eaee-df88-3cb9-a9b5-b97dc7d82d84", "optionId": "1a2e7d36...", "quantity": 1}

// [3] ์ธ๋ฒคํ† ๋ฆฌ ์„œ๋น„์Šค: ๋ฉฑ๋“ฑ์„ฑ ํ•„ํ„ฐ ์ž‘๋™ (๋‘ ๋ฒˆ์งธ ์š”์ฒญ ๋ฐฉ์–ด)
INFO [Kafka Consumer] ์žฌ๊ณ  ๋ณต๊ตฌ ์ด๋ฒคํŠธ ์ˆ˜์‹ : eventId=7500eaee-df88...
WARN [Idempotency] ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์นดํ”„์นด ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค. ์ค‘๋ณต ๋ณต๊ตฌ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. eventId=7500eaee...

๊ฒฐ๊ณผ์ ์œผ๋กœ ๋™์ผํ•œ eventId๋ฅผ ๊ฐ€์ง„ ๋กค๋ฐฑ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐœํ–‰๋˜์—ˆ๊ณ , ์ธ๋ฒคํ† ๋ฆฌ๋Š” '[Idempotency] ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์นดํ”„์นด ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.' ๋ผ๋ฉฐ ๋‘ ๋ฒˆ์งธ ์š”์ฒญ์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋ฐฉ์–ดํ–ˆ์Œ.

์ด๋ฅผ ํ†ตํ•ด At-Least-Once(์ ์–ด๋„ ํ•œ ๋ฒˆ) ์ „์†ก ๋ณด์žฅ๊ณผ ๋ฉฑ๋“ฑ์„ฑ์„ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ์ง€์ผœ๋ƒ„


4. ์ƒˆ๋กœ์šด ๋ฐœ๊ฒฌ: ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์˜ ์ตœ์ข… ๋ณด์Šค "์ˆœ์„œ ๋ฏธ๋ณด์žฅ(Out-of-Order)"๊ณผ ์žฌ๊ณ  ๋ˆ„์ˆ˜

๋ฉฑ๋“ฑ์„ฑ์„ ๋ณด์žฅํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , DB ๋ฐ์ดํ„ฐ๋ฅผ ๊ต์ฐจ ๊ฒ€์ฆํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฌธ์ œ์ƒํ™ฉ์„ ์ถ”๊ฐ€๋กœ ๋ฐœ๊ฒฌํ•จ. ์ทจ์†Œ ์ฒ˜๋ฆฌ ํ›„ ํ˜„์žฌ์˜ ์ผ์ผ ์žฌ๊ณ ๊ฐ€ 10๊ฐœ(์ตœ๋Œ€์น˜)๋กœ ์›๋ณต๋˜์ง€ ์•Š๊ณ  9๊ฐœ๋กœ ๋ˆ„์ˆ˜(Leak) ๋˜์–ด ์žˆ์—ˆ์Œ...

[์›์ธ ๋ถ„์„: ์นดํ”„์นด ํ† ํ”ฝ ๊ฐ„ ์ˆœ์„œ ๋ฏธ๋ณด์žฅ]

  1. ์˜ค๋” ์„œ๋น„์Šค๊ฐ€ ๊ฑฐ์˜ ๋™์‹œ์— ORDER_CREATED (์ฃผ๋ฌธ ํ† ํ”ฝ)์™€ STOCK_RESTORE (๋ณต๊ตฌ ํ† ํ”ฝ)๋ฅผ ์นดํ”„์นด๋กœ ๋ฐœํ–‰ํ•จ.
  2. ์„œ๋กœ ๋‹ค๋ฅธ ํ† ํ”ฝ์ด๋‹ค ๋ณด๋‹ˆ, ์ธ๋ฒคํ† ๋ฆฌ์˜ ์ปจ์Šˆ๋จธ๋“ค์ด ์ด๋ฅผ ๋™์‹œ์—(ํ˜น์€ ์ˆœ์„œ๊ฐ€ ์—ญ์ „๋˜์–ด) ์†Œ๋น„ํ•จ.
  3. ์ฐฐ๋‚˜์˜ ์ฐจ์ด๋กœ ๋ณต๊ตฌ(+1)๊ฐ€ ์ฐจ๊ฐ(-1)๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋จ.
  4. ์žฌ๊ณ ๊ฐ€ 10๊ฐœ(MAX)์ธ ์ƒํƒœ์—์„œ ๋ณต๊ตฌ(+1)๊ฐ€ ๋จผ์ € ๋“ค์–ด์˜ค๋‹ˆ ์ƒํ•œ์„ ์— ๋ง‰ํ˜€ ๊ทธ๋Œ€๋กœ 10๊ฐœ๊ฐ€ ๋จ.
  5. ์งํ›„ ์ฐจ๊ฐ(-1) ๋กœ์ง์ด ์‹คํ–‰๋˜์–ด ์žฌ๊ณ ๊ฐ€ 9๊ฐœ๊ฐ€ ๋จ.
  6. ์ดํ›„ ์˜ค๋” ์„œ๋น„์Šค๊ฐ€ ์—ฃ์ง€ ์ผ€์ด์Šค ๋ฐฉ์–ด ๋กœ์ง์œผ๋กœ ์œ ๋‘ ๋ฒˆ์งธ ๋ณต๊ตฌ(+1) ์ด๋ฒคํŠธ๊ฐ€ ๋„์ฐฉํ–ˆ์ง€๋งŒ, ์•ž์„œ 3๋ฒˆ ๋‹จ๊ณ„์—์„œ ์™„์„ฑํ•ด ๋‘” '๋ฉฑ๋“ฑ์„ฑ ํ•„ํ„ฐ'์— ๊ฑธ๋ ค ์ •์ƒ์ ์ธ ์ค‘๋ณต ์š”์ฒญ์œผ๋กœ ๊ฐ„์ฃผ๋˜์–ด ๋ฌด์‹œ๋‹นํ•จ.
  7. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์žฌ๊ณ ๊ฐ€ 9๊ฐœ๋กœ ๋ˆ„์ˆ˜๋จ.

5. ์ตœ์ข… ํ•ด๊ฒฐ ๊ณผ์ •: ๋‹จ์ผ ํ† ํ”ฝ ํ†ตํ•ฉ & ํŒŒํ‹ฐ์…˜ ํ‚ค ๋ผ์šฐํŒ…

์ด ํ˜„์ƒ์€ ๋Œ€๊ทœ๋ชจ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์˜ 'ํ† ํ”ฝ ๊ฐ„ ๋ฉ”์‹œ์ง€ ์ˆœ์„œ ์—ญ์ „(Out-of-Order Delivery)' ๋ฌธ์ œ์˜€์Œ

1) ๋‹จ์ผ ์ปค๋งจ๋“œ ํ† ํ”ฝ ํ†ตํ•ฉ (application.yml)

๊ธฐ์กด์— ๋‚˜๋‰˜์–ด ์žˆ๋˜ ์ฐจ๊ฐ/๋ณต๊ตฌ ๋ช…๋ น ํ† ํ”ฝ์„ inventory.command๋ผ๋Š” ๋‹จ์ผ ํ† ํ”ฝ์œผ๋กœ ํ†ตํ•ฉํ•จ

2) ํŒŒํ‹ฐ์…˜ ํ‚ค(Partition Key) ์ง€์ • (OrderCommandService.java)

์นดํ”„์นด๋Š” ๋™์ผํ•œ Key๋ฅผ ๊ฐ€์ง„ ๋ฉ”์‹œ์ง€๋ฅผ ๋™์ผํ•œ ํŒŒํ‹ฐ์…˜์— ์ˆœ์„œ๋Œ€๋กœ ๋„ฃ์Œ. ์ด๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด, ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ ์‹œ Key ๊ฐ’์„ ๊ธฐ์กด orderId์—์„œ reservationId๋กœ ํ†ต์ผํ•จ. (์ด์ „์— ์ž‘์—…ํ•ด๋‘” ORDER_CREATED์™€ ๋˜‘๊ฐ™์€ ์นดํ”„์นด ํŒŒํ‹ฐ์…˜์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ ์ €๋ ‡๊ฒŒ ๋„ฃ์—ˆ์Œ.)

// ํŒŒํ‹ฐ์…˜ ํ‚ค๋ฅผ reservationId๋กœ ํ†ต์ผํ•˜์—ฌ In-Order ๋ณด์žฅ
orderOutboxHelper.append(
	"ORDER",
	order.getReservationId().toString(), // Partition Key ํ†ต์ผ
  "STOCK_RESTORE",
  payload
);

6. ์ตœ์ข… ๊ฒ€์ฆ

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ, ๋™์ผ ์ฃผ๋ฌธ์— ๋Œ€ํ•œ ์ฐจ๊ฐ๊ณผ ๋ณต๊ตฌ๊ฐ€ FIFO ์ˆœ์„œ๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ์œผ๋ฉฐ ์žฌ๊ณ  ๋ˆ„์ˆ˜๋„ ํ•ด๊ฒฐ๋จ.

// [1] In-Order ๋ณด์žฅ (๋ฌด์กฐ๊ฑด ์ฐจ๊ฐ -> ๋ณต๊ตฌ ์ˆœ์„œ๋กœ ์‹คํ–‰๋จ)
19:22:09.028 [Kafka Consumer] ์‹ ๊ทœ ์ฃผ๋ฌธ ์ƒ์„ฑ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  -> ์žฌ๊ณ  ๋‹ค์ค‘ ์ฐจ๊ฐ ์‹œ๋„ (In-Order ์ฒ˜๋ฆฌ)
19:22:09.102 [Kafka Consumer] ์žฌ๊ณ  ๋ณต๊ตฌ ์ด๋ฒคํŠธ ์ˆ˜์‹  (In-Order ์ฒ˜๋ฆฌ)

// [2] ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ (์‹œ๊ฐ„์ฐจ๋กœ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•œ 2๋ฒˆ์งธ ๋ณต๊ตฌ ์š”์ฒญ ์ฐจ๋‹จ)
19:22:13.986 [Idempotency] ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์นดํ”„์นด ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค. ์ค‘๋ณต ๋ณต๊ตฌ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.