upsert 적용

2025. 10. 29. 12:44·프로젝트/웹 성능 테스트

저희 프로젝트에서는 security_vitals 테이블이 각 테스트(test_id)마다

하나의 진단 결과를 저장하도록 설계되어 있었습니다.

 

그래서 처음 스캔이 이루어질 때마다 새 결과 저장했고

동일한 test_id로 다시 스캔되거나 보안 항목이 업데이트될 때는 기존 데이터를 갱신하도록 했습니다.

 

하지만 이런 흐름에서 다음 문제가 반복적으로 발생했습니다.

  • 이미 존재하는 test_id에 대해 새로 인서트하려다 키 중복 오류가 발생
  • 존재하지 않을 때는 INSERT 해야 하지만, 존재하면 UPDATE 해야 한다는 로직이 코드에 흩어져 복잡도 증가
  • JPA로 처리할 때 조회 후 있으면 변경, 없으면 저장 형태로 구현했지만, 동시성 이슈가 있음

따라서 존재하면 업데이트, 없으면 삽입하는 구조를

효율적이고 일관되게 처리할 수 있는 방법을 찾고자 했습니다.

 


 

Upsert 개념 발견

이 문제를 해결하면서 찾은 개념이 바로 Upsert입니다.

이는 Insert 와 Update를 하나의 흐름으로 처리하는 방식입니다.


PostgreSQL에서는 아래와 같이 표현됩니다:

INSERT INTO security_vitals(…)
VALUES(…)
ON CONFLICT(test_id)
DO UPDATE SET …
 

이 구문이 의미하는 바는,

만약 test_id 에 충돌이 있다면(이미 존재한다면) UPDATE를 실행하라는 것입니다.

 

그렇다면 upsert의 장점은 무엇일까요?

  1. 별도의 조회 → 조건 판단 → 삽입/갱신 흐름을 줄일 수 있다.
  2. 단일 쿼리 수준에서 원자성을 보장한다.  INSERT나 UPDATE 중 하나만 결과로 남는다.
  3. 동시성 상황에서도 중복 삽입이나 충돌을 효과적으로 방지할 수 있다.

이러한 개념과 장점을 확인하면서

프로젝트 코드에도 적용을 시작했습니다.

 

 


 

적용 방법 및 코드 흐름

프로젝트에서는 아래 흐름으로 코드를 작성했습니다:

securityVitalsRepository.findByTest_Id(testId).ifPresentOrElse(
    found -> {
        found.updateFrom(finalResult);
        log.info("[SEC] updated testId={}", testId);
        securityVitalsRepository.flush();
    },
    () -> {
        SecurityVitalsEntity created = SecurityVitalsEntity.create(test, finalResult1);
        securityVitalsRepository.saveAndFlush(created);
        log.info("[SEC] inserted testId={}", testId);
    }
);

왜 이렇게 작성했는가?

먼저 findByTest_Id(testId)로 조회해 존재 여부를 판단합니다.

존재할 경우엔 found.updateFrom(finalResult)로 엔티티 필드들을 갱신하고 flush() 호출하고

존재하지 않을 경우엔 새 엔티티 생성 후 saveAndFlush()로 저장 및 즉시 반영합니다.

 

해당 코드에서는 flush()를 명시했는데

flush()를 명시적으로 호출한 이유는 다음과 같습니다.

  1. 후속 로직(예: AI 분석, SSE 알림 등)에서 이 변경이 즉시 DB에 반영되어야 했습니다 → 트랜잭션이 끝나기 전에 다른 서비스가 읽도록 보장해야 했습니다
  2. JPA의 변경감지(dirty checking) 만으로는 영속성 컨텍스트에는 반영되었지만 DB에 아직 커밋/반영되지 않은 상태가 존재할 수 있었습니다. 이에 따른 타이밍 이슈를 줄이기 위함이었습니다.
  3. 조회 후 갱신 구조로 구현했기 때문에 조회와 저장 사이 다른 트랜잭션이 끼어들 수 있는 동시성 위험도 있었습니다.

 

Upsert 방식으로의 전환 고려

운영 단계에서는 위 코드 흐름 대신 DB 레벨에서 Upsert 쿼리로 처리하는 방안도 함께 검토했습니다.

INSERT INTO security_vitals(test_id, score, …)
VALUES(:testId, :score, …)
ON CONFLICT (test_id)
DO UPDATE SET score = EXCLUDED.score;

 

이처럼 하면 조회 없이 바로 삽입 또는 갱신이 가능해져 성능 상 이점이 있습니다.

다만 JPA 환경에서는 엔티티 생명주기 관리, 변경감지, 영속성 컨텍스트 등과의 조합을 고려해야 하기 때문에

무조건 Upsert를 쓰자라기보다는 현재 구조 및 요구사항에 맞춰 선택하는 것이 중요한 것 같습니다.

 


 

특징 및 주의사항

특징

  • 단일 쿼리로 Insert or Update 처리 가능 → 코드 단순화 및 DB round-trip 감소
  • PostgreSQL의 ON CONFLICT DO UPDATE는 충돌이 발생할 때 UPDATE가 실행되며, 충돌 대상이 될 유니크 인덱스 혹은 제약조건이 필요합니다.
  • JPA에서 변경감지(dirty checking)가 자동으로 수행되며 별도 UPDATE 메서드를 호출하지 않아도 됩니다. 

주의사항

  • Upsert 방식은 유니크 제약조건을 기반으로 해야 합니다. 만약 제약조건이 없으면 충돌을 판단할 수 없고 오류가 발생할 수 있습니다.
  • JPA 이용 시 Upsert 쿼리를 직접 쓰면 엔티티 캐시/영속성 컨텍스트 상태가 실제 DB와 불일치할 수 있습니다. → 이 경우 EntityManager#refresh() 등이 필요할 수 있습니다
  • Upsert가 항상 최선은 아닙니다. 예컨대 엔티티 복잡도, 관계매핑, 트리거/이벤트 연동 등이 많다면, JPA 방식(조회 + 엔티티 수정)도 여전히 유효합니다
  • 성능 최적화 시에는 동시성 문제도 고려해야 합니다. Upsert는 동시 삽입 충돌 상황에서 대비가 되어 있으나, 엔티티 수준 로직이 복잡할 경우 여전히 동시성 이슈가 남을 수 있습니다
  • JPA의 변경감지 메커니즘을 잘 이해하고 있어야 합니다. 특정 필드만 바뀌었을 때 자동으로 UPDATE가 발생한다는 점을 알아야 합니다. 

 


 

마무리

프로젝트를 진행하며 특정 test_id에 대해 이미 존재하면 갱신, 없으면 저장이라는 로직이 필요해서

upsert라는 개념에 대해 알아보고 적용해보았습니다.

Upsert는 코드 단순화, 성능 이점을 주지만 JPA 생태계에서의 엔티티 관리, 캐시, 이벤트 등과 함께 고려해야 합니다

  • 결국 “어떤 구조에서 어떤 방식이 더 적절한가?”를 판단해야 하며, 이를 위해 변경감지(dirty checking), 엔티티 생명주기, 동시성 등을 이해하고 적용하는 것이 중요합니다

'프로젝트 > 웹 성능 테스트' 카테고리의 다른 글

Server Sent Events(SSE): 구현  (0) 2025.11.02
Server Sent Events(SSE): 설계  (0) 2025.11.02
DB 마이그레이션  (0) 2025.10.25
Git Flow 전략  (1) 2025.10.24
도커: 이론2(용어 정리)  (0) 2025.10.18
'프로젝트/웹 성능 테스트' 카테고리의 다른 글
  • Server Sent Events(SSE): 구현
  • Server Sent Events(SSE): 설계
  • DB 마이그레이션
  • Git Flow 전략
yoon4360
yoon4360
자바 백엔드 개발자 지망생입니다
  • yoon4360
    yoon4360님의 블로그
    yoon4360
  • 전체
    오늘
    어제
    • 분류 전체보기 (137)
      • 스프링 (17)
      • 프로젝트 (48)
        • 악취 포집기 앱 (4)
        • 기업 일정 관리 웹 (10)
        • 기술 면접 복습 플랫폼 (18)
        • 웹 성능 테스트 (16)
      • CS (9)
      • 자바 (14)
      • 독서 (1)
      • SQL (1)
      • SSAFY (14)
      • 알고리즘 (15)
      • 기술면접 (8)
      • 데이터베이스 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
yoon4360
upsert 적용
상단으로

티스토리툴바