현대 애플리케이션에서는 빠르고 효율적인 API 설계가 필수적입니다. API 성능을 최적화하면 서버 부하를 줄이고, 사용자 경험을 향상할 수 있습니다.
캐싱 (Caching)
✅ 캐싱이란?
캐싱은 동일한 API 요청이 반복될 때, 이전에 받은 응답을 저장해 두었다고 제공하는 기술입니다.
이를 통해 서버 부하를 줄이고, 응답 속도를 높이며, 사용자 경험을 향상시킬 수 있습니다.
🚀 캐싱 적용 방법
캐싱을 적용할 때는 HTTP 헤더를 활용하는 것이 일반적입니다.
캐싱 헤더 | 설명 |
Cache-Control | 클라이언트와 프록시 서버가 캐싱을 어떻게 처리해야 하는지 정의 |
ETag | 데이터가 변경되었는지 확인하는 해시 값 제공 |
Last-Modified | 마지막으로 리소스가 변경된 시간을 제공 |
캐싱 방식 3가지(Cache-Control, ETag, Last-Modified) 중 언제 어떤 걸 사용해야 할까? 🤔
1️⃣ Cache-Control → 정적 리소스(변경이 거의 없는 데이터)에 적합
특징
- 클라이언트(브라우저)와 프록시 서버가 데이터를 캐싱할 수 있도록 명시적으로 설정함
- 특정 시간 동안 데이터가 변경되지 않는다고 가정하고, 새 요청 없이 캐시된 데이터를 사용
- max-age, public/private, no-store 등의 다양한 옵션 제공
📌 언제 사용하면 좋을까?
✅ CSS, JS, 이미지 같은 정적 파일
✅ 매우 자주 요청되지만 자주 변경되지 않는 API 응답
✅ 트래픽을 줄여 성능을 최적화해야 할 때
✅ 클라이언트가 즉시 캐시된 데이터를 사용할 수 있도록 해야 할 때
🚀 예제
✔ 정적 파일 캐싱 (이미지, JS, CSS)
Cache-Control: max-age=86400, public
➡ 이 파일은 24시간(86400초) 동안 캐싱됨. 클라이언트는 서버 요청 없이 캐시된 데이터를 사용 가능.
✔ 변경 가능성이 있는 데이터 (API 응답)
Cache-Control: no-cache
➡ 캐시 사용은 가능하지만, 서버에서 변경 여부를 다시 확인한 후 데이터를 가져옴.
✔ 민감한 데이터 (로그인, 개인 정보)
Cache-Control: no-store
➡ 절대 캐싱하지 않도록 설정 (ex: 로그인 페이지, 인증 관련 데이터 등).
2️⃣ ETag → 변경 가능성이 있는 데이터에 적합
특징
- 서버가 데이터의 고유한 해시값(ETag)을 생성하여 데이터 변경 여부를 감지함
- 클라이언트가 ETag 값을 보내면 서버에서 변경 여부를 확인하고, 변경되지 않았다면 캐시된 데이터 사용(304 Not Modified 응답)
- 데이터가 자주 변경될 가능성이 있지만, 무조건 다시 다운로드할 필요가 없는 경우 유용
📌 언제 사용하면 좋을까?
✅ 자주 변경되는 데이터(API 응답, HTML 파일)
✅ 서버에서 데이터 변경 여부를 직접 확인해야 할 때
✅ 최적화가 필요하지만, 최신 데이터 유지도 중요한 경우
🚀 예제
✔ API 응답에 ETag 적용
HTTP/1.1 200 OK
ETag: "abc123"
➡ 클라이언트가 이후 요청 시 If-None-Match: "abc123"을 보내면, 서버에서 변경 여부를 확인 후 304 Not Modified 응답 가능.
✔ 클라이언트 요청 (변경되지 않은 경우)
GET /products HTTP/1.1
If-None-Match: "abc123"
✔ 서버 응답 (변경 없음)
HTTP/1.1 304 Not Modified
➡ 서버가 기존 데이터를 그대로 사용하도록 유도하여 불필요한 데이터 전송을 줄임.
3️⃣ Last-Modified → 정확한 변경 시간을 기반으로 데이터 변경 여부를 확인할 때 적합
특징
- 해당 리소스가 마지막으로 변경된 시간을 기록하여 캐싱할 수 있도록 제공
- 클라이언트가 If-Modified-Since를 보내면 서버에서 해당 날짜 이후 변경이 없으면 304 Not Modified 응답을 반환함
- ETag보다는 간단하지만, 초 단위로만 비교하므로 미세한 변경 감지는 어려울 수 있음
📌 언제 사용하면 좋을까?
✅ 날짜 기반으로 변경 여부를 판단할 수 있는 경우
✅ ETag보다 가벼운 방식이 필요할 때
✅ 데이터가 일정 주기로 업데이트되는 경우 (로그 파일, 뉴스 데이터 등)
🚀 예제
✔ 서버 응답 (최초 요청)
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
✔ 클라이언트 요청 (변경되지 않은 경우)
GET /products HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT
✔ 서버 응답 (변경 없음)
HTTP/1.1 304 Not Modified
➡ 서버는 데이터가 변경되지 않았으므로 캐시된 데이터를 그대로 사용하도록 지시함.
✅ 캐싱 방식별 사용 추천
캐싱 방식 | 사용 추천 사례 | 특징 |
Cache-Control | 정적 파일 (CSS, JS, 이미지) | 클라이언트 캐시에 특정 시간 동안 저장 |
ETag | API 응답 (자주 변경됨) | 해시값 기반 변경 감지, 서버 확인 필요 |
Last-Modified | 로그 데이터, 뉴스 데이터 | 변경 시간을 기준으로 서버에서 비교 |
스로틀링(Throttling)과 요청 제한(Request Rate Limiting)
✅ 스로틀링이란?
스로틀링은 일정한 속도로 API 요청을 제한하여 서버의 과부하를 방지하는 기술입니다. 예를 들어, 초당 10개의 요청만 허용하도록 설정할 수 있습니다.
🚀 스로틀링 적용
Spring Boot에서는 Bucket4j 또는 Spring RateLimiter를 사용하여 스로틀링을 적용할 수 있습니다.
@RestController
@RequestMapping("/api")
public class ThrottleController {
private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 초당 10개 요청 허용
@GetMapping("/data")
public ResponseEntity<String> getData() {
if (!rateLimiter.tryAcquire()) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("429 Too Many Requests");
}
return ResponseEntity.ok("Success");
}
}
➡ 클라이언트가 초당 10개 이상의 요청을 보내면 429 Too Many Requests 응답 반환
HTTP/1.1 429 Too Many Requests
Retry-After: 1 # 1초 후 다시 시도 가능
✅ 요청 제한이란?
요청 제한은 특정 기간 동안의 최대 허용 요청 수를 제한하는 방법입니다. 예를 들어, 1분에 100개의 요청을 초과하면 해당 사용자의 요청을 차단합니다.
🚀 요청 제한 적용 (Spring Boot + Bucket4j)
@RestController
@RequestMapping("/api")
public class RateLimitController {
private final Bucket bucket;
public RateLimitController() {
this.bucket = Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
.build();
}
@GetMapping("/limited-data")
public ResponseEntity<String> getLimitedData() {
if (!bucket.tryConsume(1)) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("429 Too Many Requests");
}
return ResponseEntity.ok("Success");
}
}
➡ 사용자가 1분에 100개 이상의 요청을 보낼 경우 429 Too Many Requests 응답 반환
HTTP/1.1 429 Too Many Requests
Retry-After: 60 # 60초 후 다시 요청 가능
스로틀링(Throttling)과 요청 제한(Request Rate Limiting)은 어떤 공격 기법을 방어하는가? 🤔
두 가지 기법은 서버 과부하를 방지하고 악의적인 트래픽을 차단하기 위한 보안 및 성능 최적화 방법입니다.
✅ 스로틀링(Throttling) - 서버 부하 관리 및 속도 제어
- API Abuse (API 남용) ➡️ 악의적인 사용자가 반복적으로 요청을 보내 API를 과부하 상태로 만드는 문제
- 스크래핑 (Scraping) ➡️ 웹 크롤러 또는 봇이 짧은 시간 내에 대량의 데이터를 가져가는 문제
- 서버 리소스 과부하 ➡️ 정상 사용자가 과도한 요청을 보내 서버 응답 속도가 저하되는 문제
- Qos (Quality of Service) 보장 ➡️ 특정 사용자나 봇이 너무 많은 요청을 보내 다른 사용자의 서비스 품질이 저하되는 문제
📌 즉, 스로틀링은 서버가 요청을 처리하는 속도를 제한하여 과도한 트래픽이 발생하지 않도록 함.
📌 악의적인 사용자가 무한 요청을 보내더라도 일정 속도로만 처리되므로 서버가 과부하되지 않음.
✅ 요청 제한(Request Rate Limiting) - 특정 시간 내 요청 횟수 제한
- DDos 공격 (Distributed Denial of Service) ➡️ 다수의 봇이나 IP에서 짧은 시간 내에 과도한 요청을 보내 서버를 다운시키는 공격
- 브루트 포스 공격 (Brute Force Attack) ➡️ 로그인 페이지에서 무작위 비밀번호를 시도하여 계정을 해킹하려는 공격
- 크리덴셜 스터핑 (Credential Stuffing) ➡️ 유출된 계정 정보(아이디/비밀번호)를 대량으로 입력하여 로그인 시도를 반복하는 공격
- 비정상적인 API 사용 ➡️ API Key 또는 사용자 계정이 특정 시간 내에 비정상적으로 많은 요청을 보내는 경우
📌 즉, 요청 제한은 특정 기간 내 요청 횟수를 제한하여 악성 트래픽을 차단하는 데 효과적임.
📌 예를 들어 1분에 100개 이상의 로그인 시도가 발생하면 공격자로 간주하고 차단할 수 있음.
스로틀링(Throttling)과 요청 제한(Rate Limitting) 기법을 함께 적용하면 좋은 경우
➡ 예를 들어, 로그인 API에서는 요청 제한(1분 10회) + 스로틀링(초당 1회 요청) 적용
➡ API 전체에서는 초당 100개 요청 허용 + 1분 1000개 요청 초과 시 차단
스로틀링(Throttling) | 서버가 일정 속도로만 요청을 처리하도록 조절 (부하 방지) |
요청 제한 (Request Rate Limiting) | 특정 시간 내 요청 횟수를 초과하면 차단 (공격 방어) |
압축 (Comporession)
✅ 압축이란?
서버가 클라이언트에 데이터를 전송할 때, 압축 알고리즘을 사용하여 데이터 크기를 줄이는 방법입니다. 이를 통해 대역폭을 절약하고 응답 시간을 단축할 수 있습니다.
📌 대표적인 압축 방식
압축 알고리즘 | 설명 |
GZIP | 가장 널리 사용되며, 대부분의 브라우저에서 지원 |
Brotli | GZIP 보다 높은 압축률을 제공하지만 일부 브라우저 제한 |
🚀 압축 적용 방법
1️⃣ GZIP 압축 적용 (Spring Boot 예제)
Spring Boot에서는 GZIP 압축을 사용하여 응답 데이터를 압축할 수 있습니다.
✔ 서버 설정 (application.properties)
server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,application/json
server.compression.min-response-size=1024
➡ 응답 크기가 1024 바이트 이상이면 GZIP으로 압축하여 전송
✔ 클라이언트 요청 헤더
GET /data HTTP/1.1
Accept-Encoding: gzip
✔ 서버 응답 헤더
HTTP/1.1 200 OK
Content-Encoding: gzip
2️⃣ Brotli 압축 적용 (Nginx 설정 예제)
Brotli는 GZIP 보다 높은 압축률을 제공하며, 일부 브라우저에서 지원됩니다.
✔ Nginx에서 Brotli 압축 활성화
rotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript;
➡ JSON, CSS, JS 등의 응답을 Brotli로 압축하여 전송
✔ 클라이언트 요청 헤더
GET /data HTTP/1.1
Accept-Encoding: br, gzip
✔ 서버 응답 헤더
HTTP/1.1 200 OK
Content-Encoding: br
페이징 (Paging)과 필터링(FIltering)
✅ 페이징이란?
페이징은 대량의 데이터를 여러 개의 작은 페이지로 나누어 제공하는 기법입니다. 이를 통해 한 번에 많은 데이터를 가져오는 부담을 줄일 수 있습니다.
🚀 페이징 적용 방법 (Spring Boot + JPA)
✔ Controller
@RestController
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public Page<Product> getProducts(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return productService.getProducts(PageRequest.of(page, size));
}
}
✔ Service
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Page<Product> getProducts(Pageable pageable) {
return productRepository.findAll(pageable);
}
}
✔ 요청
GET /products?page=1&size=5
✔ 응답
{
"content": [
{ "id": 6, "name": "Product 6" },
{ "id": 7, "name": "Product 7" },
...
],
"totalPages": 10,
"totalElements": 50,
"size": 5,
"number": 1
}
✅ 필터링이란?
필터링은 사용자가 원하는 특정 조건에 맞는 데이터만 조회할 수 있도록 지원하는 기법입니다.
🚀 필터링 적용 방법 (Spring Boot + JPA)
✔ Controller 코드
@GetMapping("/filter")
public List<Product> getFilteredProducts(@RequestParam String category) {
return productService.getFilteredProducts(category);
}
✔ Service 코드
public List<Product> getFilteredProducts(String category) {
return productRepository.findByCategory(category);
}
✔ Repository 코드
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByCategory(String category);
}
➡ 카테고리별로 필터링하여 조회 가능
✔ 요청
GET /products/filter?category=electronics
✔ 응답
[
{ "id": 1, "name": "Smartphone" },
{ "id": 2, "name": "Laptop" }
]
비동기 처리 (Asynchronous Processing)
✅ 비동기 처리란?
비동기 처리는 작업을 백그라운드에서 실행하여 메인 프로세스를 차단하지 않고 동시에 여러 작업을 수행하는 방식입니다. 이를 통해 API 응답 시간을 단축하고, 서버 성능을 최적화할 수 있습니다.
📌 비동기 처리가 필요한 경우
작업 유형 | 설명 |
이메일 전송 | 사용자가 회원가입 시 이메일 발송 |
이미지 업로드 | 업로드 후 썸네일 생성 및 최적화 처리 |
대량 데이터 처리 | 많은 데이터를 한 번에 처리할 경우 |
🚀 비동기 처리 적용 (Spring Boot @Async)
✔ Service 코드
@Service
public class EmailService {
@Async
public void sendEmail(String email) {
try {
Thread.sleep(3000); // 이메일 전송 시뮬레이션
System.out.println("Email sent to: " + email);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
✔ Controller 코드
@RestController
@RequestMapping("/users")
public class UserController {
private final EmailService emailService;
public UserController(EmailService emailService) {
this.emailService = emailService;
}
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestParam String email) {
emailService.sendEmail(email); // 비동기 방식으로 이메일 전송
return ResponseEntity.ok("User registered successfully. Email will be sent asynchronously.");
}
}
➡ 회원가입 요청을 받으면 비동기적으로 이메일을 전송하며, API 응답은 즉시 반환
✔ 요청
POST /users/register?email=test@example.com
✔ 응답 (즉시 반환됨)
{
"message": "User registered successfully. Email will be sent asynchronously."
}
'기술(Tech) > Network & System' 카테고리의 다른 글
gRPC에 대해 알아보자 (0) | 2025.02.15 |
---|---|
REST API vs. GraphQL: 어떤 것을 선택해야 할까?💭 (0) | 2025.02.09 |