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 쿼리에 대해서는 별도 설정 없이도 동작합니다:

  • 하나의 칼럼을 기준으로 정렬.

  • 두 개의 칼럼을 기준으로 정렬하되, 마지막 칼럼이 기본 키인 경우.

라이브러리는 nullable 칼럼과 non-distinct 칼럼을 감지하고, 이를 기반으로 기본 키를 사용하여 추가 정렬을 적용합니다. 키셋 페이지네이션은 고유한 정렬 기준 값을 필요로 하기 때문입니다:

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 메서드는 로드된 레코드와 다양한 페이지 요청을 위한 추가 정보를 포함하는 특별한 페이지네이터 객체를 반환합니다.

이 메서드는 다음 키워드 인수를 허용합니다:

  • cursor - 다음 페이지를 요청하기 위한 인코딩된 정렬 칼럼 값 (nil일 수 있음).

  • per_page - 페이지당 로드할 레코드 수 (기본값 20).

  • keyset_order_options - 키셋 페이지네이션 데이터베이스 쿼리를 구성하기 위한 추가 옵션. 성능 섹션의 UNION 쿼리 예시를 참조하세요 (선택 사항).

페이지네이터 객체는 다음 메서드를 제공합니다:

  • 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으로 반환합니다.

  • 페이지네이터 객체는 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

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

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

  • 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 절에 두 개 이상의 칼럼을 사용하는 경우, 생성된 데이터베이스 쿼리를 확인하고 올바른 인덱스 구성이 사용되고 있는지 확인하는 것이 좋습니다. 자세한 내용은 페이지네이션 가이드라인 페이지에서 확인할 수 있습니다.

첫 번째 페이지의 쿼리 성능은 좋아 보이더라도, 두 번째 페이지(커서 속성이 쿼리에 사용되는 경우)는 성능이 저하될 수 있습니다. 항상 첫 번째 페이지와 두 번째 페이지 쿼리 모두의 성능을 검증하는 것이 좋습니다.

타이-브레이커(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 메서드는 쿼리의 정렬 값이 커스텀 SQL 문자열이며 Arel AST 노드가 아니기 때문에 오류를 발생시킵니다. 키셋 라이브러리는 이러한 종류의 쿼리에서 구성 값을 자동으로 추론할 수 없습니다.

키셋 페이지네이션이 작동하도록 하려면 커스텀 정렬 객체를 구성해야 하며, 이를 위해 정렬 칼럼에 대한 정보를 수집해야 합니다:

  • 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 절에 칼럼 표현식을 노출하도록 지시합니다. 이는 키셋 페이지네이션이 다음 페이지를 요청하기 위해 레코드에서 마지막 값을 추출해야 하기 때문에 필요합니다.

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.1
원문 보기
요약

키셋 페이지네이션 라이브러리는 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 쿼리에 대해서는 별도 설정 없이도 동작합니다:

  • 하나의 칼럼을 기준으로 정렬.

  • 두 개의 칼럼을 기준으로 정렬하되, 마지막 칼럼이 기본 키인 경우.

라이브러리는 nullable 칼럼과 non-distinct 칼럼을 감지하고, 이를 기반으로 기본 키를 사용하여 추가 정렬을 적용합니다. 키셋 페이지네이션은 고유한 정렬 기준 값을 필요로 하기 때문입니다:

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 메서드는 로드된 레코드와 다양한 페이지 요청을 위한 추가 정보를 포함하는 특별한 페이지네이터 객체를 반환합니다.

이 메서드는 다음 키워드 인수를 허용합니다:

  • cursor - 다음 페이지를 요청하기 위한 인코딩된 정렬 칼럼 값 (nil일 수 있음).

  • per_page - 페이지당 로드할 레코드 수 (기본값 20).

  • keyset_order_options - 키셋 페이지네이션 데이터베이스 쿼리를 구성하기 위한 추가 옵션. 성능 섹션의 UNION 쿼리 예시를 참조하세요 (선택 사항).

페이지네이터 객체는 다음 메서드를 제공합니다:

  • 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으로 반환합니다.

  • 페이지네이터 객체는 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

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

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

  • 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 절에 두 개 이상의 칼럼을 사용하는 경우, 생성된 데이터베이스 쿼리를 확인하고 올바른 인덱스 구성이 사용되고 있는지 확인하는 것이 좋습니다. 자세한 내용은 페이지네이션 가이드라인 페이지에서 확인할 수 있습니다.

첫 번째 페이지의 쿼리 성능은 좋아 보이더라도, 두 번째 페이지(커서 속성이 쿼리에 사용되는 경우)는 성능이 저하될 수 있습니다. 항상 첫 번째 페이지와 두 번째 페이지 쿼리 모두의 성능을 검증하는 것이 좋습니다.

타이-브레이커(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 메서드는 쿼리의 정렬 값이 커스텀 SQL 문자열이며 Arel AST 노드가 아니기 때문에 오류를 발생시킵니다. 키셋 라이브러리는 이러한 종류의 쿼리에서 구성 값을 자동으로 추론할 수 없습니다.

키셋 페이지네이션이 작동하도록 하려면 커스텀 정렬 객체를 구성해야 하며, 이를 위해 정렬 칼럼에 대한 정보를 수집해야 합니다:

  • 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 절에 칼럼 표현식을 노출하도록 지시합니다. 이는 키셋 페이지네이션이 다음 페이지를 요청하기 위해 레코드에서 마지막 값을 추출해야 하기 때문에 필요합니다.

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