collection을 반환하는 모든 엔드포인트는 결과를 제한하고 다듬어야 한다. 세 가지 관심사 — 페이지네이션, 필터링, 정렬 — 은 모두 query string에 자리한다.
페이지네이션: offset vs cursor
Offset (페이지 기반) — ?page=3&limit=20 또는 ?offset=40&limit=20.
GET /orders?offset=40&limit=20 HTTP/1.1
단순하고 아무 페이지로나 점프할 수 있지만, 규모가 커지면 두 가지 문제가 있다: OFFSET 100000은 데이터베이스가 10만 행을 스캔하고 버리게 강제하고(느림), request 사이에 행이 삽입/삭제되면 항목이 밀려나 중복이나 누락을 보게 된다.
Cursor (keyset) — client가 마지막으로 본 항목을 가리키는 opaque 포인터를 전달한다.
GET /orders?limit=20&cursor=eyJpZCI6MTAyMH0 HTTP/1.1
HTTP/1.1 200 OK
{
"data": [ /* 20 orders */ ],
"page": { "next_cursor": "eyJpZCI6MTA0MH0", "has_more": true }
}
server는 cursor를 WHERE id > 1020 ORDER BY id LIMIT 20으로 변환한다 — index를 쓰는 조회로, 어느 깊이에서도 빠르게 유지되고 삽입에 대해 **안정적(stable)**이다. 트레이드오프: "500페이지"로의 임의 접근은 없다.
Offset → small datasets, admin UIs, "jump to page N"
Cursor → large/infinite feeds, real-time data, mobile scroll
GET /orders?status=shipped&min_total=100&sort=-created_at,id HTTP/1.1
status=shipped). 허용 필드를 whitelist하라.sort param으로 정렬한다. - 접두사는 내림차순을 뜻한다(-created_at). 안정적 순서를 위해 다중 키를 지원하라.limit에 상한을 두라(예: 최대 100). client가 백만 행을 요청할 수 없도록.이는 가장 실용적인 API 설계 질문 중 하나인데, 거의 모든 목록 엔드포인트가 여기에 부딪히고 그 선택이 실제 성능 결과를 낳기 때문이다. 면접관은 보통 offset-vs-cursor 트레이드오프를 파고든다: 어디서나 offset 페이지네이션에 손을 뻗는 지원자는 OFFSET 1000000이 테이블을 스캔하는 고통이나, 무한 피드를 스크롤하는 사용자가 새 게시물이 삽입되어 같은 글을 두 번 보는 "밀려나는 결과" 버그를 겪어 본 적이 없다. cursor 페이지네이션은 query를 index 기반 keyset 조회로 바꿔 둘 다 고치며, 그래서 모든 대규모 API(Twitter, Stripe, Slack)가 피드에 그것을 쓴다. 강한 답은 운영상의 가드레일도 언급한다 — 필터/정렬 필드 whitelist(index 없는 query와 injection 방지)와 limit 상한(데이터베이스 보호) — 이는 이 엔드포인트를 production에서 운영해 본 사람을 그저 읽어만 본 사람과 갈라놓는다.
주니어부터 시니어까지 상세한 답변이 포함된 IT 면접 질문 라이브러리.
후원하기