Skip to content
Published on

GraphQL 스펙, 거의 4년 만의 릴리스 — September 2025 에디션과 GraphQL.js 17 GA에 들어간 것, 아직 밖에 있는 것

공유하기
Authors

들어가며 — 멈춘 줄 알았던 표준이 움직였다

GraphQL 스펙의 마지막 에디션은 오랫동안 October 2021이었습니다. 그 사이 생태계는 계속 굴러갔지만 — Apollo Federation, 각종 게이트웨이와 클라이언트가 각자 진화하는 동안 — 표준 문서 자체는 4년 가까이 멈춰 있었습니다. "GraphQL은 이제 유지보수 모드인가"라는 냉소가 나올 만한 공백이었습니다.

그 공백이 최근 두 번의 릴리스로 깨졌습니다. 2025년 9월, September 2025 에디션이 나왔고 — 공식 발표의 표현대로 October 2021 이후 첫 에디션입니다 — 2026년 6월 15일에는 참조 구현인 GraphQL.js v17이 GA됐습니다. v17의 첫 알파가 npm에 올라온 날짜가 2022년 5월 19일이니, 알파 딱지를 떼는 데 4년이 넘게 걸린 셈입니다.

이 글은 두 릴리스를 하이프 없이 뜯어봅니다. 무엇이 실제로 표준에 들어갔는지, 무엇이 아직도 밖에 있는지(스포일러: @defer@stream), 그리고 지금 당신 팀이 무엇을 해야 하는지. 모든 날짜와 버전은 스펙 원문, npm 레지스트리, GitHub 릴리스에서 직접 확인했습니다.

날짜부터 정확히 — 두 릴리스의 타임라인

GraphQL 스펙 (spec.graphql.org)
  2015-07  첫 워킹 드래프트 (July 2015)
  2021-10-26  October 2021 에디션
  2025-09-03  September 2025 에디션   <- 3년 10개월 만
  2026-06-04  현재 워킹 드래프트 갱신 (다음 에디션 준비)

GraphQL.js (registry.npmjs.org 게시 시각)
  2022-05-19  17.0.0-alpha.1
  2025-06-11  17.0.0-alpha.9
  2026-05-07  17.0.0-beta.0
  2026-06-02  17.0.0-rc.0
  2026-06-15  17.0.0 GA              <- 알파 시작 후 4년 남짓
  2026-07-03  17.0.2 (현재 latest)

  16.x는 계속 패치 중: 16.13.0(2026-02-24), 16.14.2(2026-06-09)

스펙 릴리스 노트에 따르면 September 2025 에디션에는 October 2021 이후 30명의 기여자가 만든 100건 이상의 변경이 들어갔고, 이 릴리스는 첫 드래프트(2015년 7월)로부터 10년이 되는 시점의 릴리스이기도 합니다.

September 2025 에디션 — 4년치 변경에서 건질 것

공식 체인지로그가 꼽는 주요 변경은 다섯 가지입니다. 하나씩 봅니다.

OneOf 입력 객체 — "입력 유니온"의 표준화

GraphQL에는 출력 타입의 유니온은 있어도 입력 타입의 유니온이 없어서, "ID로 찾거나, 이메일로 찾거나, 둘 중 하나만"을 스키마로 표현할 방법이 없었습니다. 다들 필드를 전부 nullable로 열어 두고 리졸버에서 손수 검증했지요. RFC #825로 수년을 끌던 이 문제가 @oneOf로 표준화됐습니다.

input UserUniqueCondition @oneOf {
  id: ID
  username: String
  organizationAndEmail: OrganizationAndEmailInput
}

스펙 문구 그대로, OneOf 입력 객체는 정확히 하나의 필드만 제공되어야 하고 그 값은 null이 아니어야 합니다. 둘을 채우면 요청 에러, 하나를 null로 채워도 에러. 이 검증이 이제 리졸버 코드가 아니라 스펙 수준의 입력 강제(coercion) 규칙입니다. 인트로스펙션에는 __Type.isOneOf 필드가 추가되어 코드젠 도구가 유니온 타입을 정확히 생성할 수 있습니다.

Schema Coordinates — 스키마 요소의 주소 체계

RFC #794는 스키마의 모든 요소에 유일한 문자열 주소를 부여합니다.

Business                          타입
Business.name                     필드
SearchCriteria.filter             입력 필드
SearchFilter.OPEN_NOW             enum 값
Query.searchBusiness(criteria:)   필드 인자
@private                          디렉티브
@private(scope:)                  디렉티브 인자

스펙은 각 요소가 정확히 하나의 좌표를 갖도록 보장합니다. 사소해 보이지만 이건 도구 생태계의 공용 어휘입니다 — 스키마 diff, 린트 규칙, 에러 리포팅, 사용량 추적("이 필드 누가 쓰나")이 지금까지는 도구마다 제각각의 표기를 썼습니다. 실제로 에러에 스키마 좌표를 넣자는 후속 RFC가 이미 올라와 있습니다.

나머지: 문서 설명, deprecation 확대, 전체 유니코드

  • 실행 문서(쿼리·뮤테이션)에도 설명(description)을 붙일 수 있게 됐습니다(#1170). 체인지로그가 명시하는 동기가 흥미로운데, AI 소비자를 위해서입니다 — LLM이 쿼리의 의도를 파악할 컨텍스트를 문서 자체에 담자는 것.
  • deprecation이 스키마 요소 전반으로 확대되고 규칙이 정리됐습니다 — 인터페이스가 deprecate하지 않은 필드를 구현체가 deprecate할 수 없다는 제약, includeDeprecated 인자의 non-null화 등(#1053, #1142).
  • 언어 문법이 전체 유니코드 범위를 지원합니다(#849).
  • 기본값 강제 규칙이 재정의됐고(#793), 실행 알고리즘 서술이 대폭 정리됐습니다(ExecuteSelectionSetExecuteCollectedFields로 교체되는 등).

전체적으로 보면 이번 에디션은 신기능 잔치가 아니라 4년치 정합성 부채 청산 + 두 개의 굵직한 언어 기능(OneOf, Coordinates)입니다. 공식 발표문이 AI-ready를 앞세워 포장하고 있긴 하지만, 실체는 착실한 표준 정비에 가깝습니다.

스펙에 들어가지 않은 것 — defer와 stream은 아직도 RFC다

여기가 이 릴리스에서 가장 정직하게 봐야 할 부분입니다. @defer@stream — 응답을 점진적으로 나눠 보내는 incremental delivery — 는 September 2025 에디션에 없습니다. 스펙 본문을 직접 검색해 확인했습니다. defer라는 단어 자체가 0회 등장합니다. 2026년 6월자 워킹 드래프트에도 없습니다.

이게 얼마나 오래된 이야기인지 감을 잡자면: graphql 패키지의 npm 레지스트리에는 experimental-stream-defer 실험 빌드가 2020년 10월부터 올라와 있고, 그 dist-tag는 지금도 2021년 12월에 게시된 16.1.0 계열 빌드를 가리키고 있습니다. Incremental Delivery RFC는 현재 RFC 2(Draft) 단계이고 — 죽은 건 아닙니다. 이 글을 쓰는 주에도 스펙 드래프트에 커밋이 붙었습니다(2026-07-15). 하지만 5년 넘게 "거의 다 됐다"였던 기능이라는 사실은 변하지 않습니다.

왜 이렇게 오래 걸릴까요. 실행 시맨틱(중복 필드 처리, 에러 전파)과 페이로드 형식을 표준화하는 난이도도 있지만, 실전 데이터가 균질하지 않은 탓도 있습니다. GraphQLConf 2025에서 Meta는 @async라는 자체 디렉티브를 발표하면서 @defer가 남기는 숨은 비용을 이유로 들었습니다 — 세계에서 GraphQL을 가장 크게 쓰는 조직이 defer의 대안을 실험 중이라는 뜻입니다. 표준화 대상 자체가 아직 흔들리는 셈입니다.

실무 함의는 명확합니다. defer/stream을 쓰는 모든 스택은 지금도 스펙 외(pre-spec) 기능을 쓰는 것이고, 페이로드 형식이 바뀔 수 있는 위험을 안고 갑니다. 실제로 바뀌었습니다 — 바로 아래 v17 이야기입니다.

GraphQL.js 17 — 무엇이 깨지고, 무엇이 좋아지나

공식 업그레이드 가이드가 이례적으로 충실하니, 여기서는 판단에 필요한 골자만 추립니다.

깨지는 것들

  • Node.js 요구사항이 ^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0으로 올라갑니다. Node 20 이하에 묶인 환경이라면 여기서 끝입니다.
  • 패키지가 exports 조건부 export로 바뀝니다. 공식 진입점(graphql, graphql/execution 등) 밖의 deep import는 semver 보장이 없다고 명시됐고, 폐기됐던 graphql/subscription 서브패스는 제거됐습니다.
  • 개발 모드가 NODE_ENV 기반 자동 활성화에서 명시적 옵트인(enableDevMode() 또는 development 패키지 조건)으로 바뀝니다.
  • 커스텀 스칼라 API 이름이 정리됩니다 — serialize, parseValue, parseLiteral은 deprecated 별칭으로 남고 coerceOutputValue, coerceInputValue, coerceInputLiteral이 새 이름입니다.
  • subscribe()의 반환 타입이 Promise 고정에서 PromiseOrValue로 바뀌어, Promise를 가정한 TypeScript 코드는 수정이 필요합니다.

실행기 분리 — execute는 단일 결과 전용

v17의 설계에서 가장 눈에 띄는 결정은 안정 기능과 실험 기능의 물리적 분리입니다. execute()는 단일 결과 전용의 안정 실행기가 됐고, 스키마에 @defer/@stream 디렉티브가 있으면 아예 거부합니다. incremental delivery를 쓰려면 이름부터 실험임을 못박은 별도 함수를 불러야 합니다.

import { experimentalExecuteIncrementally } from 'graphql';

const result = await experimentalExecuteIncrementally({ schema, document });

if ('initialResult' in result) {
  send(result.initialResult);
  for await (const subsequent of result.subsequentResults) {
    send(subsequent);
  }
} else {
  send(result); // 점진 전송이 필요 없던 경우
}

알파 시절 defer/stream을 프로덕션에 깐 팀에게 중요한 변경: 페이로드 형식이 바뀌었습니다. 초기 알파의 형식은 pathlabel로 페이로드를 식별하고 필드 데이터가 중복될 수 있었는데, 현재 형식은 pending 항목을 id로 등록하고 completed로 완료를 알리는 방식입니다. 옛 형식이 필요한 호스트를 위해 legacyExecuteIncrementally()가 남아 있지만, 이름이 말하듯 이행 기간용입니다. 스펙 외 기능을 미리 쓰면 이런 청구서가 날아온다는 실례입니다.

에러 전파 쪽에도 실험 기능이 하나 들어왔습니다. @experimental_disableErrorPropagation을 오퍼레이션에 붙이면 non-null 필드의 실행 에러가 부모를 연쇄적으로 null로 만드는 대신 해당 위치만 null이 됩니다 — 정상화 캐시를 쓰는 클라이언트에서 조상 null이 실데이터 갱신처럼 보이는 문제를 겨냥한 것으로, 진행 중인 에러 동작(onError) 스펙 논의의 선행 구현입니다. 이 역시 이름에 experimental이 박혀 있습니다.

운영자에게 유용한 것 — AbortSignal, diagnostics_channel, Harness

깨지는 것 말고 얻는 것도 실질적입니다.

  • AbortSignal 지원이 1급 기능으로 들어왔습니다. 요청 타임아웃, 클라이언트 연결 끊김, 다운스트림 API 취소를 하나의 취소 경로로 통합할 수 있고, 리졸버는 info.getAbortSignal()로 접근합니다. 부분 데이터를 만든 뒤 중단되면 AbortedGraphQLExecutionError가 중단 원인과 부분 결과를 함께 노출합니다.
  • Node.js diagnostics_channel로 파싱·검증·실행·필드 리졸브 등 라이프사이클 이벤트가 발행됩니다. APM 계측이 몽키패치 없이 가능해집니다.
  • GraphQLHarness로 parse/validate/execute/subscribe 단계를 교체할 수 있는 공식 형태가 생겼습니다. 발표문 스스로 Envelop이 증명한 단계 모델을 따랐다고 밝힙니다 — 커뮤니티 패턴이 참조 구현으로 역수입된 사례입니다.
  • 프레임워크 저자용으로 실행 파이프라인의 하위 경계(validateExecutionArgs(), executeRootSelectionSet() 등)가 공개되어, 내부 코드를 복사하지 않고 커스텀 실행기를 만들 수 있습니다.

즉 v17의 실속은 defer/stream이 아니라 서버 운영 표면 — 취소, 계측, 확장 지점 — 에 있습니다.

페더레이션 표준화의 현주소 — Composite Schemas는 Stage 0

"연합 GraphQL의 표준화"를 기다리는 분들을 위한 상태 점검입니다. 이전 글에서 다룬 Apollo Federation은 어디까지나 한 벤더의 스펙이고, GraphQL Foundation은 2024년 5월부터 Composite Schemas 워킹그룹에서 벤더 중립 표준을 만들고 있습니다.

2026년 7월 현재의 정직한 상태: 스펙 저장소의 README는 여전히 Stage 0(Preliminary) — Draft 단계 전이며 내용이 바뀔 수 있다고 명시합니다. GitHub 릴리스는 0건입니다. 다만 죽은 프로젝트는 전혀 아닙니다 — 2026년 7월 9일에도 @key·@require 규칙을 다듬는 커밋이 여럿 병합됐고, 2026년 5월 GraphQLConf의 키노트에서 The Guild의 Uri Goldshtein은 페더레이션이 더 이상 프런티어 실험이 아니라 업계가 자신 있게 걷는 잘 닦인 길이 됐다고 진단했습니다. 실무 관점의 결론: 연합 아키텍처를 지금 설계한다면 여전히 벤더 스펙(Apollo Federation v2 계열)을 고르는 것이고, Composite Schemas는 "언젠가 갈아탈 수 있는 방향성"으로만 추적하면 됩니다.

거버넌스 쪽 변화도 하나 기록해 둘 만합니다. 2026년 6월 발표된 GAPs(GraphQL Auxiliary Proposals)는 코어 스펙이 의도적으로 다루지 않는 관례 — 페이지네이션 커넥션, @cost 같은 것들 — 를 커뮤니티 스펙으로 모으는 공식 저장소입니다. 코어 스펙은 좁게, 주변 관례는 GAPs로, 분산 실행은 Composite Schemas로 — 표준화가 세 층으로 분화하는 그림입니다. 참고로 같은 시기 Meta는 GraphQLConf 2026 키노트("The Creator's Curse")에서 10년 전 "배우고 쓰기 쉽다"던 약속이 수천 엔지니어 규모에서 전부 지켜지진 않았다고 자성하며 내부 재설계를 이야기했고, Apollo CEO Matt DeBergalis는 "GraphQL can and must be the language of AI"라고 선언했습니다. 표준은 정비 모드, 벤더는 AI 서사 — 온도차가 있는 시점입니다.

그래서 지금 무엇을 해야 하나

판단 기준으로 정리합니다.

v17로 올라갈 이유가 분명한 경우

  • 서버 프레임워크·게이트웨이·계측 도구를 만드는 입장 — 실행 경계 API, Harness, diagnostics_channel이 목적 그 자체입니다.
  • 요청 취소가 실제 문제인 서비스 — AbortSignal 통합은 v16에서 깔끔한 대안이 없습니다.
  • 신규 프로젝트 — 이제 v17이 latest이고, 문서·API 레퍼런스도 v17 기준으로 재편됐습니다.

서두르지 않아도 되는 경우

  • Node 22 미만에 묶인 환경. 엔진 요구사항이 하드 게이트입니다.
  • deep import나 내부 API에 의존하는 커스텀 코드가 많은 코드베이스 — exports 강제와 실행기 분리로 손볼 곳이 늘어납니다.
  • 16.x는 버려진 브랜치가 아닙니다 — 2026년 6월에도 16.14.2 패치가 나왔습니다. 당장 기능이 아쉽지 않다면 주변 라이브러리들의 peerDependencies가 17을 받는지 확인하고 움직여도 늦지 않습니다.

defer/stream에 대해서는 더 보수적으로

  • 신규 도입이라면: 스펙이 아직 RFC 2이고, v17에서도 함수 이름에 experimental이 박혀 있습니다. 페이로드 형식이 알파 시절 한 번 갈아엎어졌다는 사실이 최고의 경고입니다. 게이트웨이·클라이언트·캐시까지 전 구간이 같은 형식을 지원하는지 확인 전에는 켜지 마십시오.
  • 이미 알파 형식으로 운영 중이라면: legacyExecuteIncrementally()로 시간을 벌 수 있지만, 새 pending/completed 형식으로의 이행 계획을 지금 세우는 편이 좋습니다.

덧붙여, 스키마 설계 관점에서 당장 공짜로 얻을 수 있는 것은 @oneOf입니다. "여러 키 중 하나로 조회" 패턴의 입력 타입을 갖고 있다면, 리졸버 검증 코드를 스키마 선언으로 옮기는 리팩터링은 위험이 낮고 클라이언트 타입 생성 품질이 바로 좋아집니다. 물론 이것도 서버·클라이언트 라이브러리가 September 2025 에디션을 구현했는지 확인이 먼저입니다 — 스펙 릴리스와 생태계 구현 사이의 시차는 늘 있습니다.

마치며

정리하면 이렇습니다. GraphQL 표준은 거의 4년의 침묵을 깨고 September 2025 에디션으로 돌아왔고, 내용물은 화려한 신기능보다 정합성 청산 + OneOf + Schema Coordinates라는 실속형입니다. 참조 구현 GraphQL.js 17은 4년 알파 끝에 GA로 나오면서 안정 실행과 실험 기능(defer/stream, 에러 전파 제어)의 경계를 코드 수준에서 그었습니다. 한편 defer/stream은 10년째 표준 밖이고, 페더레이션 표준(Composite Schemas)은 Stage 0입니다.

이 그림을 냉소로 읽을 수도 있습니다 — "4년 걸려 이거냐". 하지만 반대로 읽는 편이 실무에는 유용합니다. 이 프로젝트는 깨지는 변경을 스펙에 넣는 대신 10년을 기다리는 쪽을 고르는 프로젝트이고, 그래서 October 2021 스펙으로 짠 서버가 지금도 유효합니다. 안정성이 기본값이고 실험은 이름표를 달고 온다 — API 계층의 기반 기술에게 바랄 만한 성질입니다. 비슷한 시기 Protobuf가 Edition 체계로 기본값을 조이는 방식과 비교해 보면, 두 진영 모두 "혁신보다 이행 비용 관리"로 수렴하는 중이라는 점이 보입니다. 표준의 속도에 실망할 시간에, @oneOf 리팩터링과 v17 업그레이드 견적부터 내 보시길.

참고 자료