올해는 머신러닝이다.
NestJS에서 페이징을 구현 방법 2가지 본문
NestJS에서 페이징을 구현하는 방법은 여러 가지가 있지만, 일반적으로 Offset 방식과 Cursor 방식이 많이 사용됩니다. 각각의 방식에 따라 장단점이 있으므로, 사용 목적과 성능 요구 사항에 맞게 선택하는 것이 중요합니다.
1. Offset 방식 (기본적인 페이징)
🔹 개념
- OFFSET과 LIMIT을 사용하여 특정 페이지의 데이터를 가져오는 방식
- page와 limit 값을 요청 파라미터로 받아서 데이터 조회
✅ 장점
- 구현이 간단하고 직관적
- 대부분의 관계형 데이터베이스에서 지원
❌ 단점
- 페이지가 커질수록 성능이 저하됨 (OFFSET N은 N개의 데이터를 스캔해야 하므로)
- 중간에 데이터가 삽입/삭제되면 페이지 불일치 가능성 존재
💡 구현 예제 (TypeORM)
@Get()
async getItems(
@Query('page') page: number = 1,
@Query('limit') limit: number = 10
) {
const [items, total] = await this.itemsRepository.findAndCount({
skip: (page - 1) * limit,
take: limit,
});
return {
data: items,
total,
page,
limit,
totalPages: Math.ceil(total / limit),
};
}
findAndCount()를 사용하면 전체 데이터 개수도 함께 조회 가능
💡 SQL 직접 사용 (QueryBuilder)
const [items, total] = await this.itemsRepository
.createQueryBuilder('item')
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();
2. Cursor 방식 (고성능 페이징)
🔹 개념
- 특정 필드(예: id, createdAt)를 기준으로 WHERE 조건을 활용하여 페이징
- OFFSET을 사용하지 않으므로 성능이 뛰어남
✅ 장점
- 빠른 성능 (대량의 데이터에서 효과적)
- 데이터 삽입/삭제 시에도 페이지 불일치 문제 없음
❌ 단점
- 이전 페이지로 돌아가는 기능 구현이 어려움
- id 또는 createdAt과 같은 정렬 기준 필드가 필요함
💡 구현 예제 (TypeORM)
@Get()
async getItems(
@Query('cursor') cursor?: string,
@Query('limit') limit: number = 10
) {
const query = this.itemsRepository.createQueryBuilder('item');
if (cursor) {
query.where('item.id > :cursor', { cursor });
}
const items = await query
.orderBy('item.id', 'ASC')
.take(limit)
.getMany();
return {
data: items,
nextCursor: items.length ? items[items.length - 1].id : null,
};
}
nextCursor를 클라이언트에 반환하여 다음 요청 시 활용 가능
3. Keyset 페이징 (Cursor 페이징의 확장)
- 여러 정렬 조건(createdAt, id 등)을 조합하여 페이징 성능을 극대화
- WHERE (createdAt, id) > (lastCreatedAt, lastId)와 같은 방식 사용
@Get()
async getItems(
@Query('cursorCreatedAt') cursorCreatedAt?: string,
@Query('cursorId') cursorId?: number,
@Query('limit') limit: number = 10
) {
const query = this.itemsRepository.createQueryBuilder('item');
if (cursorCreatedAt && cursorId) {
query.where('(item.createdAt, item.id) > (:createdAt, :id)', {
createdAt: cursorCreatedAt,
id: cursorId,
});
}
const items = await query
.orderBy('item.createdAt', 'ASC')
.addOrderBy('item.id', 'ASC')
.take(limit)
.getMany();
return {
data: items,
nextCursorCreatedAt: items.length ? items[items.length - 1].createdAt : null,
nextCursorId: items.length ? items[items.length - 1].id : null,
};
}
4. 어떤 방식을 선택해야 할까?
방식 장점 단점 추천 사용 사례
Offset 방식 | 구현이 쉬움, 익숙한 방법 | 데이터가 많아질수록 성능 저하 | 소규모 데이터, 간단한 목록 조회 |
Cursor 방식 | 높은 성능, 데이터 변경에 안정적 | 이전 페이지 탐색이 어려움 | 대량의 데이터, 무한 스크롤 |
Keyset 방식 | 성능 최적화, 안정적 | 복잡한 쿼리, 필드 요구 | 시간순 정렬이 중요한 경우 |
결론
- 데이터가 많지 않다면 Offset 방식이 간단하고 유지보수하기 쉬움.
- 대량의 데이터(수백만 건 이상)라면 Cursor 방식을 사용하여 성능을 최적화.
- 정렬 기준이 여러 개라면 Keyset 방식이 적합.
👉 NestJS + TypeORM을 사용할 경우, Cursor 방식이 가장 효율적이며, Offset 방식은 간단한 API에서 적절함.
'스터디 > NestJS' 카테고리의 다른 글
NestJs + Seeder + Faker 조합 (0) | 2025.02.13 |
---|---|
NestJS에서 Seeder(시드 데이터) 사용 방법 (0) | 2025.02.13 |
NestJS와 MkDocs를 함께 사용하여 문서를 관리(Docker) (0) | 2025.02.13 |
NestJS에서 추천하는 아키텍처 패턴 (0) | 2025.02.13 |
NestJS 필수로 알아야 하는 개념 정리 (0) | 2025.02.13 |