REST vs GraphQL: 캐싱과 에러 처리는 어떻게 다를까?
API를 개발하거나 선택할 때, 단순히 데이터를 주고받는 방식의 차이(JSON 구조 등)보다 더 깊게 고민해야 할 지점들이 있습니다. 바로 “캐싱을 어떻게 할 것인가?” 와 “에러를 어떻게 처리할 것인가?” 입니다.
이 두 가지 측면에서 REST와 GraphQL은 완전히 다른 접근 방식을 취하고 있습니다. 그 핵심적인 차이를 정리해 봅니다.
1. 캐싱 (Caching) 전략의 차이
가장 큰 차이는 “누가 캐싱을 담당하는가?” 입니다.
REST: “네트워크”가 해줍니다 (HTTP Caching)
REST는 웹의 표준 프로토콜인 HTTP를 그대로 활용합니다. 따라서 URL 자체가 고유한 리소스의 주소가 됩니다.
- 작동 원리:
GET /users/1요청은 언제나 1번 유저의 정보를 의미합니다. - 장점: 브라우저, CDN, 프록시 서버 등이 URL과 HTTP 헤더(
Cache-Control,ETag)를 보고 알아서 캐싱을 수행합니다. 개발자가 별도로 복잡한 캐싱 로직을 구현하지 않아도 네트워크 레벨에서 성능 최적화가 가능합니다.
GraphQL: “라이브러리”가 해야 합니다 (Application Caching)
GraphQL은 캐싱 관점에서 두 가지 난관이 있습니다.
- 단일 엔드포인트: 모든 요청을
/graphql이라는 하나의 주소로 보냅니다. (URL로 데이터 구분이 불가능) - POST 메서드 사용: 주로
POST를 사용하므로, HTTP 스펙상 기본적으로 캐싱되지 않습니다.
✅ 해결책: 클라이언트 내부 캐싱 (Apollo Client 등) 그래서 GraphQL은 네트워크 대신, Apollo Client 같은 클라이언트 라이브러리가 앱 내부 메모리에서 캐싱을 담당합니다.
- 정규화(Normalization): 서버에서 받은 데이터를
__typename과id를 조합해 고유 키(예:User:1)로 저장합니다. - 재사용: 이후 같은 ID의 데이터가 필요하면 서버 요청 없이 메모리에서 꺼내 씁니다. 데이터가 업데이트되면, 해당 ID를 바라보는 모든 UI가 자동으로 갱신됩니다.
한 줄 요약:
- REST: URL 기반 캐싱 (네트워크 레벨)
- GraphQL: ID 기반 정규화 캐싱 (클라이언트 앱 레벨)
2. 에러 처리 (Error Handling) 방식의 차이
두 번째 큰 차이는 성공과 실패를 판단하는 “신호(Signal)”에 있습니다.
REST: 명확한 신호등 (HTTP Status Code)
REST는 HTTP 상태 코드를 통해 요청의 결과를 명확히 알립니다.
200 OK: 성공400 Bad Request: 클라이언트 요청 실수401 Unauthorized: 로그인 필요404 Not Found: 리소스 없음500 Server Error: 서버 내부 오류
👉 특징: 프론트엔드나 모니터링 도구(Sentry 등)가 상태 코드만 보고도 즉시 에러 여부를 판단할 수 있습니다.
GraphQL: 겉과 속이 다른 200 OK
GraphQL은 네트워크 통신 자체만 성공했다면, 내부 로직에서 에러가 발생해도 대부분 HTTP 200 OK를 반환합니다. 진짜 결과는 응답 본문(Body)을 열어봐야 압니다.
⚠️ 문제점 1: 부분 실패 (Partial Failure) REST는 성공이면 성공, 실패면 실패지만, GraphQL은 “일부만 성공” 할 수 있습니다.
{
"data": {
"user": { "name": "Gemini" }, // 유저 정보 조회 성공
"posts": null // 게시글 목록 조회 실패
},
"errors": [
{ "message": "DB Error on posts", "path": ["posts"] }
]
}
⚠️ 문제점 2: 에러 판단의 모호함
- 모니터링의 사각지대: HTTP 상태 코드가
200이기 때문에, 기존의 에러 모니터링 도구(Sentry, Datadog 등)가 이를 ‘성공한 요청’으로 착각하여 로그를 남기지 않거나 알림을 보내지 않을 수 있습니다. 별도의 로깅 설정이 필요합니다. - 프론트엔드 로직의 복잡성: 클라이언트는 항상
data가 있는지 확인하는 것뿐만 아니라,errors필드가 존재하는지도 동시에 체크해야 합니다. 심지어 데이터는 왔는데 에러도 같이 오는 상황(부분 성공)까지 고려하여if-else분기 처리를 꼼꼼하게 해야 합니다.
💡 심화: 왜 GraphQL은 에러가 나도 200 OK일까?
이 차이는 “HTTP 프로토콜을 바라보는 관점” 이 다르기 때문입니다. 이해하기 쉽게 택배 기사(HTTP) 에 비유해 보겠습니다.
| 구분 | 택배 기사(HTTP)의 행동 | 비유 |
|---|---|---|
| REST | “내용물을 확인하고 배송한다” | 기사가 주소지에 갔는데 사람(데이터)이 없으면, 송장에 ‘수취인 불명(404)’ 도장을 찍습니다. |
| GraphQL | “문 앞까지만 배달한다” | 기사는 프론트 데스크(/graphql)에 상자(쿼리)만 잘 전달하면 끝입니다. 송장에 ‘배송 완료(200)’ 도장을 찍고 떠납니다. 상자를 열었을 때 내용물이 있는지는 나중에 직원이 확인합니다. |
기술적 이유: 프로토콜 중립성 (Protocol Agnostic)
GraphQL은 HTTP에 종속되지 않도록 설계되었습니다. HTTP가 아닌 WebSocket이나 다른 프로토콜을 쓸 수도 있기 때문에, HTTP 상태 코드(404, 500 등)에 의존하지 않고 응답 본문(Body)의 errors 필드를 통해 독자적으로 에러를 표현하는 방식을 택했습니다.
3. 요약 및 비교 분석
| 구분 | REST | GraphQL |
|---|---|---|
| 캐싱 주체 | 브라우저, CDN (네트워크 레벨) | 클라이언트 라이브러리 (앱 메모리) |
| 캐싱 기준 (Key) | URL (리소스 주소) | ID (__typename + id) |
| 에러 신호 | HTTP 상태 코드 (4xx, 5xx) |
Response Body의 errors 배열 |
| 성공/실패 여부 | 성공(2xx) 아니면 실패(4xx, 5xx) |
부분 성공(Partial Success) 가능 |
4. 결론
기술 선택은 결국 트레이드오프(Trade-off)의 문제입니다.
- REST는 웹의 표준(HTTP)을 충실히 따르기 때문에 인프라(CDN, 로드밸런서)의 지원을 받기 쉽고, 디버깅이 직관적입니다.
- GraphQL은 네트워크 레벨의 단순함을 포기한 대신, Apollo Client 같은 강력한 라이브러리를 통해 정교한 데이터 관리(정규화된 캐시) 와 유연성(Over/Under-fetching 해결) 을 얻는 방식입니다.
따라서 공개 API나 단순한 서비스라면 REST가 여전히 강력한 선택지이며, 복잡한 데이터 관계를 가진 대규모 프론트엔드 애플리케이션이라면 GraphQL이 주는 이점이 초기 설정의 복잡함을 상쇄하고도 남을 것입니다.