InfoGrab DocsInfoGrab Docs

GraphQL 페이지네이션

요약

GitLab은 두 가지 주요 페이지네이션 방식을 사용합니다: 오프셋(offset) 페이지네이션과 키셋(keyset)(커서 기반이라고도 함) 페이지네이션입니다. 자세한 내용은 일반 페이지네이션 가이드라인 섹션을 참고하세요.

페이지네이션 유형#

GitLab은 두 가지 주요 페이지네이션 방식을 사용합니다: 오프셋(offset) 페이지네이션과 키셋(keyset)(커서 기반이라고도 함) 페이지네이션입니다. GraphQL API는 주로 키셋 페이지네이션을 사용하며, 필요한 경우 오프셋 페이지네이션으로 대체합니다.

성능 고려사항#

자세한 내용은 일반 페이지네이션 가이드라인 섹션을 참고하세요.

오프셋 페이지네이션#

이것은 가장 일반적인 전통적인 페이지 단위 페이지네이션으로, GitLab 전반에 걸쳐 널리 사용됩니다. 페이지 하단 근처에 있는 페이지 번호 목록을 통해 확인할 수 있으며, 선택하면 해당 결과 페이지로 이동합니다.

예를 들어 Page 100을 선택하면 백엔드에 100을 전송합니다. 각 페이지에 20개의 항목이 있다고 가정하면, 백엔드는 20 * 100 = 2000을 계산하고, 처음 2000개의 레코드를 오프셋(건너뛰기)한 후 다음 20개를 가져오도록 데이터베이스를 조회합니다.

page number * page size = where to find my records

이 방식에는 몇 가지 문제점이 있습니다:

  • 성능 문제. 페이지 100을 조회하면(오프셋 2000) 데이터베이스가 테이블을 해당 특정 오프셋까지 스캔한 후 다음 20개의 레코드를 가져와야 합니다. 오프셋이 증가할수록 성능이 급격히 저하됩니다. 자세한 내용은 The SQL I Love <3. Efficient pagination of a table with 100M records를 참고하세요.

  • 데이터 안정성 문제. 페이지 100의 20개 항목(오프셋 2000)을 가져오면 GitLab은 해당 20개의 항목을 표시합니다. 그런데 페이지 99 이전에서 레코드가 삭제되거나 추가되면, 오프셋 2000의 항목들이 다른 항목들로 바뀝니다. 심지어 페이지네이션 도중 목록이 계속 변경되어 일부 항목을 건너뛰는 상황이 발생할 수도 있습니다. 자세한 내용은 Pagination: You're (Probably) Doing It Wrong을 참고하세요.

키셋 페이지네이션#

특정 레코드가 있을 때, 그 다음에 오는 것을 계산하는 방법을 알면 데이터베이스에서 해당 특정 레코드들을 조회할 수 있습니다.

예를 들어, 생성 날짜순으로 정렬된 이슈 목록이 있다고 가정합니다. 페이지의 첫 번째 항목이 특정 날짜(예: 1월 1일)를 가지고 있다는 것을 안다면, 해당 날짜 이후에 생성된 모든 레코드를 요청하여 처음 20개를 가져올 수 있습니다. 많은 레코드가 삭제되거나 추가되더라도, 항상 그 날짜 이후의 것들을 요청하므로 올바른 항목을 가져올 수 있습니다.

아쉽게도, 1월 1일에 생성된 이슈가 20페이지인지 100페이지인지 쉽게 알 수 있는 방법은 없습니다.

키셋 페이지네이션의 장단점은 다음과 같습니다.

  • 성능이 훨씬 뛰어납니다.

  • 삭제나 삽입으로 인해 목록에서 레코드가 누락되지 않으므로, 최종 사용자에게 더 높은 데이터 안정성을 제공합니다.

  • 무한 스크롤을 구현하는 데 가장 적합한 방식입니다.

  • 프로그래밍과 유지보수가 더 어렵습니다. updated_atsort_order의 경우 쉽지만, 복잡한 정렬 시나리오의 경우 복잡하거나 불가능합니다.

구현#

쿼리에서 페이지네이션이 지원되는 경우, GitLab은 기본적으로 키셋 페이지네이션을 사용합니다. 이 설정은 pagination/connections.rb에서 확인할 수 있습니다. 쿼리가 ActiveRecord::Relation을 반환하면 키셋 페이지네이션이 자동으로 사용됩니다.

이는 성능과 데이터 안정성을 지원하기 위한 의도적인 결정이었습니다.

하지만 정렬의 복잡성으로 인해 이슈에서 라벨 우선순위로 정렬하는 경우처럼, 오프셋 페이지네이션 연결인 OffsetActiveRecordRelationConnection을 사용해야 하는 경우도 있습니다.

리졸버에서 키셋 페이지네이션에 적합하지 않은(예: 정렬 순서 때문에) relation을 반환하는 경우, BaseResolver#offset_pagination 메서드를 사용하여 relation을 올바른 연결 유형으로 감쌀 수 있습니다. 예:

def resolve(**args)
  result = Finder.new(object, current_user, args).execute
  result = offset_pagination(result) if needs_offset?(args[:sort])

  result
end

키셋 페이지네이션#

키셋 페이지네이션 구현은 graphql gem의 일부인 GraphQL::Pagination::ActiveRecordRelationConnection의 서브클래스입니다. 이는 모든 ActiveRecord::Relation의 기본값으로 설치됩니다. 단, 오프셋 기반의 커서(기본값)를 사용하는 대신, GitLab은 더 전문화된 커서를 사용합니다.

커서는 관련 정렬 필드를 포함하는 JSON 객체를 인코딩하여 생성됩니다. 예:

ordering = {"id"=>"72410125", "created_at"=>"2020-10-08 18:05:21.953398000 UTC"}
json = ordering.to_json
cursor = Base64.urlsafe_encode64(json, padding: false)

"eyJpZCI6IjcyNDEwMTI1IiwiY3JlYXRlZF9hdCI6IjIwMjAtMTAtMDggMTg6MDU6MjEuOTUzMzk4MDAwIFVUQyJ9"

json = Base64.urlsafe_decode64(cursor)
Gitlab::Json.parse(json)

{"id"=>"72410125", "created_at"=>"2020-10-08 18:05:21.953398000 UTC"}

커서에 정렬 속성 값을 저장하는 이점:

  • 객체의 ID만 저장된 경우, 객체와 해당 속성을 조회해야 합니다. 이는 추가적인 쿼리가 필요하고, 객체가 더 이상 존재하지 않으면 필요한 속성을 사용할 수 없습니다.

  • 속성이 NULL인 경우 하나의 SQL 쿼리를 사용할 수 있습니다. NULL이 아닌 경우 다른 SQL 쿼리를 사용할 수 있습니다.

커서에서 정렬 중인 주요 속성 필드가 NULL인지 여부에 따라 올바른 쿼리 조건이 구성됩니다. 마지막 정렬 필드는 고유(기본 키)한 것으로 간주되며, 해당 칼럼에는 NULL 값이 포함되지 않습니다.

쿼리 복잡성#

두 개의 정렬 필드만 지원하며, 그 중 하나는 기본 키여야 합니다.

다음은 쿼리에 대한 두 가지 의사 코드 예시입니다:

두 가지 조건 쿼리. X는 커서의 값을 나타냅니다. C는 데이터베이스의 칼럼을 나타내며, 오름차순으로 정렬되고, :after 커서를 사용하며, NULL 값이 마지막에 정렬됩니다.

X1 IS NOT NULL
  AND
    (C1 > X1)
      OR
    (C1 IS NULL)
      OR
    (C1 = X1
      AND
     C2 > X2)

X1 IS NULL
  AND
    (C1 IS NULL
      AND
     C2 > X2)

아래는 after 커서가 relative_position: 1500, id: 500Issue.order(relative_position: :asc).order(id: :asc) relation을 기반으로 한 예시입니다:

when cursor[relative_position] is not NULL

    ("issues"."relative_position" > 1500)
    OR (
      "issues"."relative_position" = 1500
      AND
      "issues"."id" > 500
    )
    OR ("issues"."relative_position" IS NULL)

when cursor[relative_position] is NULL

    "issues"."relative_position" IS NULL
    AND
    "issues"."id" > 500

세 가지 조건 쿼리. 아래 예시는 완전하지 않지만, 조건을 하나 더 추가할 때의 복잡성을 보여줍니다. X는 커서의 값을 나타냅니다. C는 데이터베이스의 칼럼을 나타내며, 오름차순으로 정렬되고, :after 커서를 사용하며, NULL 값이 마지막에 정렬됩니다.

X1 IS NOT NULL
  AND
    (C1 > X1)
      OR
    (C1 IS NULL)
      OR
    (C1 = X1 AND C2 > X2)
      OR
    (C1 = X1
      AND
        X2 IS NOT NULL
          AND
            ((C2 > X2)
               OR
             (C2 IS NULL)
               OR
             (C2 = X2 AND C3 > X3)
      OR
        X2 IS NULL.....

Gitlab::Graphql::Pagination::Keyset::QueryBuilder를 사용하면 필요한 SQL 조건을 구성하여 Active Record relation에 적용할 수 있습니다.

복잡한 쿼리는 사용하기 어렵거나 불가능할 수 있습니다. 예를 들어, issuable.rborder_due_date_and_labels_priority 메서드는 매우 복잡한 쿼리를 생성합니다.

이런 유형의 쿼리는 지원되지 않습니다. 이러한 경우에는 오프셋 페이지네이션을 사용할 수 있습니다.

주의사항#

컬렉션의 순서를 문자열 구문으로 정의하지 마세요:

# Bad
items.order('created_at DESC')

대신 해시 구문을 사용하세요:

# Good
items.order(created_at: :desc)

첫 번째 예시는 정렬 정보(created_at)를 페이지네이션 커서에 올바르게 포함하지 않아, 잘못된 정렬 순서가 발생합니다.

오프셋 페이지네이션#

정렬의 복잡성이 키셋 페이지네이션이 처리할 수 있는 수준을 초과하는 경우가 있습니다.

예를 들어, ProjectIssuesResolver에서 priority_asc로 정렬할 때, 정렬이 너무 복잡하여 키셋 페이지네이션을 사용할 수 없습니다. 자세한 내용은 issuable.rb를 참고하세요.

이런 경우에는 ActiveRecord::Relation 대신 Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection을 반환하여 일반 오프셋 페이지네이션으로 대체할 수 있습니다:

    def resolve(parent, finder, **args)
      issues = apply_lookahead(Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all)

      if non_stable_cursor_sort?(args[:sort])
        # Certain complex sorts are not supported by the stable cursor pagination yet.
        # In these cases, we use offset pagination, so we return the correct connection.
        offset_pagination(issues)
      else
        issues
      end
    end

외부 페이지네이션#

다른 시스템에 저장된 데이터를 GitLab API를 통해 반환해야 하는 경우가 있을 수 있습니다. 이런 경우 서드파티 API의 페이지네이션을 처리해야 할 수 있습니다.

이에 대한 예시로 Error Tracking 구현이 있으며, 여기서는 Sentry 오류를 GitLab API를 통해 프록시합니다. Sentry API를 호출하여 처리하는데, Sentry API는 자체 페이지네이션 규칙을 적용합니다. 즉, 사용자 정의 페이지네이션을 수행하기 위해 GitLab 내의 컬렉션에 직접 접근할 수 없습니다.

일관성을 위해, Gitlab::Graphql::ExternallyPaginatedArray.new(previous_cursor, next_cursor, *items)를 사용하여 외부 API가 반환한 값을 기반으로 페이지네이션 커서를 수동으로 설정합니다.

다음 파일에서 구현 예시를 확인할 수 있습니다:

테스트#

페이지네이션과 정렬을 지원하는 GraphQL 필드는 graphql/sorted_paginated_query_shared_examples.rb에 있는 정렬된 페이지네이션 쿼리 공유 예시(shared example)를 사용하여 테스트해야 합니다. 이는 정렬 키가 호환되는지, 커서가 올바르게 동작하는지 검증하는 데 도움이 됩니다.

이는 특히 키셋 페이지네이션을 사용할 때 중요하며, 일부 정렬 키는 지원되지 않을 수 있습니다.

요청 스펙에 다음과 같이 섹션을 추가하세요:

describe 'sorting and pagination' do
  ...
end

그런 다음 issues_spec.rb를 예시로 사용하여 테스트를 구성할 수 있습니다.

graphql/sorted_paginated_query_shared_examples.rb에는 공유 예시 사용 방법에 대한 일부 문서도 포함되어 있습니다.

공유 예시에는 특정 let 변수와 메서드가 설정되어야 합니다:

describe 'sorting and pagination' do
  let_it_be(:sort_project) { create(:project, :public) }
  let(:data_path)    { [:project, :issues] }

  def pagination_query(params)
    graphql_query_for( :project, { full_path: sort_project.full_path },
      query_nodes(:issues, :id, include_pagination_info: true, args: params))
    )
  end

  def pagination_results_data(nodes)
    nodes.map { |issue| issue['iid'].to_i }
  end

  context 'when sorting by weight' do
    let_it_be(:issues) { make_some_issues_with_weights }

    context 'when ascending' do
      let(:ordered_issues) { issues.sort_by(&:weight) }

      it_behaves_like 'sorted paginated query' do
        let(:sort_param) 
        let(:first_param) { 2 }
        let(:all_records) { ordered_issues.map(&:iid) }
      end
    end
  end

GraphQL 페이지네이션

GitLab v19.1
원문 보기
요약

GitLab은 두 가지 주요 페이지네이션 방식을 사용합니다: 오프셋(offset) 페이지네이션과 키셋(keyset)(커서 기반이라고도 함) 페이지네이션입니다. 자세한 내용은 일반 페이지네이션 가이드라인 섹션을 참고하세요.

페이지네이션 유형#

GitLab은 두 가지 주요 페이지네이션 방식을 사용합니다: 오프셋(offset) 페이지네이션과 키셋(keyset)(커서 기반이라고도 함) 페이지네이션입니다. GraphQL API는 주로 키셋 페이지네이션을 사용하며, 필요한 경우 오프셋 페이지네이션으로 대체합니다.

성능 고려사항#

자세한 내용은 일반 페이지네이션 가이드라인 섹션을 참고하세요.

오프셋 페이지네이션#

이것은 가장 일반적인 전통적인 페이지 단위 페이지네이션으로, GitLab 전반에 걸쳐 널리 사용됩니다. 페이지 하단 근처에 있는 페이지 번호 목록을 통해 확인할 수 있으며, 선택하면 해당 결과 페이지로 이동합니다.

예를 들어 Page 100을 선택하면 백엔드에 100을 전송합니다. 각 페이지에 20개의 항목이 있다고 가정하면, 백엔드는 20 * 100 = 2000을 계산하고, 처음 2000개의 레코드를 오프셋(건너뛰기)한 후 다음 20개를 가져오도록 데이터베이스를 조회합니다.

page number * page size = where to find my records

이 방식에는 몇 가지 문제점이 있습니다:

  • 성능 문제. 페이지 100을 조회하면(오프셋 2000) 데이터베이스가 테이블을 해당 특정 오프셋까지 스캔한 후 다음 20개의 레코드를 가져와야 합니다. 오프셋이 증가할수록 성능이 급격히 저하됩니다. 자세한 내용은 The SQL I Love <3. Efficient pagination of a table with 100M records를 참고하세요.

  • 데이터 안정성 문제. 페이지 100의 20개 항목(오프셋 2000)을 가져오면 GitLab은 해당 20개의 항목을 표시합니다. 그런데 페이지 99 이전에서 레코드가 삭제되거나 추가되면, 오프셋 2000의 항목들이 다른 항목들로 바뀝니다. 심지어 페이지네이션 도중 목록이 계속 변경되어 일부 항목을 건너뛰는 상황이 발생할 수도 있습니다. 자세한 내용은 Pagination: You're (Probably) Doing It Wrong을 참고하세요.

키셋 페이지네이션#

특정 레코드가 있을 때, 그 다음에 오는 것을 계산하는 방법을 알면 데이터베이스에서 해당 특정 레코드들을 조회할 수 있습니다.

예를 들어, 생성 날짜순으로 정렬된 이슈 목록이 있다고 가정합니다. 페이지의 첫 번째 항목이 특정 날짜(예: 1월 1일)를 가지고 있다는 것을 안다면, 해당 날짜 이후에 생성된 모든 레코드를 요청하여 처음 20개를 가져올 수 있습니다. 많은 레코드가 삭제되거나 추가되더라도, 항상 그 날짜 이후의 것들을 요청하므로 올바른 항목을 가져올 수 있습니다.

아쉽게도, 1월 1일에 생성된 이슈가 20페이지인지 100페이지인지 쉽게 알 수 있는 방법은 없습니다.

키셋 페이지네이션의 장단점은 다음과 같습니다.

  • 성능이 훨씬 뛰어납니다.

  • 삭제나 삽입으로 인해 목록에서 레코드가 누락되지 않으므로, 최종 사용자에게 더 높은 데이터 안정성을 제공합니다.

  • 무한 스크롤을 구현하는 데 가장 적합한 방식입니다.

  • 프로그래밍과 유지보수가 더 어렵습니다. updated_atsort_order의 경우 쉽지만, 복잡한 정렬 시나리오의 경우 복잡하거나 불가능합니다.

구현#

쿼리에서 페이지네이션이 지원되는 경우, GitLab은 기본적으로 키셋 페이지네이션을 사용합니다. 이 설정은 pagination/connections.rb에서 확인할 수 있습니다. 쿼리가 ActiveRecord::Relation을 반환하면 키셋 페이지네이션이 자동으로 사용됩니다.

이는 성능과 데이터 안정성을 지원하기 위한 의도적인 결정이었습니다.

하지만 정렬의 복잡성으로 인해 이슈에서 라벨 우선순위로 정렬하는 경우처럼, 오프셋 페이지네이션 연결인 OffsetActiveRecordRelationConnection을 사용해야 하는 경우도 있습니다.

리졸버에서 키셋 페이지네이션에 적합하지 않은(예: 정렬 순서 때문에) relation을 반환하는 경우, BaseResolver#offset_pagination 메서드를 사용하여 relation을 올바른 연결 유형으로 감쌀 수 있습니다. 예:

def resolve(**args)
  result = Finder.new(object, current_user, args).execute
  result = offset_pagination(result) if needs_offset?(args[:sort])

  result
end

키셋 페이지네이션#

키셋 페이지네이션 구현은 graphql gem의 일부인 GraphQL::Pagination::ActiveRecordRelationConnection의 서브클래스입니다. 이는 모든 ActiveRecord::Relation의 기본값으로 설치됩니다. 단, 오프셋 기반의 커서(기본값)를 사용하는 대신, GitLab은 더 전문화된 커서를 사용합니다.

커서는 관련 정렬 필드를 포함하는 JSON 객체를 인코딩하여 생성됩니다. 예:

ordering = {"id"=>"72410125", "created_at"=>"2020-10-08 18:05:21.953398000 UTC"}
json = ordering.to_json
cursor = Base64.urlsafe_encode64(json, padding: false)

"eyJpZCI6IjcyNDEwMTI1IiwiY3JlYXRlZF9hdCI6IjIwMjAtMTAtMDggMTg6MDU6MjEuOTUzMzk4MDAwIFVUQyJ9"

json = Base64.urlsafe_decode64(cursor)
Gitlab::Json.parse(json)

{"id"=>"72410125", "created_at"=>"2020-10-08 18:05:21.953398000 UTC"}

커서에 정렬 속성 값을 저장하는 이점:

  • 객체의 ID만 저장된 경우, 객체와 해당 속성을 조회해야 합니다. 이는 추가적인 쿼리가 필요하고, 객체가 더 이상 존재하지 않으면 필요한 속성을 사용할 수 없습니다.

  • 속성이 NULL인 경우 하나의 SQL 쿼리를 사용할 수 있습니다. NULL이 아닌 경우 다른 SQL 쿼리를 사용할 수 있습니다.

커서에서 정렬 중인 주요 속성 필드가 NULL인지 여부에 따라 올바른 쿼리 조건이 구성됩니다. 마지막 정렬 필드는 고유(기본 키)한 것으로 간주되며, 해당 칼럼에는 NULL 값이 포함되지 않습니다.

쿼리 복잡성#

두 개의 정렬 필드만 지원하며, 그 중 하나는 기본 키여야 합니다.

다음은 쿼리에 대한 두 가지 의사 코드 예시입니다:

두 가지 조건 쿼리. X는 커서의 값을 나타냅니다. C는 데이터베이스의 칼럼을 나타내며, 오름차순으로 정렬되고, :after 커서를 사용하며, NULL 값이 마지막에 정렬됩니다.

X1 IS NOT NULL
  AND
    (C1 > X1)
      OR
    (C1 IS NULL)
      OR
    (C1 = X1
      AND
     C2 > X2)

X1 IS NULL
  AND
    (C1 IS NULL
      AND
     C2 > X2)

아래는 after 커서가 relative_position: 1500, id: 500Issue.order(relative_position: :asc).order(id: :asc) relation을 기반으로 한 예시입니다:

when cursor[relative_position] is not NULL

    ("issues"."relative_position" > 1500)
    OR (
      "issues"."relative_position" = 1500
      AND
      "issues"."id" > 500
    )
    OR ("issues"."relative_position" IS NULL)

when cursor[relative_position] is NULL

    "issues"."relative_position" IS NULL
    AND
    "issues"."id" > 500

세 가지 조건 쿼리. 아래 예시는 완전하지 않지만, 조건을 하나 더 추가할 때의 복잡성을 보여줍니다. X는 커서의 값을 나타냅니다. C는 데이터베이스의 칼럼을 나타내며, 오름차순으로 정렬되고, :after 커서를 사용하며, NULL 값이 마지막에 정렬됩니다.

X1 IS NOT NULL
  AND
    (C1 > X1)
      OR
    (C1 IS NULL)
      OR
    (C1 = X1 AND C2 > X2)
      OR
    (C1 = X1
      AND
        X2 IS NOT NULL
          AND
            ((C2 > X2)
               OR
             (C2 IS NULL)
               OR
             (C2 = X2 AND C3 > X3)
      OR
        X2 IS NULL.....

Gitlab::Graphql::Pagination::Keyset::QueryBuilder를 사용하면 필요한 SQL 조건을 구성하여 Active Record relation에 적용할 수 있습니다.

복잡한 쿼리는 사용하기 어렵거나 불가능할 수 있습니다. 예를 들어, issuable.rborder_due_date_and_labels_priority 메서드는 매우 복잡한 쿼리를 생성합니다.

이런 유형의 쿼리는 지원되지 않습니다. 이러한 경우에는 오프셋 페이지네이션을 사용할 수 있습니다.

주의사항#

컬렉션의 순서를 문자열 구문으로 정의하지 마세요:

# Bad
items.order('created_at DESC')

대신 해시 구문을 사용하세요:

# Good
items.order(created_at: :desc)

첫 번째 예시는 정렬 정보(created_at)를 페이지네이션 커서에 올바르게 포함하지 않아, 잘못된 정렬 순서가 발생합니다.

오프셋 페이지네이션#

정렬의 복잡성이 키셋 페이지네이션이 처리할 수 있는 수준을 초과하는 경우가 있습니다.

예를 들어, ProjectIssuesResolver에서 priority_asc로 정렬할 때, 정렬이 너무 복잡하여 키셋 페이지네이션을 사용할 수 없습니다. 자세한 내용은 issuable.rb를 참고하세요.

이런 경우에는 ActiveRecord::Relation 대신 Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection을 반환하여 일반 오프셋 페이지네이션으로 대체할 수 있습니다:

    def resolve(parent, finder, **args)
      issues = apply_lookahead(Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all)

      if non_stable_cursor_sort?(args[:sort])
        # Certain complex sorts are not supported by the stable cursor pagination yet.
        # In these cases, we use offset pagination, so we return the correct connection.
        offset_pagination(issues)
      else
        issues
      end
    end

외부 페이지네이션#

다른 시스템에 저장된 데이터를 GitLab API를 통해 반환해야 하는 경우가 있을 수 있습니다. 이런 경우 서드파티 API의 페이지네이션을 처리해야 할 수 있습니다.

이에 대한 예시로 Error Tracking 구현이 있으며, 여기서는 Sentry 오류를 GitLab API를 통해 프록시합니다. Sentry API를 호출하여 처리하는데, Sentry API는 자체 페이지네이션 규칙을 적용합니다. 즉, 사용자 정의 페이지네이션을 수행하기 위해 GitLab 내의 컬렉션에 직접 접근할 수 없습니다.

일관성을 위해, Gitlab::Graphql::ExternallyPaginatedArray.new(previous_cursor, next_cursor, *items)를 사용하여 외부 API가 반환한 값을 기반으로 페이지네이션 커서를 수동으로 설정합니다.

다음 파일에서 구현 예시를 확인할 수 있습니다:

테스트#

페이지네이션과 정렬을 지원하는 GraphQL 필드는 graphql/sorted_paginated_query_shared_examples.rb에 있는 정렬된 페이지네이션 쿼리 공유 예시(shared example)를 사용하여 테스트해야 합니다. 이는 정렬 키가 호환되는지, 커서가 올바르게 동작하는지 검증하는 데 도움이 됩니다.

이는 특히 키셋 페이지네이션을 사용할 때 중요하며, 일부 정렬 키는 지원되지 않을 수 있습니다.

요청 스펙에 다음과 같이 섹션을 추가하세요:

describe 'sorting and pagination' do
  ...
end

그런 다음 issues_spec.rb를 예시로 사용하여 테스트를 구성할 수 있습니다.

graphql/sorted_paginated_query_shared_examples.rb에는 공유 예시 사용 방법에 대한 일부 문서도 포함되어 있습니다.

공유 예시에는 특정 let 변수와 메서드가 설정되어야 합니다:

describe 'sorting and pagination' do
  let_it_be(:sort_project) { create(:project, :public) }
  let(:data_path)    { [:project, :issues] }

  def pagination_query(params)
    graphql_query_for( :project, { full_path: sort_project.full_path },
      query_nodes(:issues, :id, include_pagination_info: true, args: params))
    )
  end

  def pagination_results_data(nodes)
    nodes.map { |issue| issue['iid'].to_i }
  end

  context 'when sorting by weight' do
    let_it_be(:issues) { make_some_issues_with_weights }

    context 'when ascending' do
      let(:ordered_issues) { issues.sort_by(&:weight) }

      it_behaves_like 'sorted paginated query' do
        let(:sort_param) 
        let(:first_param) { 2 }
        let(:all_records) { ordered_issues.map(&:iid) }
      end
    end
  end