Lock ๐Ÿ”

๊น€๊ธฐํ›ˆยท2026๋…„ 2์›” 10์ผ

์ด๋ก 

๋ชฉ๋ก ๋ณด๊ธฐ
4/10

Lock ๐Ÿ” (Django ๊ธฐ์ค€)


๋น„๊ด€์  ๋ฝ(Pessimistic Lock) ๐Ÿ”’

  • "์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋ผ๊ณ  ๋น„๊ด€์ ์œผ๋กœ ๊ฐ€์ •"ํ•ฉ๋‹ˆ๋‹ค.

    • ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ์‹œ์ ์— ์ฆ‰์‹œ ๋ฝ์„ ๊ฑธ์–ด ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜์ •ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์›์ฒœ ์ฐจ๋‹จํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

select_for_update()

  • ๋ฐ˜๋“œ์‹œ transaction.atomic() ๋ธ”๋ก ๋‚ด์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋“œ

  • select_for_update() ๋Š” ํ•ด๋‹น ๋กœ์šฐ๊ฐ€ ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ์ ๊นŒ์ง€ ์ž ๊ธฐ๋„๋ก
  • SELECT ... FOR UPDATE SQL ๋ฌธ์„ ์ƒ์„ฑ
from django.db import transaction
from .models import Product

def decrease_stock_pessimistic(product_id, quantity):
    # 1. atomic() ํŠธ๋žœ์žญ์…˜ ๋ธ”๋ก์ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
    with transaction.atomic():
        # 2. select_for_update()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DB ์ˆ˜์ค€์˜ ๋ฝ์„ ํš๋“ํ•ฉ๋‹ˆ๋‹ค.
        # ์ด ์‹œ์ ์— ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ํ•ด๋‹น ๋กœ์šฐ๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋Œ€๊ธฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
        product = Product.objects.select_for_update().get(id=product_id)
        
        if product.stock >= quantity:
            product.stock -= quantity
            product.save() # save() ์™„๋ฃŒ ํ›„ ํŠธ๋žœ์žญ์…˜์ด ๋๋‚  ๋•Œ ๋ฝ์ด ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค.
        else:
            raise ValueError("์žฌ๊ณ ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค.")

select_for_update(nowait=True)

  • ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ด๋ฏธ ๋ฝ์„ ๊ฑธ์—ˆ์„ ๋•Œ
  • ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋ฐ”๋กœ DatabaseError๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ๋น ๋ฅธ ์‘๋‹ต ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๋น„๊ด€์  ๋ฝ์˜ ์น˜๋ช…์ ์ธ ๋‹จ์  โ€ผ๏ธ

  • ๋น„๊ด€์  ๋ฝ์€ ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, ๊ทธ๋งŒํผ ๋Œ€๊ฐ€๊ฐ€ ํฝ๋‹ˆ๋‹ค.

    • ์„ฑ๋Šฅ ์ €ํ•˜ (Throughput ๊ฐ์†Œ)

      • ํŠน์ • ๋ฐ์ดํ„ฐ์— ๋ฝ์ด ๊ฑธ๋ฆฌ๋ฉด ๋‹ค๋ฅธ ๋ชจ๋“  ์š”์ฒญ์€ ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์ด ๋๋‚  ๋•Œ๊นŒ์ง€
        • "์ค„์„ ์„œ์„œ ๋Œ€๊ธฐ"ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
      • ์ด๋Š” ์›น ์„œ๋น„์Šค์˜ ์‘๋‹ต ์†๋„๋ฅผ ๋Šฆ์ถ”๋Š” ์ฃผ๋ฒ”์ด ๋ฉ๋‹ˆ๋‹ค.
    • ๋ฐ๋“œ๋ฝ(Deadlock) ์œ„ํ—˜

      • ์—ฌ๋Ÿฌ ํŠธ๋žœ์žญ์…˜์ด ์„œ๋กœ๊ฐ€ ๊ฐ€์ง„ ๋ฝ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ต์ฐฉ ์ƒํƒœ์— ๋น ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • ์‹œ์Šคํ…œ ์ „์ฒด๊ฐ€ ๋ฉˆ์ถœ ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.
    • DB ์ปค๋„ฅ์…˜ ์ ์œ 

      • ๋ฝ์„ ์œ ์ง€ํ•˜๋Š” ๋™์•ˆ DB ์ปค๋„ฅ์…˜์„ ๊ณ„์† ๋ถ™์žก๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
      • ๋™์‹œ ์ ‘์†์ž๊ฐ€ ๋งŽ์•„์ง€๋ฉด ์ปค๋„ฅ์…˜ ํ’€์ด ๊ณ ๊ฐˆ๋˜์–ด ์„œ๋น„์Šค ์žฅ์• ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค.

๋‚™๊ด€์  ๋ฝ(Optimistic Lock) ๐Ÿ”’

  • "์ถฉ๋Œ์ด ๊ฑฐ์˜ ๋ฐœ์ƒํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ๊ณ  ๋‚™๊ด€์ ์œผ๋กœ ๊ฐ€์ •" ํ•ฉ๋‹ˆ๋‹ค.

    • ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ๋•Œ๋Š” ๋ฝ์„ ๊ฑธ์ง€ ์•Š๊ณ , ์ˆ˜์ • ์™„๋ฃŒ ์‹œ์ ์— ๋‚ด๊ฐ€ ์ฝ์—ˆ๋˜ ๋ฐ์ดํ„ฐ๊ฐ€
    • ๊ทธ์‚ฌ์ด ๋ณ€ํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ ์ถฉ๋Œ์„ ๊ฐ์ง€ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

Race Condition (๊ฒฝ์Ÿ ์ƒํƒœ)

  • ๋‘ ๊ฐœ ์ด์ƒ์˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๊ณตํ†ต ์ž์›์— ๋™์‹œ์— ์ ‘๊ทผํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค.

Version / F

  • Django๋Š” ๋ณ„๋„์˜ @Version ๊ฐ™์€ ๋‚ด์žฅ ํ•„๋“œ๊ฐ€ ์—†์Œ
  • ๋”ฐ๋ผ์„œ version ํ•„๋“œ๋ฅผ ์ง์ ‘ ์ •์˜ํ•˜๊ฑฐ๋‚˜
    • F ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•œ ์›์ž์ (Atomic) ์—…๋ฐ์ดํŠธ ๋ฐฉ์‹์„ ์ฃผ๋กœ ์‚ฌ์šฉ

F ๊ฐ์ฒด

  • ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹ (F ๊ฐ์ฒด ๋ฏธ์‚ฌ์šฉ):

    • DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ํŒŒ์ด์ฌ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฝ๋‹ˆ๋‹ค. (stock = 10)
    • ํŒŒ์ด์ฌ์—์„œ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. (10 - 1 = 9)
    • ๊ณ„์‚ฐ๋œ ๊ฒฐ๊ณผ 9๋ฅผ DB์— ๋‹ค์‹œ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. (UPDATE ... SET stock = 9)
    • ์œ„ํ—˜์„ฑ
      • ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ €์žฅํ•˜๋Š” ์ฐฐ๋‚˜์˜ ์ˆœ๊ฐ„์— ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ์žฌ๊ณ ๋ฅผ ๋ฐ”๊ฟ”๋ฒ„๋ฆฌ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๊ผฌ์ž…๋‹ˆ๋‹ค.
  • F ๊ฐ์ฒด ๋ฐฉ์‹:

    • ํŒŒ์ด์ฌ์€ ๊ณ„์‚ฐ์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    • ๋Œ€์‹  "DB์•ผ, ๋„ค๊ฐ€ ๊ฐ€์ง„ stock ๊ฐ’์—์„œ quantity๋งŒํผ ๋นผ์ค˜"๋ผ๋Š” SQL ๋ช…๋ น์–ด๋งŒ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
    • ์‹ค์ œ ๊ณ„์‚ฐ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—”์ง„ ๋‚ด๋ถ€์—์„œ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค.
  • F ๊ฐ์ฒด ์‚ฌ์šฉ์˜ ์žฅ์ 

    • ๊ฒฝ์Ÿ ์ƒํƒœ(Race Condition) ๋ฐฉ์ง€
      • ์—ฌ๋Ÿฌ ์š”์ฒญ์ด ๋™์‹œ์— ๋“ค์–ด์™€๋„ DB๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ์—ฐ์‚ฐ์„ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์ด ์ง€์ผœ์ง‘๋‹ˆ๋‹ค.
      • ์ด๊ฒƒ์ด ๋ฐ”๋กœ ๋‚™๊ด€์  ๋ฝ์˜ ํ•ต์‹ฌ ์›๋ฆฌ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.
    • ์„ฑ๋Šฅ ์ตœ์ ํ™”
      • ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ด์ฌ ๋ฉ”๋ชจ๋ฆฌ๋กœ ๋ถˆ๋Ÿฌ์˜ฌ(SELECT) ํ•„์š” ์—†์ด ๋ฐ”๋กœ UPDATE ๋ช…๋ น์„ ๋‚ด๋ฆฌ๋ฏ€๋กœ
      • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ†ต์‹  ํšŸ์ˆ˜์™€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.
    • ์›์ž์„ฑ(Atomicity)
      • ์กฐํšŒ์™€ ์ˆ˜์ •์„ ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์›์ž์ ์ธ ์ž‘์—…์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค

์ฝ”๋“œ

  • F ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•œ ์›์ž์  ์—…๋ฐ์ดํŠธ

    • ๋ณ„๋„์˜ ๋ฒ„์ „ ํ•„๋“œ ์—†์ด๋„ Race Condition์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฐ€์žฅ "ํŒŒ์ด์ฌ์Šค๋Ÿฌ์šด" ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
    • DB ์ˆ˜์ค€์—์„œ ํ˜„์žฌ ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ์—ฐ์‚ฐํ•ฉ๋‹ˆ๋‹ค
from django.db.models import F
from .models import Product

def decrease_stock_f_expression(product_id, quantity):
    # filter ์กฐ๊ฑด์— ์žฌ๊ณ  ํ™•์ธ ๋กœ์ง์„ ํฌํ•จํ•˜์—ฌ ์ฟผ๋ฆฌ ํ•œ ๋ฒˆ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    # UPDATE product SET stock = stock - quantity WHERE id = ? AND stock >= quantity
    updated_count = Product.objects.filter(
        id=product_id, 
        stock__gte=quantity
    ).update(stock=F('stock') - quantity) โ™ฆ๏ธ

    if updated_count == 0:
        # ์กฐ๊ฑด์— ๋งž๋Š” ๋กœ์šฐ๊ฐ€ ์—†๊ฑฐ๋‚˜(ID ๋ถ€์žฌ), ๊ทธ์‚ฌ์ด ์žฌ๊ณ ๊ฐ€ ์ค„์–ด๋“  ๊ฒฝ์šฐ
        raise ValueError("์žฌ๊ณ ๊ฐ€ ๋ถ€์กฑํ•˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
  • ์ˆ˜๋™ ๋ฒ„์ „ ๊ด€๋ฆฌ (JPA ๋ฐฉ์‹๊ณผ ์œ ์‚ฌ)

    • ์—”ํ‹ฐํ‹ฐ์— version ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ง์ ‘ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
from .models import Product

def update_product_manual_version(product_id, new_name):
    product = Product.objects.get(id=product_id)
    current_version = product.version โ™ฆ๏ธ

    # ๋ฐ์ดํ„ฐ ์ˆ˜์ •
    product.name = new_name
    product.version += 1

    # ์—…๋ฐ์ดํŠธ ์‹œ์ ์— ์ฒ˜์Œ ์ฝ์—ˆ๋˜ ๋ฒ„์ „์ธ์ง€ ํ™•์ธ
    updated = Product.objects.filter(
        id=product_id, 
        version=current_version
    ).update(name=new_name, version=F('version') + 1)

    if not updated:
        raise Exception("์ด๋ฏธ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์— ์˜ํ•ด ์ˆ˜์ •๋œ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.")

๋‚™๊ด€์  ๋ฝ์ด ๋น›์„ ๋ฐœํ•˜๋Š” ์ƒํ™ฉ โœจ

  • ๋‚™๊ด€์  ๋ฝ์€ "์ถฉ๋Œ์ด ๊ฐ€๋” ์ผ์–ด๋‚œ๋‹ค"๋Š” ์ „์ œํ•˜์— ์‹œ์Šคํ…œ์˜ ์ „์ฒด์ ์ธ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๊ทน๋Œ€ํ™”
    • ์ฝ๊ธฐ ์ž‘์—…์ด ์••๋„์ ์œผ๋กœ ๋งŽ์„ ๋•Œ

      • ๋Œ€๋ถ€๋ถ„์˜ ์›น ์„œ๋น„์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ์š”์ฒญ๋ณด๋‹ค ์กฐํšŒํ•˜๋Š” ์š”์ฒญ์ด ํ›จ์”ฌ ๋งŽ์Šต๋‹ˆ๋‹ค.
      • ์กฐํšŒํ•  ๋•Œ๋งˆ๋‹ค ๋ฝ์„ ๊ฑธ๋ฉด(Shared Lock ํฌํ•จ) ์„ฑ๋Šฅ์ด ์‹ฌ๊ฐํ•˜๊ฒŒ ์ €ํ•˜๋˜๋Š”๋ฐ,
      • ๋‚™๊ด€์  ๋ฝ์€ ์กฐํšŒ ์‹œ ์•„๋ฌด๋Ÿฐ ์ œ์•ฝ์„ ์ฃผ์ง€ ์•Š์œผ๋ฏ€๋กœ ๋งค์šฐ ๋น ๋ฆ…๋‹ˆ๋‹ค.
    • ํŠธ๋žœ์žญ์…˜์ด ๊ธธ์–ด์งˆ ๋•Œ

      • ์‚ฌ์šฉ์ž๊ฐ€ ๊ธ€์„ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด ํŽธ์ง‘ ์ฐฝ์„ ์—ด์–ด๋‘๊ณ  10๋ถ„ ๋’ค์— '์ €์žฅ'์„ ๋ˆ„๋ฅธ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค.
      • ๋น„๊ด€์  ๋ฝ
        • 10๋ถ„ ๋™์•ˆ ๋‹ค๋ฅธ ๋ˆ„๊ตฌ๋„ ๊ทธ ๊ธ€์„ ์ฝ๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋ง‰์Šต๋‹ˆ๋‹ค. (์‚ฌ์‹ค์ƒ ๋ถˆ๊ฐ€๋Šฅ)
      • ๋‚™๊ด€์  ๋ฝ
        • ์ €์žฅ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ์ˆœ๊ฐ„์—๋งŒ ๋ฒ„์ „์ด ๋ฐ”๋€Œ์—ˆ๋Š”์ง€ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค. ํšจ์œจ์„ฑ์ด ์••๋„์ ์ž…๋‹ˆ๋‹ค.

์‹ค๋ฌด ์ ์šฉ ๊ฐ€์ด๋“œ

๋น„๊ด€์  ๋ฝ ๊ถŒ์žฅ

  • ์žฌ๊ณ  ์ฐจ๊ฐ, ํฌ์ธํŠธ ๊ฒฐ์ œ
    • ๋ฐ์ดํ„ฐ์˜ ์ •ํ™•์„ฑ์ด ์ƒ๋ช…์ด๋ฏ€๋กœ select_for_update()๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๋‚™๊ด€์  ๋ฝ ๊ถŒ์žฅ

  • ๋‹จ์ˆœ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •, ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ณ€๊ฒฝ
    • ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ์œผ๋ฏ€๋กœ ๋ฝ ์—†์ด ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ F ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•œ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์„ฑ๋Šฅ ์ตœ์ ํ™”

  • select_for_update()๋Š” DB์˜ ์ปค๋„ฅ์…˜ ์ ์œ  ์‹œ๊ฐ„์„ ๋Š˜๋ฆฌ๋ฏ€๋กœ
    • ํŠธ๋ž˜ํ”ฝ์ด ๋งค์šฐ ๋†’๋‹ค๋ฉด Redis๋ฅผ ํ™œ์šฉํ•œ ๋ถ„์‚ฐ ๋ฝ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์„ ํƒ๊ธฐ์ค€

  • ๋น„๊ด€์  ๋ฝ์„ ์“ฐ๋Š” ๊ฒฝ์šฐ (ํŠน์ˆ˜ ์ƒํ™ฉ)

    • ์žฌ๊ณ ๊ฐ€ 1๊ฐœ ๋‚จ์€ ์ดˆํŠน๊ฐ€ ์„ธ์ผ ์ƒํ’ˆ (์ถฉ๋Œ์ด 100% ํ™•์‹คํ•  ๋•Œ)
    • ๊ธˆ์œต ์‹œ์Šคํ…œ์˜ ์ž”์•ก ์ด์ฒด (๋‹จ 0.1%์˜ ์˜ค์ฐจ๋„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„๊ฐ€ ๋งค์šฐ ๋ณต์žกํ•  ๋•Œ)
  • ๋‚™๊ด€์  ๋ฝ์„ ์“ฐ๋Š” ๊ฒฝ์šฐ (์ผ๋ฐ˜ ์ƒํ™ฉ)

    • ๋Œ€๋ถ€๋ถ„์˜ ๊ฒŒ์‹œํŒ, ํ”„๋กœํ•„ ์ˆ˜์ •, ์„ค์ • ๋ณ€๊ฒฝ
    • ๋™์‹œ ์ˆ˜์ • ํ™•๋ฅ ์ด ๋‚ฎ์ง€๋งŒ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์€ ์ง€์ผœ์•ผ ํ•  ๋•Œ
    • ์„ฑ๋Šฅ(์‘๋‹ต ์†๋„)์ด ๋งค์šฐ ์ค‘์š”ํ•œ ๋Œ€๊ทœ๋ชจ ํŠธ๋ž˜ํ”ฝ ์„œ๋น„์Šค

์ถ”๊ฐ€ ํ•™์Šต

  • ๋ถ„์‚ฐ ๋ฝ (Distributed Lock)

    • Redis์˜ Redlock ๋“ฑ์„ ์ด์šฉํ•ด DB ๋ถ€ํ•˜๋ฅผ ์ค„์ด๋ฉด์„œ ๋™์‹œ์„ฑ์„ ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•.
  • ๋ฉ”์‹œ์ง€ ํ (Message Queue)

    • ์š”์ฒญ์„ ํ์— ์Œ“์•„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ DB ๋ฝ ์—†์ด ๋™์‹œ์„ฑ์„ ํ•ด๊ฒฐํ•˜๋Š” ๊ตฌ์กฐ.
  • ๋ฉฑ๋“ฑ์„ฑ (Idempotency)

    • ์—ฌ๋Ÿฌ ๋ฒˆ ์š”์ฒญํ•ด๋„ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์€ API ์„ค๊ณ„ ๋ฐฉ์‹ (๋‚™๊ด€์  ๋ฝ ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„์™€ ์—ฐ๊ด€).

๋ ˆ๋””์Šค ๋ฝ ๐Ÿ”’

  • ์—ฌ๋Ÿฌ ๋Œ€์˜ ์„œ๋ฒ„๊ฐ€ ๊ณต์œ  ์ž์›์— ์ ‘๊ทผํ•˜๋Š” ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š”
    • '๋ถ„์‚ฐ ๋ฝ(Distributed Lock)'์˜ ๋Œ€ํ‘œ์ ์ธ ๊ตฌํ˜„ ๋ฐฉ์‹
  • ์ผ๋ฐ˜์ ์ธ DB ๋ฝ์€ ํŠน์ • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ ˆ์ฝ”๋“œ๋‚˜ ํ…Œ์ด๋ธ”์— ๋ฝ์„ ๊ฒ๋‹ˆ๋‹ค.
    • ํ•˜์ง€๋งŒ ์„œ๋น„์Šค ๊ทœ๋ชจ๊ฐ€ ์ปค์ ธ์„œ ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€(Distributed System)๊ฐ€ ๋˜๋ฉด
    • ๊ฐ ์„œ๋ฒ„๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ DB ์ปค๋„ฅ์…˜์„ ๊ฐ€์ง€๊ธฐ ๋•Œ๋ฌธ์— DB ๋ ˆ๋ฒจ์˜ ๋ฝ๋งŒ์œผ๋กœ๋Š” ํ•œ๊ณ„๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด๋•Œ Redis๋ผ๋Š” ์™ธ๋ถ€ ์ €์žฅ์†Œ๋ฅผ ํ™œ์šฉํ•ด "ํ˜„์žฌ ์ด ์ž์›์€ ๋‚ด๊ฐ€ ์‚ฌ์šฉ ์ค‘์ด๋‹ค"๋ผ๋Š” ํ‘œ์‹(Ticket)์„
    • ๋‚จ๊น€์œผ๋กœ์จ ์—ฌ๋Ÿฌ ์„œ๋ฒ„ ๊ฐ„์˜ ๋™๊ธฐํ™”๋ฅผ ๋งž์ถ”๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ ๋ ˆ๋””์Šค ๋ฝ์ž…๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๋ช…๋ น์–ด

  • SET resource_name my_random_value NX PX 30000
    • NX (Not eXists): key๊ฐ€ ์—†์„ ๋•Œ๋งŒ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. (๋ฝ ํš๋“ ์‹œ๋„)
    • PX 30000: 30,000 ๋ฐ€๋ฆฌ์ดˆ(30์ดˆ) ํ›„์— ์ž๋™์œผ๋กœ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. (๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€)
    • my_random_value: ๋ฝ์„ ํ•ด์ œํ•  ๋•Œ ๋ณธ์ธ์ด ์žก์€ ๋ฝ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ์œ  ๊ฐ’์ž…๋‹ˆ๋‹ค.

์‹ค๋ฌด์ ์ธ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  • Redisson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

    • ๋‹จ์ˆœํžˆ SETNX๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ฉด ๋ฝ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๊ด€๋ฆฌ๋‚˜ ์žฌ์‹œ๋„(Retry) ๋กœ์ง์„ ์ง์ ‘ ์งœ์•ผ ํ•˜๋ฏ€๋กœ ๋งค์šฐ ๋ณต์žก
    • ์ž๋ฐ”(Spring) ์ง„์˜์—์„œ๋Š” ์ด๋ฅผ ์ถ”์ƒํ™”ํ•œ Redisson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ‘œ์ค€์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • Redisson์„ ์ด์šฉํ•œ ๋ถ„์‚ฐ ๋ฝ ์˜ˆ์ œ ์ฝ”๋“œ

    • Watchdog ๊ธฐ๋Šฅ
      • Redisson์˜ ํŠน์ง•์œผ๋กœ, ๋กœ์ง์ด ๊ธธ์–ด์ ธ ๋ฝ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ๋‹ค๊ฐ€์˜ค๋ฉด ์ž๋™์œผ๋กœ ์‹œ๊ฐ„์„ ์—ฐ์žฅํ•ด์ค๋‹ˆ๋‹ค.
    • tryLock()
      • ๋ฝ์„ ํš๋“ํ•  ๋•Œ๊นŒ์ง€ ๋ฌดํ•œ์ • ๋Œ€๊ธฐํ•˜์ง€ ์•Š๊ณ 
      • ์ง€์ •๋œ ์‹œ๊ฐ„(waitTime)๋งŒํผ๋งŒ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‹œ์Šคํ…œ ์ „์ฒด์˜ ์Šค๋ ˆ๋“œ ์ ์œ ๋ฅผ ๋ง‰์•„์ค๋‹ˆ๋‹ค.
    • isHeldByCurrentThread()
      • ๋‚ด๊ฐ€ ์žก์ง€ ์•Š์€ ๋ฝ์„ ์–ต์ง€๋กœ ํ•ด์ œํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋ฐ˜๋“œ์‹œ ํ™•์ธ ํ›„ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class StockService {

    private final RedissonClient redissonClient;

    public StockService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public void decreaseStock(Long productId) {
        // 1. ํ•ด๋‹น ์ž์›์— ๋Œ€ํ•œ ๋ฝ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        RLock lock = redissonClient.getLock("lock:product:" + productId);

        try {
            // 2. ๋ฝ ํš๋“ ์‹œ๋„ (์ตœ๋Œ€ 10์ดˆ ๋Œ€๊ธฐ, ํš๋“ ํ›„ 1์ดˆ๊ฐ„ ์œ ์ง€)
            // waitTime: ๋ฝ์„ ์–ป๊ธฐ ์œ„ํ•ด ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„
            // leaseTime: ๋ฝ์„ ํš๋“ํ•œ ํ›„ ์ ์œ ํ•˜๋Š” ์‹œ๊ฐ„
            boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS);

            if (!available) {
                System.out.println("๋ฝ ํš๋“์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
                return;
            }

            // 3. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜ํ–‰ (์žฌ๊ณ  ๊ฐ์†Œ ๋“ฑ)
            System.out.println("์žฌ๊ณ ๋ฅผ ๊ฐ์†Œ์‹œํ‚ต๋‹ˆ๋‹ค.");

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 4. ๋ฐ˜๋“œ์‹œ ๋ฝ์„ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค. (ํ˜„์žฌ ์“ฐ๋ ˆ๋“œ๊ฐ€ ๋ฝ์„ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๋•Œ๋งŒ)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

profile
์•ˆ๋…•ํ•˜์„ธ์š”.

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