Discovery와 APIs
- 3.1 DNS의 계층적 구조
- 3.2 최종 일관성과 캐싱
- 3.3 DNS와 부하 분산
- 3.4 HTTP
- 3.5 Resources
- 3.6 Request methods
- 3.7 Response status codes
- 3.8 OpenAPI
- 3.9 Evolution
- 3.10 Idempotency
신뢰할 수 있고 안전한 채널을 만들었으면 이제 두 가지를 해야 해 — 상대를 찾고, 상대와 대화하는 거지. DNS가 이름을 IP로 바꿔서 상대를 찾아주고, REST API가 그 위에서 서비스 간 대화의 규칙을 정해. 이 두 단계가 서비스 간 통신의 전부야.
**DNS(Domain Name System)**는 인터넷의 전화번호부인데, 저자가 이걸 소개하는 진짜 이유는 DNS가 본질적으로 분산되고, 계층적이며, 최종적으로 일관성이 보장되는 키-값 저장소이기 때문이야. 분산 시스템의 핵심 개념을 처음 맛보는 거지.
DNS 조회는 계층적 탐색을 따라. 클라이언트가 로컬 캐시를 먼저 뒤지고, 없으면 재귀 DNS 리졸버에 질의해. 리졸버가 루트 네임서버 → TLD 서버 → **권한 있는 네임서버(authoritative name server)**를 차례로 밟아가며 최종 IP를 받아와. 매번 루트까지 가면 느리겠지? 각 단계에서 캐싱이 일어나니까 실제로 루트까지 가는 경우는 드물어.
여기서 재밌는 게 나와. 각 DNS 레코드에 **TTL(Time To Live)**이 있어서 캐시 유효 시간을 정해. DNS 레코드를 변경해도 계층 전체의 캐시가 즉시 바뀌지 않아 — TTL이 만료될 때까지 오래된 값이 돌아다녀. 이게 **최종 일관성(eventual consistency)**의 첫 맛보기야. TTL을 짧게 하면 전파가 빨라지지만 캐시 효과가 줄고 네임서버 부하가 늘어나 — 성능과 일관성의 트레이드오프. 이 트레이드오프는 분산 시스템 전체에서 반복되는 패턴이야.
DNS는 부하 분산에도 써. 하나의 도메인에 여러 IP를 매핑하고 라운드 로빈으로 반환하면 간단한 로드 밸런싱이 돼. 근데 캐싱 때문에 부하가 완벽하게 분산되지 않고, 헬스 체크 기능이 없어서 죽은 서버로 트래픽이 갈 수도 있어. 더 정교한 방법은 이후 장에서 다루지.
이제 상대를 찾았으니 대화할 차례야. TCP, TLS, DNS를 깔았으니 그 위에서 서비스들이 실제로 어떻게 대화하는지 — RESTful HTTP API 이야기지.
HTTP는 클라이언트-서버 모델의 요청-응답 프로토콜이야. HTTP/1.1은 하나의 TCP 연결에서 여러 요청을 보낼 수 있지만 head-of-line blocking 문제가 있거든. 앞 요청이 느리면 뒤에 줄 선 요청이 다 막혀. HTTP/2가 멀티플렉싱으로 이걸 해결했고, HTTP/3는 아예 TCP를 버리고 UDP 기반의 QUIC을 써.
REST에서 모든 건 **리소스(resource)**야. URI로 식별되고, 리소스를 명사로 표현하는 게 좋은 API 설계지. HTTP 메서드가 그 리소스에 대한 동작을 정의해 — GET(조회, 안전하고 멱등), POST(생성), PUT(전체 교체, 멱등), DELETE(삭제, 멱등). **안전(safe)**한 메서드는 서버 상태를 안 바꾸고, **멱등(idempotent)**한 메서드는 여러 번 호출해도 결과가 같아.
응답의 상태 코드도 중요해. 2xx 성공, 3xx 리다이렉션, 4xx 클라이언트 오류, 5xx 서버 오류. 클라이언트가 재시도 여부를 판단하는 데 쓰이거든. 429(Too Many Requests)는 rate limiting, 503(Service Unavailable)은 일시적 장애 — 이걸 보고 재시도할지 결정하는 거야.
OpenAPI는 REST API의 표준 명세 포맷이야. API를 기계가 읽을 수 있게 기술하면 문서 자동 생성, 클라이언트 코드 생성, 테스트 자동화가 가능해져. 서비스 간 통합에서 **계약(contract)**을 명시적으로 정의하는 게 중요하지.
API는 시간이 지나면 진화해야 하는데, 기존 클라이언트를 깨뜨리면 안 돼. 하위 호환성(backward compatibility) 유지가 핵심이야. 새 필드 추가는 보통 안전하고, 기존 필드 제거나 이름 변경은 위험해. 큰 변경은 버저닝으로 관리하면 돼.
마지막으로 분산 시스템에서 가장 중요한 개념 중 하나 — 멱등성(idempotency). 네트워크가 불안정하면 클라이언트가 요청 성공 여부를 모르니까 재시도할 수밖에 없잖아. 이때 같은 요청을 여러 번 보내도 결과가 달라지면 안 돼. GET, PUT, DELETE는 본질적으로 멱등한데, POST는 아니야. POST의 멱등성을 보장하려면 **멱등키(idempotency key)**를 써 — 클라이언트가 고유한 키를 생성해서 요청에 포함하고, 서버가 같은 키의 요청을 중복 처리하지 않는 거지.
정리
3장 읽고 기억할 거 세 가지:
- DNS는 본질적으로 분산된 최종 일관성 키-값 저장소야 — TTL과 캐싱이 만드는 성능-일관성 트레이드오프는 분산 시스템 전체에서 반복되는 패턴이지.
- REST API는 리소스 + HTTP 메서드 + 상태 코드로 구성돼 — OpenAPI로 계약을 명시적으로 정의하고, 하위 호환성을 유지하며 진화시켜야 해.
- 분산 환경에서 멱등성은 필수야 — 네트워크 불안정으로 재시도가 불가피하니까, POST에는 멱등키를 쓰자.