이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 대용량 목록 API를 설계할 때 어떤 체크리스트가 필요한가?
  • 관리자 검색과 사용자 피드는 왜 다른 전략이 필요한가?
  • 목록 API 장애를 줄이려면 어떤 지표와 제한을 둬야 하는가?

개요

대용량 목록 API는 단순한 CRUD 조회가 아니다. 필터, 정렬, pagination, count, 검색, 권한 조건이 합쳐지면 DB에 큰 부하를 만든다. 서비스가 성장하면 가장 흔한 병목 중 하나가 목록 API다.

좋은 목록 API는 기능 요구사항과 DB 비용을 함께 설계한다.

원리

대용량 목록 API의 원리는 “조건에 맞는 row를 반환한다”가 아니라 사용자 경험, DB 접근 범위, 정렬 비용, count 비용, 권한 필터, 보호장치를 하나의 API 계약으로 묶는 것이다. 목록 API는 호출 빈도가 높고 조건 조합이 많아, 작은 비효율이 커넥션 풀 고갈과 DB CPU/I/O 장애로 번지기 쉽다.

백엔드 개발자는 목록 API를 만들 때 처음부터 제한을 설계해야 한다. 최대 size, 기본 기간, 허용 정렬, cursor/offset 선택, export 분리, slow query 알림, rate limit은 기능을 막는 장치가 아니라 운영 가능한 서비스의 일부다.

설계 체크리스트

  • 필수 필터는 무엇인가?
  • 정렬 기준은 고정인가 선택형인가?
  • 전체 count가 정말 필요한가?
  • 임의 페이지 이동이 필요한가?
  • 데이터가 계속 삽입되는가?
  • 삭제와 비공개 처리 정책은 무엇인가?
  • 권한 조건이 인덱스와 충돌하지 않는가?
  • 최대 size 제한이 있는가?
  • timeout과 slow query 알림이 있는가?

사용자 피드

사용자 피드는 보통 최신순 또는 추천순이고, 무한 스크롤이 많다. cursor pagination이 적합하다.

SELECT id, title, created_at
FROM feed_items
WHERE user_id = :userId
  AND (created_at, id) < (:lastCreatedAt, :lastId)
ORDER BY created_at DESC, id DESC
LIMIT 21;
CREATE INDEX idx_feed_user_created_id
ON feed_items (user_id, created_at DESC, id DESC);

추천순 피드는 DB 쿼리만으로 실시간 계산하기보다 precomputed feed나 ranking score를 저장하는 방식이 필요할 수 있다.

관리자 검색

관리자 검색은 조건 조합이 많다.

SELECT id, status, created_at, total_price
FROM orders
WHERE (:status IS NULL OR status = :status)
  AND (:memberId IS NULL OR member_id = :memberId)
  AND created_at >= :from
  AND created_at < :to
ORDER BY created_at DESC
LIMIT 50 OFFSET 0;

모든 조건 조합을 하나의 인덱스로 빠르게 만들 수 없다. 자주 쓰는 조건과 업무상 중요한 조건을 기준으로 우선순위를 둔다. 나머지는 느릴 수 있음을 UX로 제한하거나 비동기 export로 분리한다.

관리자 검색에서는 조건 조합별 사용량을 기록하면 인덱스 우선순위를 정하기 쉽다.

orders admin search
- status + created_at: 72%
- member_id 단건: 18%
- amount range + created_at: 4%
- keyword contains: 1%

모든 1% 조건을 빠르게 만들기보다, 느린 조건은 기간 제한이나 export로 돌리는 것이 운영적으로 더 나을 수 있다.

Count 문제

전체 count는 비쌀 수 있다.

SELECT count(*)
FROM orders
WHERE status = 'PAID'
  AND created_at >= :from
  AND created_at < :to;

전체 페이지 수가 꼭 필요한지 확인한다. 많은 사용자 API는 hasNext만 있으면 충분하다. 관리자 화면도 “정확한 전체 건수” 대신 “검색 결과가 많습니다. 조건을 좁혀주세요”가 더 나은 UX일 수 있다.

제한과 보호장치

대용량 목록 API에는 제한이 필요하다.

  • 최대 size 제한
  • 검색 기간 기본 제한
  • 정렬 옵션 제한
  • 앞쪽 와일드카드 검색 제한
  • 관리자 export 비동기화
  • slow query 알림
  • rate limit

이 제한은 불친절함이 아니라 운영 가능한 API 계약이다.

장애 보호장치는 코드와 설정으로 강제한다.

int safeSize = Math.min(request.size(), 100);
LocalDate from = request.from() != null ? request.from() : LocalDate.now().minusDays(30);

프론트가 실수하거나 악의적 요청이 들어와도 서버가 상한을 지켜야 한다.

실전 팁

  • 목록 API는 PR에서 SQL과 인덱스를 함께 리뷰한다.
  • count가 필요한지 제품과 다시 확인한다.
  • 검색 조건은 사용 빈도 기반으로 인덱스 우선순위를 정한다.
  • 관리자 대량 다운로드는 별도 batch와 파일 다운로드로 분리한다.
  • 최대 조회 기간과 size는 서버에서 강제한다.
  • 목록 API는 대시보드에 p95/p99, rows scanned, slow query fingerprint, count query 시간을 따로 둔다.
  • 관리자 export는 request/response 안에서 끝내려 하지 말고 job id와 파일 다운로드 모델을 검토한다.
  • 권한 조건이 인덱스 선행 컬럼을 깨뜨리지 않는지 실제 SQL로 확인한다.

위험 신호!

  • size=10000 같은 요청을 허용한다.
  • 관리자 검색에 모든 동적 조건을 한 SQL로 넣고 인덱스 검토가 없다.
  • count 쿼리가 매 요청 실행된다.
  • 대량 export를 HTTP 요청 안에서 동기 처리한다.
  • 목록 API의 p95/p99 지표가 없다.
  • size와 기간 제한을 프론트에서만 막고 서버에서 강제하지 않는다.
  • 권한 필터가 뒤에 붙어 많은 row를 읽고 대부분 버린다.
  • count query timeout이 본문 query timeout보다 자주 발생하는데 같은 API 지연으로 뭉뚱그린다.

확인 질문

  • 대용량 목록 API에서 전체 count를 조심해야 하는 이유는 무엇인가?
    • 조건에 맞는 많은 행을 세어야 해 본문 조회보다 더 비싼 쿼리가 될 수 있기 때문이다.
  • 관리자 검색과 사용자 피드가 다른 전략을 요구하는 이유는 무엇인가?
    • 관리자 검색은 조건 조합과 정확성이 중요하고, 사용자 피드는 최신순/추천순과 빠른 다음 페이지가 중요하기 때문이다.
  • 목록 API 보호장치에는 무엇이 있는가?
    • 최대 size, 기간 제한, 정렬 옵션 제한, 비동기 export, rate limit, slow query 알림이 있다.
  • 대용량 목록 API에서 서버가 직접 강제해야 하는 제한은 무엇인가?
    • 최대 page size, 최대 검색 기간, 허용 정렬 옵션, 검색어 길이, export 전환 기준, rate limit을 서버에서 검증해야 한다.

참고 문서