우리는 Spring Boot에서 ExecutorService를 사용하여 Payment API 지연 시간을 60% 감소시켰습니다
발행: (2025년 12월 29일 오전 12:02 GMT+9)
4 min read
원문: Dev.to
Source: Dev.to
위 링크에 포함된 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. 코드 블록, URL 및 마크다운 형식은 그대로 유지하면서 번역을 진행합니다.
🏦 Business Scenario (Very Common in FinTech)
결제 처리 서비스를 상상해 보세요.
결제를 처리하기 전에 시스템은 다음을 검증해야 합니다:
- 계정 상태 (활성 / 차단)
- 잔액 확인
- 사기 위험 검사
각 검증은:
- 다른 내부 서비스를 호출하고
- 300–800 ms가 소요되며
- 독립적입니다.
잘못된 접근 방식 (순차 처리)
- 총 시간 ≈ 2 seconds
- 높은 지연 → SLA 위반
올바른 접근 방식 (ExecutorService를 이용한 병렬 처리)
- 모든 검증을 동시에 실행
- 총 시간 ≈ max(800 ms)
🧠 Why ExecutorService Here?
- Controlled thread pool (avoid thread explosion)
- Parallel execution
- Better SLA
- Clean error handling
This is exactly where ExecutorService shines.
아키텍처 흐름
Client
|
v
Payment API
|
+-- Account Validation (Thread-1)
+-- Balance Check (Thread-2)
+-- Fraud Check (Thread-3)
|
v
Final Decision
1️⃣ ExecutorService 구성
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Configuration
public class ExecutorConfig {
@Bean
public ExecutorService executorService() {
// Controlled pool for validation tasks
return Executors.newFixedThreadPool(3);
}
}
2️⃣ 검증 서비스 (병렬 작업)
package com.example.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.Callable;
@Service
public class ValidationTasks {
public Callable accountCheck() {
return () -> {
Thread.sleep(500); // simulate service call
return true; // account is active
};
}
public Callable balanceCheck() {
return () -> {
Thread.sleep(700); // simulate service call
return true; // sufficient balance
};
}
public Callable fraudCheck() {
return () -> {
Thread.sleep(800); // simulate service call
return true; // low risk
};
}
}
3️⃣ 결제 오케스트레이터 서비스
This is where ExecutorService is actually used.
package com.example.service;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@Service
public class PaymentValidationService {
private final ExecutorService executorService;
private final ValidationTasks tasks;
public PaymentValidationService(
ExecutorService executorService,
ValidationTasks tasks) {
this.executorService = executorService;
this.tasks = tasks;
}
public boolean validatePayment() throws Exception {
List> results = executorService.invokeAll(
List.of(
tasks.accountCheck(),
tasks.balanceCheck(),
tasks.fraudCheck()
)
);
// If any validation fails → reject payment
for (Future result : results) {
if (!result.get()) {
return false;
}
}
return true;
}
}
🔍 invokeAll()을 사용하는 이유
- 여러 작업을 한 번에 제출합니다
- 모든 작업이 완료될 때까지 대기합니다
- 깔끔하고 가독성 높은 오케스트레이션 로직을 제공합니다
4️⃣ REST 컨트롤러
package com.example.controller;
import com.example.service.PaymentValidationService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
private final PaymentValidationService service;
public PaymentController(PaymentValidationService service) {
this.service = service;
}
@PostMapping("/validate-payment")
public String validatePayment() throws Exception {
boolean valid = service.validatePayment();
return valid
? "Payment validation successful"
: "Payment validation failed";
}
}
5️⃣ curl 요청
curl -X POST http://localhost:8080/validate-payment
6️⃣ 응답
Payment validation successful
⏱ Performance Comparison
| Approach | Approx. Time |
|---|---|
| Sequential | ~2.0 sec |
ExecutorService (parallel) | ~0.8 sec |
➡ 60 %+ latency reduction
- Real business problem
- Parallelism (not just async hype)
- Controlled concurrency
- Best usage of
ExecutorService - SLA‑driven design
⚠ 일반적인 실수 (면접에서 이렇게 말하세요)
- 요청당 새로운
ExecutorService생성 - 무제한 스레드 풀 사용
- 모든 것을 무차별적으로 블로킹
- 타임아웃 무시
- 정상적인 종료를 수행하지 않음