포스트

DB에 맡기지 말고 직접 검증하자

DB에 맡기지 말고 직접 검증하자

고민의 시작

카카오테크캠퍼스에서 선물하기 API를 클론 코딩하면서 비슷한 고민을 두 번 했다.

  • 상품 수정에서 UPDATE를 바로 치는 게 맞나, 먼저 조회해서 상태/권한을 확인하고 수정하는 게 맞나?
  • 위시리스트 추가에서 없는 상품 ID가 들어왔을 때, DB의 외래키 예외에 기대도 되나, 애플리케이션에서 먼저 검증해야 하나?

초기에는 쿼리 수를 줄이는 방향을 우선으로 생각했다. 다만 코드 리뷰를 거치면서, 쿼리 1번을 줄이는 것보다 실패 케이스를 명확하게 다루는 게 더 중요할 때가 많다는 걸 정리했다.

사례 1: 상품 수정 API

상품 정보를 수정하는 API(JDBC)를 만들 때, 두 가지 방법을 놓고 고민했다.

방법 1: Update를 바로 실행하기

1
UPDATE product SET name = ?, price = ? WHERE id = ?

쿼리 한 번이면 끝나니 성능상 이득이라고 생각했다.

문제는 실행 결과로 affected rows(영향받은 행 수)만 확인할 수 있다는 점이었다. 결과가 0이면 원인을 구분하기 어렵다.

  • 상품 ID가 없어서?
  • 권한이 없어서?
  • 이미 삭제된 상품이어서?

클라이언트에 수정 실패만 전달하면, 사용자는 다음 행동을 판단하기 어렵다.

방법 2: 조회하고 Update

1
2
3
4
5
6
7
8
9
Product product = productRepository.findById(id)
    .orElseThrow(() -> new EntityNotFoundException("상품이 없습니다"));

if (!product.isOwnedBy(currentUser)) {
    throw new AccessDeniedException("수정 권한이 없습니다");
}

product.update(request);
productRepository.save(product);

쿼리가 한 번 더 나가지만, 실패 원인을 상황별로 나눌 수 있다. 없으면 404, 권한 없으면 403처럼 응답이 명확해진다.

성능이 걱정됐다

조회 쿼리가 추가되면 성능이 떨어지는지 걱정됐는데, 케이스를 나눠서 보면 판단 기준을 세울 수 있었다.

  1. PK로 조회하면 빠르다. B-Tree 인덱스 타면 O(log n)이고, 디스크 I/O도 몇 번 안 든다고 한다.
  2. 대부분은 정상 요청이다. 없는 상품을 수정하려는 요청은 보통 예외 케이스다.
  3. 프로파일링 없이 최적화부터 하지 말자. 병목이 어디인지 확인도 안 했는데 쿼리 1번 줄이겠다고 예외 처리를 희생하면 더 비싸질 수 있다.

사례 2: 위시리스트 상품 추가

위시리스트에 상품을 추가하는 기능을 만들다가 또 비슷한 고민이 생겼다. 없는 상품 ID가 들어오면 어떻게 처리하지?

방법 1: DB 제약조건 믿기

1
2
3
4
5
6
try {
    WishlistItem item = new WishlistItem(member, productId);
    wishlistRepository.save(item);
} catch (DataIntegrityViolationException e) {
    throw new EntityNotFoundException("없는 상품입니다");
}

외래키 제약조건이 있으니, 없는 ID가 들어오면 예외가 발생하고 이를 잡아서 처리하면 된다고 생각했다.

하지만 DataIntegrityViolationException은 외래키 위반에서만 발생하지 않는다. UNIQUE 위반, NOT NULL 위반 등 다양한 제약조건 위반에서 발생한다. 따라서 원인을 특정하기 어렵다.

방법 2: 미리 확인하기

1
2
3
4
5
Product product = productRepository.findById(productId)
    .orElseThrow(() -> new EntityNotFoundException("없는 상품입니다"));

WishlistItem item = new WishlistItem(member, product);
wishlistRepository.save(item);

상품이 없으면 EntityNotFoundException. 명확하다.

멘토님한테 물어봤다

어떤 접근이 적절한지 확인하기 위해 멘토님에게 질문했다.

“외부 검증을 신뢰하지 않고 애플리케이션에서도 검증한다는 관점에서 그렇습니다.”

DB 제약조건은 최후의 방어선에 가깝다. 버그를 막는 안전장치이지, 비즈니스 로직을 책임지는 주체는 아니라는 설명이었다.

정리

기준DB한테 맡기기직접 검증하기
쿼리 수적다1회 추가
예외 분리모호함명확함
디버깅힘듦편함
유지보수어려움쉬움

쿼리 한 번을 아끼려다 예외가 뭉개지면 사용자 경험이 떨어지고, 운영이나 디버깅 난이도도 올라간다. 이 상황에서는 DB를 마지막 안전장치로 두고, 애플리케이션에서 먼저 의미 있는 검증을 수행하는 편이 더 낫다고 정리했다.

프로젝트: 카카오테크캠퍼스 3기 선물하기 API 클론 코딩 관련 링크

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.