λ“€μ–΄κ°€λ©°

REST API λ₯Ό 깊이 κ³΅λΆ€ν•˜λ©° RESTful μ•„ν‚€ν…μ²˜ μ œμ•½μ‚¬ν•­μ— λŒ€ν•΄μ„œ μ•Œκ²Œλ˜μ—ˆλ‹€. κ·Έ 쀑 Cacheable 쑰건에 λŒ€ν•΄μ„œ 처음 μ•Œκ²Œ λ˜μ—ˆλŠ”λ°, μš”μ²­μ— λŒ€ν•œ μ„œλ²„μ˜ 응닡에 데이터가 μΊμ‹œ κ°€λŠ₯ν•œμ§€ λΆˆκ°€λŠ₯ν•œμ§€ λͺ…μ‹œν•΄μ•Ό ν•˜λŠ” 쑰건이닀. HTTP 헀더 쀑 Cache-Control μ΄λΌλŠ” 헀더λ₯Ό ν™œμš©ν•˜λ©΄ λ˜λŠ”λ°, λΈŒλΌμš°μ €κ°€ μΊμ‹œλ₯Ό μ €μž₯ν•˜κ³  ν™œμš©ν•  수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” ν‘œμ€€ 헀더이닀. 이걸둜 λΈŒλΌμš°μ €κ°€ μ•Œμ•„μ„œ 캐싱할 수 μžˆλ„λ‘ μ„€μ •ν•  수 μžˆλ‹€.

λΈŒλΌμš°μ € μΊμ‹œμ™€ ETag

μ›Ή λΈŒλΌμš°μ €μ˜ μΊμ‹œλŠ” 처음 κ΅¬ν˜„ν•΄ 보기 λ•Œλ¬Έμ— μ •λ¦¬ν•΄λ³΄μ•˜λ‹€. HTTP 헀더 쀑 Cache-Control μ΄λΌλŠ” 헀더λ₯Ό ν™œμš©ν•˜λ©΄ μΊμ‹œλ₯Ό μ œμ–΄ν•  수 μžˆλ‹€. κ·Έλ ‡μ§€λ§Œ 데이터λ₯Ό 잘λͺ» μΊμ‹œν•˜κ²Œ 되면 λ°μ΄ν„°μ˜ λΆˆμΌμΉ˜κ°€ λ°œμƒν•  수 μžˆμœΌλ―€λ‘œ μΊμ‹œ 검증도 같이 μˆ˜ν–‰ν•΄μ•Ό ν•œλ‹€. 검증을 μœ„ν•œ 헀더가 ETag 이닀.

  • Cache-Control 헀더 : μΊμ‹œμ˜ μœ νš¨κΈ°κ°„μ„ λͺ…μ‹œν•˜λŠ” 응닡 헀더.

  • ETag 헀더 : νŠΉμ • λ²„μ „μ˜ λ¦¬μ†ŒμŠ€λ₯Ό μ‹λ³„ν•˜λŠ” 고유 μ‹λ³„μž. μ„œλ²„λŠ” 데이터가 변경될 λ•Œ λ§ˆλ‹€ μƒˆ ETag λ₯Ό μƒμ„±ν•œλ‹€.

ETag λ§Œλ“œλŠ” 방법

ETag λŠ” 응닡 λ°μ΄ν„°μ˜ λ‚΄μš©μ΄ λ°”λ€Œλ©΄ μƒˆλ‘œμš΄ 값을 μƒμ„±ν•˜λ„λ‘ ν•΄μ•Ό ν•œλ‹€. (κ·Έλž˜μ•Ό 데이터가 λ³€κ²½λ˜μ—ˆλŠ”μ§€ μ—¬λΆ€λ₯Ό μ‰½κ²Œ νŒλ‹¨ν•  수 μžˆλ‹€) λ”°λΌμ„œ, ETag λŠ” λ‹€μŒ μš”μ†Œλ₯Ό 기반으둜 생성할 수 μžˆλ‹€.

  1. 응닡 데이터 자체

    • 예 : products.toString()
  2. λ°μ΄ν„°μ˜ λ§ˆμ§€λ§‰ μˆ˜μ • μ‹œκ°

    • 예 : products.getUpdatedAt()
  3. 데이터 자체λ₯Ό ν•΄μ‹œν™”ν•œ κ°’

    • 예 : MD5, SHA-256 λ“± ν•΄μ‹± μ•Œκ³ λ¦¬μ¦˜ μ‚¬μš©

이 μ€‘μ—μ„œ ν•΄μ‹œκ°’μ„ μ‚¬μš©ν•˜λŠ” 것이 ETag λ₯Ό 짧고 효율적으둜 μš΄μš©ν•  수 μžˆμ–΄ 3λ²ˆμ„ μ„ νƒν–ˆλ‹€. (μ°Έκ³ λ‘œβ€¦ MD5 와 SHA-256 의 μ°¨μ΄λŠ” ν•΄λ‹Ή 글을 μ°Έκ³ ν•˜λ„λ‘ ν•˜μžβ€¦) 일반적으둜 MD5 보닀 SHA 방식이 μ†λ„λŠ” λŠλ¦¬μ§€λ§Œ μ•ˆμ „ν•˜λ―€λ‘œ, SHA λ₯Ό ν™œμš©ν•˜μ—¬ ν•΄μ‹±ν•΄λ³΄μž !

@GetMapping("/products")
public ResponseEntity<List<Products>> getAllProducts(HttpHeaders headers) {
    try {
        // 1️⃣ μ‘°νšŒν•  데이터
        List<Products> products = productsService.findAll();

        // 2️⃣ ETag 생성 (SHA-256 ν•΄μ‹±)
        String data = objectMapper.writeValueAsString(products);
        String etag = "\"" + generateSHA256ETag(data) + "\"";

        // 3️⃣ If-None-Match 헀더와 λΉ„κ΅ν•˜μ—¬ 304 응닡
        if (etag.equals(headers.getFirst(HttpHeaders.IF_NONE_MATCH))) {
            return ResponseEntity
                    .status(HttpStatus.NOT_MODIFIED)
                    .eTag(etag)
                    .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES))
                    .build();
        }

        // 4️⃣ μƒˆλ‘œμš΄ 데이터λ₯Ό λ°˜ν™˜
        return ResponseEntity.status(200)
                .eTag(etag)
                .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES))
                .body(products);

    } catch (JsonProcessingException e) {
        e.printStackTrace();
        return ResponseEntity.status(500).build();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return ResponseEntity.status(500).build();
    }
}

μ΄λ ‡κ²Œ ν–ˆμ„ λ•Œ, 데이터에 변경이 μ—†λ‹€λ©΄ λΈŒλΌμš°μ €λŠ” 304 응닡을 λ°˜ν™˜ν•œλ‹€.

μ›Ή μΊμ‹œμ˜ ν•œκ³„ ?

그런데 쑰금 κ±°μŠ¬λ¦¬λŠ” 뢀뢄이 μžˆμ—ˆλŠ”λ°, λΈŒλΌμš°μ € μΊμ‹œλ₯Ό 섀정해놔도 μ„œλ²„ μž…μž₯μ—μ„œλŠ” μΊμ‹œλœ 데이터에 변경이 μžˆλŠ”μ§€ μ•Œμ•„λ³΄λ €λ©΄ κ²°κ΅­ DB λ₯Ό 또 μ‘°νšŒν•΄μ„œ ETag λ₯Ό 생성해 비ꡐ해야 ν•œλ‹€. DB 쑰회λ₯Ό μ΅œλŒ€ν•œ 쀄이기 μœ„ν•΄ μΊμ‹œλ₯Ό μ‚¬μš©ν•˜λŠ” 것인데, μ΄λ ‡κ²Œ 되면 도루묡 μ•„λ‹Œκ°€? μ‹Άμ—ˆλ‹€β€¦ κ·Έλž˜μ„œ μ›Ή μΊμ‹œλ₯Ό μš΄μš©ν•¨κ³Ό λ™μ‹œμ— redis λ₯Ό ν™œμš©ν•œ μ„œλ²„ μΊμ‹œλ„ 같이 μš΄μš©ν•˜λ©΄ μ™„λ²½ν•  것 κ°™λ‹€λŠ” 생각이 λ“€μ—ˆλ‹€. λ‹€μŒ μ‹œκ°„μ— 계속 μ•Œμ•„λ³΄μž !


πŸ”– 참고자료

μ›Ή λΈŒλΌμš°μ €μ˜ Cache μ „λž΅ & 헀더 닀루기