두 가지 전략이 있습니다: offset 기반(limit/offset)과 cursor 기반. GraphQL에는 내장 페이지네이션이 없지만, 커뮤니티 표준은 cursor 페이지네이션을 형식화한 Relay Connection 명세입니다.
두 가지 전략이 있습니다: offset 기반(limit/offset)과 cursor 기반. GraphQL에는 내장 페이지네이션이 없지만, 커뮤니티 표준은 cursor 페이지네이션을 형식화한 Relay Connection 명세입니다.
items(limit: 10, offset: 20))은 간단하지만 실시간 데이터에서 깨집니다: 사용자가 페이지를 넘기는 동안 행이 삽입되면 항목이 밀려서 중복이나 누락이 보입니다. 또한 깊은 offset에서 느려집니다(DB가 건너뛴 행들을 여전히 스캔합니다).connection은 목록을 edges(각각 cursor와 node를 가짐)와 pageInfo로 감쌉니다:
type Query {
posts(first: Int!, after: String): PostConnection! # first = page size, after = cursor
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PostEdge {
cursor: String! # opaque token for THIS node's position
node: Post! # the actual item
}
type PageInfo {
endCursor: String # feed this back as 'after' for the next page
hasNextPage: Boolean! # so the client knows when to stop
}
// resolver: fetch ONE extra row to compute hasNextPage cheaply
const rows = await db.posts.find({ after: args.after, limit: args.first + 1 });
const hasNextPage = rows.length > args.first;
const page = rows.slice(0, args.first);
return {
edges: page.map((p) => ({ node: p, cursor: encodeCursor(p.id) })),
pageInfo: { endCursor: page.at(-1) && encodeCursor(page.at(-1).id), hasNextPage },
};
cursor는 **불투명(opaque)**합니다 — 보통 정렬 key의 base64입니다 — 그래서 클라이언트는 이를 숫자가 아니라 토큰으로 취급합니다.
offset vs cursor를 선택하는 것은 여러분이 동시 쓰기 상황에서의 일관성과 깊은 페이지 성능을 이해한다는 것을 보여 줍니다. Relay connection 형태를 아는 것이 중요한 이유는 Apollo/Relay 클라이언트가 이에 대한 내장 캐시 처리를 갖고 있기 때문입니다.
주니어부터 시니어까지 상세한 답변이 포함된 IT 면접 질문 라이브러리.
후원하기