๋“ค์–ด๊ฐ€๋ฉฐ

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 ์ „๋žต & ํ—ค๋” ๋‹ค๋ฃจ๊ธฐ