REST API에서 확장 가능한 페이징 구현: 개념부터 생산까지
카테고리
프로그래밍/소프트웨어 개발
서브카테고리
웹 개발
대상자
- REST API를 구현하거나 페이징 기능을 도입하는 개발자
- 대규모 데이터셋을 처리하는 백엔드 개발자
- Node.js, Express, TypeScript 기반 애플리케이션 개발자
- 성능 최적화와 확장성 고려가 필요한 프로젝트에 참여하는 개발자
핵심 요약
- 페이징 전략 선택:
- Offset: 간단한 앱, 작은 데이터셋에 적합, 구현이 쉬움 (단, 대규모 시 성능 저하)
- Cursor-Based: 무한 스크롤, 소셜 피드에 적합, 안정적 성능 (단, 클라이언트 구현 복잡)
- Keyset: 고유 ID로 정렬된 데이터에 적합, 중복/건너뛰기 없음 (단, 순차적 접근 필요)
- 핵심 도구 및 기술:
- Express.js (기본 웹 프레임워크), Zod (타입 안전 스키마 검증), TypeScript (코드 품질 향상)
- Faker.js (모의 데이터 생성)
- 성능 최적화:
- 페이징 전략 선택에 따른 성능 차이 (예: 10,000건 기준, Keyset은 Offset보다 1000페이지 시 950ms → 12ms로 차이)
섹션별 세부 요약
1. 왜 페이징이 현대 API에서 중요한가?
- 대규모 데이터셋 처리 시 성능 저하 방지
- 모바일 사용자에게 더 작은 데이터 페이로드 제공
- 대역폭 비용 절감 및 예측 가능한 데이터 탐색
2. 프로젝트 설정 및 아키텍처
- 디렉토리 구조:
- data/
(모의 데이터 생성), schemas/
(검증 스키마), routes/
(API 엔드포인트), utils/
(재사용 가능한 유틸리티)
- 의존성 설치:
- npm install express zod @faker-js/faker
- npm install --save-dev typescript ts-node-dev
3. 핵심 구성 요소 구현
- Zod로 데이터 모델링:
```ts
export const userSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(2).max(100),
email: z.string().email().max(320),
createdAt: z.date(),
});
```
- Faker.js로 모의 데이터 생성:
```ts
export const generateUsers = (count: number): User[] => {
const baseDate = new Date();
return Array.from({ length: count }, (_, i) => ({
id: i + 1,
name: faker.person.fullName(),
email: faker.internet.email(),
createdAt: faker.date.past({ refDate: baseDate, years: 1 }),
}));
};
```
4. 검증 및 오류 처리
- 중앙 검증 미들웨어:
- 클라이언트 요청 파라미터를 Zod 스키마로 검증
- 검증 실패 시 400
에러 반환
- 통합 페이징 응답 형식:
```ts
{
total: number,
limit: number,
offset: number,
data: User[],
nextCursor: string | null,
nextId: number | null
}
```
5. 테스트 및 검증
- curl 명령어 예시:
- Offset: curl "http://localhost:3000/users/offset?limit=10&offset=0"
- Cursor: curl "http://localhost:3000/users/cursor?limit=10&cursor=2023-07-20T12:34:56Z"
6. 생산성 고려사항
- 성능 최적화:
- Redis 캐싱, 데이터베이스 레벨 페이징 (SQL의 WHERE/OFFSET
)
- 인덱싱된 정렬 컬럼 사용
- 보안 및 안정성:
- 커서 암호화, 요청 빈도 제한 (express-rate-limit)
- 최대 페이지 크기 제한
- 모니터링:
- 응답 크기 모니터링, 비정상 페이지 요청 경고
7. 전략 선택 가이드
- 결정 흐름도:
- 간단한 탐색 필요? → Offset
- 무한 스크롤 처리? → Cursor-Based
- 고유 ID로 정렬된 데이터? → Keyset
- 모든 조건 동일? → 실제 데이터 벤치마킹
8. 추가 기능 구현
- 필터링 추가:
```ts
const filtered = users.filter((u) => u.name.includes(searchTerm));
```
- HATEOAS 지원:
```json
{
"links": {
"next": "/users?cursor=2023-07-20T12:34:56Z",
"prev": "/users?cursor=2023-07-19T08:12:34Z"
}
}
```
결론
- 핵심 팁:
- 페이징 전략 선택 시 데이터 특성과 사용자 요구사항에 따라 결정 (예: 무한 스크롤 → Cursor)
- Redis 캐싱, 인덱싱, 요청 제한을 통한 성능 최적화
- HATEOAS와 필터링을 통한 사용자 친화적 API 설계
- "Keyset은 대규모 데이터셋에서 가장 안정적인 성능을 제공"