InfoGrab DocsInfoGrab Docs

키셋 페이지네이션

요약

키셋 페이지네이션 라이브러리는 GitLab 프로젝트 안의 HAML 기반 뷰와 REST API에서 사용할 수 있습니다. 키셋 페이지네이션과 오프셋 기반 페이지네이션의 비교는 페이지네이션 지침 페이지에서 확인할 수 있습니다.

키셋 페이지네이션 라이브러리는 GitLab 프로젝트 안의 HAML 기반 뷰와 REST API에서 사용할 수 있습니다.

키셋 페이지네이션과 오프셋 기반 페이지네이션의 비교는 페이지네이션 지침 페이지에서 확인할 수 있습니다.

API 개요#

요약#

Rails 컨트롤러에서 ActiveRecord와 함께 사용하는 키셋 페이지네이션입니다.

cursor = params[:cursor] # this is nil when the first page is requested
paginator = Project.order(:created_at).keyset_paginate(cursor: cursor, per_page: 20)

paginator.each do |project|
  puts project.name # prints maximum 20 projects
end

사용법#

이 라이브러리는 ActiveRecord 릴레이션에 #keyset_paginate 메서드 하나를 추가합니다.

이는 Kaminari의 paginate 메서드와 취지는 비슷하지만 구현은 다릅니다.

키셋 페이지네이션은 단순한 ActiveRecord 쿼리에서는 별도 설정 없이 동작합니다.

  • 칼럼 하나로 정렬하는 경우.
  • 칼럼 두 개로 정렬하고 마지막 칼럼이 기본 키인 경우.

이 라이브러리는 null 허용 칼럼과 값이 중복될 수 있는 칼럼을 감지하고, 이를 근거로 기본 키를 사용한 정렬을 추가합니다. 키셋 페이지네이션은 정렬 값이 서로 구별되기를 요구하므로 이 과정이 필요합니다.

Project.order(:created_at).keyset_paginate.records # ORDER BY created_at, id

Project.order(:name).keyset_paginate.records # ORDER BY name, id

Project.order(:created_at, id: :desc).keyset_paginate.records # ORDER BY created_at, id

Project.order(created_at: :asc, id: :desc).keyset_paginate.records # ORDER BY created_at, id  DESC

keyset_paginate 메서드는 로드된 레코드와 여러 페이지를 요청하는 데 필요한 추가 정보를 담은 특수 paginator 객체를 반환합니다.

이 메서드는 다음 키워드 인수를 받습니다.

  • cursor - 다음 페이지를 요청하기 위해 인코딩된 정렬 기준 칼럼 값(nil 일 수 있습니다).
  • per_page - 페이지당 로드할 레코드 수(기본값 20).
  • keyset_order_options - 키셋 페이지네이션 데이터베이스 쿼리를 만들 때 쓰는 추가 옵션이며, UNION 쿼리 예시는 성능 섹션을 참고합니다(선택 사항).

paginator 객체에는 다음 메서드가 있습니다.

  • records - 현재 페이지의 레코드를 반환합니다.
  • has_next_page? - 다음 페이지가 있는지 알려 줍니다.
  • has_previous_page? - 이전 페이지가 있는지 알려 줍니다.
  • cursor_for_next_page - 다음 페이지를 요청하기 위해 인코딩된 String 값입니다(nil 일 수 있습니다).
  • cursor_for_previous_page - 이전 페이지를 요청하기 위해 인코딩된 String 값입니다(nil 일 수 있습니다).
  • cursor_for_first_page - 첫 페이지를 요청하기 위해 인코딩된 String 값입니다.
  • cursor_for_last_page - 마지막 페이지를 요청하기 위해 인코딩된 String 값입니다.
  • paginator 객체는 Enumerable 모듈을 포함하며 enumerable 기능을 records 메서드와 배열에 위임합니다.

첫 페이지와 두 번째 페이지를 가져오는 예시입니다.

paginator = Project.order(:name).keyset_paginate

paginator.to_a # same as .records

cursor = paginator.cursor_for_next_page # encoded column attributes for the next page

paginator = Project.order(:name).keyset_paginate(cursor: cursor).records # loading the next page

키셋 페이지네이션은 페이지 번호를 지원하지 않으므로 이동할 수 있는 페이지는 다음으로 제한됩니다.

  • 다음 페이지
  • 이전 페이지
  • 마지막 페이지
  • 첫 페이지

paginate_with_strategies를 사용한 REST API 에서의 사용법#

REST API에서는 릴레이션에 paginate_with_strategies 헬퍼를 사용해 키셋 페이지네이션이나 오프셋 페이지네이션을 쓸 수 있습니다.

  desc 'Get the things related to a project' do
    detail 'This feature was introduced in GitLab 16.1'
    success code: 200, model: ::API::Entities::Thing
    failure [
      { code: 401, message: 'Unauthorized' },
      { code: 403, message: 'Forbidden' },
      { code: 404, message: 'Not Found' }
    ]
  end
  params do
    use :pagination
    requires :project_id, type: Integer, desc: 'The ID of the project'
    optional :cursor, type: String, desc: 'Cursor for obtaining the next set of records'
    optional :order_by, type: String, values: %w[id name], default: 'id',
      desc: 'Attribute to sort by'
    optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Order of sorting'
  end
  route_setting :authentication
  get ':project_id/things' do
    project = Project.find_by_id(params[:project_id])

    not_found! if project.blank?

    things = project.things

    present paginate_with_strategies(things), with: ::API::Entities::Thing
  end

키셋 페이지네이션이 사용되려면 다음 조건을 충족해야 합니다.

  1. params[:pagination] 이 'keyset'을 반환해야 합니다

  2. params[:order_by]와 params[:sort]가 모두 모델의 supported_keyset_orderings 클래스 메서드가 반환하는 객체에 있어야 합니다. 다음 예시에서 Thing은 ID를 기준으로 오름차순 또는 내림차순으로 정렬할 때 키셋 페이지네이션을 지원합니다.

    class Thing < ApplicationRecord
      def self.supported_keyset_orderings
       { id: [:asc, :desc] }
      end
    end
    

HAML 뷰를 사용하는 Rails 에서의 사용법#

프로젝트를 이름순으로 나열하는 다음 컨트롤러 액션을 살펴봅니다.

def index
  @projects = Project.order(:name).keyset_paginate(cursor: params[:cursor])
end

HAML 파일에서는 레코드를 다음과 같이 렌더링할 수 있습니다.

- if @projects.any?
  - @projects.each do |project|
    .project-container
      = project.name

  = keyset_paginate @projects

성능#

키셋 페이지네이션의 성능은 데이터베이스 인덱스 구성과 ORDER BY 절에 사용하는 칼럼 수에 따라 달라집니다.

기본 키(id)로 정렬하는 경우에는 기본 키가 데이터베이스 인덱스로 커버되므로 생성되는 쿼리가 효율적입니다.

ORDER BY 절에 칼럼을 두 개 이상 사용할 때는 생성되는 데이터베이스 쿼리를 확인해 올바른 인덱스 구성이 사용되는지 점검하는 것이 좋습니다. 자세한 내용은 페이지네이션 지침 페이지에서 확인할 수 있습니다.

Note

첫 페이지의 쿼리 성능은 좋아 보여도, 커서 속성이 쿼리에 사용되는 두 번째 페이지의 성능은 나쁠 수 있습니다. 첫 페이지와 두 번째 페이지 두 쿼리의 성능을 항상 확인하는 것이 좋습니다.

타이브레이커(id) 칼럼이 포함된 데이터베이스 쿼리 예시입니다.

SELECT "issues".*
FROM "issues"
WHERE (("issues"."id" > 99
      AND "issues"."created_at" = '2021-02-16 11:26:17.408466')
    OR ("issues"."created_at" > '2021-02-16 11:26:17.408466')
    OR ("issues"."created_at" IS NULL))
ORDER BY "issues"."created_at" DESC NULLS LAST, "issues"."id" DESC
LIMIT 20

OR 쿼리는 PostgreSQL에서 최적화하기 어렵습니다. 일반적으로 UNION 쿼리 사용을 권장합니다. 키셋 페이지네이션 라이브러리는 ORDER BY 절에 칼럼이 여러 개 있을 때 효율적인 UNION을 생성할 수 있습니다. Relation#keyset_paginate에 전달하는 옵션에 use_union_optimization: true를 지정하면 이 동작이 실행됩니다.

예시입니다.

# Triggers a simple query for the first page.
paginator1 = Project.order(:created_at, id: :desc).keyset_paginate(per_page: 2, keyset_order_options: { use_union_optimization: true })

cursor = paginator1.cursor_for_next_page

# Triggers UNION query for the second page
paginator2 = Project.order(:created_at, id: :desc).keyset_paginate(per_page: 2, cursor: cursor, keyset_order_options: { use_union_optimization: true })

puts paginator2.records.to_a # UNION query

복잡한 정렬 구성#

일반적인 ORDER BY 구성은 keyset_paginate 메서드가 자동으로 처리하므로 수동 설정이 필요하지 않습니다. 다만 정렬 객체 구성이 필요한 예외적인 경우가 몇 가지 있습니다.

  • NULLS LAST 정렬.
  • 함수 기반 정렬.
  • iid처럼 사용자 지정 타이브레이커 칼럼을 쓰는 정렬.

이러한 정렬 객체는 모델 클래스에 일반 ActiveRecord 스코프로 정의할 수 있습니다. 이 스코프를 다른 곳(Kaminari, 백그라운드 job)에서 쓰지 못하게 막는 동작은 없습니다.

NULLS LAST 정렬#

다음 스코프를 살펴봅니다.

scope = Issue.where(project_id: 10).order(Issue.arel_table[:relative_position].desc.nulls_last)
# SELECT "issues".* FROM "issues" WHERE "issues"."project_id" = 10 ORDER BY relative_position DESC NULLS LAST

scope.keyset_paginate # raises: Gitlab::Pagination::Keyset::UnsupportedScopeOrder: The order on the scope does not support keyset pagination

keyset_paginate 메서드는 쿼리의 정렬 값이 Arel AST 노드가 아니라 사용자 지정 SQL 문자열이기 때문에 오류를 발생시킵니다. 키셋 라이브러리는 이런 종류의 쿼리에서 구성 값을 자동으로 추론할 수 없습니다.

키셋 페이지네이션이 동작하게 하려면 사용자 지정 정렬 객체를 구성해야 합니다. 그러려면 정렬 칼럼에 대한 정보를 모아야 합니다.

  • relative_position은 고유 인덱스가 없으므로 값이 중복될 수 있습니다.
  • relative_position은 칼럼에 not null 제약이 없으므로 null 값을 가질 수 있습니다. 따라서 NULL 값이 결과 집합의 앞에 오는지 뒤에 오는지(NULLS LAST)를 정해야 합니다.
  • 키셋 페이지네이션은 서로 구별되는 정렬 칼럼을 요구하므로, 정렬을 구별되게 만들기 위해 기본 키(id)를 추가해야 합니다.
  • 마지막 페이지로 이동해 역방향으로 페이지를 넘기면 실제로는 ORDER BY 절이 뒤집힙니다. 따라서 뒤집힌 ORDER BY 절도 제공해야 합니다.

예시입니다.

order = Gitlab::Pagination::Keyset::Order.build([
  # The attributes are documented in the `lib/gitlab/pagination/keyset/column_order_definition.rb` file
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'relative_position',
    column_expression: Issue.arel_table[:relative_position],
    order_expression: Issue.arel_table[:relative_position].desc.nulls_last,
    reversed_order_expression: Issue.arel_table[:relative_position].asc.nulls_first,
    nullable: :nulls_last,
    order_direction: :desc
  ),
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'id',
    order_expression: Issue.arel_table[:id].asc,
    nullable: :not_nullable
  )
])

scope = Issue.where(project_id: 10).order(order) # or reorder()

scope.keyset_paginate.records # works

함수 기반 정렬#

다음 예시에서는 id에 10을 곱한 값으로 정렬합니다. id 칼럼은 고유하므로 칼럼을 하나만 정의합니다.

order = Gitlab::Pagination::Keyset::Order.build([
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'id_times_ten',
    order_expression: Arel.sql('id * 10').asc,
    nullable: :not_nullable,
    order_direction: :asc,
    add_to_projections: true
  )
])

paginator = Issue.where(project_id: 10).order(order).keyset_paginate(per_page: 5)
puts paginator.records.map(&:id_times_ten)

cursor = paginator.cursor_for_next_page

paginator = Issue.where(project_id: 10).order(order).keyset_paginate(cursor: cursor, per_page: 5)
puts paginator.records.map(&:id_times_ten)

add_to_projections 플래그는 해당 칼럼 표현식을 SELECT 절에 노출하도록 paginator에 알립니다. 키셋 페이지네이션이 다음 페이지를 요청하기 위해 레코드에서 마지막 값을 추출해야 하므로 이 설정이 필요합니다.

iid 기반 정렬#

이슈를 정렬할 때 데이터베이스는 프로젝트 안에서 iid 값이 서로 구별되도록 보장합니다. project_id 필터가 있다면 칼럼 하나로 정렬하는 것만으로 페이지네이션이 동작합니다.

order = Gitlab::Pagination::Keyset::Order.build([
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'iid',
    order_expression: Issue.arel_table[:iid].asc,
    nullable: :not_nullable
  )
])

scope = Issue.where(project_id: 10).order(order)

scope.keyset_paginate.records # works

키셋 페이지네이션

GitLab v19.4
원문 보기

요약

키셋 페이지네이션 라이브러리는 GitLab 프로젝트 안의 HAML 기반 뷰와 REST API에서 사용할 수 있습니다. 키셋 페이지네이션과 오프셋 기반 페이지네이션의 비교는 페이지네이션 지침 페이지에서 확인할 수 있습니다.

키셋 페이지네이션 라이브러리는 GitLab 프로젝트 안의 HAML 기반 뷰와 REST API에서 사용할 수 있습니다.

키셋 페이지네이션과 오프셋 기반 페이지네이션의 비교는 페이지네이션 지침 페이지에서 확인할 수 있습니다.

API 개요#

요약#

Rails 컨트롤러에서 ActiveRecord와 함께 사용하는 키셋 페이지네이션입니다.

cursor = params[:cursor] # this is nil when the first page is requested
paginator = Project.order(:created_at).keyset_paginate(cursor: cursor, per_page: 20)

paginator.each do |project|
  puts project.name # prints maximum 20 projects
end

사용법#

이 라이브러리는 ActiveRecord 릴레이션에 #keyset_paginate 메서드 하나를 추가합니다.

이는 Kaminari의 paginate 메서드와 취지는 비슷하지만 구현은 다릅니다.

키셋 페이지네이션은 단순한 ActiveRecord 쿼리에서는 별도 설정 없이 동작합니다.

  • 칼럼 하나로 정렬하는 경우.
  • 칼럼 두 개로 정렬하고 마지막 칼럼이 기본 키인 경우.

이 라이브러리는 null 허용 칼럼과 값이 중복될 수 있는 칼럼을 감지하고, 이를 근거로 기본 키를 사용한 정렬을 추가합니다. 키셋 페이지네이션은 정렬 값이 서로 구별되기를 요구하므로 이 과정이 필요합니다.

Project.order(:created_at).keyset_paginate.records # ORDER BY created_at, id

Project.order(:name).keyset_paginate.records # ORDER BY name, id

Project.order(:created_at, id: :desc).keyset_paginate.records # ORDER BY created_at, id

Project.order(created_at: :asc, id: :desc).keyset_paginate.records # ORDER BY created_at, id  DESC

keyset_paginate 메서드는 로드된 레코드와 여러 페이지를 요청하는 데 필요한 추가 정보를 담은 특수 paginator 객체를 반환합니다.

이 메서드는 다음 키워드 인수를 받습니다.

  • cursor - 다음 페이지를 요청하기 위해 인코딩된 정렬 기준 칼럼 값(nil 일 수 있습니다).
  • per_page - 페이지당 로드할 레코드 수(기본값 20).
  • keyset_order_options - 키셋 페이지네이션 데이터베이스 쿼리를 만들 때 쓰는 추가 옵션이며, UNION 쿼리 예시는 성능 섹션을 참고합니다(선택 사항).

paginator 객체에는 다음 메서드가 있습니다.

  • records - 현재 페이지의 레코드를 반환합니다.
  • has_next_page? - 다음 페이지가 있는지 알려 줍니다.
  • has_previous_page? - 이전 페이지가 있는지 알려 줍니다.
  • cursor_for_next_page - 다음 페이지를 요청하기 위해 인코딩된 String 값입니다(nil 일 수 있습니다).
  • cursor_for_previous_page - 이전 페이지를 요청하기 위해 인코딩된 String 값입니다(nil 일 수 있습니다).
  • cursor_for_first_page - 첫 페이지를 요청하기 위해 인코딩된 String 값입니다.
  • cursor_for_last_page - 마지막 페이지를 요청하기 위해 인코딩된 String 값입니다.
  • paginator 객체는 Enumerable 모듈을 포함하며 enumerable 기능을 records 메서드와 배열에 위임합니다.

첫 페이지와 두 번째 페이지를 가져오는 예시입니다.

paginator = Project.order(:name).keyset_paginate

paginator.to_a # same as .records

cursor = paginator.cursor_for_next_page # encoded column attributes for the next page

paginator = Project.order(:name).keyset_paginate(cursor: cursor).records # loading the next page

키셋 페이지네이션은 페이지 번호를 지원하지 않으므로 이동할 수 있는 페이지는 다음으로 제한됩니다.

  • 다음 페이지
  • 이전 페이지
  • 마지막 페이지
  • 첫 페이지

paginate_with_strategies를 사용한 REST API 에서의 사용법#

REST API에서는 릴레이션에 paginate_with_strategies 헬퍼를 사용해 키셋 페이지네이션이나 오프셋 페이지네이션을 쓸 수 있습니다.

  desc 'Get the things related to a project' do
    detail 'This feature was introduced in GitLab 16.1'
    success code: 200, model: ::API::Entities::Thing
    failure [
      { code: 401, message: 'Unauthorized' },
      { code: 403, message: 'Forbidden' },
      { code: 404, message: 'Not Found' }
    ]
  end
  params do
    use :pagination
    requires :project_id, type: Integer, desc: 'The ID of the project'
    optional :cursor, type: String, desc: 'Cursor for obtaining the next set of records'
    optional :order_by, type: String, values: %w[id name], default: 'id',
      desc: 'Attribute to sort by'
    optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Order of sorting'
  end
  route_setting :authentication
  get ':project_id/things' do
    project = Project.find_by_id(params[:project_id])

    not_found! if project.blank?

    things = project.things

    present paginate_with_strategies(things), with: ::API::Entities::Thing
  end

키셋 페이지네이션이 사용되려면 다음 조건을 충족해야 합니다.

  1. params[:pagination] 이 'keyset'을 반환해야 합니다

  2. params[:order_by]와 params[:sort]가 모두 모델의 supported_keyset_orderings 클래스 메서드가 반환하는 객체에 있어야 합니다. 다음 예시에서 Thing은 ID를 기준으로 오름차순 또는 내림차순으로 정렬할 때 키셋 페이지네이션을 지원합니다.

    class Thing < ApplicationRecord
      def self.supported_keyset_orderings
       { id: [:asc, :desc] }
      end
    end
    

HAML 뷰를 사용하는 Rails 에서의 사용법#

프로젝트를 이름순으로 나열하는 다음 컨트롤러 액션을 살펴봅니다.

def index
  @projects = Project.order(:name).keyset_paginate(cursor: params[:cursor])
end

HAML 파일에서는 레코드를 다음과 같이 렌더링할 수 있습니다.

- if @projects.any?
  - @projects.each do |project|
    .project-container
      = project.name

  = keyset_paginate @projects

성능#

키셋 페이지네이션의 성능은 데이터베이스 인덱스 구성과 ORDER BY 절에 사용하는 칼럼 수에 따라 달라집니다.

기본 키(id)로 정렬하는 경우에는 기본 키가 데이터베이스 인덱스로 커버되므로 생성되는 쿼리가 효율적입니다.

ORDER BY 절에 칼럼을 두 개 이상 사용할 때는 생성되는 데이터베이스 쿼리를 확인해 올바른 인덱스 구성이 사용되는지 점검하는 것이 좋습니다. 자세한 내용은 페이지네이션 지침 페이지에서 확인할 수 있습니다.

Note

첫 페이지의 쿼리 성능은 좋아 보여도, 커서 속성이 쿼리에 사용되는 두 번째 페이지의 성능은 나쁠 수 있습니다. 첫 페이지와 두 번째 페이지 두 쿼리의 성능을 항상 확인하는 것이 좋습니다.

타이브레이커(id) 칼럼이 포함된 데이터베이스 쿼리 예시입니다.

SELECT "issues".*
FROM "issues"
WHERE (("issues"."id" > 99
      AND "issues"."created_at" = '2021-02-16 11:26:17.408466')
    OR ("issues"."created_at" > '2021-02-16 11:26:17.408466')
    OR ("issues"."created_at" IS NULL))
ORDER BY "issues"."created_at" DESC NULLS LAST, "issues"."id" DESC
LIMIT 20

OR 쿼리는 PostgreSQL에서 최적화하기 어렵습니다. 일반적으로 UNION 쿼리 사용을 권장합니다. 키셋 페이지네이션 라이브러리는 ORDER BY 절에 칼럼이 여러 개 있을 때 효율적인 UNION을 생성할 수 있습니다. Relation#keyset_paginate에 전달하는 옵션에 use_union_optimization: true를 지정하면 이 동작이 실행됩니다.

예시입니다.

# Triggers a simple query for the first page.
paginator1 = Project.order(:created_at, id: :desc).keyset_paginate(per_page: 2, keyset_order_options: { use_union_optimization: true })

cursor = paginator1.cursor_for_next_page

# Triggers UNION query for the second page
paginator2 = Project.order(:created_at, id: :desc).keyset_paginate(per_page: 2, cursor: cursor, keyset_order_options: { use_union_optimization: true })

puts paginator2.records.to_a # UNION query

복잡한 정렬 구성#

일반적인 ORDER BY 구성은 keyset_paginate 메서드가 자동으로 처리하므로 수동 설정이 필요하지 않습니다. 다만 정렬 객체 구성이 필요한 예외적인 경우가 몇 가지 있습니다.

  • NULLS LAST 정렬.
  • 함수 기반 정렬.
  • iid처럼 사용자 지정 타이브레이커 칼럼을 쓰는 정렬.

이러한 정렬 객체는 모델 클래스에 일반 ActiveRecord 스코프로 정의할 수 있습니다. 이 스코프를 다른 곳(Kaminari, 백그라운드 job)에서 쓰지 못하게 막는 동작은 없습니다.

NULLS LAST 정렬#

다음 스코프를 살펴봅니다.

scope = Issue.where(project_id: 10).order(Issue.arel_table[:relative_position].desc.nulls_last)
# SELECT "issues".* FROM "issues" WHERE "issues"."project_id" = 10 ORDER BY relative_position DESC NULLS LAST

scope.keyset_paginate # raises: Gitlab::Pagination::Keyset::UnsupportedScopeOrder: The order on the scope does not support keyset pagination

keyset_paginate 메서드는 쿼리의 정렬 값이 Arel AST 노드가 아니라 사용자 지정 SQL 문자열이기 때문에 오류를 발생시킵니다. 키셋 라이브러리는 이런 종류의 쿼리에서 구성 값을 자동으로 추론할 수 없습니다.

키셋 페이지네이션이 동작하게 하려면 사용자 지정 정렬 객체를 구성해야 합니다. 그러려면 정렬 칼럼에 대한 정보를 모아야 합니다.

  • relative_position은 고유 인덱스가 없으므로 값이 중복될 수 있습니다.
  • relative_position은 칼럼에 not null 제약이 없으므로 null 값을 가질 수 있습니다. 따라서 NULL 값이 결과 집합의 앞에 오는지 뒤에 오는지(NULLS LAST)를 정해야 합니다.
  • 키셋 페이지네이션은 서로 구별되는 정렬 칼럼을 요구하므로, 정렬을 구별되게 만들기 위해 기본 키(id)를 추가해야 합니다.
  • 마지막 페이지로 이동해 역방향으로 페이지를 넘기면 실제로는 ORDER BY 절이 뒤집힙니다. 따라서 뒤집힌 ORDER BY 절도 제공해야 합니다.

예시입니다.

order = Gitlab::Pagination::Keyset::Order.build([
  # The attributes are documented in the `lib/gitlab/pagination/keyset/column_order_definition.rb` file
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'relative_position',
    column_expression: Issue.arel_table[:relative_position],
    order_expression: Issue.arel_table[:relative_position].desc.nulls_last,
    reversed_order_expression: Issue.arel_table[:relative_position].asc.nulls_first,
    nullable: :nulls_last,
    order_direction: :desc
  ),
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'id',
    order_expression: Issue.arel_table[:id].asc,
    nullable: :not_nullable
  )
])

scope = Issue.where(project_id: 10).order(order) # or reorder()

scope.keyset_paginate.records # works

함수 기반 정렬#

다음 예시에서는 id에 10을 곱한 값으로 정렬합니다. id 칼럼은 고유하므로 칼럼을 하나만 정의합니다.

order = Gitlab::Pagination::Keyset::Order.build([
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'id_times_ten',
    order_expression: Arel.sql('id * 10').asc,
    nullable: :not_nullable,
    order_direction: :asc,
    add_to_projections: true
  )
])

paginator = Issue.where(project_id: 10).order(order).keyset_paginate(per_page: 5)
puts paginator.records.map(&:id_times_ten)

cursor = paginator.cursor_for_next_page

paginator = Issue.where(project_id: 10).order(order).keyset_paginate(cursor: cursor, per_page: 5)
puts paginator.records.map(&:id_times_ten)

add_to_projections 플래그는 해당 칼럼 표현식을 SELECT 절에 노출하도록 paginator에 알립니다. 키셋 페이지네이션이 다음 페이지를 요청하기 위해 레코드에서 마지막 값을 추출해야 하므로 이 설정이 필요합니다.

iid 기반 정렬#

이슈를 정렬할 때 데이터베이스는 프로젝트 안에서 iid 값이 서로 구별되도록 보장합니다. project_id 필터가 있다면 칼럼 하나로 정렬하는 것만으로 페이지네이션이 동작합니다.

order = Gitlab::Pagination::Keyset::Order.build([
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'iid',
    order_expression: Issue.arel_table[:iid].asc,
    nullable: :not_nullable
  )
])

scope = Issue.where(project_id: 10).order(order)

scope.keyset_paginate.records # works