API·REST 설계 — 백엔드 면접 (실제 기출 기반·심화)
"REST와 RESTful의 차이", "HTTP 메서드의 멱등성", "GET과 POST의 근본 차이", "REST vs GraphQL vs gRPC", "결제 중복을 어떻게 막나", "페이지네이션" — 설계 감각을 보는 단골이다. (HTTP 메서드·상태코드의 프로토콜 동작은 네트워크 편, 여기선 API 설계.)
함정형 단골: "POST는 절대 캐싱 안 된다?", "GET은 안전하니 멱등하다?"(safe ≠ idempotent), "REST = HTTP+JSON?"(HATEOAS).
1. REST·RESTful — Fielding 제약 (★ 단골)
실제 질문: "REST와 RESTful의 개념을 설명해주세요." (WeareSoft·gyoogle)
REST(Representational State Transfer) = Roy Fielding이 정의한 아키텍처 스타일. 6제약:
- 클라이언트-서버·무상태(Stateless)(각 요청 독립)·캐시 가능·균일 인터페이스(Uniform Interface)·계층화·(선택)코드 온디맨드.
- 실천: 자원을 URI로 식별·행위는 HTTP 메서드·상태는 표현(JSON).
깊이 — Richardson 성숙도: L0(RPC) → L1(자원) → L2(메서드+상태코드·실무 대부분) → L3(HATEOAS·응답에 다음 행동 링크). "진짜 RESTful"은 L3까지지만 대부분 미충족.
꼬리질문: "RESTful의 핵심 제약?" → Uniform Interface(self-descriptive·HATEOAS). / "무상태의 이점?" → 서버 확장 용이(세션 없음).
함정: ❌ "REST = HTTP + JSON" → 아키텍처 스타일. HATEOAS까지 만족해야 진짜 RESTful.
2. 멱등성·안전성 (★★ 단골)
실제 질문: "HTTP Method의 **멱등성**을 설명해주세요." · "GET과 POST의 근본 차이는?" (VSFe·WeareSoft)
| 메서드 | 안전(safe) | 멱등(idempotent) |
|---|---|---|
| GET·HEAD | ✓ | ✓ |
| PUT·DELETE | ✗ | ✓ |
| POST·PATCH | ✗ | ✗ |
- 안전(safe): 서버 상태를 변경하지 않음(읽기). 멱등(idempotent): 여러 번 호출해도 서버 상태가 같음. (safe 메서드 GET·HEAD·OPTIONS·TRACE는 정의상 모두 멱등 — safe ⊂ idempotent, 역은 성립 안 함.)
- 둘은 다른 개념(★): GET은 안전하면서 멱등, PUT/DELETE는 안전하지 않지만 멱등(같은 상태로 만듦), POST는 둘 다 아님(매번 새로 생성).
깊이 — DELETE 멱등 논쟁: DELETE를 두 번 호출하면 두 번째는 404일 수 있지만, 서버 상태는 "삭제됨"으로 동일 → 멱등 유지(상태코드가 달라도 멱등 정의는 서버 상태).
꼬리질문: "GET은 안전하니 멱등?" → safe ⊂ idempotent지만 별개 개념(안전하면 멱등이나 역은 아님). / "PATCH가 비멱등인 이유?" → balance += 1 같은 상대적 변경은 호출마다 누적.
함정: ❌ "safe = idempotent" → 다른 개념. ❌ "POST는 절대 캐싱 안 됨" → 조건부 캐싱 가능(Cache-Control).
3. 멱등성 키 — 결제 중복 방지 (★★ 결제 단골)
실제 질문: "결제·주문에서 중복 요청을 어떻게 막나요?"(멱등성 키) (현업 면접·결제 시스템)
POST /payments
Idempotency-Key: 9f1c-...-a3 ← 클라가 생성한 고유 키
서버는 같은 키를 한 번만 처리하고, 재시도엔 저장된 결과를 반환 → 네트워크 재시도·더블 클릭에도 결제 1회. IETF가 Idempotency-Key 헤더를 표준화 중(Stripe·Toss 사실상 표준).
꼬리질문: "키 저장은 어디에?" → DB(유니크 제약)·Redis(TTL). / "동시 요청 2개가 같은 키로?" → **유니크 제약/분산 락**으로 하나만 처리·나머지는 진행 중/결과 반환. / "멱등성 키 vs PUT?" → PUT은 본질적 멱등, POST는 키로 멱등성 부여.
4. REST vs GraphQL vs gRPC (★★ 단골)
실제 질문: "REST, GraphQL, gRPC를 비교하라." · "GraphQL의 단점은?" (velog·VSFe)
| REST | GraphQL | gRPC | |
|---|---|---|---|
| 형식 | JSON·다중 엔드포인트 | 쿼리·단일 엔드포인트 | Protobuf 바이너리·HTTP/2 |
| 데이터 | 고정 응답(over/under-fetching) | 클라가 필요한 필드만 | 계약(.proto)·빠름·양방향 스트리밍 |
| 캐시 | HTTP 캐시 쉬움 | 어려움(POST 단일) | 자체 |
| 용도 | 범용 공개 API | 복잡한 프론트·다양한 클라 | MSA 서비스 간·고성능 |
깊이 — GraphQL 단점(★): ① 리졸버 N+1(각 필드가 쿼리 → DataLoader로 배치) ② 캐싱 어려움(단일 POST 엔드포인트) ③ 쿼리 복잡도 폭탄(깊은 중첩 → 깊이 제한·비용 분석).
꼬리질문: "gRPC는 언제?" → 내부 MSA 서비스 간 고성능·계약 기반(브라우저 공개 API엔 REST). / "GraphQL N+1?" → DataLoader(요청 단위 배치·캐시).
함정: ❌ "GraphQL이 항상 낫다" → 캐싱·복잡도·N+1 비용.
4-bis. 요청 병합 — 세 층을 구분하라 (★★ 트래픽 단골)
실제 질문: "트래픽이 급증하면 어떻게 줄이나요?" · "화면 하나에 API를 몇 번 호출하나요?" · "캐시가 만료되면 DB로 요청이 몰리는데요?"
"요청을 합친다"는 말은 서로 다른 세 가지를 가리킨다. 면접에서 이걸 구분해 답하면 층위를 이해한다는 신호가 된다.
| 무엇을 합치나 | 어디서 | 줄어드는 것 | |
|---|---|---|---|
| ① 싱글플라이트 | 같은 키에 대한 동시 중복 조회 | 서버 내부(캐시 미스 시점) | 원본(DB) 조회 횟수 |
| ② 배치 로딩 | 같은 틱의 여러 키 조회 | 서버 내부(리졸버·서비스) | 쿼리 왕복 횟수 |
| ③ 엔드포인트 통합·BFF | 클라이언트의 여러 번 호출 | 클라이언트↔서버 경계 | 네트워크 왕복·게이트웨이 부하 |
① 싱글플라이트 (request coalescing)
캐시가 만료된 순간 같은 키로 1,000개 요청이 동시에 들어오면 DB에 1,000번 간다(캐시 스탬피드).
// 같은 키는 한 건만 원본을 조회하고, 나머지는 그 결과를 기다렸다 공유한다
private final ConcurrentHashMap<String, CompletableFuture<Data>> inFlight = new ConcurrentHashMap<>();
Data load(String key) {
return inFlight.computeIfAbsent(key, k ->
CompletableFuture.supplyAsync(() -> fetchFromDb(k))
.whenComplete((v, e) -> inFlight.remove(k)))
.join();
}
DB 조회는 1,000 → 1이 된다. 자세한 내용은 09편 캐시·Redis의 스탬피드 대응(TTL 지터·stale-while-revalidate)과 함께 본다.
② 배치 로딩 (DataLoader 패턴)
목록 100건을 그리며 각 항목의 작성자를 따로 조회하면 1 + 100 쿼리가 된다(N+1). 같은 틱에 모인 키들을 모아 IN 한 번으로 바꾼다.
요청 100건: getUser(1), getUser(2), ... getUser(100)
↓ 같은 이벤트 루프 틱에 수집
SELECT * FROM users WHERE id IN (1..100) // 쿼리 1회
GraphQL 리졸버에서 특히 중요하지만 REST에서도 동일하게 적용된다. JPA라면 fetch join·@BatchSize가 같은 문제를 다른 방식으로 푼다.
③ 엔드포인트 통합 — 왕복 자체를 줄인다
①②가 서버 안의 중복 작업을 줄인다면, 이건 클라이언트가 서버를 부르는 횟수를 줄인다.
Before 화면 하나를 그리려고
GET /products/{id} → 상품 정보
GET /products/{id}/stock → 재고
GET /users/me/coupons → 쿠폰
= 왕복 3회 × 동시접속자 수
After GET /product-page/{id} → 세 가지를 한 응답으로
= 왕복 1회
효과가 서버 내부에 그치지 않는다는 점이 다르다 — 인증·암복호화 게이트웨이, TLS 핸드셰이크, 모바일 네트워크 지연이 호출 횟수에 비례하기 때문이다. 실제로 라이브 커머스 트래픽 사례에서 API 3개를 1개로 합쳐 피크 트래픽을 약 50% 줄였다.
BFF(Backend for Frontend) 가 이 발상을 구조로 만든 것이다.
┌── 웹 BFF ──┐
클라이언트 ┤ ├→ 여러 도메인 서비스
└── 앱 BFF ──┘
화면이 필요로 하는 모양대로 조립해 주는 계층을 클라이언트별로 둔다
대가: 화면과 API가 강하게 묶인다. 화면이 바뀔 때마다 서버를 고쳐야 하고, 클라이언트가 늘면 BFF도 늘어난다. GraphQL은 클라이언트가 필요한 필드를 고르게 해서 같은 문제를 다르게 푸는 선택지다.
꼬리질문: "그럼 항상 합치는 게 좋나?" → 아니다. 합치면 캐시 단위가 커지고 재사용성이 떨어진다. 재고처럼 자주 바뀌는 데이터와 상품 정보처럼 안 바뀌는 데이터를 한 응답에 묶으면 전체를 짧은 TTL에 맞춰야 한다. 변경 주기가 크게 다른 것은 나눠 두는 편이 낫다.
함정
- ❌ 세 층을 뭉뚱그려 "요청을 합쳤습니다" → 무엇이 줄었는지(DB 조회·쿼리 왕복·네트워크 왕복) 말할 수 있어야 한다.
- ❌ 싱글플라이트를 캐시로 착각 → 캐시는 결과를 저장하고, 싱글플라이트는 동시 진행 중인 작업을 공유한다. 둘은 함께 쓴다.
- ❌ 무조건 통합 → 캐시 효율과 변경 주기를 먼저 본다.
5. 페이지네이션 (★ 단골)
실제 질문: "Offset 기반과 Cursor 기반 페이지네이션의 차이는?" (velog @ygreenb)
-- offset: 단순하나 깊은 페이지에서 앞 행을 다 스캔 → 느림, 삽입 시 중복/누락
SELECT * FROM posts ORDER BY id DESC LIMIT 20 OFFSET 10000;
-- cursor(keyset): 마지막 본 위치 기준 → 안정·대용량·일정 성능
SELECT * FROM posts WHERE id < :lastId ORDER BY id DESC LIMIT 20;
- offset(페이지 번호 UI·깊을수록 느림·실시간 삽입 시 중복/누락) vs cursor(정렬 키 기준·무한 스크롤·대용량 안정).
꼬리질문: "커서 기반에서 임의 페이지 점프가 안 되는 이유?" → 직전 행 기준이라 N번째 페이지로 바로 못 감. / "깊은 offset이 느린 이유?" → 앞 OFFSET개를 읽고 버림.
함정: ❌ "offset이 항상 단순해서 낫다" → 대용량·실시간엔 커서.
6. 상태코드·에러 설계·rate limiting (★ 심화)
- 상태코드: 201 Created(+
Location)·204 No Content·401(인증) vs 403(인가)·409 Conflict(낙관적 락 충돌)·422 Unprocessable(검증)·429 Too Many Requests(rate limit). - 에러 포맷 일관:
{ "code", "message", "errors":[...] }또는 RFC 9457application/problem+json. - rate limiting: Token Bucket(버킷에 토큰 충전·burst 허용·가장 흔함)·Leaky Bucket·Sliding Window. 초과 시 429 +
Retry-After. - 엔티티 직접 노출 X → DTO(API 스펙 분리·
LazyInitializationException방지·보안 — Spring·JPA 편).
꼬리질문: "401 vs 403?" → 인증(누구인지 모름) vs 인가(권한 없음). / "동시 수정 충돌?" → 낙관적 락·ETag/If-Match → 409.
7. 실시간 통신 — 폴링·SSE·WebSocket (★★ 신규 단골)
나오는 질문: "실시간 알림 기능을 설계한다면 어떻게 하시겠어요?" · "WebSocket과 SSE 중 무엇을 고르시겠어요? 왜죠?" · "폴링 대비 장점이 뭔가요?" · "서버를 여러 대로 늘리면 어떤 문제가 생기죠?"
REST의 전제는 언제나 클라이언트가 먼저 묻는다는 것이다. 서버가 먼저 알려야 하는 일(알림·시세·채팅·진행률)은 이 모델로 표현할 수 없다. 그 우회와 해결의 계보를 아는지가 이 질문의 핵심이다.
| 방식 | 동작 | 방향 | 재연결 | 비용 |
|---|---|---|---|---|
| 폴링 | 주기적으로 같은 요청 반복 | 클라이언트 요청 | 해당 없음 | 헛걸음 요청이 대부분 |
| 롱 폴링 | 서버가 응답을 붙잡고 있다가 사건 발생 시 응답 | 클라이언트 요청 | 해당 없음 | 이벤트마다 연결 재수립 |
| SSE | 응답을 끝내지 않고 계속 흘려보냄 | 서버 → 클라이언트 | 규격 내장(Last-Event-ID) | 최초 1회 |
| WebSocket | 101로 프로토콜 전환 후 프레임 교환 | 양방향 | 직접 구현 | 프레임 헤더 2바이트~ |
깊이 — 폴링의 근본 문제는 트래픽과 지연의 맞바꿈(★). 주기를 3초로 줄이면 지연은 줄지만 요청 대부분이 헛걸음이고, 30초로 늘리면 트래픽은 줄지만 최대 30초 늦게 안다. 둘 다 좋게 만들 방법이 없다는 것이 폴링의 한계다. 롱 폴링은 헛걸음을 없애고, SSE·WebSocket은 연결 재수립까지 없앤다.
깊이 — SSE의 자동 재연결이 실무에서 크다(★★). SSE는 각 이벤트에 id를 붙일 수 있고, 연결이 끊기면 클라이언트가 알아서 다시 붙으면서 마지막 id를 Last-Event-ID 헤더로 보낸다. 서버가 그 지점부터 이어 주면 끊긴 사이에 놓친 이벤트를 복구할 수 있다. WebSocket에는 이런 규격이 없어서 재연결·유실 복구를 전부 직접 만들어야 한다. "단순한 알림에 WebSocket을 쓰면 오히려 손해" 라는 판단의 근거가 이것이다.
// SSE 응답 본문 — 빈 줄 하나가 메시지 하나의 끝
Content-Type: text/event-stream
id: 42
event: order-status
data: {"orderId":"A-1","status":"SHIPPED"}
핵심 판단 기준: "실시간 = WebSocket"이 아니라 양방향이 정말 필요한가다.
- 알림·피드·진행률·주문 상태 → SSE(서버가 밀어 주기만 하면 됨)
- 채팅·협업 편집·게임 → WebSocket(클라이언트도 자주 보냄)
WebSocket 핸드셰이크 — 왜 101인가
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols ← 200이 아니다
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
WebSocket은 평범한 HTTP 요청으로 시작해서 101을 받는 순간부터 그 TCP 연결에서 HTTP를 쓰지 않는다. 그래서 기존 80·443 포트와 프록시 인프라를 그대로 쓸 수 있다는 게 설계의 실용적 이점이다.
Sec-WebSocket-Accept는 클라이언트 키에 고정 문자열 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 을 붙여 SHA-1 → base64한 값이다. 값이 공개돼 있으니 보안 장치가 아니고, 상대가 WebSocket을 정말 이해하고 답했는지 확인해 중간 캐시·프록시의 오해를 막는 절차다.
서버를 여러 대로 늘리면 — 여기서 답이 갈린다
이게 이 주제의 진짜 심화 질문이다. WebSocket·SSE는 연결이 특정 서버 인스턴스에 붙어 있는 상태(stateful) 라, REST의 무상태 전제가 깨진다.
세 가지가 따라온다.
- 핸드셰이크 라우팅 — 업그레이드 요청과 이후 트래픽이 같은 인스턴스로 가야 한다. L7 로드밸런서가 WebSocket 업그레이드를 통과시키도록 설정하고, 필요하면 스티키 세션을 건다.
- 인스턴스 간 전달 — 서버 1이 서버 2에 붙은 사용자에게 보내야 하는 상황이 반드시 생긴다. 그래서 Redis Pub/Sub이나 메시지 브로커를 두고 모든 인스턴스가 구독한다. 인메모리 브로드캐스트만 쓰면 한 인스턴스의 접속자에게만 닿는다.
- 유휴 연결 끊김 — 로드밸런서·프록시는 대개 유휴 연결을 수 분 뒤 끊는다. WebSocket은 ping/pong 제어 프레임으로, SSE는 주석 줄(
: keep-alive)을 주기적으로 보내 살려 둔다.
깊이 — 인증은 어떻게 하나(★). 브라우저의 WebSocket API는 커스텀 헤더를 붙일 수 없다. 그래서 Authorization: Bearer ...를 그대로 쓸 수 없고, ① 쿠키(같은 도메인이면 자동 전송), ② 쿼리 파라미터로 짧은 수명의 일회용 티켓 전달, ③ 연결 직후 첫 메시지로 토큰 전송 후 인증 전까지 다른 메시지 거부 — 중 하나를 쓴다. 쿼리 파라미터에 장기 토큰을 넣는 건 로그·리퍼러에 남아 위험하다는 점이 함정이다. 그리고 WebSocket은 동일 출처 정책의 적용을 받지 않으므로 Origin 헤더를 서버가 직접 검증해야 한다(안 하면 Cross-Site WebSocket Hijacking).
STOMP — raw WebSocket 위의 메시징 규약
WebSocket 자체는 바이트를 주고받는 통로일 뿐 목적지·구독 같은 개념이 없다. 채팅방이 여러 개면 "이 메시지는 어느 방 것인가"를 직접 정의해야 한다. **STOMP**는 그 위에 얹는 단순한 텍스트 메시징 프로토콜로, CONNECT·SEND·SUBSCRIBE·MESSAGE 같은 명령과 목적지 헤더를 표준화한다. 목적지 규칙(/topic은 브로드캐스트, /queue는 1:1)과 구독 관리를 프레임워크에 맡길 수 있어, 방·구독이 여러 개인 서비스에서 직접 규약을 만드는 수고를 던다.
꼬리질문: "SSE는 브라우저 연결 제한이 있다던데요?" → HTTP/1.1은 도메인당 6연결이라 탭을 여러 개 열면 막힌다. HTTP/2 멀티플렉싱으로 해소된다(네트워크 편 HTTP 버전과 연결). / "WebSocket이 HTTP/2 위에서도 되나요?" → RFC 8441의 확장 CONNECT로 가능하고 RFC 9220이 HTTP/3까지 넓혔지만, 2026년 기준 HTTP/3 위 프로덕션 구현은 아직 없어 실제로는 1.1·2 위가 대부분이다. / "SSE로 클라이언트가 보내려면?" → 일반 REST 요청을 따로 쓴다. 실제로 SSE(수신) + REST(송신) 조합이 흔하고, 이게 WebSocket보다 단순한 경우가 많다.
함정: ❌ "실시간이면 무조건 WebSocket" → 단방향이면 SSE가 더 단순하고 재연결까지 공짜다. ❌ "WebSocket도 무상태라 그냥 스케일아웃하면 된다" → 연결이 인스턴스에 묶인다. 브로커가 필요하다. ❌ "Authorization 헤더로 인증하면 된다" → 브라우저 WebSocket API는 커스텀 헤더를 못 붙인다.
(출처: RFC 6455 — The WebSocket Protocol·RFC 8441 — Bootstrapping WebSockets with HTTP/2·MDN — Using server-sent events 교차검증. 보강 2026-08.)
흔한 오답·함정 정리
- safe = idempotent → 다른 개념(GET은 둘 다, PUT/DELETE는 멱등만).
- 실시간이면 무조건 WebSocket → 단방향이면 SSE가 더 단순하고 재연결이 규격에 있다.
- WebSocket도 스케일아웃하면 그만 → 연결이 인스턴스에 묶여 브로커(Redis Pub/Sub 등)가 필요하다.
- WebSocket 인증은
Authorization헤더로 → 브라우저 API가 커스텀 헤더를 못 붙인다. 쿠키·티켓·첫 메시지 인증. Sec-WebSocket-Accept가 보안 장치 → 고정 문자열 기반이라 보안이 아니라 프로토콜 확인용.- POST는 절대 캐싱 안 됨 → 조건부 캐싱 가능.
- REST = HTTP + JSON → 아키텍처 스타일(HATEOAS).
- GraphQL이 항상 낫다 → 캐싱·N+1·복잡도 비용.
- 결제 중복은 프론트에서만 막으면 됨 → 서버 멱등성 키.
한국 면접 단골 Q&A (답변 골격)
| 질문 | 핵심 답 |
|---|---|
| REST·RESTful | 자원/메서드/무상태·Richardson L2/L3·HATEOAS |
| 멱등성·안전성 | safe(읽기) vs idempotent(상태 동일)·POST 둘 다 X |
| DELETE 멱등? | 404여도 서버 상태 동일 → 멱등 |
| 결제 중복 | Idempotency-Key·유니크/분산락 |
| REST vs GraphQL vs gRPC | 고정/필드선택/MSA 고성능 |
| GraphQL 단점 | 캐싱·리졸버 N+1·복잡도 폭탄 |
| 페이지네이션 | offset(단순·느림) vs cursor(대용량) |
| 상태코드 | 201/204·401 vs 403·409·422·429 |
| rate limiting | Token Bucket·429·Retry-After |
| 실시간 알림 설계 | 폴링→롱폴링→SSE→WebSocket 계보·단방향이면 SSE |
| SSE vs WebSocket | 단방향+자동재연결 vs 양방향+직접구현 |
| 폴링의 한계 | 트래픽↔지연 맞바꿈·둘 다 좋게 못 함 |
| WebSocket 핸드셰이크 | Upgrade+101·Sec-WebSocket-Accept는 확인용(보안 X) |
| 여러 대로 스케일아웃 | 연결이 인스턴스에 묶임 → 스티키+Redis Pub/Sub·브로커 |
| WebSocket 인증 | 커스텀 헤더 불가 → 쿠키·일회용 티켓·첫 메시지·Origin 검증 |
| STOMP | raw WebSocket 위 목적지·구독 규약(/topic·/queue) |
꼬리질문 대비 (상 난이도)
- "결제 더블 클릭?" → 멱등성 키로 1회 처리·결과 재사용.
- "PUT vs PATCH 멱등성?" → PUT 전체 교체(멱등), PATCH는 구현 따라 비멱등.
- "GraphQL N+1?" → 각 필드 리졸버가 쿼리 → DataLoader 배치.
- "API 응답에 엔티티 그대로?" → 결합·LazyInit·보안 → DTO.
- "버전 안 올리고 필드 추가?" → 호환 변경(클라는 모르는 필드 무시) → 깨는 변경만 버전.
- "알림 서비스를 처음부터 설계해 보세요" → 단방향이니 **SSE**가 1안.
id+Last-Event-ID로 유실 복구, 인스턴스 간 전달은 Redis Pub/Sub, 유휴 끊김은 주기적 keep-alive, 클라이언트 송신은 일반 REST. - "채팅에서 상대가 접속 중인지 어떻게 아나요?" → WebSocket ping/pong으로 생존 확인 + 연결 종료 이벤트를 브로커로 전파해 다른 인스턴스에도 알린다.
- "연결이 자꾸 끊깁니다" → 로드밸런서·프록시의 유휴 타임아웃이 1순위 용의자. keep-alive 주기를 그 값보다 짧게.
- "WebSocket에 CSRF 같은 공격이 있나요?" → Cross-Site WebSocket Hijacking. 동일 출처 정책이 적용되지 않으므로 서버가
Origin을 직접 검증해야 한다.
한 줄 요약 — REST=자원(URI)+메서드+무상태(Fielding·Richardson L2/L3 HATEOAS). safe≠idempotent(POST 둘 다 X·PUT/DELETE 멱등). 결제 중복은 Idempotency-Key(유니크/분산락). REST↔GraphQL(필드 선택·리졸버 N+1·캐싱 어려움)↔gRPC(MSA·Protobuf). 페이지네이션 offset(단순·느림)↔cursor(대용량). 상태코드(201/204·401 vs 403·409·422·429)·에러 일관(RFC 9457)·rate limit(Token Bucket·429)·DTO. 실시간은 폴링(트래픽↔지연 맞바꿈)→롱 폴링→SSE(단방향·
Last-Event-ID로 유실 복구·HTTP 그대로)→WebSocket(Upgrade+101·양방향·재연결 직접 구현)의 계보 — 양방향이 정말 필요한가가 갈림길이고, 스케일아웃 시 연결이 인스턴스에 묶여 스티키+브로커(Redis Pub/Sub)가 필요하며, 인증은 커스텀 헤더 불가라 쿠키·티켓·첫 메시지 +Origin검증(Cross-Site WebSocket Hijacking)이다.
(출처 — 한국 면접 기출·교차검증 2026-06: VSFe/Tech-Interview — Network·gyoogle — REST API·WeareSoft — network·velog @ygreenb — offset vs cursor · RFC 9110(멱등성·상태코드)·RFC 9457(Problem Details)·draft-ietf-httpapi-idempotency-key-header 교차검증.)