
HTTP 쿼리 메서드에 대하여: GET과 POST 사이의 간극을 찾다
요약
HTTP GET과 POST 사이의 설계적 한계를 해결하기 위해 제안된 새로운 QUERY 메서드(RFC 10008)를 소개합니다. QUERY 메서드는 GET의 안전성과 POST의 데이터 표현력을 결합하여, GraphQL 등의 읽기 쿼리를 HTTP 캐시 계층에서 효율적으로 처리할 수 있도록 설계되었습니다.
핵심 포인트
- GET은 바디 사용이 제한적이고 URL 길이에 한계가 있음
- POST는 바디 사용이 자유롭지만 캐싱이 어렵고 부작용을 전제함
- QUERY 메서드는 GET의 시맨틱스와 POST의 표현력을 결합함
- GraphQL 등 복잡한 쿼리를 HTTP 캐시 영역으로 복귀시키는 것이 목표
Web API의 검색 엔드포인트를 설계할 때, 많은 엔지니어가 한 번쯤 생각해 본 적이 있을 것이다. '읽기 전용인데도 바디(body)로 JSON을 보내고 싶다'는 요구사항을 어떻게 처리할 것인가? GET은 바디를 가질 수 없고, URL에는 2000~8000 문자 정도의 구현 한계가 있다. POST는 바디를 가질 수 있지만 '부작용이 있을 수도 있다'고 취급되기 때문에 CDN이나 중간 캐시를 통과하지 못한다.
현장에서는 어쩔 수 없이 타협했다. Elasticsearch의 Search API는 같은 검색 엔드포인트를 GET과 POST 모두로 받는 설계가 되어 있다. GET with body는 RFC 9110이 '의미를 정의하지 않는다'고만 적어 놓은 모호한 영역이며, 일부 중간 장치에서는 바디를 누락하는 것이 알려져 있다. 다른 많은 제품들은 포기하고 POST를 선택했으며, 캐시는 자체적으로 재구축했다.
지난 20년간 Web API의 검색은 'HTTP 캐시를 버릴 것인가, 규약의 그레이존을 걸칠 것인가'라는 두 가지 선택지 사이에서 싸워왔다. HTTP QUERY 메서드(RFC 10008, 2026년 6월 발행)는 여기에 세 번째 선택지를 제시하는 제안이다.
같은 문제의식 자체는 예전부터 있었다. WebDAV는 RFC 5323(2008년 11월)에서 SEARCH 메서드를 정의했다. 임의의 쿼리를 바디로 보내는, safe하고 idempotent한 메서드다. 다만 WebDAV 문맥이 너무 강해서 일반 웹에서는 보급되지 않았다.
그 후 Julian Reschke, Ashok Malhotra, James M. Snell 세 사람이 'WebDAV에 국한되지 않은 SEARCH'를 목표로 한 개인 제안(Snell 명의 초안, 2020년 9월 업데이트 중단 및 폐기)을 진행했다. 이 작업이 나중에 IETF의 HTTP 표준화를 담당하는 워킹 그룹(httpbis WG)에서 QUERY로서 채택되었다. CalDAV에서 사용되는 REPORT 메서드도 같은 발상이지만, 이 역시 WebDAV 계열에 국한되어 있다.
10년 이상 동안 이 문제는 '독자적 메서드 진영'과 'GET+body 진영'의 줄다리기에 머물러 있었다.
QUERY의 존재 가치를 이해하려면, HTTP 캐시가 무엇을 키(key)로 작동하는지 파악할 필요가 있다.
HTTP 캐시(브라우저 캐시, CDN, 리버스 프록시의 공유 캐시 모두 포함)는 기본적으로 URL + 일부 헤더를 키로 하여 응답을 저장한다. 같은 URL에 다음 요청이 오면, 저장된 응답을 반환한다. 단순하지만 강력한 메커니즘이며, 전 세계 웹은 이 위에 세워져 있다.
GET은 쿼리를 전부 URL에 담기 때문에 이 메커니즘과 궁합이 좋다. GET /search?q=foo
와 GET /search?q=bar
는 별개의 URL이므로 각각 캐시되고, q=foo가 다시 오면 즉시 반환된다.
POST가 캐시되지 않는 것은 부작용이 있을 수 있다는 이유 외에, 또 다른 구현상의 이유가 있다. 캐시 키가 URL 중심적으로 설계되어 있기 때문에, 바디가 달라도 같은 URL로 취급해 버린다. POST /search
에 `{
보조 수단으로 Accept-Query
응답 헤더(Response Header)를 통해 "서버가 어떤 QUERY 형식(미디어 타입, Media Type)을 수락할 수 있는지"를 클라이언트에게 광고할 수 있다.
요컨대 QUERY는 "GET의 시맨틱스(Semantics)를 POST의 표현력으로 보낼 수 있도록 만든" 메서드이며, 이를 위해 HTTP 캐시 계층에 최소한이지만 본질적인 확장을 요구하고 있다.
QUERY의 승부처는 명확하다. GraphQL이나 독자적인 RPC의 읽기 쿼리(Read Query)를 HTTP 캐시의 영역으로 되돌려 놓는 것이다. 이 한 점에 모든 것이 달려 있다. GraphQL은 POST를 선택할 수밖에 없었기에 캐시를 직접 구현해야만 했고, gRPC-Web은 브라우저와 HTTP/2에 국한된 이야기였다. QUERY는 "GET과 동일하게 취급해도 되는 안전한(Safe) 메서드"를 끼워 넣음으로써, CDN·공유 캐시(Shared Cache)·로드 밸런서(Load Balancer)와 같은 검증된 자산들을 재사용할 수 있게 한다.
병목 요인은 결국 CDN 측이다. Cloudflare나 Fastly 급의 서비스들이 바디 캐싱(Body Caching)을 일급 시민(First-class citizen)으로 다루느냐에 따라 QUERY의 정착 여부가 결정될 것이다. 이 부분이 움직인다면, 읽기 집약적인(Read-heavy) API 설계는 "기본은 GET, 복잡하면 QUERY, 쓰기만 POST"라는 3층 구조가 표준이 될 것이다. 움직이지 않는다면, 다시 10년 뒤의 숙제로 남게 될 것이다.
신규 API를 설계하는 입장에서는 판단 기준이 단순하다. 검색 엔드포인트에서 캐시 히트율(Cache Hit Rate)이 지배적인 워크로드라면 QUERY를 일급(First-class)으로 수용하고, POST를 호환용으로 남겨둔다. 쓰기와 뮤테이션(Mutation)이 섞여 있는 GraphQL 엔드포인트를 통째로 교체할 필요는 없다.
새로운 메서드는 새로운 공격 표면(Attack Surface)을 만든다. 다음은 일차적인 정보의 근거라기보다, 기존의 캐시·WAF·SIEM 운영에 QUERY를 도입했을 때 나타날 논리적인 귀결로서 유의해야 할 점들이다.
로그의 사각지대가 가장 크다. nginx의 combined 로그나 ALB의 액세스 로그는 기본적으로 메서드(Method)와 URL만 기록한다. GET/POST 시대에는 쿼리 문자열(Query String)이 URL에 포함되어 있어 사후 추적이 가능했지만, QUERY에서는 쿼리 본체가 바디(Body)로 이동한다. SQLi나 NoSQLi 시도, 민감 정보의 검색 이력, 인가 우회(Authorization Bypass)의 징후와 같은 SIEM 측의 탐지 소재가 통째로 사라진다. 바디를 별도의 채널로 안전하게 캡처하는 메커니즘을 먼저 마련하지 않으면 가시성이 떨어진다.
WAF의 허점도 주의해야 한다. 미지의 메서드를 차단하는 WAF는 많지만, 반대로 "알 수 없는 것이니 통과시킨다"는 구현도 존재한다. 메서드 허용 목록(Allow-list)을 엄격하게 관리하지 않는 규칙은 QUERY를 그대로 통과시켜 버린다. IPS/IDS의 시그니처(Signature) 또한 URL을 전제로 하는 경우가 많아, 바디 검사를 활성화하지 않으면 탐지를 놓치게 된다.
캐시 오염(Cache Poisoning)의 새로운 유형도 피할 수 없다. 바디가 캐시 키(Cache Key)에 포함된다는 것은, 공격자가 제어 가능한 요소를 의도치 않게 키에 포함시킴으로써 캐시 오염의 변종을 늘릴 수 있음을 의미한다. 특히 인증된 사용자 대상의 응답을 공유 캐시에 올리는 설계는 CDN 측의 구현이 성숙해질 때까지 피해야 한다.
다음은 명세의 직접적인 결과가 아니라, 현재의 AI 시스템 설계에 QUERY를 적용했을 때의 고찰이다.
RAG(검색 증강 생성, Retrieval-Augmented Generation) 시스템 내에서 가장 많이 호출되는 것은 사실 벡터 검색(Vector Search) 부분이다. 사용자의 질문을 임베딩 벡터(Embedding Vector)로 변환하여 Pinecone, Weaviate, pgvector 등에 "이와 유사한 문서 10개를 달라"고 요청한다. 이 작업은 완전히 읽기 전용이며, 동일한 질문에는 동일한 결과를 반환해야 한다. 그야말로 GET의 세계처럼 보인다.
하지만 구현은 거의 모두 POST로 되어 있다. 768차원이나 1536차원의 벡터를 URL에 담을 수 없기 때문이다. 결과적으로 사내 챗봇이 "경비 정산 방법은?"이라는 질문을 하루에 1,000번 받더라도, 벡터 검색은 매번 실행된다. 배후에서 GPU나 벡터 DB의 인덱스 탐색이 1,000번 돌아간다. CDN이나 공유 캐시는 POST를 그냥 통과시키므로, 이러한 중복을 흡수할 수 없다.
현재의 우회책은 "시맨틱 캐시(Semantic Cache)"라고 불리는 애플리케이션 계층의 메커니즘이다. 질문을 임베딩하여 과거의 질문 임베딩과 유사하면 캐시를 반환하는 방식이다. 다만 이는 각 애플리케이션이 직접 구현해야 하며, Redis나 전용 미들웨어를 사이에 두어야 한다. CDN에서는 처리할 수 없다.
벡터 검색 엔드포인트(Vector search endpoint)를 QUERY로 받을 수 있게 된다면, 이러한 중복을 CDN이나 공유 캐시(Shared cache)에서 흡수할 수 있는 가능성이 생긴다. "경비 정산 방법은?"의 임베딩 벡터(Embedding vector)를 바디(Body)에 실은 QUERY는 캐시 키(Cache key)가 바디를 포함하여 결정되므로, 두 번째 요청부터는 CDN이 응답할 수 있다. 이는 추론 비용(Inference cost) 및 GPU 소비에 직접적으로 영향을 미치는 문제다.
또 다른 하나는 AI 에이전트(Agent) 설계에 관한 이야기다. 에이전트는 외부 도구(Tool)를 호출하러 가지만, 네트워크 에러로 인해 실패할 때가 있다. 이때 "다시 호출해도 되는가"를 판단하는 것은 쉽지 않다.
예를 들어 에이전트가 POST /api/create-invoice를 호출했는데 타임아웃(Timeout)이 발생했다고 가정하자. 송장(Invoice)이 생성되었을 가능성도 있고, 요청이 도착하기 전에 연결이 끊겼을 가능성도 있다. 안전을 위해 자동 재시도(Retry)를 하지 않는 것이 원칙이며, 재시도를 하고 싶다면 Idempotency-Key 헤더를 추가하는 등의 추가 구현이 필요하다.
반면 POST /api/search-documents (읽기 전용)의 경우에도, 프로토콜상으로는 동일한 POST이므로 에이전트 측에서는 구분할 수 없다. 쓰기 작업일 수도 있다는 전제로 다룰 수밖에 없다. 이는 읽기 전용 도구의 재시도를 어렵게 만든다.
QUERY로 제공되는 읽기 계열 도구라면, safe/idempotent가 프로토콜 계층에서 보장되므로 에이전트는 아무 생각 없이 재시도해도 된다. MCP와 같은 도구 프로토콜(Tool protocol)에서 resources의 읽기에 해당하는 부분을 QUERY로 매핑하는 구현이 향후 등장할 여지가 있다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기