GraphQL Connection 커스텀 구현을 통한 검색 엔진 통합 사례

カスタムしながら理解するGraphQL Connection / yana-gi - Kaigi on Rails 2024

3줄 요약

  • GraphQL에서 효율적인 페이지네이션 구현을 위해 Connection을 사용합니다.
  • 외부 API 등 표준 지원 범위를 벗어나는 데이터 소스는 Custom Connection 구현이 필요합니다.
  • 오프셋 기반 API 연동 시, 리졸버에서의 API 호출 시점과 오프셋 계산 시점의 불일치는 Promise를 활용한 지연 평가로 해결합니다.

본 발표는 핸드메이드 마켓플레이스 민네(Minne)에서 새로운 검색 엔진 도입 시 마주한 GraphQL Connection 구현 과제와 해결 방안에 대해 다룹니다. 민네는 점진적으로 프론트엔드를 Rails에서 Next.js로 전환하며, API 레이어 역시 REST에서 GraphQL로 이관하고 있습니다. 이러한 과정에서 새로운 검색 엔진을 기존 시스템에 통합하는 과정에서 GraphQL의 페이지네이션 기능인 Connection을 활용하게 되었으나, 새로운 검색 엔진의 API가 오프셋(Offset) 기반 페이지만을 지원하여 GraphQL의 커서(Cursor) 및 오프셋 페이지네이션 요구사항과 충돌하는 문제가 발생했습니다. 본 발표는 이 문제를 해결하기 위해 GraphQL Ruby gem을 활용한 Custom Connection 구현 상세 내용을 공유하고, 특히 오프셋 계산 시점과 API 호출 시점의 불일치를 해결하기 위한 지연 평가(Lazy Evaluation) 기법 적용 사례를 중심으로 설명합니다.

GraphQL은 클라이언트가 필요한 데이터만 선택적으로 가져올 수 있는 효율적인 데이터 질의 언어입니다. REST API가 여러 엔드포인트를 통해 데이터를 가져오는 것과 달리, GraphQL은 단일 엔드포인트에서 여러 종류의 데이터를 동시에 요청할 수 있습니다. GraphQL에서 대량의 데이터를 다룰 때 필수적인 페이지네이션은 Connection이라는 개념을 통해 구현됩니다. Connection은 데이터 목록과 페이지 정보(pageInfo), 각 항목의 커서(cursor) 정보를 포함하며, first, after 등의 인자를 통해 쉽게 페이지네이션을 처리할 수 있도록 지원합니다. GraphQL Ruby gem은 Array나 ActiveRecord와 같은 표준 Ruby 클래스에 대한 Connection 구현을 기본적으로 지원합니다. 그러나 민네의 새로운 검색 엔진은 REST API를 통해 데이터를 제공하며, 이 데이터는 ActiveRecord 객체가 아니므로 표준 Connection으로는 페이지네이션을 구현할 수 없었습니다. 따라서 GraphQL::Pagination::Connection 클래스를 상속받아 nodes, hasNextPage, hasPreviousPage, cursor_for 등의 필수 메서드를 직접 구현하는 Custom Connection 방식이 필요했습니다.

Custom Connection 구현 과정에서 중요한 과제에 직면했습니다. 새로운 검색 엔진 API는 오프셋 기반 페이지만을 지원하는데, GraphQL Connection은 커서 정보를 기반으로 오프셋을 계산합니다. 문제는 GraphQL 쿼리 처리 흐름상 오프셋 값은 Connection 인스턴스가 생성되고 페이지네이션 계산 로직이 실행되는 시점, 즉 리졸버(Resolver)가 데이터를 가져오는 시점보다 늦게 결정된다는 점입니다. 리졸버는 검색 엔진 API에 요청을 보내야 하지만, 이때 필요한 오프셋 값을 아직 알 수 없는 상황이 발생했습니다. 이 문제를 해결하기 위해 지연 평가 기법이 도입되었습니다. 리졸버는 즉시 검색 엔진 API를 호출하는 대신, API 호출 로직을 캡슐화한 Promise 객체를 반환합니다. 이후 Connection 인스턴스가 페이지네이션 정보를 바탕으로 오프셋과 리미트 값을 계산하면, 이 값들이 Promise 객체에 전달되어 비로소 실제 검색 엔진 API 호출이 실행되고 결과가 반환되는 방식입니다. 이러한 지연 평가를 통해 오프셋 값 결정 시점과 API 호출 시점의 의존성 문제를 해결하고, GraphQL Connection을 통한 효율적인 페이지네이션 구현이 가능해졌습니다.

하지만 Custom Connection 구현 및 지연 평가 적용 후에도 추가적인 개선 과제가 확인되었습니다. 현재 구조에서는 전체 결과 수(total count)나 다음 페이지 존재 여부(hasNextPage)를 확인하기 위해 검색 엔진 API에 중복 호출이 발생하는 상황입니다. 이는 성능 저하를 유발할 수 있으므로, 향후 검색 엔진 API 단에서 페이지네이션 메타 정보만을 제공하는 별도의 엔드포인트를 추가하는 등의 개선이 필요할 것으로 예상됩니다.

결론적으로, GraphQL에서 다양한 데이터 소스에 대한 유연하고 효율적인 페이지네이션을 구현하기 위해서는 Connection 개념의 이해가 필수적이며, 특히 외부 API 연동 등 표준 지원 범위를 넘어서는 경우에는 Custom Connection 정의를 통해 요구사항을 충족시킬 수 있습니다. 오프셋 기반 API와 연동 시 발생하는 오프셋 계산 시점 문제는 Promise를 활용한 지연 평가 패턴을 적용하여 해결할 수 있음을 확인했습니다. 이러한 경험을 통해 GraphQL Connection의 동작 원리와 Custom Connection 구현의 중요성, 그리고 복잡한 데이터 흐름 속에서 지연 평가 기법이 어떻게 문제를 해결하는지에 대한 이해를 높일 수 있었습니다. 향후 중복 API 호출 문제 개선 등을 통해 더욱 견고하고 성능 효율적인 시스템을 구축할 수 있을 것으로 기대됩니다.