Redis sorted set?

์ด์ „ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒŒ์ดํŠธ์›จ์ด ๋Œ€๊ธฐ์—ด์„ ๊ตฌํ˜„ํ•  ๋•Œ Redis์˜ Sorted Set์„ ์ด์šฉํ•˜์˜€๋‹ค. API ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ ํ˜„์žฌ ๊ฐ€์šฉ ์ธ์› ์ˆ˜๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ์•Œ๋งž๋Š” queue์— ์ ์žฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์˜€๋‹ค.

์ดํ›„ (์‚ฌ์ง„์—๋Š” 30์ดˆ์ง€๋งŒ) 3์ดˆ์— ํ•œ๋ฒˆ์”ฉ ๋น„ํ™œ์„ฑ ์œ ์ €๋ฅผ ํ™•์ธํ•œ ๋’ค active queue์—์„œ ์ œ๊ฑฐํ•˜๊ณ  ๋Œ€๊ธฐ ํ์—์„œ ์˜ฎ๊ฒจ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„ํ•˜์˜€๋‹ค.

Sorted Set ํŠน์ง•

  • Redis์˜ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ
  • ์ค‘๋ณต๋˜์ง€ ์•Š๋Š” ๊ฐ’(Unique value)๊ณผ ํ•ด๋‹น ๊ฐ’์— ํ• ๋‹น๋œ ์ ์ˆ˜(score)๋ฅผ ํ•จ๊ป˜ ์ €์žฅ
  • ์ ์ˆ˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌ๋œ ์ปฌ๋ ‰์…˜
  • ๋™์ผํ•œ socre๋ฅผ ๊ฐ–๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์ˆœ์œผ๋กœ ์ •๋ ฌ

์—ฌ๊ธฐ์„œ ๊ฐ’์ด ์ค‘๋ณต๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ๊ณผ ์ ์ˆ˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค๋Š” ์ ์—์„œ sorted set์„ ์„ ํƒํ–ˆ๋‹ค.

์—ฌ๊ธฐ์„œ unique value๋Š” ๊ฐ ์œ ์ €์˜ id, score๋Š” ์œ ์ €๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ธ ์‹œ๊ฐ„์œผ๋กœ ์ง€์ •ํ•˜์—ฌ ์ €์žฅํ•˜์˜€๋‹ค. ๋งŒ์•ฝ ๋Œ€๊ธฐํ์— ์žˆ๋Š” ์ƒํƒœ์—์„œ ์š”์ฒญ์„ ๋˜ ๋ณด๋‚ด๊ฒŒ ๋œ๋‹ค๋ฉด ์ˆœ์„œ๊ฐ€ ๋ฐ€๋ฆฌ๊ฒŒ ๋œ๋‹ค..~~ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ™œ์„ฑ ํ์— ์žˆ๋Š” ์œ ์ €๊ฐ€ ์ผ์ •์‹œ๊ฐ„๋™์•ˆ ์š”์ฒญ์ด ์—†์–ด ์ ์ˆ˜๊ฐ€ ๊ฐฑ์‹ ๋˜์ง€ ์•Š์œผ๋ฉด ์ œ๊ฑฐ ๋Œ€์ƒ์ด ๋˜์–ด ๋‹ค์‹œ ์š”์ฒญํ•˜๋ฉด ๋Œ€๊ธฐํ•ด์•ผํ•œ๋‹ค.

๋Œ€๊ธฐ์—ด ๊ตฌํ˜„ ๋ฐฉ์‹์—๋„ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์ด ์žˆ์—ˆ๋‹ค

  1. ์€ํ–‰ ์ฐฝ๊ตฌ ๋ฐฉ์‹
  2. ๋†€์ด๊ณต์› ๋ฐฉ์‹

์€ํ–‰์ฐฝ๊ตฌ ๋ฐฉ์‹์€ ๋ง ๊ทธ๋Œ€๋กœ ์€ํ–‰ ์ฐฝ๊ตฌ์ฒ˜๋Ÿผ ๋Œ€๊ธฐ์—ด์— ๋“ฑ๋ก๋œ ์ˆœ์„œ๋Œ€๋กœ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ์‚ฌ์šฉ์ž๋“ค์ด ๋Œ€๊ธฐํ•  ๋•Œ ํ˜„์žฌ ๋Œ€๊ธฐ ์ˆœ์œ„๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋†€์ด๊ณต์› ๋ฐฉ์‹์€ ์ผ์ • ์‹œ๊ฐ„ ์ดํ›„์— ์ผ์ • ์‚ฌ์šฉ์ž์—๊ฒŒ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋Œ€๊ธฐ์‹œ๊ฐ„์„ ์ œ๊ณตํ•œ๋‹ค.

๋‚˜๋Š” ์€ํ–‰์ฐฝ๊ตฌ ๋ฐฉ์‹์„ ์„ ํƒํ•˜์˜€๋Š”๋ฐ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ์„œ๋น„์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์ฆ‰์‹œ ์š”์ฒญ์„ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๊ณ , ๋†€์ด๊ณต์› ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฐ๊ตญ ์‚ฌ์šฉ์ž๊ฐ€ ์ ์  ๋Š˜์–ด๋‚˜ ์„œ๋น„์Šค์— ๋ถ€ํ•˜๋ฅผ ์ค„ ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

  • priority queues ์šฐ์„ ์ˆœ์œ„ ๋Œ€๊ธฐ์—ด
  • low-latency leadrboards ์ €์ง€์—ฐ ๋ฆฌ๋”๋ณด๋“œ
  • secondary indexing ๋ณด์กฐ ์ธ๋ฑ์‹ฑ
  • ๋Œ€๊ทœ๋ชจ ์˜จ๋ผ์ธ ๊ฒŒ์ž„์—์„œ ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜์˜ ์ •๋ ฌ๋œ ๋ชฉ๋ก ์œ ์ง€ (๋ฆฌ๋”๋ณด๋“œ)
  • ์†๋„ ์ œํ•œ๊ธฐ - ์ •๋ ฌ๋œ set์„ ์‚ฌ์šฉํ•˜์—ฌ ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ ์†๋„ ์ œํ•œ๊ธฐ ๋นŒ๋“œ โ†’ ๊ณผ๋„ํ•œ API ์š”์ฒญ ๋ฐฉ์ง€
public Mono<RegisterUserResponse> registerUser(String userId) {  
  return reactiveRedisTemplate.opsForZSet().size(USER_QUEUE_ACTIVE_KEY)  
      .flatMap(activeUsers -> {  
        if (activeUsers < MAX_ACTIVE_USERS) {  
          return reactiveRedisTemplate.opsForZSet()  
              .add(USER_QUEUE_ACTIVE_KEY, userId, getCurrentTime())  
              .thenReturn(new RegisterUserResponse(activeUsers + 1));  
        }  
        return reactiveRedisTemplate.opsForZSet()  
            .add(USER_QUEUE_WAIT_KEY, userId, getCurrentTime())  
            .flatMap(success ->  
                reactiveRedisTemplate.opsForZSet().rank(USER_QUEUE_WAIT_KEY, userId)  
            )  
            .map(rank -> new RegisterUserResponse(rank + 1));  
      });

~~์„œ๋น„์Šค ๊ตฌํ˜„ ํ›„ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ํ…Œ์ŠคํŠธํ•˜์˜€๋Š”๋ฐโ€ฆ active queue์— ์ ์žฌ๋  ๋•Œ๋Š” 100/sec ์ •๋„์˜ ์ฒ˜๋ฆฌ๋Ÿ‰์ด๋ผ๋ฉด, ๋Œ€๊ธฐํ์— ๋“ค์–ด๊ฐˆ ๋• ๊ฑฐ์˜ 900/sec ์ด์ƒ์˜ ์ฒ˜๋ฆฌ๋Ÿ‰์ด ๋‚˜์™”๋‹คโ€ฆ ์™œ ์ด๋ ‡๊ฒŒ ์ฐจ์ด๊ฐ€ ๋‚˜๋Š”์ง€ ์•„์ง ์ด์œ ๋ฅผ ๋ชจ๋ฅด๊ฒ ๋‹คโ€ฆ ใ…  ~~

Ref