did_story

[Redis] Redis๋ฅผ ํ™œ์šฉํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ ์„ค๊ณ„๊ธฐ ๋ณธ๋ฌธ

BackEnd๐Ÿƒ

[Redis] Redis๋ฅผ ํ™œ์šฉํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ ์„ค๊ณ„๊ธฐ

์–ด์ œ์‹œ์ž‘ 2025. 8. 29. 15:59

1. ๋ฐœ๋‹จ: ๋ถˆ์•ˆ์ •ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

๋งํฌ ์ €์žฅ ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๊ณผ์ •์—์„œ, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.

ํ•œ ๋ฒˆ์˜ ์š”์ฒญ๋‹น ํ‰๊ท  700ms์˜ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜์—ˆ๊ณ , ๋™์‹œ์— ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญ์„ ๋ณด๋ƒˆ์„ ๋•Œ ์ผ๋ถ€๋Š” ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ์ง€๋งŒ, ์ผ๋ถ€๋Š” URL๋งŒ ์ €์žฅ๋˜๊ฑฐ๋‚˜ ์•„์˜ˆ ์ €์žฅ์ด ๋˜์ง€ ์•Š๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

์ฆ‰, ๋™์ผํ•œ API ํ˜ธ์ถœ์ž„์—๋„ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ถˆ์•ˆ์ •์„ฑ์ด ์žˆ์—ˆ๊ณ , ์ด ๋ฌธ์ œ๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ์ง์ ‘์ ์ธ ์•…์˜ํ–ฅ์„ ๋ผ์น  ๊ฒƒ์ด๋ผ ์˜ˆ์ƒํ–ˆ๋‹ค. (์ฝ”๋“œ๋ฅผ ํ™•์‹คํžˆ ์ž˜๋ชป ์งฐ๋‹ค..)

2. ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ๋‹ค: Redis๋ฅผ ๋– ์˜ฌ๋ฆฌ๋‹ค

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ๋ฐฉ์•ˆ์„ ๊ณ ๋ฏผํ•˜๋˜ ์ค‘, ๊ณผ๊ฑฐ ์ฑ„ํŒ… ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ–ˆ๋˜ Redis๊ฐ€ ๋– ์˜ฌ๋ž๋‹ค.

Redis๋Š” ๋‹จ์ˆœํ•œ ์บ์‹œ ์„œ๋ฒ„๋ฅผ ๋„˜์–ด์„œ ์ธ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜์˜ ๋น ๋ฅธ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ๋‹ค์–‘ํ•œ ์ž๋ฃŒ๊ตฌ์กฐ ์ง€์›, TTL ๊ธฐ๋ฐ˜ ์ž๋™ ๋งŒ๋ฃŒ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

ํŠนํžˆ, “๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์˜ ์ƒํƒœ์™€ ๊ฒฐ๊ณผ๋ฅผ ์ž„์‹œ๋กœ ์ €์žฅํ•˜๋Š” ๊ณต๊ฐ„”์ด ํ•„์š”ํ–ˆ๋˜ ๋‚ด ์ƒํ™ฉ๊ณผ Redis์˜ ํŠน์„ฑ์ด ์ž˜ ๋งž์•„๋–จ์–ด์กŒ๋‹ค.

3. ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„: Queue + Status + Cache

Redis๋ฅผ ๋„์ž…ํ•˜๋ฉด์„œ ์ „์ฒด ํ๋ฆ„์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๊ณ„ํ–ˆ๋‹ค.

  1. Request Queue
        public Map<String, String> enqueue(MetadataRequestDto req) throws JsonProcessingException {
            Map<String, String> job = Map.of("requestId", req.getId(), "url", req.getUrl());
            redisTemplate.opsForList().leftPush("metadata:queue", objectMapper.writeValueAsString(job));
            return Map.of("requestId", requestId);
        }
    ํด๋ผ์ด์–ธํŠธ๊ฐ€ URL์„ ์ œ์ถœํ•˜๋ฉด, ์„œ๋ฒ„๋Š” requestId๋ฅผ ๋ฐœ๊ธ‰ํ•œ ๋’ค Redis Queue(List ๋˜๋Š” Stream)์— ๋“ฑ๋กํ•œ๋‹ค.

  2. Worker/Processor
    @Scheduled(fixedDelay = 1000)
        public void processQueueBatchAsync() {
        // batchSize๋Š” ์ ๋‹นํžˆ..
            for (int i = 0; i < batchSize; i++) {
                String jobJson = redisTemplate.opsForList().rightPop("metadata:queue");
                if (jobJson == null) break;
    
                asyncProcessor.processJobAsync(jobJson);
            }
        }
    ๋ณ„๋„์˜ ์›Œ์ปค๊ฐ€ Queue์—์„œ ์š”์ฒญ์„ ๊บผ๋‚ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  3. Result Cache (String/JSON)
        @Async
        public CompletableFuture<Object> processJobAsync(String jobJson) {
            try {
                Map<String, String> job = objectMapper.readValue(jobJson, new TypeReference<>() {});
                String requestId = job.get("requestId");
                String url = job.get("url");
    
                return processor.fetchMetadata(url)
                        .toFuture()
                        .thenApplyAsync(metadata -> {
                            try {
                                String resultJson = objectMapper.writeValueAsString(metadata);
                                redisTemplate.opsForValue().set("metadata_result:" + requestId, resultJson, ...);
                            } catch (Exception e) {
                                log.error(" ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ JSON ์ง๋ ฌํ™” ์‹คํŒจ", e);
                            }
                            return null;
                        })
                        .exceptionally(e -> {
                            log.error(" Async ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹คํŒจ", e);
                            return null;
                        });
            } catch (Exception e) {
                log.error(" Async ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹คํŒจ", e);
            }
            return CompletableFuture.completedFuture(null);
        }
    ์ตœ์ข… ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฒฐ๊ณผ๋ฅผ JSON ํ˜•ํƒœ๋กœ ์ €์žฅํ•œ๋‹ค. ์ด ์—ญ์‹œ TTL์„ ์„ค์ •ํ•˜์—ฌ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์ž๋™์œผ๋กœ ์‚ญ์ œ๋œ๋‹ค.
  4. Status ๋ฐ˜ํ™˜ (Hash)
    public Map<String, Object> getStatus(String requestId) throws JsonProcessingException {
            String resultJson = redisTemplate.opsForValue().get("metadata_result:" + requestId);
            if (resultJson == null) {
                return Map.of("status", "pending");
            }
            MetadataResponseDto result = objectMapper.readValue(resultJson, MetadataResponseDto.class);
            return Map.of("status", "done", "data", result);
        }
    
    ๊ฐ requestId์˜ ์ƒํƒœ(pending, done)๋ฅผ Hash ๊ตฌ์กฐ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  5. ํด๋ผ์ด์–ธํŠธ ํด๋ง
    ํด๋ผ์ด์–ธํŠธ๋Š” requestId๋ฅผ ์‚ฌ์šฉํ•ด ์ƒํƒœ์™€ ๊ฒฐ๊ณผ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์กฐํšŒํ•œ๋‹ค. ์™„๋ฃŒ๋˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์™€ UI์— ๋ฐ˜์˜ํ•œ๋‹ค.

์ด ๊ตฌ์กฐ ๋•๋ถ„์—, ์š”์ฒญ์€ ์ฆ‰์‹œ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๊ณ , ์‹ค์ œ ๋ฌด๊ฑฐ์šด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ๋กœ์ง์€ ์›Œ์ปค๊ฐ€ ๋’ค์—์„œ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

4. ์šด์˜ ์‹œ ๊ณ ๋ คํ•œ ๋ถ€๋ถ„

  • ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ: Redis์˜ eviction ์ •์ฑ… ๋•Œ๋ฌธ์— ํ๋‚˜ ์ƒํƒœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚ ์•„๊ฐ€์ง€ ์•Š๋„๋ก ๋ฉ”๋ชจ๋ฆฌ ์—ฌ์œ ๋ฅผ ํ™•๋ณดํ•ด์•ผํ–ˆ๋‹ค.
  • ๋ฉฑ๋“ฑ์„ฑ: ๊ฐ™์€ requestId๊ฐ€ ์ค‘๋ณต ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๋„๋ก Key๊ฐ’์„ UUID๋กœ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.
  • ๋ชจ๋‹ˆํ„ฐ๋ง: ์š”์ฒญ ์ฒ˜๋ฆฌ๋Ÿ‰, ํ‰๊ท  ์ง€์—ฐ ์‹œ๊ฐ„, ์‹คํŒจ์œจ ๋“ฑ์„ ์ง€ํ‘œ๋กœ ์ˆ˜์ง‘ํ•ด ์šด์˜ ์ƒํ™ฉ์„ ๊ฐ€์‹œํ™”๋ฅผ ํ•˜์˜€๋‹ค (๊ทธ๋ผํŒŒ๋‚˜, ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค ์‚ฌ์šฉ).

5. ๊ฒฐ๋ก 

Redis๋Š” ๋‹จ์ˆœํžˆ “์บ์‹œ ์„œ๋ฒ„”๋ผ๋Š” ์ด๋ฏธ์ง€๋ฅผ ๋„˜์–ด์„œ, ๋น„๋™๊ธฐ ์ž‘์—… ํ + ์ƒํƒœ ์ €์žฅ์†Œ + ์ž„์‹œ ์บ์‹œ ์—ญํ• ์„ ๋™์‹œ์— ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋‹ค. Redis ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ ์šฉํ•œ ํ›„, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์š”์ฒญ์˜ ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„์ด 700ms์—์„œ 400ms๋กœ ์•ฝ 43% ๋‹จ์ถ•๋˜์—ˆ๋‹ค. ๋‹จ์ˆœํžˆ ์•ˆ์ •์„ฑ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์„ฑ๋Šฅ ๊ฐœ์„  ํšจ๊ณผ๋„ ํ•จ๊ป˜ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์•ž์œผ๋กœ๋Š” Redis Streams, Consumer Group, Dead Letter Queue ๋“ฑ์„ ๋„์ž…ํ•ด ํ™•์žฅ์„ฑ๊ณผ ๋ณต์›๋ ฅ์„ ๋”์šฑ ๊ฐ•ํ™”ํ•  ๊ณ„ํš์ด๋‹ค. (๊ณต๋ถ€ํ• ๊ฒŒ ๋งŽ๋‹ค.. ์ฆ๊ฒ๋‹ค..!!)

'BackEnd๐Ÿƒ' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Redis] Redis๋Š” ๋ฌด์—‡์ผ๊นŒ?  (1) 2025.08.29
์ž ์ด ํ™•๊นจ๋Š” 404 ์—๋Ÿฌ... ์ด์œ ๋Š”?!  (1) 2025.06.11