
안녕하세요, 최근 우리 팀은 주문 API의 성능 저하 문제에 직면했었고, 그 중심에는 예외 처리 로직이 있었습니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ApiError> handleOrderNotFoundException(OrderNotFoundException ex) {
log.error("주문을 찾을 수 없습니다: {}", ex.getMessage(), ex); // 전체 스택트레이스 로깅
ApiError error = new ApiError(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
LocalDateTime.now()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// 다른 예외들에 대해 비슷한 패턴 반복
}
@RestControllerAdvice
@Slf4j
public class OptimizedExceptionHandler {
private final ErrorResponseCache errorResponseCache;
private final MetricService metricService;
public OptimizedExceptionHandler(ErrorResponseCache errorResponseCache,
MetricService metricService) {
this.errorResponseCache = errorResponseCache;
this.metricService = metricService;
}
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ApiError> handleOrderNotFoundException(OrderNotFoundException ex) {
// 메트릭 수집
metricService.incrementCounter("exception.order.notfound");
// 로그 레벨 최적화
if (log.isDebugEnabled()) {
log.debug("주문을 찾을 수 없습니다: {}", ex.getMessage());
}
// 캐시된 에러 응답 사용
return errorResponseCache.getErrorResponse(
ExceptionType.ORDER_NOT_FOUND,
ex.getMessage()
);
}
}
@Component
public class ErrorResponseCache {
private final Map<ExceptionType, ApiError> cache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
// 자주 사용되는 에러 응답을 미리 생성하여 캐시
Arrays.stream(ExceptionType.values())
.forEach(type -> cache.put(type, createApiError(type)));
}
public ResponseEntity<ApiError> getErrorResponse(ExceptionType type, String message) {
ApiError error = cache.get(type).clone(); // 프로토타입 패턴 적용
error.setMessage(message);
return new ResponseEntity<>(error, type.getStatus());
}
}
@RestController
@RequestMapping("/api/v1/metrics")
public class MetricController {
private final MetricService metricService;
@GetMapping("/exceptions")
public ExceptionMetrics getExceptionMetrics() {
return metricService.getExceptionMetrics();
}
}
@Service
public class MetricService {
private final MeterRegistry meterRegistry;
public ExceptionMetrics getExceptionMetrics() {
return ExceptionMetrics.builder()
.orderNotFoundCount(getCount("exception.order.notfound"))
.orderValidationFailCount(getCount("exception.order.validation"))
.responseTimeP99(getP99("exception.response.time"))
.build();
}
}
이번 최적화를 통해 대규모 트래픽 환경에서의 예외 처리가 얼마나 중요한지 깨달았습니다.
단순히 예외를 처리하는 것을 넘어, 시스템 전반의 성능에 미치는 영향을 고려한 설계가 필요합니다.
향후에는 다음과 같은 부분을 추가로 개선할 계획입니다.