sbyeol3/articles

[번역] Netflix가 GraphQL Federation으로 API를 확장하는 방법 (Part 1)

Opened this issue · 0 comments

원문 : How Netflix Scales its API with GraphQL Federation (Part 1)

넷플릭스는 루즈한 결합과 확장성이 뛰어난 마이크로서비스 아키텍처를 사용하는 것으로 알려져 있습니다. 독립적인 서비스를 통해 서로 다른 속도로 발전하며 독립적으로 확장시킬 수 있습니다. 그러나 여러 서비스에 걸쳐 사용되는 경우 복잡성을 가중시키기도 합니다. UI 개발자에게 100가지의 마이크로서비스를 노출시키는 대신 넷플릭스는 엣지에서 통합된 API 계층을 제공합니다.

UI 개발자는 큰 도메인에 대한 하나의 개념적인 API로 작업하는 단순함을 굉장히 좋아합니다. 백엔드 개발자는 API 계층이 제공하는 디커플링과 탄성을 좋아합니다. 그러나 우리의 비즈니스가 확장됨에 따라, 빠르게 혁신하는 우리의 능력이 보이지 않는 점근선에 가까워졌습니다. 개발자 수가 증가하고 도메인 복잡성이 커짐에 따라 API 집합체 계층을 개발하는 것은 더욱 어려워졌습니다.

이러한 문제를 해결하기 위해 API 계층을 강화하는 연합된(federated) GraphQL 플랫폼을 개발했습니다. 이는 확장성 및 운영성과 같은 차원들을 최소화하며 많은 일관성과 개발속도 문제를 해결합니다. 우리는 넷플릭스 스튜디오 생태계에서 이 접근법을 성공적으로 구축했으며 다른 도메인에서 동작할 수 있는 여러 패턴과 적용들을 탐사하고 있습니다. 우리는 다른 사람들에게 영감을 주고 다른 곳 또한 적용 가능성에 대한 대화를 장려하고자 이야기를 나누고자 합니다.

사례 연구 : Studio Edge

Intro to Studio Ecosystem

Netflix는 굉장히 빠르게 오리지널 컨텐츠를 제작하고 있습니다. TV 쇼나 영화가 넷플릭스에서 방영될 때까지 아주 많은 일들이 일어나고 있습니다. 이는 스카우트, 캐스팅 뿐만 아니라 거래와 계약 협상, 제작, 시각 효과와 애니메이션, 자막과 더빙 등을 포함하여 아주 많은 것들이 존재합니다. 스튜디오 엔지니어링은 이 워크플로우들을 강화하는 수백가지의 어플리케이션과 툴을 제작합니다.

image

Studio API

몇 년전으로 돌아가보면, 스튜디오 공간에서의 한 가지 문제점은 데이터와 데이터 관계들의 증가하는 복잡성이었습니다. 위에서 설명한 워크플로우는 연결되어 있으나 데이터와 데이터 관계는 여러 마이크로서비스로 분산되어 존재했습니다. 프로덕트 팀은 두 가지 구조적 패턴으로 이를 해결했습니다.

  1. 단 하나의 결합된 레이어 : 루즈하게 결합되었기 때문에 많은 팀들이 중복되는 코드를 작성하고 각자의 프로덕트 니즈에 따라 여러 결합 레이어를 작성하는 데 많은 시간을 소모했습니다. 이는 BFF를 통해 UI 팀이나 미드티어 서비스의 백엔드 팀에 의해 수행되었습니다.

  2. 다른 팀의 데이터에 대한 구체화된 뷰(materialized views) : 어떤 팀들은 시스템의 요구에 따라 다른 팀의 데이터의 구체화된 뷰를 구축하는 패턴을 사용했습니다. 구체화된 뷰는 성능상의 이점은 가지지만 데이터 일관성에 있어서는 부족했습니다. 여러 스튜디오 어플리케이션을 가르는 일관되지 않은 데이터는 2018년 스튜디오 엔지니어링에서 가장 먼저 다루어야 하는 문제점이었습니다.

Graph API : 근본적인 필요성을 더 잘 다루기 위해, 우리 팀은 "Studio API"라고 불리는 큐레이션 graph API를 제작하기 시작했습니다. 목표는 데이터와 관계를 기반으로 하나로 통일된 추상화를 제공하는 것이었습니다. Studio API는 핵심적인 공유 데이터에 접근하기 위해 유용성을 제공하는 기본적인 API 기술인 GraphQL을 사용했습니다. Studio API의 컨슈머는 그래프를 탐색하고 더 빠르게 새로운 기능을 제작할 수 있었습니다. 또한 GraphQL의 모든 필드들이 데이터를 가져오는 하나의 코드로 해결되었으므로 다른 UI 어플리케이션에서 더 적은 데이터 불일치를 관찰할 수 있었습니다.

image

image

Studio API의 병목현상

Studio API에서 노출되는 하나의 그래프는 엄청난 성공을 거두었습니다. 프로덕트 팀들은 재사용성과 쉽고 일관되는 데이터 접근을 아주 좋아했기 때문입니다. 그러나 무수히 많은 컨슈머와 그래프가 생성하는 대량의 데이터 덕분에 병목현상에 부딪히게 되었습니다.

먼저 Studio API 팀은 도메인 전문가와 프로덕트 니즈로부터 연결이 끊김으로써 스키마에 부정적인 영향을 미쳤습니다. 두 번째로 백엔드의 새로운 요소들과 Graph API를 연결하는 것은 수동이었고 마이크로서비스 아키텍처가 보장하는 빠른 진화를 방해했습니다. 마지막으로 하나의 작은 팀이 확대되는 그래프에 대한 운영과 지원을 담당하는 것은 매우 감당하기 어려운 일이었습니다.

우리는 더 좋은 방법이 필요하다고 생각했습니다. 하나로 통일되면서 디커플링되고 큐레이트되며 빠르게 움직이는 것이죠.

핵심 원칙으로 돌아오기

이 병목현상을 해결하기 위해 마이크로서비스와 모노리스를 분해한 여러 이야기들에 기댔습니다. Studio API의 하나로 통일된 GraphQL 스키마를 유지함과 동시에 리졸버의 구현을 각 도메인 팀으로 비중앙화하고자 했습니다.

2019년 초 새로운 아키텍처에 대해 브레인스토밍하고 있을 때 Apollo는 GraphQL Federation 명세를 발표했습니다. 이는 분산된 오너십과 구현과 함께 하나의 스키마의 이점을 약속했습니다. 그런 결과를 기대하며 스펙의 테스트 구현을 시도했고 GraphQL Federation 미래에 대해 아폴로와 협업하는 것에 도달했습니다. 우리의 다음 세대 구조인 "Studio Edge"는 federation을 중요한 요소로 하여 등장했습니다.

GraphQL Federation 도화선

GraphQL Federation의 목표는 두 가지입니다. 컨슈머에게 하나의 API를 제공하며 백엔드 개발자에게는 유연함과 서비스의 독립성을 주는 것입니다. 이를 달성하고자 스키마는 생성되고 어떻게 소유권이 분산되었는지 알려주도록 설명이 필요합니다. 세 가지 엔티티와 함께 예시를 살펴봅시다.

  1. Movie : 넷플릭스에서 타이틀을 생성합니다. 단순함을 위해 각 타이틀은 Movie 객체라고 합시다.
  2. Production : 각 Movie는 스튜디오 프로덕션과 연관되어 있습니다. 하나의 Production 객체는 위치 선정, 노점상 등 Movie를 만들기 위해 필요한 모든 것들을 추적합니다.
  3. Talent : Movie에서 일하는 사람들이 Talent입니다. 배우와 감독 등을 포함합니다.

세 도메인은 세 개의 분리된 엔지니어링 팀이 소유하며 각 팀은 각자의 데이터 소스나 비즈니스 로직, 상응하는 마이크로서비스를 담당합니다. 연합되지 않은 구현에서는 Studio API 팀이 소유하며 구현하는 스키마와 리졸버를 가질 것입니다. GraphQL 프레임워크는 클라이언트로부터 쿼리를 받아 광범위한 순회를 통해 해당하는 리졸버를 호출하도록 조정합니다.

image

연합되는 구조로 전환하기 위해 통합된 스키마를 희생시키지 않고 각 리졸버들의 소유권을 각 담당하는 도메인으로 옮겨야 했습니다. 그러기 위해 Movie 타입을 GraphQL 서비스 바운더리로 확장해야 했죠.

image

Movie 타입을 GraphQL 서비스 바운더리로 확장하는 것이 기능은 Movie 타입을 Federated Type으로 만듭니다. 주어진 필드를 해결하는 것은 게이트웨이 계층으로부터 소유 도메인 서비스로 위임하는 것이 필요합니다.

Studio Edge 구조

타입을 연합하는 기능을 사용하는 것은 다음과 같은 구조를 구상했습니다.

image

주요한 구조적 컴포넌트

**Domain Graph Service (DGS)**는 독립적이며 규격을 준수하는 GraphQL 서비스입니다. 개발자는 각자의 연합된 GrpahQL 스키마를 DGS에서 정의합니다. API의 세부항목을 담당하는 도메인 팀은 DGS를 소유하고 운영합니다. DGS 개발자는 기존 마이크로서비스를 DGS로 전환할 것인지 아예 새로운 서비스를 스핀업 할 것인지 자유롭게 결정합니다.

Schema Registry는 모든 DGS에 대해 모든 스키마와 스키마 변경사항을 저장하는 상태가 있는 컴포넌트입니다. 개발자 툴과 CI/CD 파이프라인에서 사용되는 스키마에 대한 CRUD API를 노출합니다. 개별적인 DGS 스키마와 결합된 스키마 모두에 대해 유효성을 확인합니다. 마지막으로 레지스트리는 하나의 통합된 스키마를 구성하며 이를 게이트웨이에 제공합니다.

GraphQL 게이트웨이는 기본적으로 GraphQL 쿼리를 컨슈머에게 제공하는 역할을 합니다. 클라이언트로부터 쿼리를 받아 작은 서브 쿼리들(쿼리플랜)로 쪼개고 적절한 DGS들에게 호출을 프록싱하여 쿼리 플랜을 실행합니다.

구현 세부 사항

GrpahQL Federation을 강화하는 세 가지 주요한 비즈니스 로직 구성 요소가 있습니다.

Schema Composition

Composition은 모든 federated DGS 스키마들을 받아 하나로 통합된 스키마로 합하는 과정입니다. 구성된 스키마는 게이트웨이에 의해 그래프의 컨슈머들에게 노출됩니다.

image

새로운 스키마가 DGS에 추가될 때마다 스키마 레지스트리는 다음 사항들을 검증합니다.

  1. 새로운 스키마는 유효한 GrpahQL 스키마이다.
  2. 새로운 스키마가 나머지 DGS 스키마와 잘 구성되어 유효한 합성된 스키마를 생성한다.
  3. 새로운 스키마는 이전 버전과 호환된다.

만약 모든 조건이 만족되면, 그 스키마는 스키마 레지스트리로 체크인됩니다.

Query Planning and Execution

federation config는 모든 개별적인 DGS 스키마와 구성된 스키마로 이루어집니다. 게이트웨이는 쿼리 플랜을 생성하기 위해 federation config와 클라이언트 쿼리를 사용합니다. 쿼리 플랜은 클라이언트 쿼리를 더 작은 서브 쿼리로 쪼갠 것인데 실행을 위해 DGS로 보내지고 순차적인 실행 순서와 병렬 실행 순서를 포함합니다.

image

위에서 참조한 스키마로부터 간단한 쿼리를 만들고 어떻게 쿼리플랜이 생성되는지 봅시다.

image

이 쿼리에서 게이트웨이는 federation config를 기반으로 하여 어떤 필드가 어떤 DGS에 있는지 알고 있습니다. 이 정보를 사용하여 게이트웨이는 클라이언트 쿼리를 세 가지 개별적인 쿼리로 분해합니다. 첫 번째 쿼리는 Movie DGS로 보내지는데 루트 필드 movies는 해당 DGS에서 소유하고 있기 때문입니다. 이에 대한 결과로 데이터셋에서의 첫 10가지 영화들에 대한 movieIdtitle을 가져옵니다. 그러고 나서 이전 요청에서 얻은 movieId들을 사용하여 게이트웨이는 10가지 영화에 대한 productionactors 필드를 가져오기 위해 Prodction DGS와 Talent DGS에 두 병렬 요청을 수행합니다. 완료되면 서브쿼리의 응답은 하나로 합쳐져 호출자에게 결합된 데이터 응답을 보냅니다.

성능에서 참고할 사항 : 쿼리 플래닝과 실행은 최악의 경우 1~10ms 오버헤드를 더합니다. 이는 쿼리 플랜을 생성하고 DGS 응답의 deserialization과 합쳐진 게이트웨이 결과의 serialization을 포함합니다.

Entity Resolver

이제 여러분들은 어떻게 Prodction과 Talent DGS에서 병렬적인 서브 쿼리가 동작하는지 궁금하실 겁니다. DGS가 지원하는 것은 없습니다. 이것이 퍼즐의 마지막 조각입니다.

federated 타입인 Movie로 돌아가 봅시다. 게이트웨이가 DGS들을 넘나들며 Movie를 합치려면 Movie를 정의하고 확장하는 모든 DGS는 하나 이상의 필드에 대해 프라이머리 키(e.g. movieId)를 정의해야 합니다. 이를 동작시키고자 아폴로는 @key directive를 명세에서 소개했습니다. 또한 DGS들은 일반적인 Query 필드인 _entities에 대해 리졸버를 구현해야 합니다. _entities 쿼리는 모든 federated 타입들에 대한 하나의 타입을 반화합니다. 게이트웨이는 _entities 쿼리를 사용하여 movieIdMovie를 찾습니다.

실제로 어떻게 쿼리플랜이 생겨는지 살펴봅시다.

image

image

표현 객체는 movieId로 이루어져 있고 Movie DGS의 첫 번째 요청에 대한 응답으로부터 생성됩니다. 첫 10개의 영화 데이터를 요청했기 때문에 Prodction과 Talent DGS에 보낼 10개의 표현 객체를 가집니다.

이는 Realy의 객체 식별과 비슷하지만 약간의 차이점이 있습니다. _Entity는 하나의 union 타입이지만 Relay의 Node는 인터페이스입니다. 또한 @key를 사용하여 복합 키 뿐만 아니라 변수 키 이름과 타입을 지원할 수 있는데 Relay에서는 단일한 ID필드입니다.

함께 결합되면, 이것들은 federated API 구조의 핵심에 힘을 실어주는 요소들입니다.

요약

우리의 스튜디오 생태계 구조는 각기 다른 양면으로 진화해왔고, 아이디어와 구현 사이의 시간을 줄이며 개발자의 경험을 향상시키고, 운영을 국소화시키는 방향으로 동기부여됐습니다. 구조적인 단계는 아래와 같습니다.

image

주목하세요

지난 몇년동안 우리는 Studio Edge에서 federated API 구조 구성요소들을 구현했습니다. 여기까지 도달하려면 빠른 반복, 많은 부서간의 협업, 몇 가지의 피벗과 지속적인 투자가 필요합니다. 현재 70가지의 DGS를 가지며 수백명의 개발자가 있는데 Studio Edge 구조를 사용하고 있습니다. 다음 넷플릭스 테크 블로그 포스팅에서는 전체적인 해결책을 구축하는데 필요한 교차적인 관심사를 포함하여 우리가 배운 것들에 대해 공유할 것입니다.