계속 추가되는 데이터에서 페이지 기준을 유지하는 방법: 앵커 기반 조회
최신순 목록을 offset으로 조회하는 동안 새 데이터가 들어오면, 사용자가 보고 있던 페이지의 기준은 어떻게 달라질까?
발송 이력은 사용자가 목록을 조회하는 동안에도 계속 추가됩니다. 페이지 번호 기반 UI를 유지하면서 신규 데이터 유입으로 인한 중복·누락 체감을 줄이기 위해 최초 조회 시점의 정렬 기준을 앵커로 고정했습니다. 이 글에서는 문제 상황과 구현 흐름, 완전한 커서 페이징과의 차이를 정리합니다.
1. 문제: offset의 기준 데이터가 계속 움직인다
발송 이력을 최신순으로 정렬해 한 페이지에 5건씩 보여준다고 가정해 보겠습니다.
최초 1페이지
A B C D E
신규 데이터 X, Y 유입
Y X A B C D E
사용자가 1페이지를 본 뒤 2페이지로 이동할 때 단순 offset을 다시 계산하면 신규 데이터 때문에 기존 데이터의 위치가 뒤로 밀립니다. 앞에서 본 데이터가 다음 페이지에 다시 나타나거나 일부 데이터가 건너뛴 것처럼 느껴질 수 있습니다.
offset 자체가 잘못된 것은 아닙니다. 문제는 페이지를 이동하는 동안 조회 대상 데이터셋이 변한다는 점입니다.
2. 선택: 최초 조회 시점의 앵커를 고정한다
첫 조회에서 가장 최신 데이터의 정렬 기준을 앵커로 저장합니다. 이후 같은 조회 흐름에서는 앵커 이전의 데이터만 페이지 대상으로 사용합니다.
최초 조회
↓
정렬 기준 시점과 고유 식별자를 앵커로 저장
↓
이후 페이지 조회
↓
앵커 이하의 데이터만 조회
정렬 기준이 발송 시각과 고유 식별자라면 조건은 다음과 같이 표현할 수 있습니다.
WHERE sent_at < :anchorTime
OR (sent_at = :anchorTime AND message_id <= :anchorId)
ORDER BY sent_at DESC, message_id DESC
시각만 비교하면 같은 시각에 생성된 여러 행의 순서가 불안정할 수 있습니다. 따라서 고유 식별자를 함께 사용해 정렬 순서를 결정적으로 만듭니다.
3. 신규 데이터는 숨기지 않고 별도로 알린다
앵커를 고정하면 이후에 들어온 데이터는 현재 목록에 즉시 섞이지 않습니다. 대신 별도 count API로 앵커 이후의 신규 건수를 확인해 화면에 표시했습니다.
새로운 발송 이력 12건이 있습니다. [새로고침]
사용자가 새로고침을 선택하면 현재 시점의 최신 데이터를 다시 조회하고 앵커를 갱신합니다. 이를 통해 페이지 탐색 중에는 동일한 조회 기준을 유지하면서도 새로운 발송 이력의 존재는 사용자에게 전달할 수 있습니다.
4. 완전한 커서 페이징과의 차이
이 구현은 페이지 번호 기반 UI를 유지하면서 조회 상한만 고정한 방식입니다. 다음 커서만으로 이동하는 전형적인 커서 페이징과는 목적과 제약이 다릅니다.
| 기준 | 앵커를 적용한 페이지 번호 방식 | 커서 페이징 |
|---|---|---|
| 임의 페이지 이동 | 가능 | 제한적 |
| 신규 데이터와 기존 조회 분리 | 가능 | 가능 |
| 깊은 페이지의 offset 비용 | 남아 있음 | 상대적으로 작음 |
| 기존 UI 변경 범위 | 작음 | 큼 |
기존 관리자 화면은 페이지 번호 이동이 필요했고 프로젝트 기간도 제한적이었습니다. 따라서 UI를 전면 변경하기보다 조회 기준을 고정해 사용자가 체감하는 데이터 이동을 줄이는 방식을 선택했습니다.
5. 트레이드오프와 보완점
- 앵커를 적용해도 깊은 페이지의 offset 비용은 남습니다.
- 정렬 조건이 바뀌면 앵커도 해당 조건에 맞게 다시 생성해야 합니다.
- 데이터 수정이나 삭제까지 포함한 완전한 스냅샷 격리를 보장하는 방식은 아닙니다.
- 데이터 규모가 커지고 순차 탐색이 중심이 된다면 커서 페이징 전환을 검토해야 합니다.
6. 정리
- 실시간으로 데이터가 추가되는 최신순 목록에서는 페이지 이동 중 데이터 위치가 바뀔 수 있습니다.
- 최초 조회의 정렬 기준을 앵커로 고정하면 같은 조회 흐름의 대상 범위를 유지할 수 있습니다.
- 앵커 이후의 신규 데이터는 별도 건수로 알리고 사용자가 선택할 때 기준을 갱신할 수 있습니다.
- 이 방식은 완전한 스냅샷이나 커서 페이징이 아니라 기존 페이지 번호 UI와 조회 일관성 사이의 절충안입니다.
페이징 방식은 쿼리 문법만의 문제가 아니라 사용자가 어떤 데이터셋을 보고 있다고 인식하게 할 것인지에 대한 설계 문제였습니다.