GraphQL: 엔터프라이즈 허니문은 끝났다
Source: Hacker News

By John James
Published on December 14, 2025
Read time ~3 min
저는 실제 엔터프라이즈급 애플리케이션에서 GraphQL, 특히 Apollo Client와 Server를 몇 년간 사용했습니다. 장난감 앱이 아니라, 그린필드 스타트업도 아닙니다. 여러 팀, BFF, 하위 서비스, 가시성 요구사항, 실제 사용자를 가진 정식 프로덕션 환경입니다.
그리고 그 모든 시간을 보낸 뒤, 꽤 지루한 결론에 도달했습니다:
GraphQL은 실제 문제를 해결하지만, 그 문제는 사람들이 생각하는 것보다 훨씬 틈새입니다. 대부분의 엔터프라이즈 환경에서는 이미 다른 곳에서 해결되고 있으며, 트레이드오프를 모두 합산하면 GraphQL은 종종 순수히 부정적인 결과를 낳습니다.
이 글은 “GraphQL이 나쁜” 글이 아니라 “GraphQL이 신혼여행을 끝낸” 글입니다.
GraphQL이 해결하려는 문제
GraphQL이 해결하려는 주요 문제는 오버패칭(overfetching) 입니다. 아이디어는 간단하고 매력적입니다:
- 클라이언트가 정확히 필요한 필드만 요청한다
- 더 이상, 더 적게도 아니다
- 낭비되는 바이트가 없다
- 새로운 UI 요구사항마다 백엔드 변경이 필요 없다
이론적으로는 훌륭합니다. 실제로는 상황이 더 복잡합니다.
오버패칭은 이미 BFF로 해결된다
대부분의 엔터프라이즈 프론트엔드 아키텍처는 이미 BFF(Backend for Frontend)를 가지고 있습니다. BFF는 특히 다음을 위해 존재합니다:
- UI에 맞게 데이터 형태를 맞춘다
- 여러 하위 호출을 집계한다
- 백엔드 복잡성을 숨긴다
- UI가 필요로 하는 정확한 데이터를 반환한다
BFF 뒤에서 REST를 사용한다면, 오버패칭은 이미 해결 가능한 문제입니다. BFF는 응답을 축소하고 UI가 필요로 하는 것만 반환할 수 있습니다.
GraphQL도 물론 할 수 있습니다. 하지만 대부분의 하위 서비스는 여전히 REST이기 때문에, GraphQL 레이어는 여전히 그 API들에서 오버패칭을 하고, 그 후에 응답을 재구성해야 합니다. 오버패칭을 없앤 것이 아니라 한 층 아래로 옮긴 것에 불과합니다. 이는 GraphQL의 핵심 판매 포인트를 크게 약화시킵니다.
GraphQL이 승리하는 경우가 있습니다: 여러 페이지가 같은 엔드포인트를 호출하지만 약간씩 다른 필드를 필요로 할 때, GraphQL은 쿼리별로 차이를 범위화할 수 있게 해줍니다. 하지만 이는 보통 몇 개의 필드만 절약하고, 대신에:
- 더 많은 설정
- 더 많은 추상화
- 더 많은 간접성
- 유지보수해야 할 코드 증가
라는 매우 비싼 대가를 치르게 됩니다. 몇 킬로바이트를 절약하기 위해 치러야 할 비용이 너무 큽니다.
구현 시간은 REST보다 훨씬 길다
GraphQL은 REST BFF보다 구현에 훨씬 더 많은 시간이 소요됩니다.
REST 워크플로우
- 하위 서비스 호출
- 응답을 변환
- UI가 필요로 하는 형태로 반환
GraphQL 워크플로우
- 스키마 정의
- 타입 정의
- 리졸버 정의
- 데이터 소스 정의
- 어댑터 함수 작성 (필수)
- 스키마, 리졸버, 클라이언트를 동기화 유지
GraphQL은 소비를 최적화하는 대신 생산 속도를 희생합니다. 엔터프라이즈 환경에서는 생산 속도가 이론적 우아함보다 더 중요합니다.
가시성은 기본적으로 더 나쁘다
GraphQL은 특이한 상태 코드 규칙을 사용합니다:
400– 쿼리를 파싱할 수 없을 때200+errors배열 – 실행 중 오류가 발생했을 때200– 성공하거나 부분적으로 성공했을 때500– 서버에 접근할 수 없을 때
가시성 관점에서 이는 고통스럽습니다. REST에서는:
2XX– 성공4XX– 클라이언트 오류5XX– 서버 오류
대시보드에서 2XX만 필터링하면 해당 요청이 성공했음을 바로 알 수 있습니다. GraphQL에서는 200이라도 부분 혹은 전체 실패를 의미할 수 있습니다. Apollo는 이 동작을 커스터마이징할 수 있지만, 그만큼 추가 설정, 관례, 정신적 부담이 늘어납니다—REST가 기본 제공하는 편리함을 포기하게 되는 셈입니다.
캐싱은 매력적이지만 실제로는 복잡하다
Apollo의 정규화된 캐싱은 이론적으로 인상적이지만 실제로는 깨지기 쉽습니다. 두 쿼리가 단 하나의 필드만 다를 경우, Apollo는 이를 별개의 쿼리로 취급해 수동으로 다음을 연결해야 합니다:
- 기존 필드는 캐시에서 가져옴
- 차이나는 필드만 새로 가져옴
그 결과:
- 여전히 라운드‑트립이 발생
- 코드 복잡도 증가
- 캐시 디버깅 자체가 별도 문제로 부상
반면 REST는 몇 개의 여분 필드를 오버패칭하고 전체 응답을 캐시한 뒤 넘어갑니다. 여분의 킬로바이트는 저렴하지만 복잡성은 그렇지 않습니다.
ID 요구사항은 새는 추상화다
Apollo는 기본적으로 모든 객체에 id 혹은 _id 필드가 있기를 기대합니다. 없을 경우 커스텀 식별자를 설정해야 합니다. 많은 엔터프라이즈 API는:
- ID를 반환하지 않는다
- 자연스러운 고유 키가 없다
- 전역적으로 식별 가능한 엔터티로 모델링되지 않았다
따라서 BFF는 GraphQL 클라이언트를 만족시키기 위해 로컬에서 ID를 생성해야 하며, 이는 추가 로직, 추가 필드, 그리고 결국 불필요한 필드까지 가져오게 됩니다—오버패칭을 줄이려는 원래 목표와는 정반대입니다. REST 클라이언트는 이런 제약이 없습니다.
파일 업로드와 다운로드는 어색하다
GraphQL은 바이너리 데이터를 다루기에 적합하지 않습니다. 실제로는 다음과 같은 흐름이 됩니다:
- 다운로드 URL을 반환
- 그 URL을 사용해 REST로 파일을 가져온다
큰 페이로드(예: PDF)를 GraphQL 응답에 직접 포함하면 응답이 부풀어 오르고 성능이 저하됩니다. 이는 “단일 API”라는 이야기를 깨뜨립니다.
온보딩이 느리다
대부분의 프론트엔드·풀스택 개발자는 REST에 더 익숙합니다. GraphQL을 도입하면 다음을 가르쳐야 합니다:
- 스키마
- 리졸버
- 쿼리 구성
- 캐싱 규칙
- 오류 의미론
이 학습 곡선은 마찰을 만들고, 팀이 빠르게 움직여야 할 때 큰 장애가 됩니다. REST는 지루하지만, 그 지루함이 매우 잘 확장됩니다.
오류 처리도 더 복잡하다
GraphQL 오류 응답은… 이상합니다. 다음과 같은 요소가 있습니다:
- nullable vs non‑nullable 필드
- 부분 데이터
errors배열- 커스텀 상태 코드를 담은 extensions
- 어느 리졸버가 왜 실패했는지 추적 필요
이 모든 것이 간접성을 추가합니다. 반면 단순한 REST 설정에서는:
- 입력 검증 실패 →
400 - 백엔드 오류 →
500 - 검증 라이브러리 오류 → 끝
단순한 오류는 “우아한” 오류보다 이해하기 쉽습니다.
최종 결론
GraphQL은 확실히 유효한 사용 사례가 있습니다. 하지만 대부분의 엔터프라이즈 환경에서는:
- 이미 BFF가 존재한다
- 하위 서비스는 REST이다
- 오버패칭이 가장 큰 문제는 아니다
- 가시성, 신뢰성, 속도가 더 중요하다
이 모든 것을 합산하면, GraphQL은 좁은 문제만을 해결하면서 새로운 문제들을 더 많이 도입합니다. 그래서 수년간 프로덕션에서 사용해 본 결과, 저는 이렇게 말하고 싶습니다:
GraphQL이 나쁜 것은 아니다. 단지 틈새에 불과하다. 그리고 이미 문제를 해결한 아키텍처라면 굳이 필요하지 않을 가능성이 높다.