1. 서두: 성능 최적화를 위한 아키텍처 설계 상황
현재 프로젝트의 성능을 극대화하기 위해 Full-Async 지향 구조를 설계했습니다.
- WebClient: FastAPI와의 외부 통신 비동기화.
- Reactive Redis: Upstash 캐시 접근 비동기화.
- Postgres (JPA): 유일한 동기(Blocking) 구간인 DB 접근을 스케줄러 분리를 통해 해결.
2. 전략: 스케줄러 분리와 자원 동기화
Postgres는 현재 JPA(동기) 방식을 사용하므로, 메인 비동기 일꾼(Event Loop)이 DB 작업 때문에 멈추는 것을 방지하기 위해 **boundedElastic**이라는 별도의 주차장(스레드 풀)을 할당했습니다.
🔧 설정 1: 환경 변수 (.env 또는 application.properties)
DB 커넥션(열쇠)의 개수를 명확히 정의합니다.
# application.properties
spring.datasource.hikari.maximum-pool-size=20
🔧 설정 2: 스케줄러 설정 (DatabaseConfig.java)
비동기 일꾼의 수를 열쇠 수와 일치시킵니다.
@Configuration
public class DatabaseConfig {
@PostConstruct
public void init() {
// 일꾼(Thread)의 수를 열쇠(Connection) 수와 동일하게 20개로 제한
System.setProperty("reactor.schedulers.defaultBoundedElasticSize", "20");
}
}
3. 왜 '열쇠'와 '일꾼'의 수가 동일해야 하는가?
이것이 이번 최적화의 핵심입니다.
- 일꾼(Thread) > 열쇠(Connection): 일꾼은 많은데 열쇠가 부족하면, 남은 일꾼들은 열쇠가 날 때까지 주차장에서 대기하며 메모리만 낭비하고 컨텍스트 스위칭 비용만 발생시킵니다.
- 일꾼(Thread) < 열쇠(Connection): 열쇠는 남는데 일꾼이 없으면 DB의 처리 능력을 100% 활용하지 못합니다.
- 결론: **1:1 매칭(20개:20개)**을 통해 일꾼이 일을 시작하자마자 열쇠를 쥐고 DB에 접근할 수 있는 최적의 병목 제거 상태를 구현했습니다.
4. 실제 사용 코드
동기 DB 작업을 Mono.fromCallable로 감싸고 전용 일꾼에게 맡기는 방식입니다.
public Mono<AnalysisResult> saveToDb(AnalysisResult data) {
return Mono.fromCallable(() -> repository.save(data))
.subscribeOn(Schedulers.boundedElastic()); // 20명의 전용 일꾼 중 하나 사용
}
5. 회고 요약
완벽한 비동기 DB(R2DBC)로 가기 전, 기존 JPA의 안정성을 유지하면서도 서버 전체의 응답성을 확보할 수 있는 최선의 하이브리드 전략을 성공적으로 구축했습니다.
'SPRING BOOT' 카테고리의 다른 글
| [Spring Boot] 11. Cluster DB (0) | 2026.05.20 |
|---|---|
| [Spring Boot] 10. Redis Pub/Sub 기반 실시간 알림 시스템 (0) | 2026.03.27 |
| [Spring Boot] 8. 비동기 Spring WebClient, Mono와 Flux (1) | 2026.03.04 |
| [Spring Boot] 7. Spring Boot CORS 중복 응답(web & webflux 충돌) (0) | 2026.03.04 |
| [Spring Boot] 6. Java 21 가상 스레드 VS 기존 스레드 (0) | 2026.03.03 |