← 전체 글로 돌아가기

API

API 페이지네이션이 불안정할 때 확인할 것들

페이지네이션 응답이 일관되지 않을 때 원인을 체계적으로 추적하는 방법.

API에서 페이지네이션을 제공할 때, 같은 요청을 여러 번 해도 다른 결과가 올 수 있다. 이건 데이터 증가, 정렬 불안정성, 또는 캐싱 문제 때문이다.

첫 번째: 상태 코드 확인

# 페이지 1 요청
curl -i 'https://api.example.com/api/items?page=1&limit=10'

응답 상태 코드를 보자.

  • 200: 정상
  • 400: 페이지 번호 형식 에러
  • 500: 서버 에러

각 코드에 따라 접근이 달라진다.

응답 Body의 구조 확인

{
  "data": [...],
  "meta": {
    "page": 1,
    "limit": 10,
    "total": 100,
    "pages": 10
  }
}

확인할 것:

  • data 배열의 크기가 limit과 일치하나?
  • total이 정말 전체 개수인가?
  • pages 계산이 맞나? (total / limit을 올림한 값)

정렬 기준이 명확한가?

# 같은 요청 두 번
curl -s 'https://api.example.com/api/items?page=1' | jq '.data[].id'
curl -s 'https://api.example.com/api/items?page=1' | jq '.data[].id'

결과가 다르면 정렬 기준이 없거나 불안정하다.

안정적인 정렬

# ✅ 좋은 예
curl -s 'https://api.example.com/api/items?page=1&sort=created_at&order=desc'

# ❌ 나쁜 예
curl -s 'https://api.example.com/api/items?page=1'  # 정렬 기준 없음

항상 명시적인 정렬 기준이 필요하다. 보통 id 또는 created_at으로 정렬한다.

데이터 변화 감지

페이지네이션 중에 데이터가 추가되거나 삭제될 수 있다.

# 1. 전체 개수 확인
curl -s 'https://api.example.com/api/items?page=1' | jq '.meta.total'
# 결과: 100

# 2. 시간이 지난 후
curl -s 'https://api.example.com/api/items?page=1' | jq '.meta.total'
# 결과: 101 (데이터 추가됨)

Total이 변했다는 건 중간에 데이터가 들어왔다는 뜻이다. 일관성을 유지하려면:

# 같은 cursor를 유지
curl -s 'https://api.example.com/api/items?cursor=abc123&limit=10'

Cursor 기반 페이지네이션이 더 안정적이다.

비교 기준 설정

# 상황 1: Offset-based pagination
API가 page와 limit을 지원
문제: 중간에 삽입/삭제되면 틀림

# 상황 2: Cursor-based pagination
API가 cursor를 지원
문제: 덜 함

# 상황 3: Keyset pagination
API가 after/before를 지원
문제: 거의 없음

로컬과 운영 비교

# 로컬
curl -s 'http://localhost:3000/api/items?page=1&limit=5' > local-page1.json

# 운영
curl -s 'https://api.example.com/api/items?page=1&limit=5' > prod-page1.json

# 비교
jq '.meta' local-page1.json
jq '.meta' prod-page1.json

메타 정보가 일치해야 한다.

경계 케이스 확인

# 마지막 페이지는?
curl -s 'https://api.example.com/api/items?page=10&limit=10' | jq '.data | length'
# 10개가 나오나? 10개 미만이 나오나?

# 존재하지 않는 페이지는?
curl -i 'https://api.example.com/api/items?page=999&limit=10'
# 빈 배열을 반환하나? 404를 반환하나?

# 페이지 0은?
curl -i 'https://api.example.com/api/items?page=0&limit=10'
# 에러? 첫 페이지로 처리?

캐싱이 방해하지 않나?

# 캐시를 무시하고 요청
curl -i -H 'Cache-Control: no-cache' 'https://api.example.com/api/items?page=1'

# CDN 캐시 확인
# 응답 헤더의 Cache-Control, ETag 확인
curl -i 'https://api.example.com/api/items?page=1' | grep -i 'cache-control\|etag'

캐시가 너무 길면 새로운 데이터가 안 보인다.

클라이언트 검증

interface PaginatedResponse<T> {
  data: T[];
  meta: {
    page: number;
    limit: number;
    total: number;
    pages: number;
  };
}

function validatePagination<T>(response: PaginatedResponse<T>) {
  // 데이터 개수가 limit 이하인가?
  if (response.data.length > response.meta.limit) {
    throw new Error('Too many items in page');
  }

  // total이 음수인가?
  if (response.meta.total < 0) {
    throw new Error('Invalid total count');
  }

  // pages 계산이 맞나?
  const expectedPages = Math.ceil(response.meta.total / response.meta.limit);
  if (response.meta.pages !== expectedPages) {
    throw new Error('Page count mismatch');
  }

  return response;
}

응답을 받으면 먼저 검증한다.

문제 해결 체크리스트

  1. 같은 요청을 반복해서 결과가 일치하나?
  2. 정렬 기준이 명시되어 있나?
  3. Total이 일정한가? (데이터 추가/삭제 감지)
  4. 로컬과 운영이 일치하나?
  5. 캐싱이 문제는 아닌가?
  6. 경계 케이스 (마지막 페이지, 존재 없는 페이지)는 안 되나?
  7. 응답 구조가 일관된가?

페이지네이션이 불안정하다는 건 보통 정렬이 명시되지 않았거나, 캐싱이 너무 길거나, 중간에 데이터가 변했기 때문이다. 체계적으로 하나씩 확인하면 대부분 찾을 수 있다.