ASP.NET Core와 응답 캐싱

등록일시: 2018-05-28 08:00,  수정일시: 2018-05-31 12:23
조회수: 3,916
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

응답 캐싱은 클라이언트나 프록시가 웹 서버에 요청하는 횟수를 줄여줍니다. 그리고 응답 캐싱은 웹 서버가 응답을 생성하기 위해 수행해야 하는 작업의 총량 또한 줄여줍니다. 응답 캐싱은 클라이언트, 프록시, 그리고 미들웨어가 응답을 캐싱해야 하는 방식을 지시하는 헤더에 의해서 제어됩니다.

응답 캐싱 미들웨어를 추가하면 웹 서버가 응답을 캐시할 수 있습니다.

HTTP 기반 응답 캐싱

HTTP 1.1 캐싱 사양은 인터넷 캐시가 동작해야 하는 방식을 기술하고 있습니다. 캐싱에 사용되는 가장 기본적인 HTTP 헤더는 Cache-Control로, 이 헤더는 캐시 지시문을 지정하는 데 사용됩니다. 지시문은 요청이 클라이언트에서 서버로 전달될 때의 캐싱 동작과 응답이 서버에서 클라이언트로 되돌아올 때의 캐싱 동작을 제어합니다. 프록시 서버를 통해서 이동하는 요청 및 응답, 그리고 프록시 서버 역시 HTTP 1.1 캐싱 사양을 준수해야 합니다.

기본적인 Cache-Control 지시문은 다음 표와 같습니다.

지시문 동작
public 캐시가 응답을 저장할 수 있습니다.
private 공유 캐시는 응답을 저장하지 않습니다. 사설 캐시는 응답을 저장하고 재사용 할 수 있습니다.
max-age 클라이언트는 지정된 초 수보다 오래된 응답을 수락하지 않습니다. 예: max-age=60 (60초), max-age=2592000 (1개월)
no-cache 요청 시: 캐시는 저장된 응답을 이용해서 요청에 대응하면 안됩니다. 주의: 원본 서버는 클라이언트에 대한 응답을 다시 생성하고 미들웨어는 캐시에 저장된 응답을 갱신해야 합니다.

응답 시: 원본 서버에서 유효성을 검사받지 않은 응답을 후속 요청에 사용하면 안됩니다.
no-store 요청 시: 캐시가 요청을 저장하지 않습니다.

응답 시: 캐시가 응답의 모든 부분에서 응답을 저장하지 않습니다.

캐싱 역할을 수행하는 다른 캐시 헤더는 다음 표와 같습니다.

헤더 기능
Age 응답이 생성되거나 원본 서버에서 정상적으로 검증된 이후로부터 지난 초 단위의 총 추정 시간.
Expires 응답이 만료된 것으로 간주되는 날짜/시간.
Pragma no-cache 동작 설정에 대한 HTTP/1.0 캐시와의 하위 호환성을 지원하기 위해 존재합니다. Cache-Control 헤더가 존재할 경우 Pragma 헤더는 무시됩니다.
Vary 모든 Vary 헤더 필드가 캐시된 응답의 원본 요청 및 새 요청 모두와 일치하지 않는 한 캐시된 응답이 전송되지 않도록 지정합니다.

HTTP 기반 캐싱은 Cache-Control 지시문을 준수합니다.

Cache-Control 헤더에 대한 HTTP 1.1 캐싱 사양은 캐시에게 클라이언트가 전송하는 유효한 Cache-Control 헤더를 준수할 것을 요구합니다. 가령 클라이언트는 no-cache 헤더 값을 설정한 요청을 전송함으로써 모든 요청에 대해 서버가 새로운 응답을 생성하도록 강제할 수 있습니다.

HTTP 캐싱의 목적을 고려해본다면 항상 클라이언트의 Cache-Control 요청 헤더를 준수하는 것이 이치에 맞습니다. 공식 사양에서 캐싱은 클라이언트, 프록시 및 서버 간의 네트워크에서 요청의 대기 시간 및 네트워크 오버헤드를 만족스럽게 줄이기 위한 것입니다. 원본 서버에서 부하를 제어하기 위한 방법이 필수적인 것은 아닙니다.

응답 캐싱 미들웨어를 사용할 때 캐싱 동작에 관해서 개발자가 제어할 수 있는 부분은 현재 존재하지 않으며, 그 이유는 미들웨어가 공식 캐싱 사양을 따르고 있기 때문입니다. 다만 향후에는 캐시된 응답을 제공하기로 결정할 경우 미들웨어가 요청의 Cache-Control 헤더를 무시하게 구성할 수 있도록 미들웨어를 개선할 것입니다. 이렇게 하면 미들웨어를 사용할 때 서버의 부하를 보다 세밀하게 제어할 수 있습니다.

ASP.NET Core의 다른 캐싱 기술들

메모리 내 캐싱

메모리 내 캐싱은 서버 메모리를 이용해서 캐시된 데이터를 저장합니다. 이 방식의 캐싱은 단일 서버나 고정 세션을 사용하는 복수의 서버에 적합합니다. 고정 세션은 클라이언트의 요청이 처리를 위해 항상 동일한 서버로 라우트 된다는 것을 뜻합니다.

보다 자세한 정보는 메모리 내 캐시를 참고하시기 바랍니다.

분산 캐시

응용 프로그램이 클라우드나 서버 팜에서 호스팅 될 때 메모리에 데이터를 저장하려면 분산 캐시를 사용합니다. 이 캐시는 요청을 처리하는 서버들 간에 서로 공유됩니다. 클라이언트에 대한 캐시 데이터를 사용할 수 있는 경우 클라이언트는 그룹의 어떤 서버에서나 처리할 수 있는 요청을 제출할 수 있습니다. ASP.NET Core는 SQL Server 및 Redis 분산 캐시를 제공합니다.

보다 자세한 정보는 분산 캐시 사용하기를 참고하시기 바랍니다.

캐시 태그 도우미

캐시 태그 도우미를 사용하면 MVC 뷰 또는 Razor 페이지의 콘텐츠를 캐시할 수 있습니다. 캐시 태그 도우미는 메모리 내 캐시에 데이터를 저장합니다.

보다 자세한 정보는 ASP.NET Core MVC와 캐시 태그 도우미를 참고하시기 바랍니다.

분산 캐시 태그 도우미

분산 캐시 태그 도우미를 사용하면 분산 클라우드 또는 웹 팜 시나리오에서 MVC 뷰 또는 Razor 페이지의 콘텐츠를 캐시할 수 있습니다. 분산 캐시 태그 도우미는 SQL Server 또는 Redis에 데이터를 저장합니다.

보다 자세한 정보는 분산 캐시 태그 도우미를 참고하시기 바랍니다.

ResponseCache 특성

ResponseCacheAttribute는 응답 캐싱이 적절한 헤더를 설정하기 위해서 필요한 매개 변수를 지정합니다.

경고

인증된 클라이언트의 정보를 포함하는 콘텐츠는 캐싱하면 안됩니다. 사용자 ID 또는 사용자 로그인 여부와 무관하게 변경되지 않는 콘텐츠만 캐싱해야 합니다.

VaryByQueryKeys 속성은 지정한 쿼리 키 목록의 값에 따라 저장된 응답을 변경합니다. 단일 값으로 * 를 지정하면 모든 쿼리 문자열 매개 변수에 의해 미들웨어의 응답이 달라집니다. VaryByQueryKeys를 사용하려면 ASP.NET Core 1.1 이상이 필요합니다.

응답 캐싱 미들웨어는 반드시 VaryByQueryKeys 속성을 설정할 수 있어야 합니다. 그렇지 않을 경우 런타임 예외가 던져집니다. VaryByQueryKeys 속성에 대응하는 HTTP 헤더는 존재하지 않습니다. 이 속성은 응답 캐싱 미들웨어에 의해서 처리되는 HTTP 기능입니다. 미들웨어가 캐시된 응답을 제공하기 위해서는 쿼리 문자열 및 쿼리 문자열 값이 이전 요청과 동일해야 합니다. 예를 들어, 다음 표는 요청 순서에 따른 결과를 보여줍니다.

요청 결과
http://example.com?key1=value1 서버에서 반환됨
http://example.com?key1=value1 미들웨어에서 반환됨
http://example.com?key1=value2 서버에서 반환됨

첫 번째 요청은 서버에서 반환되고 미들웨어에 캐시됩니다. 두 번째 요청은 쿼리 문자열이 이전 요청과 일치하기 때문에 미들웨어에서 반환됩니다. 세 번째 요청은 쿼리 문자열 값이 이전 요청과 일치하지 않기 때문에 미들웨어 캐시에 존재하지 않습니다.

ResponseCacheAttributeResponseCacheFilter를 구성하고 생성하기 위해서 (IFilterFactory를 통해서) 사용됩니다. 그리고 ResponseCacheFilter는 응답의 적절한 HTTP 헤더 및 기능을 갱신하는 작업을 수행합니다. 이 필터는:

  • 기존에 존재하는 Vary, Cache-Control, 및 Pragma 헤더를 모두 제거합니다.
  • ResponseCacheAttribute에 설정된 속성을 기반으로 적절한 헤더를 작성합니다.
  • VaryByQueryKeys 속성이 설정된 경우 응답 캐싱 HTTP 기능을 갱신합니다.

Vary

이 헤더는 VaryByHeader 속성이 설정된 경우에만 작성됩니다. 이 속성은 Vary 헤더의 값을 설정합니다. 다음 예제는 VaryByHeader 속성을 사용합니다:

[ResponseCache(VaryByHeader = "User-Agent", Duration = 30)]
public IActionResult About2()
{

브라우저의 네트워크 도구를 사용하면 응답 헤더를 확인할 수 있습니다. 다음 그림은 About2 액션 메서드를 갱신했을 때 Edge F12 개발자 도구의 네트워크 탭의 출력을 보여줍니다:

About2 액션 메서드 호출시 Edge F12 개발자 도구의 네트워크 탭의 출력

NoStore 및 Location.None

NoStore 속성은 다른 대부분의 속성을 덮어씁니다. 이 속성이 true로 설정되면 Cache-Control 헤더가 no-store로 설정됩니다. 만약 LocationNone으로 설정되면:

  • Cache-Controlno-store,no-cache로 설정됩니다.
  • Pragmano-cache로 설정됩니다.

NoStorefalse로, 그리고 LocationNone으로 설정되면, Cache-ControlPragmano-cache로 설정됩니다.

보통 오류 페이지에서 NoStoretrue로 설정하는 경우가 많습니다. 예를 들어:

[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
    return View();
}

그 결과 다음과 같은 헤더가 만들어집니다:

Cache-Control: no-store,no-cache
Pragma: no-cache

Location 및 Duration

캐싱을 사용하기 위해서는 Duration을 양수 값으로 설정하고 LocationAny (기본값) 또는 Client이어야 합니다. 이 경우 Cache-Control 헤더는 응답의 max-age가 뒤에 추가된 위치 값으로 설정됩니다.

노트

Location 옵션의 AnyClient는 각각 Cache-Control 헤더의 값, publicprivate로 변환됩니다. 앞에서 설명한 것처럼 LocationNone으로 설정하면 Cache-ControlPragma 헤더가 모두 no-cache로 설정됩니다.

다음은 Duration을 설정하고 Location을 기본 값 그대로 변경하지 않고 생성하는 헤더를 보여주는 예제입니다:

[ResponseCache(Duration = 60)]
public IActionResult Contact()
{
    ViewData["Message"] = "Your contact page.";

    return View();
}

이 코드는 다음과 같은 헤더를 생성합니다:

Cache-Control: public,max-age=60

캐시 프로필

수 많은 컨트롤러 액션 속성마다 ResponseCache 설정을 반복하는 대신, StartupConfigureServices 메서드에서 MVC를 설정할 때 캐시 프로필을 옵션으로 설정할 수 있습니다. 참조된 캐시 프로필에 지정된 값은 ResponseCache 특성에 의해 기본값으로 사용되며 특성에 지정된 모든 속성에 의해 덮어써집니다.

먼저 캐시 프로필을 설정합니다:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.CacheProfiles.Add("Default",
            new CacheProfile()
            {
                Duration = 60
            });
        options.CacheProfiles.Add("Never",
            new CacheProfile()
            {
                Location = ResponseCacheLocation.None,
                NoStore = true
            });
    });
}

그리고 캐시 프로필을 참조합니다:

[ResponseCache(Duration = 30)]
public class HomeController : Controller
{
    [ResponseCache(CacheProfileName = "Default")]
    public IActionResult Index()
    {
        return View();
    }

ResponseCache 특성은 액션(메서드) 및 컨트롤러(클래스) 모두에 적용할 수 있습니다. 메서드 수준 특성은 클래스 수준 특성에 지정된 설정을 덮어씁니다.

위의 예제에서는 클래스 수준 특성이 30 초의 지속 시간을 지정하는 반면, 메서드 수준 특성은 지속 시간이 60 초로 설정된 캐시 프로필을 참조하고 있습니다.

결과 헤더:

Cache-Control: public,max-age=60

추가 자료