서비스를 운영하다 보면 가장 자주 듣는 말 중 하나가 바로 “API 속도 좀 개선해 주세요.” 다. 사용자가 느린 응답을 경험하면 서비스 만족도는 즉시 떨어지고, 심하면 이탈까지 이어진다. 그렇다면 서버 개발자는 API 응답 속도를 어떻게 개선할 수 있을까?
방법은 다양하지만 많은 프로젝트에서 가장 효과가 높고 적용 난도가 적당한 것이 바로 캐싱(Caching) 과 페이징(Pagination) 처리다.
이 글에서는 실제로 제가 여러 프로젝트를 진행하면서 경험한 내용을 중심으로 왜 이 두 가지가 중요한지, 어떻게 적용하는지, 각각의 장단점은 무엇인지 정리해본다.
API 성능은 왜 느려질까?
API가 느려지는 원인은 크게 세 가지다.
1) DB 조회 부하
- 복잡한 조인
- 대량 데이터 Full Scan
- 인덱스 미사용
- 명령어 자체의 비용(ORDER BY, GROUP BY 등)
2) 네트워크/시스템 부하
- 많은 사용자 동시 접속
- 외부 API 호출 지연
- 과도한 직렬화/역직렬화(Json 파싱 등)
3) 비효율적인 API 설계
- 필요한 것보다 많은 데이터를 조회
- N+1문제
- 페이징 없음
- 캐싱 없음
이 중 실무에서 가장 빠르게 개선할 수 있는 부분이 바로 “캐싱”과 “페이징 처리”다.
캐싱(Caching)으로 API 속도를 높이는 방법
캐싱이란?
한 번 계산하거나 조회한 결과를 메모리나 외부 저장소에 저장해두고 재사용하는 것이다.
예시
“한 번 검색한 인기 게시판 목록은 5분 동안 다시 DB에서 조회하지 않는다.”
즉, DB를 100번 읽는 대신 캐시에서 99번 읽게 만드는 기술이다.
캐싱 적용 시 어떤 데이터가 효과가 좋은가?
캐싱은 모든 API에 적용하면 오히려 역효과가 난다. 실제로 효과가 좋은 유형을 골라야 한다.
1) 변화가 적은 데이터(정적 데이터)
- 공통 코드 테이블
- 카테고리 목록
- 지역 목록
- 공지사항 Top N
2) 조회량이 많고, 결과가 일정한 API
- 인기 게시글
- 메인 페이지 구성 데이터
- 추천 상품 목록
3) 정렬·필터링 정책이 고정된 리스트
캐싱 방법들
Spring Cache (로컬 캐싱)
예시
Caffeine / ConcurrentHashMap 기반
@Cacheable(cacheNames = "hotPosts", key = "'top10'")
public List<Post> getHotPosts() {
return postRepository.findTop10ByOrderByViewCountDesc();
}
장점
- 적용 쉬움(애노테이션 한 줄)
- 응답 속도 매우 빠름
- API 서버의 메모리를 활용하므로 외부 의존 적음
단점
- 서버가 여러 대일 경우 캐시 불일치 발생
- 메모리 사용량 증가
Redis 캐싱(분산 캐시)
대부분의 실무 서비스에서 가장 많이 쓰는 방식.
예시
String key = "post:" + postId;
String cached = redis.get(key);
if (cached != null) return cached;
// 없으면 DB에서 조회 후 Redis에 저장
장점
- 모든 서버 간 캐시 공유 가능
- 높은 처리량
- TTL을 활용한 안정적 만료 처리 가능
단점
- Redis 장애 시 성능 급락
- 설정 및 운영 비용 증가
- 네트워크 Hop이 추가되어 로컬 캐싱보다는 느림
Reverse Proxy 캐싱(Nginx, CloudFront 등)
API 서버 앞단에서 캐싱하는 방식.
장점
- API 서버로 트래픽 자체가 감소
- CDN을 쓰면 글로벌 캐싱 가능
단점
- 적용 범위 제한(주로 GET 요청)
- 캐시 무효화(Invalidation) 전략 필요
캐싱 시 주의해야 할 문제들
1) 캐시 불일치(Cache Inconsistency)
데이터는 변경되었는데 캐시는 그대로 남아 있는 상황.
해결
- TTL(TIme to Live) 설정
- 변경 시 캐시 삭제(Cache Evict)
2) 캐시 중복 갱신(Stampede)
동시에 여러 요청이 캐시를 갱신하려고 DB를 때림.
해결
- Redis 분산 락
- Cache-aside 패턴
- Lazy caching + single flight 방식
3) 캐시 메모리 부족
TTL이 짧으면 의미가 없고 길면 메모리가 부족해짐.
해결
- LRU, LFU 등 캐시 정책 적용
- 캐시 대상 선정 철저히
페이징(Pagination)으로 API 속도 개선하기
왜 페이징이 중요한가?
많은 초급 개발자들이 놓치는 부분이 하나 있다.
“대량 데이터 조회는 캐싱보다 페이징이 먼저다.”
예를 들어 게시판 글이 100만 건이라면 100만 건을 API로 다 내려줄 리는 없다. 페이징을 안 하면 그 순간부터 서비스는 느려진다.
- DB의 Full Scan
- 네트워크 전송량 증가
- Jackson 직렬화 부하
그래서 대부분의 실무에서는 페이징이 사실상 필수 기능이다.
페이지네이션 Type별 설명
Offset 기반 페이징 (일반적인 페이징)
SQL의 LIMIT, OFFSET 사용.
예시
SELECT *
FROM posts
ORDER BY id DESC
LIMIT 20 OFFSET 40;
장점
- 개발이 쉬움
- 대부분의 RDB에서 표준 지원
단점
- 페이지 번호가 커질수록 느려짐 (OFFSET 비용 증가)
- 대규모 테이블에서 비효율적
Cursor 기반 페이징 (Keyset Pagination)
특정 기준 값 이후 데이터를 가져오는 방식.
예시
SELECT *
FROM posts
WHERE id < :cursor
ORDER BY id DESC
LIMIT 20;
장점
- 대규모 데이터에서도 매우 빠름
- 인덱스만 타면 성능 최상
단점
- 페이지 점프가 어려움
- 구현 난이도가 조금 있음
캐싱 + 페이징 조합 전략
실무에서는 자주 아래처럼 조합한다.
| 상황 | 추천 전략 |
| 메인 화면의 인기 게시글 | 캐싱(Caffeine/Redis) |
| 통계 조회처럼 변경이 거의 없는 데이터 | 장기 캐싱 |
| 게시판 목록처럼 데이터 양이 많음 | 페이징 필수 |
| 검색 결과처럼 매번 달라지는 데이터 | 캐싱 비효율, 페이징 사용 |
| 트래픽이 많은 인기 API | 페이징 + 일부 구간 캐싱 조합 |
예시
- 인기 게시글 Top 100 → 캐싱
- 게시판 전체 글 목록 → 페이징(Offset or Cursor)
- 게시판 내 인기 글 모음 → 캐싱 + 페이징 혼합
실무 시나리오 예시
제가 개인 프로젝트에서 겪었던 문제 중 하나는
“메인 페이지에 인기 게시글 8개를 노출하는 API가 너무 느리다.”
DB 정렬 + 집계 쿼리가 무거웠기 때문이다.
해결 방법
- 해당 결과를 캐싱(5분 TTL)
- 게시판 전체 글 목록은 Offset → Cursor 페이징으로 변경
결과
- 200ms → 20ms로 응답 속도 10배 개선
- DB 부하 60% 감소
지금은 대부분의 서비스에서 “조회량 많은 데이터는 캐시, 대량 리스트는 페이징”
이 조합을 기본 전략으로 잡고 있다.
마무리
API 응답 속도를 빠르게 만들기 위해 무조건 서버 스펙을 늘리거나 DB 튜닝을 하는 것이 답은 아니다.
많은 경우 단순히 캐싱과 페이징만 잘 적용해도 성능이 극적으로 향상된다.
정리하자면
- 캐싱은 변경이 적고 조회가 많은 데이터에 강력
- 페이징은 대량 데이터 조회에서 필수
- 캐싱 + 페이징 조합은 대부분의 서비스에서 사실상 표준 구성
특히 REST API 기반의 서비스라면 캐싱과 페이징을 통해 체감 성능을 크게 올릴 수 있다.