GraphQL API Rate Limiting을 위한 실용적인 전략
January 4, 2024
REST API에 속도 제한을 구현하는 것은 상대적으로 간단합니다. 일반적으로 URI 경로를 사용하여 특정 API 리소스를 나타내고, HTTP 메서드를 사용하여 리소스에 대한 작업을 나타내기 때문입니다. 프록시 계층은 이 정보를 기반으로 미리 정의된 속도 제한 규칙을 쉽게 적용할 수 있습니다.
그러나 GraphQL API로 넘어가면 상황이 훨씬 더 복잡해집니다. 이러한 문제를 어떻게 극복할 수 있는지 살펴보겠습니다.
간단한 시나리오
REST API와 달리, GraphQL은 독자적인 쿼리 언어를 사용합니다. 더 이상 경로와 HTTP 메서드를 사용하여 리소스를 검색하고 조작하지 않으며, 대신 데이터 쿼리와 작업을 Query와 Mutation으로 통합합니다. 여기서 Query는 데이터를 가져오고, Mutation은 생성, 업데이트, 삭제와 같은 데이터 조작을 수행합니다.
GET /users
GET /users/1
POST /users
PUT /users/1
DELETE /users/1
query { users { fullName } }
query { user(id: 1) { fullName } }
mutation { createUser(user: { lastName: "Jack" }) { id, fullName } }
mutation { updateUser (id: 1, update: { lastName: "Marry" }) { fullName } }
mutation { deleteUser (id: 1) }
위 예제는 API 쿼리 방법의 변화를 보여줍니다. REST와 달리, GraphQL은 리소스에 대한 함수를 호출하는 것과 유사하며, 필요한 입력 매개변수를 전달하고, 응답에는 쿼리된 데이터가 포함됩니다. 쿼리 방법의 차이 외에도, GraphQL은 일반적으로 단일 엔드포인트(예: /graphql)를 통해 API를 노출하며, 쿼리와 입력 매개변수는 POST 본문을 통해 전송됩니다.
다음 예제를 고려해보세요:
query {
users {
fullName
}
photos {
url
uploadAt
}
}
이 시나리오에서는 앨범의 홈페이지를 시뮬레이션하며, /graphql
엔드포인트에 대한 API 호출은 사용자와 사진 목록을 동시에 쿼리합니다. 이제 전통적인 리버스 프록시 전략이 GraphQL API에 여전히 적용 가능한지 고민해보세요.
답은 '아니오'입니다. 전통적인 리버스 프록시 서버는 쿼리 자체를 포함하는 GraphQL API 호출을 효과적으로 처리할 수 없어, 속도 제한과 같은 정책을 적용할 수 없습니다. GraphQL API의 경우, "HTTP 요청"의 세분화 수준이 너무 거칠어 보입니다.
그러나 API 게이트웨이인 Apache APISIX는 GraphQL to HTTP 기능을 내장하고 있습니다. 관리자는 쿼리 문을 미리 구성할 수 있으며, 클라이언트는 GraphQL 세부 사항을 이해하지 않고도 HTTP POST를 통해 직접 호출할 수 있습니다. 이는 보안을 강화할 뿐만 아니라, 이 컨텍스트에서 HTTP API 정책을 적용할 수 있게 합니다.
이것은 효과적으로 동적 GraphQL 쿼리를 API 제공자가 제공하는 지식 기반의 정적 쿼리로 변환하며, 장단점이 있습니다. 때로는 GraphQL의 동적 쿼리 기능을 포기하고 싶지 않을 수 있습니다. 다른 시나리오에 대한 논의를 계속해보겠습니다.
복잡한 시나리오
GraphQL은 데이터 모델링과 API 설명을 위해 특수한 언어를 사용하며, 중첩된 데이터 구조를 허용합니다. 이전 예제를 확장해보겠습니다:
query {
photos(first: 10) {
url
uploadAt
publisher {
fullName
avatar
}
comments(first: 10) {
content
sender {
fullName
avatar
}
}
}
// users...
}
이 경우, 처음 10개의 사진을 검색하고, 각 사진의 게시자와 처음 10개의 댓글 및 그 발신자를 포함하는 시뮬레이션을 합니다. 백엔드 서비스는 여러 데이터베이스 테이블 또는 마이크로서비스 호출을 포함하는 쿼리를 처리해야 합니다. 이러한 중첩 쿼리 시나리오에서 데이터 양과 중첩 수준이 증가함에 따라 백엔드 서비스와 데이터베이스의 계산 부담이 기하급수적으로 증가합니다.
복잡한 쿼리가 서비스를 압도하지 않도록 하기 위해, 프록시 계층에서 이러한 쿼리를 검사하고 차단하고 싶을 수 있습니다. 이 전략을 적용하려면, 프록시 구성 요소가 쿼리 문을 구조화된 데이터로 파싱하고, 각 계층의 중첩 필드를 얻기 위해 순회하며, GraphQL의 일반적인 관행에 따라 필드에 쿼리 비용으로 복잡성 값을 할당해야 합니다. 그런 다음 전체 쿼리 복잡성에 대한 전역 제한을 적용할 수 있습니다. 위 쿼리의 경우, 각 개별 필드에 대한 비용을 1로 가정하면:
10 * photo (url + uploadAt + publisher.fullName + publisher.avatar + 10 * comment (content + sender.fullName + sender.avatar))
10 * (1 + 1 + 1 + 1 + 10 * (1 + 1 + 1)) = 340
총 쿼리 비용은 340으로, 허용 가능해 보이며, 이러한 규칙을 기반으로 API 쿼리 비용 제한을 구성할 수 있습니다. 그러나 악의적인 클라이언트가 단일 쿼리에서 100개의 사진 데이터를 가져오려고 시도하면, 쿼리 비용이 3400으로 급증하여 미리 정의된 제한을 초과하고, 요청이 거부됩니다.
클라이언트 쿼리당 최대 비용을 제한하는 것 외에도, 시간 간격에 대한 추가 제한을 설정할 수 있습니다. 예를 들어, 클라이언트가 분당 총 2000개의 쿼리를 허용하고 초과 쿼리를 거부함으로써 악의적인 크롤러를 방지할 수 있습니다.
이러한 기능을 구현하려면, 프록시 구성 요소가 쿼리 비용을 파싱하고 계산할 수 있어야 합니다. API7 Enterprise는 이러한 기능을 지원하며, GraphQL 쿼리를 동적으로 파싱하고 구성에 따라 사용자 속도 제한을 구현할 수 있습니다.
GraphQL API는 프록시 계층에서 도전에 직면하며, 전통적인 리버스 프록시는 GraphQL 쿼리 문의 복잡성과 중첩 관계를 효과적으로 인식하고 처리하기 어렵습니다. 반면, API 게이트웨이 기술은 이러한 도전을 극복하는 데 매우 유용하며, API7 Enterprise는 훌륭한 선택이 될 수 있습니다.