Node.js 생태계에서 Prisma는 "그냥 쓰는 ORM"이 됐다. 자동완성이 잘 되고, 스키마 파일 하나로 DB를 관리할 수 있다는 게 매력이다. 그런데 실제로 써보면 생각보다 불편한 지점도 꽤 있다. 이 글에서는 Prisma의 장점과 단점을 솔직하게 정리해본다.

장점이래서 다들 쓴다
타입 안전성이 진짜다
schema.prisma에 모델을 정의하면, 모든 쿼리 결과와 파라미터에 자동으로 타입이 붙는다. TypeScript와 함께 쓰면 런타임 에러가 눈에 띄게 줄어든다. IDE 자동완성도 잘 된다.
스키마 파일 하나로 모든 게 끝난다
prisma/schema.prisma 파일이 DB 구조의 단일 진실 공급원이 된다. 마이그레이션 생성, 클라이언트 코드 생성까지 prisma migrate dev 하나로 연결된다. 팀원이 새로 들어와도 온보딩이 빠르다.
가독성 높은 쿼리 문법
SQL을 직접 쓰거나 메서드 체이닝으로 복잡해지는 다른 ORM과 달리, Prisma는 객체 형태의 쿼리 문법을 사용한다. 중첩 관계 조회도 include 하나로 직관적으로 표현된다.
Prisma Studio — GUI로 데이터 확인
npx prisma studio를 실행하면 브라우저에서 DB 데이터를 바로 보고 수정할 수 있다. 개발 중 빠른 확인에 유용하다.

 

마이그레이션 히스토리 관리
변경 사항이 SQL 파일로 누적되고, prisma migrate deploy로 프로덕션에 안전하게 적용할 수 있다. Docker Compose 기반 배포에서 컨테이너 시작 시 자동 마이그레이션 패턴과 궁합이 좋다.

단점이건 알고 써야 한다
복잡한 쿼리는 결국 Raw SQL
GROUP BY, 윈도우 함수, 복잡한 서브쿼리 등 Prisma의 추상화가 커버하지 못하는 영역이 생각보다 넓다. 결국 $queryRaw를 써야 하는 상황이 오고, 그러면 타입 안전성도 사라진다.
N+1 문제가 조용히 발생한다
관계 데이터를 반복문 안에서 조회하면 쿼리가 N번 나간다. Prisma는 이를 자동으로 막아주지 않는다. 의식적으로 include를 쓰거나, findMany로 미리 가져와야 한다.
번들 사이즈와 콜드 스타트
Prisma 클라이언트는 쿼리 엔진 바이너리를 포함하기 때문에 번들이 무겁다. Lambda 같은 서버리스 환경에서는 콜드 스타트가 눈에 띄게 느려지는 경우가 있다.
스키마 변경 시 팀 협업 충돌
여러 명이 동시에 schema.prisma를 수정하면 마이그레이션 히스토리 충돌이 발생할 수 있다. Git 브랜치 전략과 마이그레이션 관리 규칙을 팀 차원에서 미리 정해두지 않으면 골치 아프다.
JPA에 익숙하다면 개념이 다르다
Dirty Checking, 영속성 컨텍스트, LAZY 로딩 같은 JPA 개념이 Prisma에는 없다. 명시적으로 데이터를 가져오고 업데이트해야 하며, 트랜잭션 처리 방식도 다르다. Spring 배경이라면 초반에 적응 비용이 있다.

한 줄 결론
Prisma는 중소 규모 Node.js 프로젝트에서 생산성이 높다. 타입 안전성과 개발자 경험은 업계 최고 수준이다. 다만 복잡한 쿼리가 많거나 서버리스 환경이라면 Raw SQL 혼용 전략을 처음부터 고려해야 한다. 무조건 좋은 도구는 없고, 프로젝트 성격에 맞게 선택하는 게 맞다.
LIST

📌 서론 — 왜 이 구조를 쓰는가

MSA를 설계하다 보면 항상 부딪히는 고민이 있다.

  • DB를 서비스마다 완전히 분리할 것인가?
  • 아니면 하나의 DB 인스턴스에서 스키마만 나눌 것인가?

특히 초기 스타트업이나 사내 시스템에서는 다음 이유로
PostgreSQL 단일 인스턴스 + 스키마 분리를 선택하는 경우가 많다.

  • 인프라 비용 절감
  • 운영 복잡도 감소
  • 빠른 개발 속도

하지만 이 구조는 “MSA처럼 보이지만 실제로는 강하게 결합된 구조”가 될 위험이 있다.


📌 구조 개요

PostgreSQL (Single Instance)
├── auth_schema
├── order_schema
├── payment_schema
└── user_schema
 
  • 하나의 DB 인스턴스
  • 서비스별 schema 분리
  • 물리적 분리는 없음 (논리적 분리)

📌 영향도 분석 (핵심)

1️⃣ 장애 전파 (Blast Radius 문제)

문제

  • DB 인스턴스 하나가 죽으면 전체 서비스 다운

예시

  • payment에서 lock 발생 → DB 전체 성능 저하 → auth까지 영향

👉 MSA의 핵심인 장애 격리 실패


2️⃣ 리소스 경쟁 (CPU / I/O / Connection Pool)

문제

  • 모든 서비스가 동일 리소스 공유

케이스

  • 배치 (정산) 작업 → 디스크 I/O 폭증
  • → 실시간 API latency 증가

👉 특히 르무엘님 하는 “정산 배치”에서 자주 터짐


3️⃣ 트랜잭션 결합 위험

문제

  • cross-schema join이 쉬움
 
SELECT *
FROM order_schema.orders o
JOIN payment_schema.payments p
ON o.id = p.order_id;
 

👉 이 순간 MSA 깨짐

  • 서비스 간 DB 의존 발생
  • 나중에 분리 불가능한 구조 됨

4️⃣ 배포 독립성 저하

문제

  • DB migration 충돌

예시

  • auth 서비스 migration 실행
  • payment 서비스와 lock 충돌

👉 Flyway/Liquibase 운영 난이도 상승


5️⃣ 보안/권한 관리 복잡도

문제

  • schema level 권한 관리 필요
 
GRANT USAGE ON SCHEMA auth_schema TO auth_user;
 

👉 잘못 설정하면:

  • 다른 서비스 데이터 접근 가능
  • 데이터 격리 실패

6️⃣ 스케일링 한계

문제

  • 특정 서비스만 확장 불가

예:

  • search 서비스만 트래픽 폭증
  • → DB 전체 스케일 필요

👉 비용 비효율 발생

 

 

📌 언제 써도 되는가 (현실적인 기준)

✅ 적합한 상황

  • 초기 스타트업 / PoC
  • 트래픽 낮음
  • 팀 규모 작음
  • 빠른 개발이 중요한 경우

👉 “MSA 흉내 + 빠른 실행” 단계


❌ 피해야 하는 상황

  • 금융 / 결제 / 정산 시스템
  • 트래픽 높은 서비스
  • 서비스 간 완전한 독립 필요

👉 르무엘님 구조는 사실 여기 해당됨


📌 실무 대응 전략 (중요)

1️⃣ 강제 격리 룰 설정

  • cross-schema join 금지
  • DB 접근은 서비스 내부에서만

👉 코드 리뷰로 강제


2️⃣ 커넥션 풀 분리

  • 서비스별 datasource 분리
 
maxPoolSize: 10 (service별 제한)
 

👉 특정 서비스가 DB 독점 못하게


3️⃣ 점진적 분리 전략

초기:

  • single instance + schema

중기:

  • read replica 분리

후기:

  • DB per service

👉 “탈출 전략” 반드시 설계해야 함


4️⃣ 이벤트 기반으로 전환

  • DB join 대신
  • Kafka / 이벤트 사용
OrderCreated → PaymentService consume
 

👉 DB 의존 제거


5️⃣ 배치 분리

  • 정산 / 통계는 별도 DB or replica

👉 운영 안정성 핵심 포인트


📌 결론

PostgreSQL 단일 인스턴스 + 스키마 분리 전략은
**“MSA의 입문 단계에서만 유효한 타협안”**이다.

하지만 다음을 명확히 인지해야 한다.

  • 장애는 공유된다
  • 성능은 경쟁한다
  • 구조는 쉽게 무너진다

👉 결국 중요한 건 하나다

“나중에 분리할 수 있는 구조인가?”


📌 한줄 정리

👉 스키마 분리는 시작일 뿐, 끝이 아니다 — 탈출 전략 없는 MSA는 모놀리식이다

LIST

데이터베이스 스키마 형상 관리는 백엔드 개발자에게 숙명과도 같습니다. Java 진영의 전통 강자 Flyway와 Node.js/TypeScript 생태계의 혁신 Prisma Migrate는 서로 다른 철학을 가지고 있습니다. 8년 차 개발자의 시선으로 두 도구의 차이점과 장단점을 분석해 봅니다.


1. 철학의 차이: SQL 중심 vs 모델 중심

가장 큰 차이는 **'무엇을 기준으로 스키마를 정의하느냐'**에 있습니다.

  • Flyway (Versioned Migration): "SQL이 곧 진리다." 개발자가 직접 V1__create_table.sql 같은 순수 SQL 파일을 작성합니다. DB 상태를 코드(SQL)의 실행 순서로 관리하는 방식입니다.
  • Prisma (Declarative Migration): "데이터 모델이 곧 진리다." 개발자는 schema.prisma라는 선언적 파일에 엔티티 구조를 정의합니다. Prisma 엔진이 현재 DB 상태와 모델의 차이(Diff)를 분석해 마이그레이션 파일을 자동으로 생성합니다.

2. 장단점 비교

비교 항목 Flyway (SQL 기반) Prisma (모델 기반)
제어권 매우 높음. DB 전용 문법이나 복잡한 인덱스 설정을 100% 활용 가능. 보통. 대부분 지원하지만, 매우 특수한 DB 기능은 직접 SQL을 수정해야 함.
생산성 낮음. 테이블 변경 시마다 SQL을 직접 짜고 오타를 체크해야 함. 매우 높음. 필드 추가 후 명령어 하나면 SQL 파일이 자동 생성됨.
학습 곡선 낮음. SQL만 알면 바로 사용 가능. 보통. Prisma 고유의 DSL(Domain Specific Language) 학습 필요.
안정성 인간의 실수(오타, 순서 누락) 가능성이 존재함. 모델 기반 자동 생성으로 휴먼 에러가 현격히 줄어듦.
타입 지원 별도의 라이브러리 없이는 DB와 코드 간 타입 동기화가 어려움. 최강. 스키마 변경 시 클라이언트 타입 정의가 실시간 업데이트됨.

3. 어떤 상황에 무엇을 써야 할까?

Flyway를 추천하는 경우

  • 복잡한 레거시 DB: 이미 수백 개의 테이블이 있고, 세밀한 SQL 튜닝이 필수적인 경우.
  • 다국어/다양한 스택: 한 프로젝트에서 Java, Go, Python 등 여러 언어가 동일한 DB를 바라볼 때(Flyway는 독립적인 실행이 용이함).
  • DBA 중심 환경: DBA가 직접 SQL 스크립트를 검수하고 승인하는 프로세스가 엄격한 조직.

Prisma를 추천하는 경우

  • TypeScript 기반 프로젝트: 타입 안정성(Type Safety)이 프로젝트의 핵심 가치일 때.
  • 빠른 이터레이션: 스타트업이나 신규 프로젝트처럼 스키마 변경이 빈번하게 일어날 때.
  • 객체 지향적 개발: SQL의 세부 사항보다는 비즈니스 로직과 데이터 모델링에 더 집중하고 싶을 때.

4. 결론: 도구는 목적을 위한 수단일 뿐

Flyway는 **"내가 명시한 대로만 움직여라"**라는 통제 중심의 도구이며, Prisma는 **"내가 원하는 결과(모델)를 만들어줘"**라는 결과 중심의 도구입니다. 

LIST

그리고 실제 원인은 무엇인가

Spring Boot 프로젝트를 하다 보면 이런 이야기를 자주 듣습니다.

JPA는 느리다
MyBatis가 더 빠르다
 

하지만 실제로는 JPA 자체가 느린 것이 아니라 잘못 사용하는 경우가 많습니다.

이번 글에서는 JPA가 느리다고 느껴지는 대표적인 이유와 실제 원인을 정리합니다.


1. N+1 문제

JPA 성능 문제의 90%는 N+1 문제입니다.

예를 들어

 
List<Order> orders = orderRepository.findAll();
 

그리고

 
order.getMember().getName();
 

이렇게 접근하면 실행되는 SQL은 다음과 같습니다.

SELECT * FROM orders
SELECT * FROM member WHERE id = 1
SELECT * FROM member WHERE id = 2
SELECT * FROM member WHERE id = 3
 

1 + N
 

쿼리가 실행됩니다.


해결 방법

fetch join 사용

 
@Query("""
SELECT o FROM Order o
JOIN FETCH o.member
""")
 

또는

@EntityGraph
 

사용.


2. Lazy Loading 오해

JPA 기본 전략

ManyToOne → EAGER
OneToMany → LAZY
 

하지만 실무에서는 대부분 이렇게 변경합니다.

모든 관계 → LAZY
 

이유는

불필요한 JOIN 방지
 

입니다.


3. Dirty Checking 비용

JPA는 트랜잭션 종료 시

Dirty Checking
 

을 수행합니다.

엔티티 변경 여부 검사
 

입니다.

대량 데이터 처리 시

10000 entities
 

이면 Dirty Checking 비용이 커집니다.


해결 방법

조회 트랜잭션

 
@Transactional(readOnly = true)
 

4. Persistence Context 메모리 증가

JPA는 조회한 엔티티를 1차 캐시(Persistence Context)에 저장합니다.

 
List<Order> orders = orderRepository.findAll();
 

만약

100000 rows
 

를 조회하면

메모리 증가
 

합니다.


해결 방법

배치 처리 시

flush
clear
 

사용합니다.


5. 불필요한 SELECT

 
orderRepository.save(order);
 

이 경우 JPA는

SELECT
INSERT
 

두 번 실행할 수 있습니다.

이유

merge 동작
 

때문입니다.


6. 잘못된 Fetch 전략

 
@OneToMany(fetch = FetchType.EAGER)
 

이 경우

JOIN 폭발
 

이 발생합니다.

결과

Cartesian Product
 

문제가 발생합니다.


7. JPA를 ORM이 아닌 SQL처럼 사용

JPA는

객체 그래프 관리
 

를 위한 도구입니다.

하지만 이렇게 사용하는 경우가 있습니다.

 
for (Order order : orders) {
orderRepository.save(order);
}
 

결과

1000 UPDATE
 

이 실행됩니다.


실제로 JPA는 느린가?

대부분 경우

JPA = SQL 생성 도구
 

입니다.

JPA가 생성하는 SQL이

좋은 SQL이면 빠르고
나쁜 SQL이면 느립니다.
 

문제는 JPA가 아니라 SQL입니다.
 

JPA가 강력한 이유

JPA는 다음 기능을 제공합니다.

1차 캐시
Dirty Checking
지연 로딩
Entity Graph
JPQL
 

이 기능을 잘 사용하면 SQL보다 더 효율적인 코드가 됩니다.


실무에서 추천 전략

JPA를 사용할 때 다음 규칙을 권장합니다.

모든 관계 → LAZY
조회 쿼리 → fetch join
readOnly 트랜잭션 사용
배치 처리 시 flush / clear
 

정리

JPA 성능 문제는 대부분 다음에서 발생합니다.

N+1 문제
잘못된 Fetch 전략
대량 데이터 조회
 

JPA 자체가 느린 것이 아니라 잘못 사용했을 때 느려집니다.


✔ 핵심 한 줄

JPA는 느린 것이 아니라
SQL을 잘못 생성하면 느려진다.
LIST

JPA 트랜잭션을 제대로 이해하지 않으면 발생하는 장애 사례

Spring Boot에서 @Transactional은 가장 많이 사용하는 기능 중 하나입니다.

 
@Transactional
public void createOrder() {
orderRepository.save(order);
}
 

하지만 트랜잭션을 제대로 이해하지 않고 사용하면 성능 문제, 데이터 오류, Deadlock까지 발생할 수 있습니다.

이번 글에서는 실무에서 자주 발생하는 트랜잭션 문제 7가지를 정리합니다.


1. Self Invocation 문제

Spring 트랜잭션은 AOP Proxy 기반으로 동작합니다.

 
@Service
public class OrderService {

public void create() {
save(); // 트랜잭션 적용 안됨
}

@Transactional
public void save() {
orderRepository.save(order);
}
}
 

이 경우

 
create() → save()
 

내부 호출이기 때문에 Proxy를 거치지 않습니다.

결과

 
@Transactional 적용 안됨
 

해결 방법

서비스를 분리합니다.

 
@Service
public class OrderService {

private final OrderTxService orderTxService;

public void create() {
orderTxService.save();
}
}
 

2. 트랜잭션 안에서 외부 API 호출

이것도 실무에서 많이 발생합니다.

 
@Transactional
public void order() {

paymentApi.call(); // 문제

orderRepository.save(order);
}
 

문제

 
API 호출 동안 DB lock 유지
 

결과

Deadlock
Connection Pool 부족
성능 저하
 

해결 방법

 
외부 API는 트랜잭션 밖에서 실행
 

3. readOnly 옵션 미사용

조회 쿼리에도 트랜잭션을 사용하지만 readOnly 설정을 안 하는 경우가 많습니다.

 

@Transactional
public List<Order> findOrders() {
return orderRepository.findAll();
}
 

이 경우 Hibernate는

 
Dirty Checking
Flush 관리
 

등을 수행합니다.


해결 방법

 
@Transactional(readOnly = true)
 

이렇게 설정하면

Flush 비활성화
성능 개선
 

됩니다.


4. 너무 긴 트랜잭션

 
@Transactional
public void processOrder() {

orderRepository.save(order);

Thread.sleep(3000);

inventoryService.update();
}
 

이 경우

3초 동안 row lock 유지
 

됩니다.

결과

Deadlock
DB lock 증가
 

5. 대량 데이터 처리

 
@Transactional
public void batch() {

List<Order> orders = orderRepository.findAll();

for (Order o : orders) {
o.setStatus("COMPLETE");
}
}
 

문제

Persistence Context 메모리 증가
 

결과

OutOfMemory
성능 저하
 

해결 방법

배치에서는

flush
clear
 

를 사용합니다.


6. 트랜잭션 전파 이해 부족

Spring 트랜잭션에는 여러 propagation 옵션이 있습니다.

 

@Transactional(propagation = Propagation.REQUIRES_NEW)
 

이 옵션은

새 트랜잭션 생성
 

을 의미합니다.

잘못 사용하면

트랜잭션 폭증
성능 문제
 

가 발생할 수 있습니다.


7. Exception 처리 문제

Spring은 기본적으로

RuntimeException
 

만 rollback 합니다.

 
@Transactional
public void order() throws Exception {

orderRepository.save(order);

throw new Exception();
}
 

이 경우

rollback 안됨
 

입니다.


해결 방법

 
@Transactional(rollbackFor = Exception.class)
 

정리

Spring Boot에서 트랜잭션을 사용할 때 반드시 다음을 이해해야 합니다.

AOP Proxy 구조
트랜잭션 범위
Lock 유지 시간
Propagation
Rollback 규칙
 

✔ 핵심 한 줄

@Transactional은 기능이 아니라 동시성 제어 도구다.
LIST

CPU, TPS, 쿼리시간 기준으로 HikariCP pool size 산정하기

Spring Boot 애플리케이션에서 DB 성능 문제의 상당수는 커넥션 풀 설정에서 시작됩니다.

많은 개발자가 다음처럼 설정합니다.

 
spring:
datasource:
hikari:
maximum-pool-size: 10
 

하지만 이 값은 서버 환경, DB 성능, 트래픽에 따라 달라져야 합니다.

이번 글에서는 HikariCP 커넥션 풀 크기를 실무적으로 계산하는 방법을 설명합니다.


1. 커넥션 풀은 동시 DB 작업 수를 의미한다

커넥션 풀은 다음 의미입니다.

동시에 실행 가능한 DB 작업 개수
 

maximum-pool-size = 10
 

의미

동시에 10개의 SQL만 실행 가능
 

구조

Client Request

Spring Thread

HikariCP Pool

Database
 

2. 가장 기본 공식 (CPU 기반)

많은 시스템에서 다음 공식으로 시작합니다.

connection pool ≈ CPU * 2
 

CPU = 4 core
 

추천 pool

8 ~ 12
 

왜냐하면 DB 작업은 대부분

I/O wait
 

상태가 있기 때문입니다.

즉 CPU보다 약간 많은 커넥션이 필요합니다.


3. TPS 기준 계산 방법

조금 더 정확하게 계산하려면 TPS와 쿼리 시간을 사용합니다.

공식

필요 커넥션 수 = TPS × 평균 DB 처리시간
 

예제

서비스

TPS = 100
평균 쿼리 시간 = 50ms
 

계산

100 × 0.05 = 5
 

필요 커넥션

5
 

안전하게

10 ~ 15
 

정도로 설정합니다.


4. 느린 쿼리가 많을 때

TPS = 100
평균 쿼리 시간 = 300ms
 

계산

100 × 0.3 = 30
 

pool = 30 이상
 

필요합니다.

이 경우 문제는 보통 쿼리 최적화입니다.


5. DB max connection 고려

PostgreSQL 기본값

max_connections = 100
 

하지만 DB는

관리용 커넥션
replication
monitoring
 

등을 사용합니다.

그래서 보통

20 ~ 30
 

정도는 남겨야 합니다.


계산 예

DB max_connections = 100
 

사용 가능

80
 

애플리케이션 서버

4대
 

가능한 pool

80 / 4 = 20
 

따라서

maximum-pool-size = 20
 

6. MSA 환경에서 문제

마이크로서비스 환경에서는 문제가 더 커집니다.

service A
service B
service C
service D
 

각각

pool = 20
 

이면

20 × 4 = 80
 

DB 커넥션이 사용됩니다.

MSA에서는 DB 커넥션 설계가 중요합니다.


7. 너무 큰 커넥션 풀의 문제

많은 개발자가 이렇게 생각합니다.

pool 크게 잡으면 좋다
 

하지만 그렇지 않습니다.

문제

DB context switching 증가
메모리 증가
lock contention 증가
 

결과

DB 성능 저하
 

그래서 커넥션 풀은 최소한으로 설정하는 것이 좋습니다.


8. 실무 추천 값

일반적인 Spring Boot 서비스 기준

CPU = 4
TPS = 100 ~ 300
 

추천

 
spring:
datasource:
hikari:
maximum-pool-size: 15
minimum-idle: 5
connection-timeout: 30000
max-lifetime: 1800000
 

9. 모니터링은 필수

커넥션 풀 설정은 한 번 정하고 끝나는 것이 아닙니다.

다음 지표를 항상 봐야 합니다.

Active Connections
Idle Connections
Pending Threads
 

Spring Boot Actuator + Prometheus + Grafana로 확인할 수 있습니다.


정리

HikariCP 커넥션 풀 크기는 단순한 숫자가 아니라 시스템 전체 성능에 영향을 주는 핵심 설정입니다.

설정할 때 반드시 고려해야 할 요소는 다음입니다.

CPU 코어 수
TPS
쿼리 실행 시간
DB max_connections
서버 수
 

✔ 핵심 한 줄

커넥션 풀은 크게 잡는 것이 아니라 정확하게 계산해야 한다.
LIST

DB 커넥션 풀 튜닝 실패 사례 분석

Spring Boot는 기본적으로 HikariCP를 사용합니다.
대부분 개발자는 다음 설정만 보고 넘어갑니다.

 
spring:
datasource:
hikari:
maximum-pool-size: 10
connection-timeout: 30000
max-lifetime: 1800000
 

하지만 이 설정을 제대로 이해하지 못하면 운영 환경에서 장애가 발생합니다.

이번 글에서는 실무에서 실제로 자주 발생하는 HikariCP 장애 5가지 사례를 정리합니다.


1. Connection Pool Exhausted

(커넥션 풀 고갈)

가장 흔한 장애입니다.

로그

HikariPool-1 - Connection is not available, request timed out
 

의미

커넥션 풀에 남은 DB 연결이 없음
 

 


발생 상황

maximum-pool-size = 10
 

동시에

20개 요청 발생
 

구조

10개 → DB 실행
10개 → 대기
 

대기 시간이 connection-timeout을 넘으면

SQLTransientConnectionException
 

이 발생합니다.


해결 방법

1️⃣ 커넥션 풀 증가

maximum-pool-size: 20
 

2️⃣ 느린 쿼리 제거

3️⃣ 트랜잭션 범위 축소


2. DB Max Connection 초과

이건 DB 서버가 죽는 케이스입니다.

PostgreSQL max_connections = 100
 

애플리케이션 서버 5대

pool = 30
 

전체 커넥션

30 * 5 = 150
 

결과

FATAL: too many connections
 

DB가 새로운 연결을 거부합니다.


해결 방법

커넥션 풀은 항상 다음 기준으로 잡아야 합니다.

(DB max_connections - 관리용) / 서버 수
 

100 - 20 = 80
80 / 4 servers = 20
 

따라서

maximum-pool-size = 20
 

3. Broken Pipe / Connection Reset

운영에서 많이 보는 로그입니다.

org.postgresql.util.PSQLException: Connection reset
 

또는

Broken pipe
 

원인

DB 또는 네트워크가 idle connection을 끊음
 

  • AWS RDS
  • NAT Gateway
  • Firewall

이 경우 커넥션 풀에 죽은 커넥션이 남아있습니다.


해결 방법

max-lifetime을 DB timeout보다 작게 설정합니다.

DB idle timeout = 1시간
 

설정

max-lifetime = 30분
 
 
max-lifetime: 1800000
 

 


4. Thread Pool 고갈

이건 커넥션 풀 문제지만 웹 서버까지 영향을 줍니다.

connection-timeout = 5분
 

커넥션 풀이 부족하면

요청 Thread 대기
 

이 상태가 됩니다.

Tomcat 구조

Request

Tomcat Thread

DB Connection Pool
 

커넥션이 없으면

Thread block
 

결과

Tomcat thread pool exhausted
 

서버가 응답을 못 합니다.


해결 방법

connection-timeout을 크게 잡지 않습니다.

보통

30초
 

정도가 적절합니다.


5. 커넥션 누수 (Connection Leak)

가끔 이런 로그가 보입니다.

HikariPool - Connection leak detection triggered
 

의미

커넥션을 반환하지 않음
 

 
Connection conn = dataSource.getConnection();
 

닫지 않으면

pool 고갈
 

이 발생합니다.


해결 방법

Spring에서는 대부분 다음을 사용합니다.

@Transactional
JpaRepository
JdbcTemplate
 

이 경우 자동으로 반환됩니다.

그래서 직접

getConnection()
 

을 쓰지 않는 것이 좋습니다.


6. 실무 추천 설정

보통 Spring Boot 운영 환경에서는 이렇게 시작합니다.

 
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
 

7. 커넥션 풀 설계 기준

설계할 때 고려해야 할 것

CPU 코어 수
DB max_connections
서버 수
트래픽 TPS
쿼리 시간
 

간단한 공식

pool ≈ CPU * 2
 

CPU 4 core
pool = 8 ~ 16
 

정리

HikariCP 설정은 단순한 옵션처럼 보이지만 운영 장애와 직결됩니다.

특히 다음 세 가지를 항상 함께 고려해야 합니다.

maximum-pool-size
connection-timeout
max-lifetime
 

이 설정은 애플리케이션 성능이 아니라 DB 안정성을 위한 설정입니다.


✔ 핵심 한 줄

DB 장애의 30%는 커넥션 풀 설정에서 시작된다.
LIST

+ Recent posts