[발 번역] Caching HTTP POST Requests and Responses

해당 블로그는 KT UCloud의 지원을 받고 있습니다.

해당 글은 http://www.ebaytechblog.com/2012/08/20/caching-http-post-requests-and-responses/ 의 글을 발번역 한 것입니다. 아마도 온라인 쇼핑 관련 사이트도 트래픽이 상위권에 속할 것입니다. 그 중에서도 eBay 나 아마존등은 말할 필요도 없을듯 합니다. 그럼 오역에 주의하세요.

Caching HTTP POST Requests and Responses

by SURESH MATHEW on 08/20/2012

HTTP 캐싱의 기본 목적은 어플리케이션에 더 좋은 확장성을 주고 성능을 빠르게 하기 위한 메커니즘을 제공하는 것입니다. 그러나 HTTP 캐싱은 멱등한 요청에만 적용됩니다. 오직 Itempotent 하고 Nullpotent 한 요청만 여러번 호출되어도 같은 결과를 제공합니다.( 역자 주: Itempotent는 멱등하다라는 어려운 한글 뜻이 있습니다. set A=3; 처럼 여러번 수행해도 동일한 결과를 보장합니다. Nullpotent는 한글 뜻은 잘 모르겠고, side effect 가 전혀 없는, 일종희 함수형 언어처럼, 데이터 값을 아예 바꾸지 않는 것을 의미합니다. 이런 경우에만, HTTP Caching을 하는 의미가 있는 것입니다. ) HTTP 세상에서는 GET 요청은 캐싱할 수 있지만 POST는 안된다라는 것을 의미합니다.

 그러나, idempotent 한 요청의 경우에도  유명한 소프트웨어들이 요청이 제한을 넘어버려서 간단하게 GET을 이용하여 보낼 수 없는 경우가 있습니다. 예를 들어, 검색 API는 일반적으로 많은 수의 파라매터를 사용하고, 특별히 실환경에서 더더욱 수 많은 특성들이 있고, 파라매터로서 전부 통과되어야 합니다. 이런 상황은 다음과 같은 질문들을 이끌어냅니다. “GET 요청이 허락된 크기보다 더 많은 파라매터를 포함하고 있는 요청의 경우 커뮤니케이션을 위해서 어떤 방법을 추천하나요?” 여기에 몇 가지 답변이 있습니다.

  • 많은 수의 파라매터를 요구한다면, 인터페이스 설계에 대해서 재 평가하기를 원할지도 모릅니다. Idempotent 요청은 일반적으로 GET 제한 내에서 잘 통과하는 파라매터 수를 가지고 있습니다.
  • 스펙에 따른, 강하고, 빠른 제약 사항은 없습니다. 그래서 HTTP 스펙 자체는 비난 받을 것이 아닙니다. 서버와 클라이언트가 이런 제약을 강제합니다. 어떤 종류는 8 KB까지 지원하지만, 2KB 이하로 유지하는 것이 안전합니다.
  • GET 요청 안에 내용을 보냅니다.

여기서, 실 환경에서는 위의 모든 답변은 만족스럽지 못합니다. 상황을 바꾸거나 극복할 수 있는 방법을 전혀 제시하지 않습니다.

HTTP caching basics

이 주제의 나머지 부분에 대해서 알아보기 위해서, 먼저 캐싱 메커니즘에 대해서 빠르게 살펴보도록 하겠습니다.

HTTP 캐싱은 클라이언트, 프록시, 서버를 포함합니다. 이 글에서는, 주로 클라이언트와 서버 사이에 존재하는 프로시에 관해서 얘기할 것입니다. 일반적으로, Reverse 프록시는 server 가까이 배포되고, Forward 프로시는 클라이어트 가까이 배포됩니다. Figure 1은 기본적인 토폴로지를 보여줍니다. 아래 그림을 보면 forward 프록시에서의 캐시 히트는 네트웍 밴드위스와 Round-Trip Time(RTT)와 지연시간을 줄여주는 것은 명확합니다. 그리고 Reverse 프록시에서의 캐시 히트는 서버의 로드를 줄여줍니다.

forward cache proxy between client and internet, and reverse cache proxy between internet and server

Figure 1. Basic topology of proxy cache deployment in a network

HTTP 스펙에서는 다음 상황을 만족할 때 캐시에서 응답을 내보내는 것을 허용하고 있습니다.

  1. 캐시된 응답은 원 서버에서 해당 요청을 처리한 결과와 동일해야 한다. 요약하면, 프록시는 캐시된 응답과 원 서버의 응답이 동일하다는 것을 보장해야 한다.
  2.  Freshness는 클라이언트에게 허용됩니다.
  3.  적절한 경고가 추가되면 Freshness는 클라이언트에게 허용되지 않습니다.

스펙에서 많은 수의 기능과 연관된 헤더와 제어 방식이 있습니다. 좀 더 자세한 스펙을 보고 싶다면  http://tools.ietf.org/html/rfc2616 를 보시면 되고, 좀 더 자세한 캐시 컨트롤을 보고 싶다면 http://tools.ietf.org/html/rfc2616#section-14.9을 보시면 됩니다.

보통 프로시는 idempotent 한 요청을 캐싱합니다. 프로시가 요청을 받으면, 캐시 헤더를 확인하고, 그것을 서버로 전달합니다. 그리고 프록시는 응답을 확인하고, 캐시 가능하면, 그것을 URL을 키로 지정하고( 특정 케이스에는 헤더도 함께 캐싱합니다. ) 그리고 응답을 값으로 캐시로 저장하게 됩니다. 해당 스키마는 GET 요청에서는 같은 URL이 요청될 경우 응답이 바뀌지 않기 때문에  잘 동작합니다. 그러나 idempotent  한 POST 요청일 경우에는 그렇지 못합니다. 같은 URL이라도 다른 파라매터를 가지고 있을 수 있므로 URL을 키로 사용할 수 없습니다.( 역자 주: GET의 경우 url string 만 이용해서 모든 정보가 전달되므로 그냥 URL만 캐싱하면 간단하지만, POST의 경우는 stdin 으로 추가 파라매터가 들어가므로 이에 대해서 파싱해서 동일한 요청인지 확인해야 합니다.)

POST body digest

해결 방법은 POST 바디를 몇몇 헤더와 함께 이해하고 요약하는 것입니다. URL 역시 요약 정보에 추가하고 이 요약 정보를 URL 대신에 캐시 키로 이용합니다.( Figure 2를 보세요.), 다른 말로, 캐시 키는  URL 정보와 포함한 내용에 따라서 수정되어집니다. 같은 내용을 가진 다음 번 요청은 서버에서 처리되지 않고 캐시에서 히트될 것입니다. 실제로는, 적절한 유즈 케이스로써, 몇몇 헤더 정보를 캐시 키를 유일하게 만들 기 위해서 추가합니다. 추천할만한 적절한 알고리즘은 없지만, body의 요약을 위해서 MD5를 사용하기도 했고, Content-MD5는 헤더로도 사용할 수 있습니다.

Figure 2. Digest-based cache

이제 문제는 Idempotent POST 요청을  그렇지 않은 것과 구분하는 것입니다. 이 문제를 다루는 방법이 몇 가지 있습니다.

  • 매칭이 되면 캐쉬 하지 않기 위해서, 프록시에 URL 과 패턴을 설정합니다.
  • 리퀘스트들을 구분하기 위해서 컨텍스트 관련 헤더를 추가합니다.
  • 캐시를 위해 명명 규칙을 추가합니다. 예를 들어, API 이름이 set, add, delete 형태로 시작하면 캐시하지 않고 언제나 서버로 보냅니다.

Handling Non-Idempotent Requests

여기서 어떻게 Idempotent 하지 않은 요청을 처리했는지에 대해서 얘기하겠습니다.

  • 다음 경우에 하나라도 속하면 원 서버로 보냅니다.
    • “캐시 하지 말 것”으로 설정된 URL list로 설정된 URL이 있는 경우
    • 요약 정보가 일치하지 않을 경우
    • 캐시 expire 타임이 지난 경우
    • 재확인해야 하는 요청을 받았을 경우
  • 내용이 오래되었다는 경고를 추가함으로써, 스펙을 만족시킵니다.
  • 프록시를 사용하지 않는 클라이언트 쪽의 툴을 이용해서 사용자가 원 서버에 요청하도록 허용합니다.

Apache Traffic Server 를 이용해서 솔루션을 구현했습니다. POST 요청과 캐시 키를 캐시하도록 커스터마이징했습니다.

Advantages

해당 솔루션은 다음과 같은 장점을 제공합니다.

  • 프록시에서 원서버로 데이터를 확인하는 작업을 수행하지 않으므로, 요청에 응답하는 시간이 빨라졌습니다.
  • 호스트된 솔루션으로써, 헤더가 적합하면 단지 한 유저의 다음 요청뿐만 아니라, 다른 유저의 요청에도, 캐시된 정보를 제공할 수 있습니다.
  • 프록시와 원 서버간의 네트웍 밴드위스를 절약할 수 있습니다.

다음은 Forward Proxy를 배포하고 20KB 정도의 데이터를 전달할 때의 성능 비교입니다.

No Caching With Caching
188 ms 90 ms

Forward 프록시와 Reverse 프록시 또는 둘 다를 함께 이용해서, 다양한 솔루션이 요청과 응답을 캐시하기 위해서 사용될 수 있습니다.

Cache handshake

완전한 이득을 얻기 위해서, 해당 솔루션에서, Forward 프록시를 클라이언트 쪽에, 그리고 Reverse Proxy를 서버 단에 배포했습니다. 클라이언트가 요청을 Forward 프록시로 보내면, 프록시는 캐시를 검색하기 시작합니다. 캐시가 없다면, Forward 프록시는 요청을 요약하고, 요약된 정보를 Reverse 프록시로 전달합니다. Reverse 프록시는 캐시된 요청중에 적합한 것을 찾고, 발견하게 되면, 요청을 서버로 보냅니다. 차이점은 완전한 요청을 Forward 프록시로 부터 Reverse 프록시로 보내지 않는다는 것입니다.

서버는 응답을 Reverse 프록시로 보냅니다. Reserve 프록시에서 응답은 요약되고, 완전한 응답이 아닌(Figure .3) 오직 이 요약 정보만 Forward 프록시로 전달합니다. 기본적으로, 캐시가 히트하지 못했을 때, (요약된 정보의) round trip 비용에서 POST Data 부분 만큼 절약하게 됩니다. 대부분의 네트워크에서 클라이언트와 Forward 프록시 사이의 RTT 와 서버와 Reverse 프록시 간의 RTT 는 클라이언트와 서버사이의 RTT에 비하면 무시해도 될 정도입니다. 이 사실은 일반적으로 Forward 프록시 와 클라이언트가 같은 LAN 안에서 서로 가깝게 위치하고 있기 때문입니다. 이와 같이, Reverse 프록시와 서보도 같은 LAN 안에서 서로 가까운 위치에 존재합니다. 프록시 사이의 네트웍 전송 지연 시간은 데이터가 인터넷을 통해서 전달되는 시간입니다.

Sending only digests across the Internet

Figure 3. Cache handshake

해당 솔루션은 클라이언트나 서버 측  수정 비용면에서 한쪽에서만 적용할 수 도 있습니다. 이런 경우에, 클라이언트나 서버는 전체 정보 대신에 요약된 정보를 보내야만 합니다. 두 프록시 아키텍처에서, 클라이언트와 서버는 변경되지 않은 채로 남아있습니다. 그 결과, HTTP 클라이언트나 서버는 최적화 될 수 있습니다.

POST 요청은 일반적으로 큰 사이즈로 구성됩니다. 프록시가 전체 요청과 전체 응답을 보내지 않음으로써, 큰 요청과 응답을 포함해서 네트웍 밴드위스를 아끼는 것 뿐만 아니라, 응답 시간도 줄일 수 있습니다.

테스트로는 그 아끼는 폭이 작아보이더라도, 실제 트래픽이 들어온다면, 그렇지 않을 것입니다. 이미 알고 있듯이, POST 응답이 캐시되지 않더라도, 완전한 데이터를 보내지 않음으로써 여전히, 네트웍 밴드위스는 절약할 수 있습니다. 해당 솔루션은 네트웍에서 분산 캐시를 배포했을 때, 좀 더 이점을 얻을 수 있습니다.

Advantages

캐시 핸드쉐이킹 토폴로지의 장점을 요약하면 다음과 같습니다.

  • 완전한 요청 내용은 오직 Reverse 프록시에서 캐시되어 있지 않을 때만, 전송됩니다. 그 결과 양쪽의 RTT와 네트웍 밴드위스가 좋아집니다. 응답에도 똑 같이 적용됩니다.
  • 호스트된 솔루션으로, 하나의 요청 캐시는 다른 유저의 요청이 프록시간 전송되는 비용을 아껴줄 수 있습니다.
  • 프록시를 제거하더라도, HTTP 에 맞는 솔루션을 여전히 이용가능합니다. 어떤 기술적인 부담이 없습니다.

Conclusion

HTTP 캐싱은 GET 요청만을 위한 것이 아닙니다. POST 내용을 요약함으로써, idempotent 하지 못한 요청도 다룰 수 있고 itempotent 한 요청과 그렇지 않은 요청도 구별할 수 있습니다. 네트웍 밴드위스와 응답 시간도 상당히 절약됨을 실감할 수 있을 것입니다.  추가적인 절약을 위해서, 이상적으로  캐시 핸드쉐이킹을 클라이언트 쪽에 Forward 프록시를, 서버쪽에는 Reverse 프록시를 구현해서 인터넷을 통해서는 오직 요약된 정보만 보낼 수 있습니다. 하지만 하나의 프록시만으로 성능 차이를 만드는 것이 충분합니다.(역자 주: 다만 한 군데의 프록시라면, 아까 말했던 여러가지 문제점이 생길 수 있습니다. 메시지를 요약할 수 있도록 클라이언트나 서버가 수정되어야 하거나, 요약 정보의 전송이 안된다는 부분들이 문제가 될듯 하네요.)