SCRUM-15521 | 커밋 수: 4개 | 기간: 2025.09


문제상황

이미지 캐시 정책이 일괄 적용되어 유연성이 부족했습니다. 오더픽처럼 재고 변동이 잦은 도메인과 배너처럼 변경이 적은 도메인의 캐시 요구사항이 상이했지만, 동일한 정책이 적용되고 있었습니다.

주요 개선사항

Cache-Control 헤더를 통해 TTL을 커스텀으로 설정할 수 있는 기능을 추가했습니다. 오더픽과 스퀘어 목록에 이미지 캐시 TTL 커스텀을 적용했으며, loadImage 함수를 단일책임 원칙에 따라 분리했습니다.

정량적 성과

이미지 로딩 함수를 단일책임 원칙에 따라 리팩토링했습니다. 도메인별로 캐시 TTL을 개별 설정할 수 있게 되어, 오더픽과 스퀘어는 5분, 상품은 60분, 배너는 1440분(24시간)으로 차등 적용했습니다.

도메인별 캐시 TTL 설정
항목비교값 (분)
오더픽55 분
스퀘어55 분
상품6060 분
배너14401440 분

비즈니스 임팩트

도메인 특성에 맞는 캐시 정책을 적용하여 이미지 로딩 성능을 최적화했습니다. 자주 변경되는 이미지는 짧은 TTL로 최신성을 보장하고, 변경이 적은 이미지는 긴 TTL로 네트워크 비용을 절감했습니다.


기술적 구현 상세

AS-IS

장점

  • 단순한 구현

고려할 점

  • 모든 이미지에 동일 캐시 정책
  • 도메인별 요구사항 반영 불가
  • 자주 변경되는 이미지도 오래 캐시

TO-BE

장점

  • 도메인별 TTL 커스텀 설정
  • loadImage 단일책임 분리
  • 네트워크 비용 절감

고려할 점

  • 설정 관리 포인트 증가

AS-IS

기존에는 모든 이미지에 동일한 캐시 정책이 적용되었습니다.

// 기존 구조 (더미 예시)
const loadImage = (src) => {
  const img = new Image();
  img.src = src;
  return img;
};

문제점:

자주 변경되는 오더픽 이미지도 오래 캐시되어 최신 상태를 반영하지 못했습니다. 도메인별 캐시 요구사항을 반영할 수 있는 구조가 아니었습니다.

TO-BE

Cache-Control 헤더를 통해 도메인별 TTL을 커스텀 설정합니다.

  1. 이미지 요청
  2. 도메인 판별
  3. TTL 적용
  4. 캐시 로딩

핵심 구현

1. loadImage 함수 리팩토링 (단일책임 원칙)

// public/js/common.js

// 이미지 로딩 함수 (단일책임: 이미지 로딩만 담당)
const loadImage = (src, options = {}) => {
  return new Promise((resolve, reject) => {
    const img = new Image();

    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error(`Failed to load image: ${src}`));

    // 캐시 TTL 설정
    if (options.cacheTTL) {
      img.src = appendCacheControl(src, options.cacheTTL);
    } else {
      img.src = src;
    }
  });
};

// 캐시 제어 함수 (단일책임: 캐시 설정만 담당)
const appendCacheControl = (src, ttl) => {
  const url = new URL(src, window.location.origin);
  url.searchParams.set('cache-control', `max-age=${ttl}`);
  return url.toString();
};

// 이미지 프리로딩 함수 (단일책임: 프리로딩만 담당)
const preloadImages = async (srcs, options = {}) => {
  const promises = srcs.map(src => loadImage(src, options));
  return Promise.allSettled(promises);
};

2. Cache-Control 헤더 설정 로직

// public/js/common.js
const ImageCacheConfig = {
  // 자주 변경되는 이미지 (짧은 TTL)
  SHORT: 60 * 5,        // 5분

  // 일반 이미지 (중간 TTL)
  MEDIUM: 60 * 60,      // 1시간

  // 거의 변경되지 않는 이미지 (긴 TTL)
  LONG: 60 * 60 * 24,   // 24시간
};

// 도메인별 캐시 TTL 설정
const getDomainCacheTTL = (domain) => {
  const domainConfig = {
    orderpick: ImageCacheConfig.SHORT,   // 오더픽: 재고 변동 잦음
    esquare: ImageCacheConfig.SHORT,     // 스퀘어: 피드 업데이트 잦음
    product: ImageCacheConfig.MEDIUM,    // 상품: 중간 빈도
    banner: ImageCacheConfig.LONG,       // 배너: 변경 적음
  };

  return domainConfig[domain] ?? ImageCacheConfig.MEDIUM;
};

3. 컴포넌트에서 사용

<!-- components/orderpick/ProductListItem.vue -->
<script setup lang="ts">
const props = defineProps<{
  product: OrderPickProduct;
}>();

// 오더픽 이미지는 짧은 TTL 적용
const imageOptions = {
  cacheTTL: 300, // 5분
};
</script>

<template>
  <div class="product-item">
    <img
      :src="product.imageUrl"
      :data-cache-ttl="imageOptions.cacheTTL"
      loading="lazy"
    />
  </div>
</template>

<!-- components/esquare/PopularFeedListItem.vue -->
<script setup lang="ts">
const props = defineProps<{
  feed: SquareFeed;
}>();

// 스퀘어 피드 이미지는 짧은 TTL 적용
const imageOptions = {
  cacheTTL: 300, // 5분
};
</script>

<template>
  <article class="feed-item">
    <img
      :src="feed.thumbnailUrl"
      :data-cache-ttl="imageOptions.cacheTTL"
      loading="lazy"
    />
  </article>
</template>


회고

잘한 점

loadImage 함수를 단일책임 원칙에 따라 로딩, 캐시 제어, 프리로딩으로 분리했습니다. 도메인별 캐시 TTL 설정으로 유연성을 확보하여, 각 도메인의 특성에 맞는 캐시 전략을 적용할 수 있게 되었습니다.

개선할 점

서버 측 Cache-Control 헤더와의 연동에 대한 검토가 필요합니다. 클라이언트 측 설정과 서버 측 헤더가 충돌하지 않도록 정책을 정리해야 합니다.

배운 점

이미지 캐시 전략이 사용자 경험에 미치는 영향의 중요성을 인식했습니다. 단일책임 원칙을 함수 단위에 적용하는 방법을 체득했고, 도메인별 캐시 요구사항을 분석하여 차등 적용하는 패턴을 익혔습니다.