2018 M04 26

GraphQL의 결과 값을 CDN에 캐싱 하기 (Caching GraphQL results in your CDN)

Apollo Cache Control, Engine 및 Automatic Persisted Queries 를 함께 사용하면 손쉽게 적용할 수 있습니다.

언뜻보면 GraphQL 은 REST 그리고 다른 HTTP API 기술과 많이 달라보여서 모든 인프라(infrastructure 이하 인프라)를 처음부터 다시 시작 해야할 것처럼 느껴집니다. 하지만 아래와 같이 인프라를 재구축 할 필요 없이 기존 설정에 GraphQL을 추가하여 원하는 결과를 즉시 얻을 수 있습니다.
  1. REST API 를 GraphQL 로 대치할 수 있기에 기존 backend 와 비지니스 로직을 유지할 수 있습니다.
  2. 기존 React app에 GraphQL을 컴포넌트 단위로 적용할 수 있습니다.
  3. GraphQL 툴(tools)은 server code가 있는 곳이라면 어디든 실행 가능하므로 기존 application 인프라로 손쉽게 통합 가능합니다.
여기서, 우리는 컨텐츠 전송망 (Content Delivery Networks)— CDN(CDNs 이하 CDN) 에 대해 이야기할 것 입니다. 처음 GraghQL을 접하면 기존 CDN 설정들 예를들면 Fastly, CloudFlare 또는 Akamai 와 함께 사용하기 쉽지 않다고 느낄 수 있지만 아래 몇가지 간단한 단계만 거치면 손쉽게 사용 가능합니다.
우리는 이번에 선보인 Engine을 통해 보다 더 손쉽게 적용할 수 있는 새로운 기능을 소개할 수 있어서 정말 기쁩니다! 본격적으로 시작하기 앞서, Apollo Engine 설정 후 Engine CDN 통합 문서 를 진행해야 합니다
CDN이 어떤 역할을 하는지 또 이를 GraphQL과 함께 사용하기 위한 다양한 질문 및 해결책을 확인 하고 싶다면 계속해서 읽어 보세요!



CDN이 당신의 API 에 어떤 도움을 주나요?

application에 자주 변경되지 않는 많은 public data를 신속하게 불러오는것이 중요하다면 CDN 을 사용하여 API 결과(results 이하 결과)를 캐시(cache 이하 캐시)하는 것이 도움이 될 수 있습니다. 이는 뉴스 사이트 및 블로그와 같은 미디어(media 이하 미디어) 또는 컨텐츠(contents) 회사에 특히 중요합니다.
CDN은 API 결과를 네트워크의 “edge” 와 가깝게 저장합니다.—즉, 사용자가 속해있는 지역과 가장 가깝게— 그리고 실제(actual) server로 왕복 수행하는 것보다 훨씬 빠르게 캐싱(caching 이하 캐싱)된 결과를 전송 할 수 있습니다. 추가적 이점으론 query가 API에 영향을 주지 않으므로 server load를 줄일 수 있습니다.
Edge 캐싱은 application이 자주 변경되거나 private data를 많이 다룬다면 별 이득이 없지만, 컨텐츠(publishing)나 미디어처럼 자주 변경되지 않는 public data를 다룬다면 반드시 최적화해야 합니다.

오해들(Misconceptions): GraphQL 그리고 CDN

우리가 자주 접하는 질문중 하나가 이러한 유형의 인프라가 GraphQL과 함께 사용될 수 있는지 여부 입니다. 얼핏 보면, GraphQL은 CDN 캐싱을 더욱 힘들게 하는 것처럼 보일수 있지만 그렇지 않습니다. 이는 REST API 사용하는 것보다 더 손쉽게 적용 가능합니다.
자 그럼 몇가지 일반적인 오해들을 살펴보고, 어떻게 쉽게 완화 되는지 살펴 봅시다.

1. GraphQL 요청(requests 이하 요청)이 너무 다양합니다
답변: 대부분의 app에서 사용중인 직접 코딩한(hand-coded) REST endpoint(endpoints 이하 endpoint)보다 다양하지 않습니다.
GraphQL은 REST와 함께 사용되는 URL보다 훨씬 더 유연한 query language이기 때문에 server에 요청한 query 수가 너무 많아 전체 query 결과를 캐시하기가 어려울 수 있습니다. 일부 app에 완전히 동적인 query가 포함된 경우도 있지만 최근 GraphQL 우수 사례(best practices)에 따르면 대부분의 frontend는 매우 한정된 query 집합(very finite set of queries)을 server로 보내는 것을 확인할 수 있었습니다.
GraphQL 사용 전에는 특정 UI 기능에 부합하는 사용자 지정 endpoint생성을 위해 종종 backend-for-frontend 패턴(pattern)을 사용했습니다. 이는 application의 특정 부분을 최적화 하기 위해 유사한 data를 처리하는 수십 또는 수백개의 endpoint가 생성될 수 있음을 의미 합니다.
하지만 GraphQL을 사용하면 관련 endpoint를 생성 및 유지하지 않아도 동일한 효율적 이점을 얻을 수 있습니다. 다시말해 URL 집합(set of URLs)을 직접 코딩하지 않아도 application은 비슷한 수의 고유한 API를 호출(calls) 합니다. 따라서 query와 variables를 통한 특정 뷰(views)의 GraphQL 전체 캐싱 결과는 REST API 전체 캐싱 만큼 합리적인 것을 알 수 있습니다.
GraphQL로 UI code 에 static query 를 작성하기 가장 좋은 방법을 확인해본 결과 Lokka나 Relay Classic를 통해 예측할 수 없는 query를 자동으로 생성하는 툴(tool)을 사용하기 보다는—Apollo Client 또는 Relay Modern 가 더 좋다는 것을 알 수 있었습니다. 다시말해 기존(order) client가 query를 만들고 보내는 방법의 문제(issue)가 오늘날 이러한 오해가 생기는 가장 큰 이유라 할 수 있습니다.

2. GraphQL 요청이 너무 크면, GET 대신 POST 를 사용합니다
답변: Apollo Link 와 Apollo Engine을 겹합하면 손쉽게 작은 요청(smaller requests)으로 전환하여 GET 을 사용할 수 있습니다.
REST 핵심 원칙은 URL을 통해 데이터를 식별하고 HTTP GET 요청으로 data 쓰기(write)가 아닌 읽기(read)작업을 수행 한다는 것입니다. 이는 backend에서 수정 작업이 없기 때문에 CDN에 결과를 캐싱하는 것이 괜찮다는 것을 의미 합니다.
이와 대조적으로 대부분의 GraphQL 툴(tools)은 POST를 사용하여 HTTP 요청을 보내고 URL 대신 query 와 variables 를 포함한 복합적인 요청문을 사용합니다. 이같은 추가적인 복합성으로 인해 일부 브라우저(browsers)에서는 URL 크기 제한으로 URL에 query 및 variables 을 추가하여 GET 으로 처리하기 어려웠습니다
하지만 Apollo 는 client를 위한 모듈형(modular) 네트워크 인터페이스인 Apollo Link 와 Apollo Engine의 “Automatic Persisted Queries” 기능을 결합하여 위와 같은 문제를 모두 해결했습니다. Engine을 설정한 후에는 다음과 같이 손쉽게 client code에 apollo-link-persisted-queries 를 추가할 수 있습니다:
import { createPersistedQueryLink } from "apollo-link-persisted-queries"; import { createHttpLink } from "apollo-link-http"; import { InMemoryCache } from "apollo-cache-inmemory"; import ApolloClient from "apollo-client"; ApolloLink.from([ createPersistedQueryLink({ useGETForHashedQueries: true }), createHttpLink({ uri: "/graphql" }) ]); const client = new ApolloClient({ cache: new InMemoryCache(), link: link });
이를 통해 다음 두가지를 수행할 수 있습니다:
  1. POST 대신 HTTP GET query를 전송하면서 mutation(mutations 이하 mutation)은 지속적으로 POST 를 사용할 수 있습니다.
  2. GET URL 에 간결한 지속적(shortened persisted) query ID를 적용하면 CDN 캐시 키(key)가 짧아지고 URL 크기 제한을 초과하지 않고 사용 가능합니다
GraphQL은 CDN이 처리하도록 설계된 일반적인 HTTP GET 요청에 훨씬 가깝습니다. 문서를 통해 Engine의 Automatic Persisted Queries 를 좀 더 자세히 확인 가능합니다.

3. GraphQL query에 캐시 힌트(cache hints 이하 캐시 힌트)를 지정하는 것은 매우 어렵습니다
답변: Apollo Cache Control을 사용하면 각 query에 대한 힌트(hints)를 별도로 지정할 필요가 없습니다—GraghQL types 및 fields 로 해결 가능합니다.
REST API를 사용하면 특정 endpoint에서 Cache-Control header 를 반환하고 새로운 endpoint을 작성할때까지 동일한 endpoint가 유지 됩니다. 그러나 GraphQL을 사용하면 frontend에 대한 query 를 지속적으로 개선하고 다른 버전의 UI에 필요한 fields를 추가 및 제거 할 수 있습니다. 자 그렇다면 결과에 포함된 데이터가 변경되도 cache control 힌트(hints)를 사용하여 query 를 최신 상태로 유지하려면 어떻게 해야 할까요?
바로 이를 해결하기 위해 Apollo Cache Control 이 설계된 것 입니다. 이는 GraphQL 서버가 각 field 에서 캐시 힌트를 반환하는 방법에 대한 스펙입니다. 다음은 캐시 힌트를 지정하는 방법을 JavaScript 로 구현한 레퍼런스(reference) 입니다:
전체 schema 경우:
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema, context: {}, tracing: true,
// Set a max age of 5 seconds for the whole schema cacheControl: { defaultMaxAge: 5, }, }));
GraphQL type 또는 field 경우:
type Post @cacheControl(maxAge: 240) { id: Int! title: String author: Author votes: Int @cacheControl(maxAge: 30) readByCurrentUser: Boolean! @cacheControl(scope: PRIVATE) }
또는 resolver 를 한번만 실행하는 경우:
const resolvers = { Query: { post: (_, { id }, _, { cacheControl }) => { cacheControl.setCacheHint({ maxAge: 60 }); return find(posts, { id }); } } }
이는 API가 다양한 data의 만료(expiration)를 지정하고 frontend code가 요구한 모든 query를 자유롭게 유지시켜주는 중요한 기능 입니다.
마지막으로, Engine은 이러한 모든 힌트(hints)를 CDN 이 이해할 수 있도록 간편하게 하나의 Cache-Conotrol header로 결합합니다.
노트: CDN을 사용하지 않는 경우, cache control을 사용하여 Apollo Engine 캐싱 기능을 강화할 수 있습니다. 이 캐싱 문서를 읽어보시기 바랍니다.

종합해보면

확인한 바와같이 edge 에서 CDN 캐싱 이점을 누릴수 있는 GraphQL 데이터가 있다면 이 모든것을 정상 동작하도록 설정하는 것은 간단합니다. 다음은 최근 작업한 다양한 툴(tools) 사이의 상호 작용(interplay)을 보여주는 훌륭한 예시 입니다:
  1. Apollo Link의 Automatic Persisted Queries 는 mutation 이 POST 를 사용하면서 GET query 가능하도록 도와 줍니다.
  2. Apollo Cache Control을 사용하면 세분화된(fine-grained) schema 지향 방식으로 cache control 정보를 지정할 수 있습니다.
  3. Apollo Engine은 GET 요청에서 사용 가능한 작은 query IDs를 생성해 캐시 크기를 제한하여 CDN 의 Cache-Control header를 설정 합니다.
마지막으로 이를 종합 후 반영하면 여러분이 즐겨찾는 CDN 서비스에서 캐시 된 결과를 확인할 수 있습니다:
presentation
데모 app의 Fastly dashboard 스크린샷 입니다.

Engine CDN 문서를 통해 직접 설정할 수 있는 방법을 확인해보세요!
작년 GraphQL Summit에서 저는 client, API gateway, server 등—스택 전반에 더 많은 툴(tools)이 함께 동작하는 것을 보면서 GraphQL 성능이 어떻게 향상될 것인지에 대해 이야기 했습니다. 이러한 기술들이 결합되는 현상은 매우 흥미롭고 신나는 일이며, 벌써 다음이 기다려집니다!
presentation
Martijn Walraven 과 David Glasser 에게 감사드립니다.


이 글은 번역글 입니다.
원문은 아래를 참고 바랍니다.