๐Ÿ’จ Redis ์บ์‹ฑ - ixi-U/ixi-U-be GitHub Wiki

๐Ÿ”Ž ๋ฌธ์ œ ๋ถ„์„ ๋ฐ ์›์ธ ํŒŒ์•…

  • ๋ชจ๋“  ์กฐํšŒ ์š”์ฒญ๋งˆ๋‹ค DB/Neo4j ์ง์ ‘ ํ˜ธ์ถœ
  • ๋ฐ˜๋ณต ์กฐํšŒ ๋ถ€ํ•˜๋กœ ๋ ˆ์ดํ„ด์‹œ ์ƒ์Šน

๐Ÿš€ ๊ฐœ์„  ์ „๋žต ๋ฐ ์‹คํ–‰ ๊ณผ์ •

1. Cache-Aside (Look-Aside) ํŒจํ„ด ๋„์ž…

  • @Cacheable(cacheNames="plans", key="#โ€ฆ")
  • @Cacheable(cacheNames="planCounts", key="#โ€ฆ")

2. Redis ์„ค์ • ์ตœ์ ํ™”

planListPages, planCounts ๋ชจ๋‘ TTL 10๋ถ„์œผ๋กœ ์„ค์ •

TTL 10๋ถ„ ์„ค์ • ๊ทผ๊ฑฐ

  • ์š”๊ธˆ์ œ ๋ณ€๊ฒฝ ๋นˆ๋„

    • ์š”๊ธˆ์ œ ์‹ ๊ทœ ๋“ฑ๋ก, ์ˆ˜์ •๋Š” ํ•˜๋ฃจ ์ค‘์—๋„ ์ž์ฃผ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ
    • 10๋ถ„ ์ด๋‚ด๋ฉด ์ถฉ๋ถ„ํžˆ ์ตœ์‹  ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์–ด ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ ์ถฉ์กฑ
  • ์บ์‹œ ์ ์ค‘๋ฅ  vs. ์‹ ์„ ๋„ ๊ท ํ˜•

    • ๋„ˆ๋ฌด ์งง์œผ๋ฉด ์บ์‹œ ์ ์ค‘๋ฅ ์ด ๋–จ์–ด์ ธ DB ์กฐํšŒ๊ฐ€ ์žฆ์•„์ง€๊ณ ,
    • ๋„ˆ๋ฌด ๊ธธ๋ฉด ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ
    • 10๋ถ„์€ โ€œDB ๋ถ€ํ•˜ ๊ฐ์†Œโ€์™€ โ€œ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ์‹œ์ โ€ ์‚ฌ์ด์˜ ์ ์ ˆํ•œ ์ ˆ์ถฉ์ 

3. Eviction ์ •์ฑ… ์ ์šฉ

  • @CacheEvict(cacheNames={"planListPages","planCounts"}, allEntries=true)
  • Plan ์ƒ์„ฑ, ์ˆ˜์ • ์‹œ ์ „์ฒด ์บ์‹œ ๋ฌดํšจํ™”

4. ์บ์‹œ ์ €์žฅ ๊ฐ’ ๋ฐ ํ‚ค ์„ค๊ณ„

findPlans(Pageable, GetPlansRequest)

  • ์–ด๋…ธํ…Œ์ด์…˜
@Transactional(readOnly = true)
@Cacheable(
value = "planListPages",
key = "'planListPages:' + #pageable.pageNumber + '-' +
        #request.planTypeStr() + '-' +
        #request.planSortOptionStr() + '-' +
        #request.searchKeyword() + '-' +
        (#request.planId() != null ? #request.planId() : '') + '-' +
        #request.cursorSortValue()"
)
  • ํ‚ค ํฌ๋งท: ์š”๊ธˆ์ œ ์กฐํšŒ ์‹œ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ, ์š”๊ธˆ์ œ ํƒ€์ž…, ์š”๊ธˆ์ œ ์ •๋ ฌ ์กฐ๊ฑด, ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ, ํŽ˜์ด์ง€๋„ค์ด์…˜์—์„œ ํ•„์š”ํ•œ planId, cursorSortValue๋ฅผ key๊ฐ’์œผ๋กœ ์„ค์ •
planListPages:
{pageNumber}-{pageSize}-{planType}-{sortOption}-{searchKeyword}-{planId}-{cursorSortValue}

countPlans()

  • ์–ด๋…ธํ…Œ์ด์…˜
@Transactional(readOnly = true)
@Cacheable(value = "planCounts", key = "'all'")
public PlansCountResponse countPlans() {
        return PlansCountResponse.from(planRepository.countPlans());
}
  • ํ‚ค ํฌ๋งท: countPlans๋Š” ๋‹จ์ผ ํ‚ค๋กœ ์„ค์ •
planCounts:
all

5. ์ธ๋ฑ์‹ฑ ํŠœ๋‹ (์š”๊ธˆ์ œ ํƒ€์ž… ํ•„ํ„ฐ & ์ •๋ ฌ ํ•„๋“œ ์žฌ๊ตฌ์„ฑ)

CREATE INDEX plan_type_index IF NOT EXISTS FOR (p:Plan) ON (p.planType);

๐Ÿ“Š ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ

ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ

  • Concurrent Users: 1,000
  • Test Duration: 60์ดˆ
  • Dataset Size: ์•ฝ 5,000๊ฐœ ์š”๊ธˆ์ œ

๋ถ€ํ•˜ ์„ค์ • ๊ทผ๊ฑฐ

  • LG U+ ์‚ฌ์ดํŠธ ์ผ์ผ ๋ฐฉ๋ฌธ์ž: ์•ฝ 25,200๋ช…
  • ํ‰๊ท  ์„ธ์…˜(5๋ถ„) ๋™์‹œ์ ‘์†์ž: (25,200 ร— 300) รท 86,400 โ‰ˆ 87.5๋ช…
  • ํ”ผํฌ ๊ณ„์ˆ˜(1.52) ์ ์šฉ ์‹œ: 135~175๋ช…
  • ์•ˆ์ •์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•ด 1๋ถ„ 1,000๋ช… ํ™˜๊ฒฝ์œผ๋กœ ์„ค์ •

ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค

  1. Baseline (Pre-Cache): ์บ์‹œยท์ธ๋ฑ์Šค ๋ฏธ์ ์šฉ
  2. Optimized (With Cache): ์บ์‹œ + ์ธ๋ฑ์Šค ์ ์šฉ

โ€ป ๋™์ผํ•œ ์Šค๋ ˆ๋“œ, ์š”์ฒญ ๋นˆ๋„, ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ๊ฑด์œผ๋กœ ๋น„๊ต

๊ฒฐ๊ณผ ์š”์•ฝ

๊ตฌ๋ถ„ ํ‰๊ท  ์ง€์—ฐ (Avg) P95 ์ง€์—ฐ ๊ฐœ์„ ์œจ
๋ชฉ๋ก ์กฐํšŒ (Pre) 31 ms 38 ms โ€”
๋ชฉ๋ก ์กฐํšŒ (With) 6 ms 12 ms Avg โ†“80%, P95 โ†“68%
๊ฐœ์ˆ˜ ์กฐํšŒ (Pre) 13 ms 18 ms โ€”
๊ฐœ์ˆ˜ ์กฐํšŒ (With) 4 ms 6 ms Avg โ†“69%, P95 โ†“66%

์ตœ์ข… ๊ฒฐ๋ก 

์บ์‹œ ๋ฐ ์ธ๋ฑ์Šค ์ ์šฉ์œผ๋กœ ๋ชฉ๋ก ์กฐํšŒ ํ‰๊ท  ์‘๋‹ต์ด 31 ms โ†’ 6 ms(80%โ†“), P95 38 ms โ†’ 12 ms(68%โ†“), ๊ฐœ์ˆ˜ ์กฐํšŒ ํ‰๊ท  13 ms โ†’ 4 ms(69%โ†“), P95 18 ms โ†’6 ms(66%โ†“) ๋กœ ๋Œ€ํญ ๊ฐœ์„