Initial commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Handling Data Changes During Pagination
|
||||
|
||||
## The Problem
|
||||
|
||||
**Offset Pagination Issue:** Duplicates or missing records when data changes between page loads.
|
||||
|
||||
### Example Scenario
|
||||
|
||||
1. User loads page 1 (posts 1-20)
|
||||
2. New post is inserted at position 1
|
||||
3. User loads page 2 (posts 21-40)
|
||||
4. **Post 21 appears on both pages** (was post 20, now post 21)
|
||||
|
||||
### Why It Happens
|
||||
|
||||
Offset pagination uses absolute positions:
|
||||
- Page 1: Records at positions 0-19
|
||||
- Page 2: Records at positions 20-39
|
||||
|
||||
When a record is inserted:
|
||||
- Page 1 positions: 0-19 (includes new record at position 0)
|
||||
- Page 2 positions: 20-39 (old position 20 is now position 21)
|
||||
- **Position 20 was seen on page 1, appears again on page 2**
|
||||
|
||||
## Cursor Pagination Solution
|
||||
|
||||
Cursor pagination is immune to this problem:
|
||||
|
||||
```typescript
|
||||
const posts = await prisma.post.findMany({
|
||||
take: 20,
|
||||
skip: cursor ? 1 : 0,
|
||||
cursor: cursor ? { id: cursor } : undefined,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
```
|
||||
|
||||
**Why it works:**
|
||||
- Uses record identity (cursor), not position
|
||||
- Always starts from the last seen record
|
||||
- New records appear in correct position
|
||||
- No duplicates or gaps
|
||||
|
||||
## Mitigation for Offset Pagination
|
||||
|
||||
If you must use offset pagination:
|
||||
|
||||
### Strategy 1: Accept the Limitation
|
||||
Document behavior for admin tools where occasional duplicates are acceptable.
|
||||
|
||||
### Strategy 2: Timestamp Filtering
|
||||
Create stable snapshots using timestamp filtering:
|
||||
|
||||
```typescript
|
||||
const snapshotTime = new Date();
|
||||
|
||||
async function getPage(page: number) {
|
||||
return await prisma.post.findMany({
|
||||
where: {
|
||||
createdAt: { lte: snapshotTime },
|
||||
},
|
||||
skip: page * pageSize,
|
||||
take: pageSize,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Limitations:**
|
||||
- Doesn't show new records during pagination session
|
||||
- User must refresh to see new data
|
||||
|
||||
### Strategy 3: Switch to Cursor
|
||||
|
||||
The best solution is to redesign using cursor pagination.
|
||||
Reference in New Issue
Block a user