REST 는 단순하지만 오버페칭이 생기고, GraphQL 은 클라이언트가 필요한 것만 고르며, gRPC 는 스키마로 코드를 생성해 서비스 간 통신에 맞습니다.
REST vs GraphQL vs gRPC
선택 기준
| 항목 | REST | GraphQL | gRPC |
|---|
| 포맷 | JSON | JSON | Protobuf |
| 데이터 선택권 | 서버 | 클라이언트 질의 | 서버(스키마) |
| 스키마 | 선택(OpenAPI) | 필수 | 필수(.proto) |
| 스트리밍 | 제한적 | subscription | 양방향 기본 |
| HTTP 캐시 | 활용 가능 | 별도 구현 | 별도 구현 |
| 브라우저 직접 호출 | 가능 | 가능 | 프록시 필요 |
언제 무엇을
- REST: 공개 API, 캐시 이득이 큰 조회, 단순 CRUD
- GraphQL: 화면마다 필요한 필드가 다른 클라이언트가 여럿일 때. 과다와 과소 조회 해소
- gRPC: 내부 서비스 간 통신, 낮은 지연과 높은 처리량, 스트리밍
부르는 쪽이 누구냐로 갈린다
세 방식의 차이는 성능이 아니라 누가 쓰고 무엇을 아쉬워하는가입니다.
| 부르는 쪽 | 아쉬운 것 | 맞는 것 |
|---|
| 남의 서비스 | 문서와 안정성 | 널리 쓰이는 방식 |
| 우리 앱 화면 | 화면마다 필요한 것이 달라 왕복이 많다 | 필요한 것만 골라 받는 방식 |
| 우리 서버끼리 | 지연과 규약 일치 | 규약을 코드로 굳히는 방식 |
둘째가 흔한 동기입니다. 화면 하나에 API 다섯 번을 부르는 것이 모바일에서 특히 아픕니다.
다만 골라 받게 하면 서버가 어떤 요청을 받을지 미리 알 수 없다는 새 문제가 생깁니다.
골라 받는 방식의 대가
유연함이 그대로 위험이 됩니다.
깊이 파고드는 질의 하나가 DB 를 수십 번 때릴 수 있다
캐시하기 어렵다. 요청 모양이 매번 다르다
비용을 미리 재고 상한을 두어야 한다
셋째가 필수입니다. 깊이와 개수에 상한이 없으면 요청 하나로 서버를 세울 수 있습니다.
그래서 이 방식은 공개 API 보다 우리 앱용으로 쓰는 쪽이 다루기 쉽습니다.
실무 포인트
- GraphQL의 실제 비용은 N+1 조회와 쿼리 복잡도 폭주다. 배칭과 깊이, 복잡도 제한이 사실상 필수
- gRPC는 브라우저에서 직접 호출이 어렵다. 엣지는 REST, 내부는 gRPC 조합이 흔하다
- 세 방식은 배타적이지 않다. 한 시스템에서 계층별로 나눠 쓴다
- 전환 판단 기준은 취향이 아니라 클라이언트 수, 필드 요구 편차, 지연 예산이다
Q.모바일과 웹이 요구하는 필드가 달라 응답이 계속 커집니다. 어떤 선택지가 있나요?
선택지는 넷이고, 비용이 낮은 것부터 봅니다.
| 선택지 | 내용 | 비용 |
|---|
| 필드 선택 파라미터 | ?fields=id,name 으로 골라 받게 한다 | 가장 낮다 |
| 화면 전용 엔드포인트 | 화면마다 필요한 모양으로 응답을 만든다 | 낮다. 개수가 늘면 관리가 는다 |
| 응답 조립 계층 | 클라이언트별로 조립해 주는 층을 둔다 | 중간 |
| 필드를 골라 받는 질의 언어 | 클라이언트가 필요한 것만 요청한다 | 높다. 캐싱과 비용 제한을 새로 설계 |
첫 줄로 끝나는 경우가 많습니다. 큰 필드 몇 개가 문제라면 그것만 선택적으로 빼도
전송량이 크게 줍니다. 구조를 바꾸지 않고 오늘 할 수 있습니다.
둘째는 화면 수가 적을 때 좋습니다. 다만 화면이 늘수록 엔드포인트가 늘고, 어느 것이
어디서 쓰이는지 추적이 어려워집니다.
넷째로 갈지는 화면마다 요구가 얼마나 다른가로 판단합니다. 대부분의 화면이 비슷한
데이터를 쓰는데 몇 개만 다르다면 과한 선택입니다.
흔한 실수: 응답이 커진다는 이유만으로 질의 언어를 도입하는 것. 그 문제는 훨씬 싼
수단으로 풀리고, 도입하면 캐싱과 비용 제한, 권한 검사를 처음부터 다시 설계해야 합니다.
Q.GraphQL 도입 시 예상되는 성능 리스크와 대응 방법은?
유연함이 그대로 위험이 됩니다.
| 위험 | 내용 | 대응 |
|---|
| 질의 하나가 DB 를 수백 번 때린다 | 중첩된 필드마다 개별 조회가 나간다 | 묶음 조회로 합친다 |
| 깊이 제한이 없다 | 재귀적으로 파고드는 질의가 가능하다 | 깊이와 복잡도에 상한을 둔다 |
| 캐싱이 안 된다 | 요청 모양이 매번 달라 URL 단위 캐시가 무의미 | 필드 단위 캐시를 따로 설계 |
| 비용을 미리 모른다 | 실행해 봐야 얼마나 무거운지 안다 | 질의 비용을 계산해 한도를 건다 |
| 느린 필드가 전체를 잡는다 | 한 필드가 느리면 응답 전체가 늦는다 | 타임아웃을 필드 단위로 |
첫 줄이 가장 먼저 터집니다. 목록 10개를 받고 각 항목의 작성자를 요청하면 조회가
11번 나갑니다. 같은 종류의 조회를 한 요청 안에서 모았다가 한 번에 처리하는 장치가
사실상 필수입니다.
둘째와 넷째는 공개 API 일 때 특히 중요합니다. 상한이 없으면 요청 하나로 서버를
세울 수 있습니다. 그래서 이 방식은 외부 공개보다 우리 앱 전용으로 쓰는 쪽이 다루기
쉽습니다.
흔한 실수: 조회 횟수 문제를 도입 후에 발견하는 것. 작은 데이터로 시험하면 안 보이고,
운영에서 목록이 길어진 뒤에 드러납니다. 묶음 조회는 처음부터 넣고 시작합니다.
Q.내부 서비스 간 통신에 gRPC를 쓰는 이유는 무엇인가요?
내부 통신에서 아쉬운 것이 지연과 규약 일치인데, 둘 다에 답이 됩니다.
| 이유 | 내용 |
|---|
| 규약을 코드로 굳힌다 | 스키마에서 양쪽 코드를 생성해 필드 오타가 컴파일에서 잡힌다 |
| 전송량이 작다 | 이진 직렬화라 같은 데이터가 더 작다 |
| 연결을 재사용한다 | 다중화로 한 연결에 여러 요청을 흘린다 |
| 스트리밍이 자연스럽다 | 양방향 스트림을 규약이 지원한다 |
| 마감과 취소가 규약에 있다 | 호출자가 취소하면 서버까지 전해진다 |
첫 줄이 가장 큽니다. 내부 서비스가 늘수록 "필드 이름이 바뀐 걸 몰랐다" 류의 사고가
늘어나는데, 스키마에서 생성하면 그 부류가 사라집니다.
마지막 줄도 실무에서 값이 큽니다. 호출자가 이미 포기한 요청을 서버가 계속 처리하는
낭비를 막아 줍니다.
대신 포기하는 것이 있습니다. 사람이 눈으로 읽기 어렵고, 브라우저에서 바로 못 부르며,
중간 프록시와 도구 생태계가 HTTP 만큼 넓지 않습니다. 그래서 외부 공개에는 잘 안
맞습니다.
흔한 실수: 빠르다는 이유로 전부 바꾸는 것. 호출 빈도가 낮은 경로에서는 차이가
안 느껴지고, 도구와 디버깅이 불편해진 대가만 남습니다. 자주 부르고 지연이 중요한
경로부터 적용합니다.
Q.REST의 HTTP 캐시 이점을 포기해야 할 때는 언제인가요?
HTTP 캐시는 같은 주소에 같은 응답일 때 값이 납니다. 그 전제가 깨지면 이점 자체가
이미 없습니다.
| 상황 | 캐시가 듣나 |
|---|
| 사용자마다 응답이 다르다 | 거의 안 듣는다. 공유 캐시에 못 올린다 |
| 데이터가 초 단위로 바뀐다 | 수명을 짧게 줘야 해서 이득이 작다 |
| 요청 모양이 매번 다르다 | 주소가 안 겹쳐 적중이 안 된다 |
| 쓰기가 대부분이다 | 캐시할 것이 없다 |
| 내부 서비스 사이 | 중간 캐시가 대개 없다 |
첫 줄이 실무에서 가장 흔합니다. 로그인한 사용자별 응답은 공유 캐시에 못 올리므로,
HTTP 캐시의 큰 이점인 여럿이 한 복사본을 나눠 쓰는 것이 처음부터 불가능합니다.
이럴 때는 캐시를 포기하는 것이 아니라 처음부터 없던 것입니다.
반대로 포기하면 안 되는 자리는 명확합니다. 공개 콘텐츠, 목록, 이미지처럼 누구에게나
같은 응답은 HTTP 캐시만으로 서버 부하가 크게 줍니다. 이건 다른 수단으로 대체하기
어렵습니다.
포기할 때는 대체 계층을 함께 설계해야 합니다. 응답 캐시가 없어지면 그 부하가 전부
DB 로 갑니다. 필드나 엔티티 단위 캐시를 따로 두는 식입니다.
흔한 실수: 캐시 이점을 따져 보지 않고 "캐시는 나중에" 로 미루는 것. 어떤 응답이
공유 가능한지는 설계 시점에 정해지고, 나중에 바꾸려면 주소 체계부터 손대야 합니다.
먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.
읽었으면 문제로 확인해보세요
API 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.