[Backend/API] 캐시와 rate limit을 설계하는 기준
Backend 앱 API를 운영하다 보면 같은 콘텐츠를 여러 사용자가 반복해서 조회하는 경우가 많습니다. 공지사항, 배너, 카테고리, 관광지 목록처럼 공개 데이터는 매번 DB에서 조회하지 않아도 되는 경우가 많죠. 이럴 때 캐시를 사용하면 응답 속도를 개선하고 DB 부하를 줄일 수 있습니다.
반대로 요청이 너무 많이 들어오는 상황도 생각해야 합니다. 검색 API를 짧은 시간에 반복 호출하거나, 봇이 목록 API를 과도하게 요청하면 서버 자원을 빠르게 소모할 수 있습니다. 이때 rate limit을 적용하면 일정 기준을 넘는 요청을 제한할 수 있습니다.
처음에는 캐시는 성능을 빠르게 만드는 기능, rate limit은 요청을 막는 기능 정도로만 생각하기 쉽습니다. 하지만 실제로 정리해보면 캐시와 rate limit은 기준 없이 붙이면 오히려 장애 원인이 될 수 있습니다.
이버ㄴ글에서는 Backend 앱 API에서 캐시와 rate limit을 설계하는 기준을 정리해보겠습니다. API 성능과 안정성을 검토할 때 놓치기 쉬운 캐시 범위, key 설계, TTL, 무효화, rate limit 응답 구조를 중심으로 살펴보겠습니다.
이 글에서 다룰 문제
캐시와 rate limit 기준이 없으면 다음 문제가 생길 수 있습니다.
- 공개 콘텐츠 목록을 매번 DB에서 조회해 부하가 커진다.
- 캐시 key에 검색 조건이 빠져 잘못된 결과가 재사용된다.
- 사용자별 응답이 공유 캐시에 들어가 개인정보가 섞일 수 있다.
- CMS에서 데이터를 수정했는데 앱 API에는 오래된 데이터가 계속 내려간다.
- 과도한 요청이 들어와도 제한이 없어 API 응답이 느려진다.
- rate limit에 걸렸을 때 앱에서 처리할 JSON 응답 형식이 없다.
- 로그나 예제에 실제 서버 URL, IP, 토큰, DB 정보가 남을 수 있다.
캐시는 빠르게 만드는 도구이고, rate limit은 과도한 요청을 막는 도구입니다. 둘 다 유용하지만 응답 범위와 실패 처리 기준을 정하지 않으면 오히려 문제를 만들 수 있습니다.
캐시하기 좋은 API와 조심해야 할 API
모든 API를 캐시하면 안 됩니다. 먼저 공개 데이터와 사용자별 데이터를 나눠봐야 합니다. 가장 중요한 기준은 이 응답을 다른 사용자에게 보여줘도 되는지입니다.
| API 유형 | 캐시 적합도 | 이유 |
|---|---|---|
| 공지 목록 | 높음 | 여러 사용자가 같은 데이터를 조회함 |
| 배너 목록 | 높음 | 변경 빈도가 낮고 공개 데이터임 |
| 카테고리 목록 | 높음 | 구조 데이터라 반복 조회가 많음 |
| 관광지 공개 목록 | 중간 | 검색/필터 조건을 key에 포함해야 함 |
| 사용자 즐겨찾기 | 낮음 | 사용자별 데이터라 공유 캐시 위험 |
| 마이페이지 | 낮음 | 개인정보가 포함될 수 있음 |
| 관리자 API | 주의 | 권한과 최신성이 중요함 |
사용자별 데이터라면 공유 캐시에 넣으면 안 됩니다. 캐시를 쓰더라도 userId나 권한 범위가 key에 포함되어야 하고, 보통은 더 조심스럽게 접근하는 편이 좋습니다.
처음 캐시를 붙일 때는 공개 API부터 시작하는 것이 안전합니다. 사용자별 API는 성능이 필요하더라도 개인정보와 권한 범위가 섞이지 않는지 먼저 확인해야 합니다.
cache key 설계 기준
캐시 key는 응답을 구분하는 기준입니다. 검색어, 페이지, 정렬, 필터가 달라지면 응답도 달라지므로 key에도 포함되어야 합니다.
final class CacheKey
{
public static function noticeList(array $params): string
{
$page = max(1, (int) ($params['page'] ?? 1));
$limit = min(50, max(1, (int) ($params['limit'] ?? 20)));
$keyword = trim((string) ($params['keyword'] ?? ''));
$category = trim((string) ($params['category'] ?? ''));
$sort = (string) ($params['sort'] ?? 'latest');
return implode(':', [
'api',
'v1',
'notices',
'list',
"page={$page}",
"limit={$limit}",
'keyword=' . sha1($keyword),
"category={$category}",
"sort={$sort}",
]);
}
}
검색어를 key에 그대로 넣으면 길이가 길어지거나 특수문자가 섞일 수 있습니다. 필요하면 hash로 바꿔서 key를 안정적으로 만드는 것이 좋습니다. 다만 실제 개인정보가 검색어에 들어갈 수 있다면 로그와 key 관리도 함께 조심해야 합니다.
처음에는 URL이나 파라미터를 대충 이어붙여도 동작하는 것처럼 보일 수 있습니다. 하지만 key에 빠진 조건이 하나만 있어도 서로 다른 요청이 같은 응답을 공유할 수 있습니다. 이 부분은 캐시를 붙일 때 특히 놓치기 쉽습니다.
TTL을 어떻게 정할까
TTL은 캐시가 유지되는 시간입니다. 짧으면 최신성은 좋지만 캐시 효과가 줄고, 길면 성능은 좋아지지만 오래된 데이터가 로드될 수 있습니다.
| 데이터 | 추천 TTL 예시 | 기준 |
|---|---|---|
| 카테고리 | 1시간~1일 | 변경 빈도 낮음 |
| 배너 | 5분~30분 | 노출 기간 변경 가능 |
| 공지 목록 | 1분~10분 | 최신성 중요도 중간 |
| 검색 결과 | 30초~5분 | 조건이 다양하고 변경 가능 |
| 사용자 데이터 | 캐시 신중 | 개인정보/권한 위험 |
정답은 없습니다. 데이터 변경 빈도, 앱에서 요구하는 최신성, DB 부하, CMS 운영 방식에 따라 달라집니다. 처음에는 짧은 TTL로 시작하고 모니터링하면서 조정하는 편이 안전합니다.
캐시 무효화 기준
CMS에서 데이터를 수정했는데 앱 API 캐시가 그대로 남아 있으면 사용자는 오래된 정보를 보게 됩니다. 그래서 무효화 전략이 필요합니다.
final class NoticeCacheInvalidator
{
public static function flushListCache(): void
{
Cache::tags(['api:v1:notices'])->flush();
}
}
Laravel에서 tag cache를 사용하려면 캐시 드라이버 지원 여부를 확인해야 합니다. 지원하지 않는 드라이버라면 key prefix를 관리하거나, 관련 key 목록을 별도로 관리해야 할 수 있습니다.
캐시 무효화는 아래 시점에 검토해볼 수 있습니다.
- CMS에서 공개 상태가 바뀔 때 무효화한다.
- 제목, 본문, 이미지가 수정될 때 무효화한다.
- 게시 시작/종료 시간이 바뀔 때 무효화한다.
- 삭제 또는 복구가 발생할 때 무효화한다.
- 카테고리나 정렬 기준에 영향을 주는 값이 바뀔 때 무효화한다.
처음에는 TTL만 짧게 두면 충분하다고 생각할 수 있습니다. 하지만 CMS 수정 직후 최신성이 중요한 데이터라면 명시적인 무효화 기준을 함께 두는 편이 좋습니다.
사용자별 응답 공유 캐시 금지
캐시에서 가장 조심해야 하는 부분은 사용자별 응답입니다. 예를 들어 사용자 즐겨찾기 목록이나 마이페이지 데이터를 공통 key로 캐시하면 다른 사용자에게 잘못된 로드될 수 있습니다.
| 상황 | 주의점 |
|---|---|
| 로그인 사용자 응답 | userId 또는 권한 범위 없이 공유 캐시 금지 |
| 권한별 데이터 | role, permission scope를 key에 반영해야 함 |
| 위치 기반 응답 | 좌표를 정규화하거나 캐시하지 않는 기준 필요 |
| 검색어 기반 응답 | 개인정보 검색어가 로그/key에 남지 않도록 주의 |
| 관리자 응답 | 캐시보다 정확성과 권한 확인이 우선 |
사용자별 응답을 잘못 캐시하면 성능 문제가 아니라 보안 문제가 됩니다. 그래서 캐시 적용 전에 이 응답이 여러 사용자에게 공유되어도 되는지 먼저 확인해야 합니다.
rate limit 기본 구조
rate limit은 일정 시간 동안 허용할 요청 수를 정하는 정책입니다. 공개 API와 인증 API는 기준을 다르게 잡을 수 있습니다.
// 예시: Laravel RouteServiceProvider 또는 middleware 구성에서 개념적으로 사용
RateLimiter::for('public-api', function ($request) {
return Limit::perMinute(60)->by($request->ip());
});
RateLimiter::for('user-api', function ($request) {
return Limit::perMinute(120)->by(optional($request->user())->id ?: $request->ip());
});
실제 값은 서비스 규모와 API 특성에 맞춰 조정해야 합니다. 검색 API처럼 부하가 큰 API는 더 낮게 잡고, 가벼운 카테고리 API는 조금 여유 있게 둘 수 있습니다.
처음에는 모든 API에 같은 제한을 걸고 싶을 수 있지만 공개 API, 로그인 사용자 API, 검색 API는 부하와 사용 패턴이 다르기 때문에 기준을 나눠보는 편이 좋습니다.
429 JSON 응답 형식
rate limit에 걸렸을 때 앱이 처리할 수 있는 JSON 응답을 내려야 합니다. HTML 오류 페이지가 내려가면 앱에서 사용자 안내를 만들기 어렵습니다.
{
"error": {
"code": "RATE_LIMITED",
"message": "요청이 많습니다. 잠시 후 다시 시도해 주세요.",
"retryAfterSeconds": 30
}
}
앱에서는 이 값을 보고 재시도 버튼을 잠시 비활성화하거나 안내 메시지를 보여줄 수 있습니다. 사용자에게 내부 제한 정책이나 IP 정보를 그대로 보여줄 필요는 없습니다.
캐시와 rate limit 테스트 기준
캐시는 응답이 빨라졌는지뿐 아니라 잘못된 응답이 재사용되지 않는지도 테스트해야 합니다. rate limit은 제한이 걸리는지뿐 아니라 앱이 처리 가능한 응답 형식으로 로드되는지도 확인해야 합니다.
page,keyword,filter,sort가 cache key에 반영되는지 확인한다.- 사용자별 응답이 공유 캐시에 들어가지 않는지 확인한다.
- CMS 수정 후 관련 캐시가 무효화되는지 확인한다.
- TTL이 데이터 최신성 기준과 맞는지 확인한다.
- rate limit 초과 시 429 JSON 응답이 내려가는지 확인한다.
- rate limit 기준이 공개 API와 사용자 API에서 분리되는지 확인한다.
- 로그에 실제 토큰, IP, 개인정보가 과도하게 남지 않는지 확인한다.
자주 하는 실수
1. 캐시 key에 page나 filter를 빼먹는 경우가 있습니다. 이렇게 되면 서로 다른 요청이 같은 캐시를 공유해 잘못된 목록이 나올 수 있습니다.
2. 사용자별 응답을 공개 데이터처럼 캐시하는 경우도 있습니다. 개인정보나 사용자별 권한이 섞일 수 있어 매우 위험합니다.
3. 캐시 무효화를 생각하지 않는 경우가 있습니다. TTL만 믿으면 CMS 수정 후 한동안 오래된 데이터가 로드될 수 있습니다.
4. rate limit 응답을 기본 HTML 오류로 두는 경우도 있습니다. 앱 API라면 JSON 오류 계약을 유지해야 합니다.
결론
Backend 앱 API에서 캐시와 rate limit은 성능과 안정성을 높이는 중요한 장치입니다. 하지만 기준 없이 적용하면 잘못된 응답이 재사용되거나, 사용자별 데이터가 섞이거나, 앱에서 처리하기 어려운 오류가 로드될 수 있습니다.
좋은 시작점은 공개 콘텐츠 API부터 캐시를 적용하고, cache key에 page, keyword, filter, sort를 포함하며, CMS 수정 시 무효화 기준을 정하는 것입니다. rate limit은 공개 API와 사용자 API를 분리하고, 초과 시 앱이 이해할 수 있는 429 JSON 응답을 내려야 합니다.
처음에는 캐시가 단순히 remember()를 붙이는 작업처럼 보일 수 있습니다. 하지만 운영에서는 key 설계, TTL, 무효화, 사용자별 데이터 분리가 더 중요합니다. 성능 개선보다 먼저 안전한 캐시 범위를 정하는 것이 좋습니다.
참고 자료
- Laravel Cache, Rate Limiting 공식 문서
- HTTP 429 Too Many Requests 응답 설계 관련 문서
- API 캐시 key와 TTL 설계 관련 문서
- 프로젝트 내부 공개 API, 사용자 API, 캐시 무효화 문서