--- id: 06788903-3dfa-4f70-b159-f1224384fd0b kind: REFERENCE slug: keyset-pagination-design title: Keyset Pagination 설계 기준 topic: JPA 피드 조회 성능 project: Liner N + 1문제 status: 게시 전 studio: "https://hyeonworks.com/studio/documents/06788903-3dfa-4f70-b159-f1224384fd0b/edit" --- # Keyset Pagination 설계 기준 OFFSET은 건너뛸 행까지 만든 뒤 버린다. keyset은 이전 페이지의 마지막 행을 커서로 삼아 그 지점 이후만 읽는다. 다만 커서와 같은 순서의 정렬키 인덱스가 있어야 이 이점이 생긴다. ## 관계 - **Visibility OR이 Keyset Index를 깨뜨린 문제** 이 기준의 전제가 깨지는 조건을 확인한 기록이다. - **Feed Pagination은 Keyset을 사용한다** 이 기준에서 나온 결정이다. - **PostgreSQL Query Plan 측정 기준** 깊이별 비용을 실행계획으로 확인하는 기준이다. ## 목적 무한 스크롤에서는 뒤쪽 페이지일수록 OFFSET이 커진다. 정렬키 인덱스가 있어도 건너뛸 튜플을 훑어야 하고, 깊으면 전량 스캔과 정렬로 떨어진다. 페이지 깊이와 무관하게 읽는 행수를 일정하게 유지하려면 커서 방식이 필요하다. ## 규칙 ### 1. 커서에 정렬키를 모두 담는다 정렬이 여러 컬럼이면 커서도 같은 컬럼을 모두 가진다. 앞 컬럼만 커서로 쓰면 값이 같은 행이 있을 때 경계에서 빠지거나 중복된다. ### 2. tie-break 컬럼을 정렬과 커서에 넣는다 정렬키에 중복이 있을 수 있으면 유일한 컬럼을 마지막 정렬키로 더한다. 커서에도 같이 담는다. ### 3. 정렬키, 커서, 인덱스의 컬럼과 방향을 일치시킨다 셋 중 하나라도 어긋나면 인덱스가 순서를 주지 못해 Sort가 다시 생긴다. 방향까지 같아야 한다. ### 4. 정렬키 전용 인덱스를 확인한다 선두 컬럼이 다른 인덱스는 이 쿼리에 쓰이지 않는다. 필터가 없는 정렬 쿼리라면 정렬키만으로 된 인덱스가 필요하다. 인덱스가 없으면 keyset도 전량을 스캔한다. keyset 문법이 아니라 인덱스가 비용을 줄인다. ### 5. 깊이별로 훑은 행을 측정한다 OFFSET은 offset에 페이지 크기를 더한 만큼 훑는다. keyset은 페이지 크기만큼 훑는다. 훑은 행은 Limit 하위의 actual rows로 읽는다. 한 페이지만 재면 차이가 보이지 않는다. 깊이를 바꿔 가며 곡선으로 확인한다. ### 6. 결과가 OFFSET과 같은지 검증한다 커서로 넘긴 페이지가 같은 순서의 같은 행을 반환하는지 대조한다. 페이지 크기, 순서, 식별자를 모두 확인한다. ### 7. 필터를 얹으면 전제가 깨질 수 있다 선택 조건이 여러 분기로 갈리면 플래너가 분기별로 스캔한 뒤 합치면서 정렬 순서를 잃는다. 이때 Sort가 다시 나타난다. 필터가 있는 keyset은 필터를 포함한 인덱스 설계나 쿼리 분해가 함께 필요하다. ### 8. 정렬키에 null이 있을 수 있는지 먼저 정한다 정렬키가 nullable이면 null의 정렬 위치와 커서 표현을 정의해야 한다. 이 판단을 미루면 커서 비교식이 경계에서 어긋난다. ## 적용 조건 - 무한 스크롤이나 깊은 페이지를 지원할 때 - 정렬 순서가 고정돼 있고 인덱스를 만들 수 있을 때 - 전체 페이지 수가 필요하지 않을 때 ## 예외 - 임의 페이지 점프가 필요하면 커서만으로는 부족하다. OFFSET을 함께 두거나 다른 탐색을 설계한다. - 전체 건수를 화면에 표시해야 하면 count를 별도로 다룬다. 커서 결과에는 전체 건수가 없다. - 정렬 기준이 자주 바뀌면 기준마다 인덱스가 필요하다. 인덱스 수와 쓰기 비용을 함께 본다. ## 예시 - OFFSET 훑은 행 : offset + 페이지 크기 - keyset 훑은 행 : 페이지 크기 (깊이 무관) - 커서 : (정렬키, tie-break) 조합 - 전제 인덱스 : 정렬키와 같은 컬럼·같은 방향 - 인덱스 없는 keyset : 전량 스캔, 이점 없음 - 필터 추가 : 분기가 갈리면 Sort 재등장