현대 애플리케이션에서는 빠르고 효율적인 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."
}