Foundry
API 설계
중급

REST vs GraphQL vs gRPC

유행이 아니라 호출 주체를 보고 고른다

REST 는 단순하지만 오버페칭이 생기고, GraphQL 은 클라이언트가 필요한 것만 고르며, gRPC 는 스키마로 코드를 생성해 서비스 간 통신에 맞습니다.

REST vs GraphQL vs gRPC

선택 기준

정해진 모양을 받으면 필요 없는 것이 함께 오거나 여러 번 불러야 한다 정해진 모양을 준다 필요한 것은 앞부분뿐 쓰지 않는 것까지 실려 온다 반대로 부족하면 다른 주소를 또 불러야 한다 필요한 것만 골라 달라고 한다 요청한 것만 온다 대신 어떤 질의가 올지 몰라 서버가 무거운 요청을 받을 수 있다 캐시도 어려워진다. 같은 주소에 다른 답이 오기 때문이다
항목RESTGraphQLgRPC
포맷JSONJSONProtobuf
데이터 선택권서버클라이언트 질의서버(스키마)
스키마선택(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문제를 먼저 풀어볼 수도 있어요.