Post

๐Ÿ‘ ์ข‹์•„์š” ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋Š” ๊ฑด 1์ดˆ, ์„ค๊ณ„ํ•˜๋Š” ๊ฑด ํ•˜๋ฃจ

๐Ÿ‘ ์ข‹์•„์š” ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋Š” ๊ฑด 1์ดˆ, ์„ค๊ณ„ํ•˜๋Š” ๊ฑด ํ•˜๋ฃจ

TL;DR ๐Ÿ’ก ์ข‹์•„์š” ๊ธฐ๋Šฅ์„ ์„ค๊ณ„ํ•˜๋ฉด์„œ ์‚ญ์ œ ์ „๋žต, ์ค‘๋ณต ์š”์ฒญ(๋”ฐ๋‹ฅ) ์ฒ˜๋ฆฌ, ์ƒํ’ˆ ์ƒํƒœ ๋ถ„๋ฆฌ๊นŒ์ง€ ๊ณ ๋ฏผ์ด ๊ผฌ๋ฆฌ๋ฅผ ๋ฌผ์—ˆ๋‹ค. โ€˜๋‹จ์ˆœํ•œ ๊ธฐ๋Šฅโ€™์€ ์—†์—ˆ๋‹ค.

๐Ÿ‘ ์ข‹์•„์š” ํ•˜๋‚˜์ฏค์ด์•ผ

์ด์ปค๋จธ์Šค ์„ค๊ณ„ ๊ณผ์ œ์—์„œ ์ข‹์•„์š” ๊ธฐ๋Šฅ์„ ์ ‘ํ–ˆ๋‹ค. ์ฒ˜์Œ์—” likes ํ…Œ์ด๋ธ” ํ•˜๋‚˜ ๋งŒ๋“ค๊ณ  INSERT/DELETE ํ•˜๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€?โ€™๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๊ทผ๋ฐ ๋ง‰์ƒ ์„ค๊ณ„๋ฅผ ์‹œ์ž‘ํ•˜๋‹ˆ๊นŒ ์งˆ๋ฌธ์ด ๋๋„ ์—†์ด ๋‚˜์™”๋‹ค.

  • ์ทจ์†Œํ•˜๋ฉด ์ง„์งœ ์ง€์›Œ? ๋‚จ๊ฒจ๋‘ฌ?
  • ๊ฐ™์€ ๋ฒ„ํŠผ ๋‘ ๋ฒˆ ๋ˆŒ๋ฆฌ๋ฉด?
  • ์ข‹์•„์š” ๋ˆ„๋ฅธ ์ƒํ’ˆ์ด ํ’ˆ์ ˆ์ด๋ฉด ์–ด๋–ป๊ฒŒ ๋ณด์—ฌ์ค˜?

ํ•˜๋‚˜์”ฉ ์ •๋ฆฌํ•˜๋‹ค ๋ณด๋‹ˆ ํ•˜๋ฃจ๊ฐ€ ๊ฐ”๋‹ค.


1. ๐Ÿ—‘๏ธ ์ง€์šธ ๊ฑฐ์•ผ, ๋‚จ๊ธธ ๊ฑฐ์•ผ

์ข‹์•„์š” ์ทจ์†Œ๋ฅผ ๋ˆ„๋ฅด๋ฉด DB์—์„œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€. ๋‘ ๊ฐ€์ง€ ์„ ํƒ์ง€๊ฐ€ ์žˆ์—ˆ๋‹ค.

Soft Delete โ€” deleted_at์— ์‹œ๊ฐ„๋งŒ ์ฐ๊ณ  ํ–‰์€ ๋‚จ๊ฒจ๋‘ . ์ด๋ ฅ์ด ๋ณด์กด๋˜๋‹ˆ๊นŒ ๋‚˜์ค‘์— ์ถ”์ฒœ/๋žญํ‚น ๋ฐ์ดํ„ฐ๋กœ ํ™œ์šฉ ๊ฐ€๋Šฅ.

Hard Delete โ€” ๋ฌผ๋ฆฌ ์‚ญ์ œ. ํ…Œ์ด๋ธ”์ด ๊น”๋”ํ•˜์ง€๋งŒ ์ทจ์†Œ ์ด๋ ฅ์ด ์‚ฌ๋ผ์ง.

์ข‹์•„์š” ํŠน์„ฑ์ƒ ๋“ฑ๋ก/์ทจ์†Œ๊ฐ€ ๋นˆ๋ฒˆํ•˜๋‹ค. Soft Delete๋กœ ๊ฐ€๋ฉด UK(Unique Key) ์„ค๊ณ„๊ฐ€ ๊ท€์ฐฎ์•„์ง„๋‹ค.

UNIQUE (user_id, product_id)๋กœ ์žก์œผ๋ฉด deleted_at์ด ์ฐํžŒ ํ–‰ ๋•Œ๋ฌธ์— ์žฌ๋“ฑ๋ก์ด ๋ง‰ํžŒ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  UNIQUE (user_id, product_id, deleted_at)์œผ๋กœ ์žก์œผ๋ฉด? MySQL์—์„œ NULL์€ UK ์ค‘๋ณต ์ฒดํฌ์—์„œ ๋น ์ง€๊ธฐ ๋•Œ๋ฌธ์— ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

๊ฑฐ๊ธฐ๋‹ค ๋ชจ๋“  ์กฐํšŒ ์ฟผ๋ฆฌ์— WHERE deleted_at IS NULL์„ ๋นผ๋จน์œผ๋ฉด ์‚ญ์ œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์„ž์—ฌ ๋‚˜์˜จ๋‹ค.

์š”๊ตฌ์‚ฌํ•ญ์— โ€˜์ข‹์•„์š” ๋ฐ์ดํ„ฐ๋Š” ํ–ฅํ›„ ์ถ”์ฒœ/๋žญํ‚น ๊ธฐ์ดˆ ๋ฐ์ดํ„ฐ๋กœ ํ™œ์šฉ ๊ฐ€๋Šฅโ€™์ด๋ผ๋Š” ๋ฌธ์žฅ์ด ์žˆ์–ด์„œ ์ข€ ๊ฑธ๋ ธ๋‹ค. ์ด๋ ฅ์ด ์—†์œผ๋ฉด ํ–‰๋™ ํŒจํ„ด ๋ถ„์„์„ ๋ชป ํ•˜๋‹ˆ๊นŒ.

๊ทผ๋ฐ ๋‹ค์‹œ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ, ์ถ”์ฒœ/๋žญํ‚น์— ํ•„์š”ํ•œ ๊ฑด โ€˜์ง€๊ธˆ ๋ˆ„๊ฐ€ ๋ญ˜ ์ข‹์•„์š” ํ•˜๊ณ  ์žˆ๋Š”์ง€โ€™์ด์ง€, โ€˜3์ผ ์ „์— ๋ˆŒ๋ €๋‹ค ์ทจ์†Œํ–ˆ๋‹คโ€™ ๊ฐ™์€ ์ด๋ ฅ์€ ์•„๋‹Œ ๊ฒƒ ๊ฐ™๋‹ค. ํ–‰๋™ ํŒจํ„ด ๋ถ„์„์ด ์ •๋ง ํ•„์š”ํ•ด์ง€๋ฉด, likes ํ…Œ์ด๋ธ”์„ ๊ฑด๋“œ๋ฆฌ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๋ณ„๋„ ์ด๋ฒคํŠธ ํ…Œ์ด๋ธ”์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒŒ ๋งž์ง€ ์•Š์„๊นŒ.

ํšŒ์‚ฌ ์ฝ”๋“œ(favorite_goods)๋„ ํ™•์ธํ•ด๋ดค๋Š”๋ฐ ๋ฌผ๋ฆฌ ์‚ญ์ œ ๋ฐฉ์‹์ด์—ˆ๋‹ค.

๊ฒฐ๋ก : Hard Delete. ์ข‹์•„์š”์˜ ํ•ต์‹ฌ ๊ฐ€์น˜๋Š” โ€˜ํ˜„์žฌ ์ƒํƒœโ€™์ด์ง€ โ€˜์ด๋ ฅโ€™์ด ์•„๋‹ˆ๋ผ๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.


2. ๐Ÿ–ฑ๏ธ ๋”ฐ๋‹ฅ โ€” ๊ฐ™์€ ๋ฒ„ํŠผ ๋‘ ๋ฒˆ ๋ˆŒ๋ ธ์„ ๋•Œ

์ข‹์•„์š” ๋ฒ„ํŠผ์„ ๋น ๋ฅด๊ฒŒ ๋‘ ๋ฒˆ ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ. ํ”„๋ก ํŠธ์—์„œ ๋ง‰์•„์•ผ ํ•˜๋Š” ๊ฑฐ ์•„๋‹ˆ๋ƒ๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๋ฐฑ์—”๋“œ๋Š” โ€˜ํ”„๋ก ํŠธ๊ฐ€ ์•ˆ ๋ง‰์•˜์„ ๋•Œโ€™๋„ ๋Œ€๋น„ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์ผ๋‹จ ์ค‘๋ณต์ด๋ฉด ๋ญ˜ ๋ฐ˜ํ™˜ํ• ์ง€

์ด๋ฏธ ์ข‹์•„์š”ํ•œ ์ƒํ’ˆ์— ๋‹ค์‹œ POST๊ฐ€ ์˜ค๋ฉด?

  • A: ํ† ๊ธ€ โ€” ์žˆ์œผ๋ฉด ์‚ญ์ œ, ์—†์œผ๋ฉด ์ƒ์„ฑ. UX๋Š” ๊ฐ„๋‹จํ•œ๋ฐ POST/DELETE ์˜๋ฏธ๊ฐ€ ๋ชจํ˜ธํ•ด์ง
  • B: 200 OK โ€” ์ด๋ฏธ ์žˆ์–ด๋„ ์„ฑ๊ณต ๋ฐ˜ํ™˜. ๋ฉฑ๋“ฑ์ ์ด๋ผ ์žฌ์‹œ๋„์— ์•ˆ์ „. ๊ทผ๋ฐ ์‹ ๊ทœ ์ƒ์„ฑ์ด๋ž‘ ๊ตฌ๋ถ„์ด ์•ˆ ๋จ
  • C: 409 CONFLICT โ€” ์ด๋ฏธ ์กด์žฌํ•˜๋ฉด ์—๋Ÿฌ. HTTP ์ŠคํŽ™์— ์ถฉ์‹ค

C์•ˆ(409)์„ ์„ ํƒํ–ˆ๋‹ค. POST๋Š” โ€˜์ƒ์„ฑโ€™์ด๋‹ˆ๊นŒ ์ด๋ฏธ ์žˆ์œผ๋ฉด ์ถฉ๋Œ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๋‹ค๋งŒ B์•ˆ(200)๋„ ํ‹€๋ฆฐ ๊ฑด ์•„๋‹Œ ๊ฒƒ ๊ฐ™๊ณ , ์ด๊ฑด ์ข€ ์ •๋‹ต์ด ์—†๋Š” ์˜์—ญ์ด๋ผ๊ณ  ๋А๊ผˆ๋‹ค.

409๋กœ ๋์ด ์•„๋‹ˆ๋‹ค

๋ฌธ์ œ๋Š” ํƒ€์ด๋ฐ์ด๋‹ค. ๊ฑฐ์˜ ๋™์‹œ์— ๋‘ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด:

1
2
์š”์ฒญ A: SELECT โ†’ ์—†์Œ โ†’ INSERT โ† ์„ฑ๊ณต
์š”์ฒญ B: SELECT โ†’ ์—†์Œ โ†’ INSERT โ† ???

์š”์ฒญ B๊ฐ€ SELECTํ•˜๋Š” ์‹œ์ ์— A์˜ INSERT๊ฐ€ ์•„์ง ์ปค๋ฐ‹ ์•ˆ ๋์œผ๋ฉด, B๋„ โ€˜์—†์Œโ€™์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  INSERT๋ฅผ ์‹œ๋„ํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ DB์˜ Unique Key๊ฐ€ ์ตœ์ข… ๋ฐฉ์–ด์„  ์—ญํ• ์„ ํ•œ๋‹ค:

1
ALTER TABLE likes ADD CONSTRAINT uk_likes UNIQUE (user_id, product_id);

B์˜ INSERT๊ฐ€ UK ์œ„๋ฐ˜์œผ๋กœ DataIntegrityViolationException์ด ํ„ฐ์ง€๋ฉด, ์ด๊ฑธ ์žก์•„์„œ 409 CONFLICT๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด ๋œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋งค๋ฒˆ DB๊นŒ์ง€ ๊ฐ€์•ผ ํ•ด?

ํšŒ์‚ฌ์—์„œ ๋น„์Šทํ•œ ๋ฌธ์ œ๋ฅผ Redis๋กœ ํ•ด๊ฒฐํ•œ ๊ฒฝํ—˜์ด ์žˆ๋‹ค. @Cooldown์ด๋ผ๋Š” ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค์–ด์„œ ์ผ๋‹ค.

Cooldown์€ ๊ฒŒ์ž„์—์„œ ์Šคํ‚ฌ ์“ด ๋‹ค์Œ ์ผ์ • ์‹œ๊ฐ„ ๋™์•ˆ ์žฌ์‚ฌ์šฉ์ด ์•ˆ ๋˜๋Š” ๊ทธ โ€˜์ฟจํƒ€์ž„โ€™์—์„œ ๋”ฐ์˜จ ์ด๋ฆ„์ด๋‹ค. ๊ฐ™์€ ์š”์ฒญ์ด ์งง์€ ์‹œ๊ฐ„ ์•ˆ์— ๋ฐ˜๋ณต๋˜๋ฉด, ์ฟจํƒ€์ž„์ด ์•ˆ ๋๋‚ฌ์œผ๋‹ˆ๊นŒ ๋ฌด์‹œํ•˜๋Š” ๊ฐœ๋….

๊ตฌ์กฐ๋Š” AOP + Redis + SpEL ์กฐํ•ฉ์ด๋‹ค:

1
2
3
4
@Cooldown(key = RECENT_GOODS, value = "'target:' + #goodsId + ':buyer:' + #buyerId", ttl = 3)
public void addRecentGoods(Long goodsId, Long buyerId) {
    // ...
}

@Cooldown์„ ๋ฉ”์„œ๋“œ์— ๋ถ™์ด๋ฉด AOP(CooldownAspect)๊ฐ€ ๊ฐ€๋กœ์ฑˆ๋‹ค. value์— SpEL ํ‘œํ˜„์‹์„ ์“ธ ์ˆ˜ ์žˆ์–ด์„œ, ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์กฐํ•ฉํ•ด ๋™์ ์œผ๋กœ ํ‚ค๋ฅผ ๋งŒ๋“ ๋‹ค. ์œ„ ์˜ˆ์‹œ์—์„œ๋Š” RECENT_GOODS:target:123:buyer:456 ๊ฐ™์€ ํ‚ค๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

ํ•ต์‹ฌ์€ CooldownService.acquire():

1
2
3
4
5
public boolean acquire(String key, Duration ttl) {
    return Boolean.TRUE.equals(
        redisTemplate.opsForValue().setIfAbsent(key, "1", ttl)
    );
}

Redis์˜ setIfAbsent๋Š” SETNX ๋ช…๋ น์–ด์— ๋Œ€์‘ํ•œ๋‹ค. ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ์„ธํŒ…ํ•˜๊ณ  true, ์ด๋ฏธ ์žˆ์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•˜๊ณ  false. ์ด๊ฒŒ ์›์ž์ (atomic)์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ ์š”์ฒญ์ด ์™€๋„ ๋”ฑ ํ•˜๋‚˜๋งŒ ํ†ต๊ณผํ•œ๋‹ค. TTL์„ ๊ฐ™์ด ๊ฑธ์–ด์„œ, 3์ดˆ๊ฐ€ ์ง€๋‚˜๋ฉด ํ‚ค๊ฐ€ ์ž๋™ ์‚ญ์ œ๋˜๊ณ  ๋‹ค์‹œ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค.

AOP์—์„œ๋Š” acquire()์˜ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋ถ„๊ธฐํ•œ๋‹ค:

1
2
3
4
5
6
7
8
9
10
11
12
13
// CooldownAspect.java (ํ•ต์‹ฌ๋งŒ)
boolean firstHit;
try {
    firstHit = cooldownService.acquire(key, Duration.ofSeconds(cooldown.ttl()));
} catch (Exception e) {
    log.warn("Redis ์žฅ์•  ๋ฐœ์ƒ โ€“ ์ค‘๋ณต ์š”์ฒญ ์ œ์–ด ๋ถˆ๊ฐ€. key={}", key);
    firstHit = true;  // Redis ์žฅ์•  ์‹œ โ†’ ๊ทธ๋ƒฅ ํ†ต๊ณผ (๊ฐ€์šฉ์„ฑ ์šฐ์„ )
}

if (!firstHit) {
    return null;  // ์ฟจํƒ€์ž„ ์•ˆ ๋๋‚จ โ†’ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰ ์•ˆ ํ•จ
}
return pjp.proceed();  // ์ฒซ ์š”์ฒญ โ†’ ์ •์ƒ ์‹คํ–‰

Redis๊ฐ€ ์ฃฝ์–ด๋„ ์„œ๋น„์Šค๊ฐ€ ๋ฉˆ์ถ”๋ฉด ์•ˆ ๋˜๋‹ˆ๊นŒ catch์—์„œ firstHit = true๋กœ ๋น ์ง„๋‹ค. ๋”ฐ๋‹ฅ ๋ฐฉ์ง€๋ณด๋‹ค ๊ฐ€์šฉ์„ฑ์ด ๋” ์ค‘์š”ํ•˜๋‹ค๋Š” ํŒ๋‹จ์ด์—ˆ๋‹ค.

์ข‹์•„์š”์— ์ ์šฉํ•˜๋ฉด ์ด๋Ÿฐ 3์ค‘ ๋ฐฉ์–ด๊ฐ€ ๋œ๋‹ค:

๋ ˆ์ด์–ด๋ฐฉ์–ด ์ˆ˜๋‹จ์—ญํ• 
1์ฐจRedis SETNX like:{userId}:{productId} TTL 3์ดˆ๋”ฐ๋‹ฅ(์—ฐํƒ€) ์ฐจ๋‹จ โ€” DB๊นŒ์ง€ ์•ˆ ๊ฐ
2์ฐจService์—์„œ SELECT EXISTS์ •์ƒ์ ์ธ ์ค‘๋ณต ์š”์ฒญ 409 ๋ฐ˜ํ™˜
3์ฐจDB UNIQUE (user_id, product_id)์ตœ์ข… ๋ฐฉ์–ด์„  โ€” Race Condition ๋Œ€๋น„

1์ฐจ๊ฐ€ ์—†์œผ๋ฉด ๋”ฐ๋‹ฅ ์š”์ฒญ์ด ์ „๋ถ€ DB๊นŒ์ง€ ๋‚ด๋ ค๊ฐ„๋‹ค. 3์ฐจ๊ฐ€ ์—†์œผ๋ฉด Redis ์žฅ์•  ์‹œ ์ค‘๋ณต ์‚ฝ์ž…์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•œ ๊ฒน๋งŒ์œผ๋กœ๋Š” ๋ถˆ์•ˆํ•˜๋‹ค.


3. ๐Ÿท๏ธ ํ’ˆ์ ˆ์ธ๋ฐ ๋ณด์—ฌ์ค˜์•ผ ํ•ด?

์ข‹์•„์š” ๋ˆ„๋ฅธ ์ƒํ’ˆ์ด ํ’ˆ์ ˆ์ด ๋๋‹ค. ์ข‹์•„์š” ๋ชฉ๋ก์—์„œ ๊ทธ ์ƒํ’ˆ์„ ์–ด๋–ป๊ฒŒ ๋ณด์—ฌ์ค˜์•ผ ํ• ๊นŒ?

์ฒ˜์Œ์—” โ€˜ํ’ˆ์ ˆ์ด๋ฉด ๋ชฉ๋ก์—์„œ ๋นผ๋ฉด ๋˜์ง€โ€™๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๊ทผ๋ฐ ๋‚ด๊ฐ€ ์ฐœํ•ด๋‘” ์ƒํ’ˆ์ด ์–ด๋А ๋‚  ๊ฐ‘์ž๊ธฐ ์‚ฌ๋ผ์ ธ ์žˆ์œผ๋ฉด? ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋Š” ๋‹นํ™ฉ์Šค๋Ÿฝ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์‡ผํ•‘๋ชฐ์—์„œ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ•˜๋Š”์ง€ ๋– ์˜ฌ๋ ค๋ณด๋ฉด:

๋ฌด์‹ ์‚ฌ ํ’ˆ์ ˆ ์ƒํ’ˆ ๋”ค ์ฒ˜๋ฆฌ ์˜ˆ์‹œ ๋ฌด์‹ ์‚ฌ โ€” ํ’ˆ์ ˆ ์ƒํ’ˆ์€ ํšŒ์ƒ‰ ์˜ค๋ฒ„๋ ˆ์ด + โ€˜ํ’ˆ์ ˆโ€™ ๋ฑƒ์ง€๋กœ ํ‘œ์‹œ๋œ๋‹ค

ํ’ˆ์ ˆ์ด์–ด๋„ ๋ชฉ๋ก์—๋Š” ๋ณด์—ฌ์ค€๋‹ค. ๋‹ค๋งŒ ์ด๋ฏธ์ง€๊ฐ€ ์–ด๋‘ก๊ฒŒ(๋”ค ์ฒ˜๋ฆฌ) ๋˜๊ณ  ํ’ˆ์ ˆ ๋ฑƒ์ง€๊ฐ€ ๋ถ™๋Š”๋‹ค.

์ด๊ฑธ DB ์„ค๊ณ„๋กœ ์˜ฎ๊ธฐ๋ฉด, ์ƒํ’ˆ์— ๋‘ ๊ฐ€์ง€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์š”ํ•˜๋‹ค:

  • ํŒ๋งค ๊ฐ€๋Šฅ ์—ฌ๋ถ€ โ€” ๊ตฌ๋งค ๋ฒ„ํŠผ์ด ํ™œ์„ฑํ™”๋˜๋Š”๊ฐ€?
  • ๋ชฉ๋ก ๋…ธ์ถœ ์—ฌ๋ถ€ โ€” ์ƒํ’ˆ ๋ชฉ๋ก์— ๋ณด์ด๋Š”๊ฐ€?

์ด ๋‘ ๊ฐœ๋ฅผ status ํ•˜๋‚˜๋กœ ํ‰์น˜๋ฉด โ€˜๋ณด์ด์ง€๋งŒ ๊ตฌ๋งค ๋ถˆ๊ฐ€โ€™ ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค.

1
2
status = ACTIVE     โ†’ ๊ตฌ๋งค ๊ฐ€๋Šฅ + ๋…ธ์ถœ
status = INACTIVE   โ†’ ๊ตฌ๋งค ๋ถˆ๊ฐ€ + ???

INACTIVE์ธ๋ฐ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๋‚˜, ์ˆจ๊ฒจ์•ผ ํ•˜๋‚˜? status๋งŒ์œผ๋กœ๋Š” ๋‹ต์ด ์•ˆ ๋‚˜์˜จ๋‹ค.

๊ทธ๋ž˜์„œ status + displayYn์„ ๋ถ„๋ฆฌํ–ˆ๋‹ค:

statusdisplayYn๋ชฉ๋ก ๋…ธ์ถœ๊ตฌ๋งค ๊ฐ€๋ŠฅUI
ACTIVEtrueโœ…โœ…์ผ๋ฐ˜ ์ƒํ’ˆ ์นด๋“œ
ACTIVEfalseโŒโœ… (URL ์ง์ ‘)๊ด€๋ฆฌ์ž๊ฐ€ ์˜๋„์ ์œผ๋กœ ์ˆจ๊ธด ์ƒํƒœ
INACTIVEtrueโœ…โŒ๋”ค ์ฒ˜๋ฆฌ + โ€˜ํŒ๋งค์ค‘์ง€โ€™ ๋ฑƒ์ง€
INACTIVEfalseโŒโŒ์™„์ „ ๋ฏธ๋…ธ์ถœ

ํ”„๋ก ํŠธ์—์„œ๋Š” ์ด ๋‘ ํ•„๋“œ๋ฅผ ์กฐํ•ฉํ•ด์„œ UI๋ฅผ ๋ถ„๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค:

1
2
3
displayYn = false              โ†’ ๋ชฉ๋ก์—์„œ ์ œ์™ธ
displayYn = true, ACTIVE       โ†’ ์ •์ƒ ์ƒํ’ˆ ์นด๋“œ
displayYn = true, INACTIVE     โ†’ ๋”ค์ฒ˜๋ฆฌ + 'ํŒ๋งค์ค‘์ง€' ๋ผ๋ฒจ

โ€˜์ด๊ฑฐ ์˜ค๋ฒ„์—”์ง€๋‹ˆ์–ด๋ง ์•„๋‹ˆ์•ผ?โ€™๋ผ๋Š” ์ƒ๊ฐ๋„ ๋“ค์—ˆ๋‹ค. ๊ณผ์ œ ๋ฒ”์œ„์—์„œ ๋”ค ์ฒ˜๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ํ•„์š”ํ•œ ๊ฑด ์•„๋‹ˆ๋‹ˆ๊นŒ.

๊ทผ๋ฐ Shopify, Amazon, ๋„ค์ด๋ฒ„, ์ฟ ํŒก ์ „๋ถ€ ์ฐพ์•„๋ดค๋Š”๋ฐ, ์„ฑ์ˆ™ํ•œ ํ”Œ๋žซํผ์€ ๋Œ€๋ถ€๋ถ„ status โ‰  visibility๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๋ฐฉํ–ฅ์„ฑ์€ ํ‹€๋ฆฌ์ง€ ์•Š์•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

displayYn ๋„ค์ด๋ฐ๋„ ๊ณ ๋ฏผ์ด ์žˆ์—ˆ๋‹ค. Kotlin์—์„œ isDisplay๋กœ ํ•˜๋ฉด Jackson ์ง๋ ฌํ™” ์‹œ display๋กœ ๋ณ€ํ™˜๋˜๋Š” ์•Œ๋ ค์ง„ ๋ฒ„๊ทธ(jackson-module-kotlin #80)๊ฐ€ ์žˆ๋‹ค. ํ•œ๊ตญ ์ด์ปค๋จธ์Šค ๊ด€๋ก€๋ฅผ ๋”ฐ๋ผ Yn ์ ‘๋ฏธ์‚ฌ๋กœ ๊ฐ”๋‹ค.


๐Ÿ“ ๋Œ์•„๋ณด๋ฉด

์ข‹์•„์š” ๊ธฐ๋Šฅ ํ•˜๋‚˜์—์„œ ๋‚˜์˜จ ๊ณ ๋ฏผ๋“ค:

  1. ์‚ญ์ œ ์ „๋žต โ€” Hard Delete vs Soft Delete โ†’ ํ˜„์žฌ ์ƒํƒœ vs ์ด๋ ฅ ๋ณด์กด
  2. ์ค‘๋ณต ์ฒ˜๋ฆฌ โ€” 409 vs 200 โ†’ HTTP ์˜๋ฏธ๋ก  vs ํด๋ผ์ด์–ธํŠธ ํŽธ์˜์„ฑ
  3. ๋”ฐ๋‹ฅ ๋ฐฉ์ง€ โ€” Redis SETNX โ†’ Service ์ฒดํฌ โ†’ DB UK 3์ค‘ ๋ฐฉ์–ด
  4. ์ƒํƒœ ๋ถ„๋ฆฌ โ€” status ร— displayYn โ†’ โ€˜๋ณด์ด์ง€๋งŒ ๊ตฌ๋งค ๋ถˆ๊ฐ€โ€™๋ฅผ ํ‘œํ˜„

์ฒ˜์Œ์— โ€˜INSERT/DELETE๋ฉด ๋˜์ง€โ€™๋ผ๊ณ  ์ƒ๊ฐํ•œ ๊ฒŒ ๋ถ€๋„๋Ÿฌ์šด ๊ฑด ์•„๋‹Œ๋ฐ, ํŒŒ๊ณ ๋“ค์ˆ˜๋ก ํŒ๋‹จ์˜ ์—ฐ์†์ด์—ˆ๋‹ค. ๊ฐ๊ฐ์— โ€˜์ •๋‹ตโ€™์ด ์žˆ๋‹ค๊ธฐ๋ณด๋‹ค๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๊ฑฐ๊ณ .

์„ค๊ณ„ ์ฃผ์ฐจ๋ผ ์ฝ”๋“œ๋Š” ์•ˆ ์งฐ์ง€๋งŒ, ์ด๋Ÿฐ ๊ณ ๋ฏผ๋“ค์„ ๋จผ์ € ํ•ด๋‘๋‹ˆ๊นŒ ๊ตฌํ˜„ํ•  ๋•Œ ๋œ ํ—ค๋งฌ ๊ฒƒ ๊ฐ™๋‹ค. ์•„๋งˆ.

์ข‹์•„์š” ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋Š” ๊ฑด 1์ดˆ. ๊ทธ ๋’ค๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ๊ฑด ํ•˜๋ฃจ๊ฐ€ ๋„˜๊ฒŒ ๊ฑธ๋ ธ๋‹ค.

This post is licensed under CC BY 4.0 by the author.