[Nest.js] Pagination 구현하기: 특징과 종류, 문제점 분석

궁금하면 500원·2024년 8월 17일
0

1. Pagination의 특징

Pagination(페이징)은 데이터베이스에서 대량의 데이터를 효율적으로 처리하고 사용자에게 보여주기 위한 방법입니다.

주요 특징은 다음과 같습니다.

데이터 분할

데이터셋을 여러 페이지로 나누어, 한 번에 너무 많은 데이터를 로드하지 않고도 필요한 정보에 접근할 수 있게 합니다.

이는 사용자 경험을 개선하고, 시스템의 메모리 사용을 줄이는 데 도움이 됩니다.

네비게이션 컨트롤

사용자가 페이지 간 쉽게 이동할 수 있도록 하는 네비게이션 요소
(예: 이전, 다음, 첫 페이지, 마지막 페이지 링크)를 제공합니다.

균일한 데이터 규격

페이지에 표시되는 데이터의 형식과 구조를 일관되게 유지하여
사용자에게 친숙한 경험을 제공합니다.

상태 유지

사용자가 현재 위치한 페이지를 기억하고, 이전 상태로 돌아갈 수 있도록 합니다.
이는 특히 검색 및 필터링을 수행하는 경우 유용합니다.

퍼포먼스 최적화

데이터베이스에서 필요한 데이터만 로드하여 성능을 향상시킵니다.
전체 데이터를 한 번에 가져오는 것보다 페이지 단위로 요청하여 서버 및 클라이언트 측의 부하를 줄일 수 있습니다.

2. 간단한 예제 코드

Nest.js에서의 Pagination을 구현하기 위한 간단한 예제 코드입니다.

다음은 GET /users?page=1&limit=10 형식으로 사용자 목록을 페이지네이션하여 가져오는 API의 예시입니다.

2.1. DTO와 Service 구현

user.dto.ts

import { IsInt, IsNotEmpty } from 'class-validator';

export class PaginationDto {
  @IsInt()
  @IsNotEmpty()
  page: number;

  @IsInt()
  @IsNotEmpty()
  limit: number;
}

user.service.ts

import { Injectable } from '@nestjs/common';
import { PaginationDto } from './user.dto';

@Injectable()
export class UserService {
  private readonly users = [ /* ... User Data ... */ ];

  findAll(paginationDto: PaginationDto) {
    const { page, limit } = paginationDto;

    const startIndex = (page - 1) * limit;
    const endIndex = page * limit;

    const paginatedUsers = this.users.slice(startIndex, endIndex);
    return {
      data: paginatedUsers,
      total: this.users.length,
      page,
      limit,
    };
  }
}

2.2. Controller 구현

user.controller.ts

import { Controller, Get, Query } from '@nestjs/common';
import { UserService } from './user.service';
import { PaginationDto } from './user.dto';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll(@Query() paginationDto: PaginationDto) {
    return this.userService.findAll(paginationDto);
  }
}

3. Pagination의 종류

Pagination의 종류에는 여러 가지가 있습니다

Page-Based Pagination (페이지 기반)

사용자가 특정 페이지 번호를 요청하여 데이터를 가져오는 방식입니다.
예를 들어, GET /users?page=2&limit=10.

Cursor-Based Pagination (커서 기반)

각 페이지에 대한 데이터의 "커서"를 사용하여 다음 데이터를 가져오는 방식입니다.
이는 데이터의 위치를 기반으로 하여, 특정 데이터부터 다음 데이터를 가져오는 것입니다.

예: GET /users?after=cursorValue&limit=10.

Offset-Based Pagination (오프셋 기반)

데이터의 오프셋(시작 위치)을 지정하여 가져오는 방식입니다.

예: GET /users?offset=20&limit=10.

Keyset Pagination (키셋 기반)

특정 데이터의 키를 사용하여 다음 데이터를 가져오는 방식으로,
성능과 일관성을 유지하는 데 유리합니다.

4. Page-Based Pagination의 문제점과 Cursor-Based Pagination

Page-Based Pagination의 문제점

데이터 변경에 대한 불안정성:

사용자가 페이지를 이동하는 동안 데이터가 추가되거나 삭제되면, 결과 세트가 바뀌어 이전에 보던 데이터와 다른 데이터가 나타날 수 있습니다.

성능 문제: 대규모 데이터셋에서는 페이지가 깊어질수록 성능 저하가 발생할 수 있습니다.
데이터베이스에서 페이지 번호에 따라 많은 데이터를 건너뛰어야 하므로 비효율적일 수 있습니다.

Cursor-Based Pagination

장점: Cursor-Based Pagination은 이전 페이지의 마지막 항목을 기준으로 다음 페이지를 로드하기 때문에, 데이터 변경 시에도 안정적인 결과를 제공합니다.

사용자가 위치한 항목을 기준으로 다음 데이터를 쉽게 가져올 수 있습니다.

단점: 커서를 사용해야 하므로, 구현이 다소 복잡할 수 있습니다.

또한 커서가 무효화될 경우(예: 데이터가 삭제되거나 변경됨) 문제가 발생할 수 있습니다.

5. EnableImplicitTransformation

EnableImplicitTransformation은 Nest.js에서 데이터 전송 객체(DTO)와 요청 본문 간의 변환을 자동으로 처리할 수 있도록 설정하는 옵션입니다.

이 설정을 활성화하면, 요청에서 들어온 데이터가 DTO의 타입과 일치하는지 자동으로 검사하고,
필요에 따라 변환을 수행합니다.

main.ts


import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
  await app.listen(3000);
}
bootstrap();

이렇게 설정하면 DTO에서 정의한 데이터 유형에 맞게 들어온 요청을 변환하여 유효성을 검증할 수 있습니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글