GraphQL vs REST
두 API 패러다임의 설계 철학 차이. 선택은 팀 규모·클라이언트 다양성·쿼리 복잡도에 달려 있다. GraphQL의 리졸버 구조상 관계 데이터 조회 시 N+1 쿼리 발생 위험.
Article
M
Me
hyunyoun's Blog
software-engineering-devopssoftware-engineeringdev-opsarchitecturedesigngraph-ql-vs-restdocumentationlearning10 min read
1. Overview
REST와 GraphQL의 역학(GraphQL vs REST)은 프론트엔드가 백엔드의 API 규격에 끌려다니던 고전적인 클라이언트-서버 종속성을 부수고, 클라이언트가 스스로 원하는 데이터의 생김새(Shape)를 결정하는 데이터 질의(Query)의 권력 역전을 해부합니다.
학습자는 화면 하나를 그리기 위해 User, Post, Comment API를 3번이나 호출해야 하는 REST의 **언더페칭(Under-fetching)**과, 이름만 필요한데 프로필 사진 URL까지 다 가져오는 **오버페칭(Over-fetching)**의 네트워크 비효율성을 뜯어봅니다. 나아가 단 하나의 엔드포인트(/graphql)를 뚫어놓고 프론트엔드가 정확히 필요한 필드만 조립해서 가져가는 GraphQL의 혁명적 자유도를 장악합니다. 마지막으로, 그 자유도의 대가로 백엔드 서버가 감당해야 하는 N+1 쿼리 문제와 캐싱(Caching)의 어려움을 방어하는 트레이드오프(Trade-off) 엔지니어링 역량을 확보합니다.
2. Scope & Boundaries
In-Scope
- RESTful API Physics: 자원(Resource) 중심의 URI 설계, HTTP 메서드(GET, POST)와 상태 코드의 물리적 의미.
- GraphQL Physics: 스키마(Schema) 주도 설계, 단일 엔드포인트, Query와 Mutation.
- Over/Under Fetching: 네트워크 페이로드(Payload) 낭비와 폭포수(Waterfall) 네트워크 호출 문제.
- The N+1 Problem: GraphQL이 DB를 찢어발기는 최악의 쿼리 지연 문제와 DataLoader(배치/캐싱) 방어술.
Out-of-Scope
- Apollo Client/Server 구현체: 특정 JS 라이브러리의 상세 사용법 언어/프레임워크 개별 문서로 위임.
- gRPC와 Protobuf: 서버 간 고성능 통신 방식 HTTP/2 기반 내부 통신이므로 08. Network 시스템 아키텍처 파트로 분리.
Boundaries
- REST vs GraphQL: "GraphQL이 REST보다 무조건 좋다"는 주니어의 환상을 부수어 버립니다. REST는 HTTP 표준 캐싱(CDN)을 100% 활용할 수 있고 설계가 직관적이지만 통신 횟수가 낭비됩니다. GraphQL은 프론트엔드의 생산성을 극대화하지만 백엔드의 복잡도(N+1 방어, CDN 캐싱 불가)를 극한으로 끌어올립니다. API를 '자원의 위치(REST)'로 볼 것인가, '데이터의 질의(GraphQL)'로 볼 것인가 하는 패러다임의 경계를 명확히 긋습니다.
3. Counterexample
- 언더페칭의 폭포수 (REST): 모바일 앱 메인화면에 유저 정보와 최신 작성 글 5개, 달린 댓글 10개를 띄워야 합니다. REST API를 쓰다 보니
GET /users/1, 그 응답을 기다렸다가GET /users/1/posts, 또 기다렸다가GET /posts/5/comments를 3번 순차적으로 호출합니다. 네트워크를 3번 왕복(Waterfall)하느라 로딩이 5초나 걸립니다. 클라이언트가 여러 자원을 조합할 때 REST가 가진 태생적 비효율성입니다. - 데이터베이스 폭파 (GraphQL N+1): 주니어가 GraphQL을 도입하고 자랑스러워합니다. 프론트엔드가
게시글 10개와 각각의 작성자 정보를 쿼리했습니다. 백엔드는 게시글을 가져오는 쿼리 1번, 그리고 각 게시글의 작성자를 가져오기 위해 쿼리를 10번(총 11번) 날립니다. 만약 게시글이 10,000개였다면 DB에 10,001번의 쿼리가 난사되어 DB가 폭사(OOM)합니다. 자유도의 뒷면에 숨겨진 N+1의 파국입니다.
4. Prerequisites
- HTTP 프로토콜 (Basic): HTTP Request/Response 구조, 메서드, 상태 코드. (08. Network)
- 관계형 데이터베이스 (Basic): N+1 문제의 개념과 Join 메커니즘. (06. Data Management)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 명사와 동사의 엄격한 분리 (The Anatomy of REST)
- Why to Learn: URL에
GET /getUserInfo나POST /deleteUser처럼 동사(행위)를 떡칠하는 근본 없는 API 설계를 부수고, 우주 공통의 규칙(HTTP Method)을 준수하는 아름다운 아키텍처를 짜기 위함입니다. - What to Learn:
- Concepts: REST (REpresentational State Transfer), Resource (자원, 명사), HTTP Methods (GET, POST, PUT, DELETE), Stateless.
- Skills: "사용자 번호 5번의 정보를 수정한다"는 요구사항을
POST /updateUser?id=5가 아니라PUT /users/5라는 완벽한 RESTful URI로 번역하기.
- How to Learn:
- 1단계: 자원(Resource): REST에서 가장 중요한 건 '명사'입니다. URL은 오직 명사들의 계층 구조만 나타내야 합니다.
/users/5/orders/2. "5번 유저의 2번 주문"이라는 리소스의 정확한 위치(Location)를 가리킵니다. - 2단계: 행위(Verb): 이 자원을 어떻게 할 것인가는 URL에 적지 않습니다. HTTP 프로토콜이 제공하는 메서드(동사)에 100% 위임합니다. 조회(GET), 생성(POST), 전체 수정(PUT), 삭제(DELETE).
- 3단계: Self-descriptive (자기 서술성): 잘 짠 REST API는 설명서가 필요 없습니다.
DELETE /users/5만 봐도 우주인조차 "5번 유저를 삭제하려무나"라고 이해할 수 있는 완벽한 규약의 아름다움을 해부합니다.
- 1단계: 자원(Resource): REST에서 가장 중요한 건 '명사'입니다. URL은 오직 명사들의 계층 구조만 나타내야 합니다.
- Implement: 엉망진창 API RESTful API 리팩터링 모사.
Before:GET /makeComment?postId=3&text=hello(동사 떡칠, POST를 GET으로 쓰는 만행).Action: 명사 계층 구조와 HTTP 메서드 매핑.After:POST /posts/3/comments(Body:{ "text": "hello" }). 완벽한 의미론적(Semantic) 분리로 프론트엔드의 예측 가능성을 극대화하는 시각화.
Recommended
Core Topic 02: 페칭의 고통 (The Pain of Fetching)
- Why to Learn: 잘 만들어진 REST API가, 화면이 복잡해지는 모바일 앱 환경에서 어떻게 '데이터 낭비'와 '로딩 속도 지연'의 주범으로 전락하는지 물리적 한계를 깨닫기 위함입니다.
- What to Learn:
- Concepts: Over-fetching (오버페칭), Under-fetching (언더페칭), Waterfall Network Requests (폭포수 네트워크 요청).
- Skills: 프론트엔드가 겪는 "필요 없는 데이터를 버리는 낭비"와 "필요한 데이터를 모으기 위해 API를 5번 연달아 호출하는 고통"을 트래픽(Bytes)과 지연 시간(Latency)으로 수치화하여 증명하기.
- How to Learn:
- 1단계: Over-fetching: 홈 화면에 유저의 '이름'만 띄우면 됩니다. 그래서
GET /users/1을 호출했더니, 백엔드가 이름뿐 아니라 비밀번호 암호문, 집 주소, 가입일 등 50개의 컬럼을 무식하게 던져줍니다. 모바일 데이터 20KB가 낭비되었습니다. - 2단계: Under-fetching: 이번엔 유저의 최근 '주문 목록' 3개도 띄워야 합니다.
GET /users/1응답에는 주문 목록이 없습니다. 프론트엔드는 어쩔 수 없이GET /users/1/orders를 두 번째로 호출합니다. - 3단계: Waterfall (폭포수): 만약 각 주문의 '상품명'을 알려면?
GET /orders/1/items를 또 호출해야 합니다. 앞선 API가 끝나야만 다음 API를 부를 수 있는 직렬 호출이 네트워크 지연(Latency)을 3배로 늘려버리는 구조적 참사를 뜯어봅니다.
- 1단계: Over-fetching: 홈 화면에 유저의 '이름'만 띄우면 됩니다. 그래서
- Implement: 모바일 화면 로딩 폭포수 모델링.
요구화면: "내 프로필(이름) + 최신 주문 1건(상품명)".REST의 늪:GET /me(300ms 대기) 받은 userId로GET /users/{id}/orders?limit=1(300ms 대기) 받은 orderId로GET /orders/{id}/items(300ms 대기). 총 900ms 소요. 유저가 화면을 볼 때까지 1초나 걸리는 모바일 시대의 UX 붕괴 렌더링.
Practical
Core Topic 03: 권력의 역전 (GraphQL: Shift of Power)
- Why to Learn: 백엔드가 주는 대로 받아먹어야 했던 프론트엔드의 설움을 끝내고, 프론트엔드가 마치 DB에 SQL을 날리듯 정확히 필요한 뼈대(Shape)만 조립해서 1번 만에 긁어오는 혁명적 자유도를 쥐기 위함입니다.
- What to Learn:
- Concepts: GraphQL, 단일 엔드포인트(Single Endpoint), Schema (스키마), Query (조회), Mutation (변경).
- Skills: REST API 3개를 써야 했던 화면을 단 1개의 GraphQL 엔드포인트(
/graphql)에 쿼리를 날려, 오버페칭과 언더페칭을 0으로 만드는 코드 설계하기.
- How to Learn:
- 1단계: 단일 엔드포인트: GraphQL은 URL이
/graphql딱 하나입니다. 더 이상 자원의 계층(/users/1/orders)을 URL로 표현하지 않습니다. - 2단계: 주문서 작성 (Query): 클라이언트는
POST /graphql의 Body에 주문서(Query)를 씁니다. "나는 유저 이름(name)이랑, 걔의 주문 목록 중 첫 번째 거(orders(first<1>1>))의 상품명(itemName)만 줘." - 3단계: 맞춤형 응답 (Shape): 백엔드는 정확히 그 주문서대로만 조립해서 JSON으로 줍니다. 900ms 걸리던 폭포수 네트워크 통신이 단 1번(300ms)으로 끝납니다. 오버페칭(데이터 낭비)과 언더페칭(추가 호출)이 수학적으로 소멸하는 위대한 패러다임 전환을 해부합니다.
- 1단계: 단일 엔드포인트: GraphQL은 URL이
- Implement: GraphQL Query vs REST Response 비교 렌더링.
Query 작성:CODEquery { user(id: 1) { name, orders(first: 1) { items { name } } } }서버의 응답: 정확히 요청한 형태 그대로 반환. 불필요한address나password필드는 단 1바이트도 내려오지 않음. 네트워크 페이로드(Payload) 크기를 1/10로 줄여 모바일 환경을 구원하는 시각화.
Advanced
Core Topic 04: 자유의 대가와 N+1 쿼리 (The Cost of Freedom)
- Why to Learn: 프론트엔드에게 무한한 쿼리의 자유를 주었더니, 백엔드 서버가 DB를 1만 번씩 폭격(N+1 문제)하다 OOM으로 장렬하게 전사하는 참사를 막기 위함입니다.
- What to Learn:
- Concepts: The N+1 Problem, Resolver (리졸버 트리), DataLoader (배치/캐싱), HTTP Caching (CDN).
- Skills: GraphQL 리졸버가 멍청하게 DB 쿼리를 N번 날리는 걸 가로채어(DataLoader), 1번의
IN (...)쿼리로 묶어서(Batching) DB를 구원하는 백엔드 방어술 구축하기.
- How to Learn:
- 1단계: N+1의 재앙: 유저 10명의 정보를 가져오고(쿼리 1번), 각 유저의 프로필 사진을 가져옵니다. 프론트엔드는 쿼리 1번으로 보냈지만, 백엔드의 GraphQL 엔진(리졸버)은 유저 10명 각각에 대해 "프로필 사진 가져와" 쿼리를 10번 실행(N번)합니다. DB가 1+10=11번 비명을 지릅니다.
- 2단계: 방어막 (DataLoader): 페이스북이 만든 DataLoader를 끼워 넣습니다. 리졸버들이 프로필 사진을 달라고 10번 아우성치면, 곧바로 DB에 가지 않고 잠깐(수 밀리초) 기다렸다가 ID 10개를 묶어서(Batch)
SELECT * FROM profile WHERE user_id IN (1,2...10)딱 1번의 쿼리로 해결합니다. - 3단계: 캐싱의 저주: REST는 URL이 다르기 때문에 브라우저나 CDN 단에서 GET 요청을 기가 막히게 캐싱(Caching)합니다. 하지만 GraphQL은 무조건
POST /graphql1개뿐입니다. HTTP 표준 캐싱이 완전히 박살 납니다. 이 트레이드오프를 인정하고 인메모리 캐시(Apollo Cache)로 우회하는 엔지니어링의 타협점을 뜯어봅니다.
- Implement: N+1 쿼리 폭주 DataLoader 방어 렌더링.
Before:SELECT * FROM user LIMIT 5(1번)SELECT * FROM post WHERE user_id = 1...user_id = 5(5번). (총 6회 쿼리).Action: DataLoader Batching 적용.After:SELECT * FROM user LIMIT 5(1번)SELECT * FROM post WHERE user_id IN (1,2,3,4,5)(1번). (총 2회 쿼리로 압축). 자유도를 허용하면서도 DB의 생명을 연장하는 경이로운 백엔드 아키텍처 시각화.
7. Terminology
8. References
Primary
- [P1] CS2023 - Software Engineering (SE) - Web and Mobile Development (REST APIs and GraphQL)
- [P5] SFIA - System Design (SYMD) - API design and interface specification
Secondary
- [RESTful Web Services] Leonard Richardson - Resource-Oriented Architecture
- [Learning GraphQL] Eve Porcello, Alex Banks - Graph Theory, Resolvers, DataLoader
Industry
- [GraphQL.org] - Thinking in Graphs, DataLoader
- [Roy Fielding's Dissertation] - Architectural Styles and the Design of Network-based Software Architectures (REST)
9. Final Checklist
Primary
- 사용자 정보를 조회할 때 URL에
GET /getUserInfo라고 명명하는 것이 왜 REST의 원칙(명사와 동사의 분리)을 파괴하는 행위인지 지적하고, 이를 완벽한 RESTful URI 형식으로 리팩터링할 수 있는가? - 모바일 앱 화면 하나를 그리기 위해 REST API를 직렬로 3번 호출하는 폭포수(Waterfall) 모델의 한계(Under-fetching)와, 불필요한 50개 컬럼을 통째로 받아오는 낭비(Over-fetching)를 네트워크 대역폭 관점에서 설명할 수 있는가?
Secondary
- REST API가 여러 개의 엔드포인트(
/users,/posts)를 가지는 반면, GraphQL은 단일 엔드포인트(/graphql)만을 제공하여 프론트엔드가 필요한 필드만 쿼리(Query)로 선별해 네트워크 페이로드(Payload)를 압축하는 권력의 역전을 논증할 수 있는가? - HTTP 표준 캐싱 시스템(CDN, 브라우저 캐시)이 URL 리소스 단위로 동작하는 REST API에서는 완벽하게 기능하지만, 오직
POST메서드로 단일 엔드포인트를 사용하는 GraphQL에서는 캐싱 인프라가 무력화되는 치명적 트레이드오프를 설명할 수 있는가?
Industry
- 프론트엔드 개발자가 자유롭게 작성한 GraphQL 쿼리가 백엔드의 리졸버(Resolver) 트리 탐색을 거치며 1개의 메인 쿼리와 N개의 서브 쿼리(N+1 문제)로 폭증해 데이터베이스를 마비시키는 연쇄 장애 원리를 추적할 수 있는가?
- N+1 쿼리 문제를 방어하기 위해 Facebook이 고안한
DataLoader아키텍처를 적용하여, N번의 개별 DB 조회 요청을 수 밀리초 동안 대기시켰다가 1번의IN (...)배치(Batching) 쿼리로 묶어 처리하는 성능 최적화 파이프라인을 설계할 수 있는가?