단순히 ORM을 ‘사용’한 경험이 아니라, ORM ↔ 트랜잭션의 동작 흐름을 이해하고 이를 바탕으로 성능 튜닝과 장애 포인트 식별/대응까지 할 수 있는지를 검증한다.”

핵심 평가 축

1. 엔티티 라이프사이클 & 영속성 컨텍스트



1차 캐시, 지연쓰기(write-behind), flush/clear 타이밍, dirty checking 이해


2. 연관관계 매핑 & 로딩 전략



LAZY/EAGER, fetch join, 그래프 엔트리 포인트, N+1 진단/해결 능력


3. 트랜잭션 경계 & 격리/전파



@Transactional 경계, readOnly, 격리수준(READ COMMITTED 등), 전파(PROPAGATION_REQUIRED/REQUIRES_NEW), 타임아웃


4. 락킹 & 동시성 제어



낙관적/비관적 락, 버전 필드, Lost Update/Phantom Read 방지 전략


5. 쿼리 최적화 & 배치



JPQL/Criteria/Native 적절한 선택, 배치 insert/update, fetch size, 인덱스 활용과 계획 확인


6. 리소스 & 커넥션 풀링



커넥션/스레드 풀 고갈 징후, 트랜잭션 장기화 원인 진단


인터뷰 질문(예시)

“flush는 언제 일어나며, 커밋과 어떤 관계가 있나요? 수동 flush가 필요한 사례는?”

“N+1이 언제 발생했었고, 어떻게 재현·진단·해결했나요? (fetch join/EntityGraph/DTO 변환/캐시 등)”

“@Transactional(readOnly=true)의 내부 동작 이점과 주의사항은?”

“낙관적 락 실패가 빈번할 때 어떤 지표를 보고 어떤 대안(재시도, 분할, 큐잉, 비관적 락)을 선택했나요?”

“대량 저장 시 Hibernate batch insert 설정과 주의할 점은?”


실전 시나리오(미션 형태)

1. N+1 재현 및 제거



조건: Post–Comment(1:N, LAZY), 최근 Post 100개 조회 화면이 느림

요구: 원인 설명 → 실행 SQL 확인 → fetch join 또는 전용 조회 DTO로 해결 → 성능 전/후 수치 보고


2. 동시성 업데이트 충돌



조건: 재고 감소 API 동시 호출

요구: Lost Update 방지 설계(낙관적 락+재시도 / 비관적 락 / 분산락) 비교 및 선택 근거


3. 배치 적재 최적화



조건: 50만 건 CSV 적재 지연

요구: jdbc.batch_size, StatelessSession/벌크, flush/clear 주기, 인덱스 일시 비활성화 전략 설명 및 측정


튜닝 레버(스프링 JPA/Hibernate 중심)

로딩: 기본 LAZY, 화면 전용 조회는 DTO 프로젝션/네이티브/조회 전용 레포 분리

쿼리: fetch join(필요한 범위만), 페이징+fetch join 주의(컬렉션 조인 금지, 서브쿼리/두 단계 조회)

배치: hibernate.jdbc.batch_size, order_inserts/updates, flush/clear 주기화

캐시: 1차 캐시 적절 활용, 2차 캐시/쿼리 캐시는 신중 도입(패턴 고정·변동성 낮을 때)

트랜잭션: readOnly, 타임아웃, 격리수준 설정; 경계 최소화(서비스 레벨)

락: @Version(낙관적), 필요 시 PESSIMISTIC_WRITE/READ + 타임아웃

풀 설정: HikariCP maxPoolSize, connectionTimeout, slow query log, 메트릭 관찰


대표 장애 포인트 & 탐지법

N+1: 호출 수 급증, APM에서 동일 쿼리 반복/카디널리티 급등

장수 트랜잭션: 커넥션 점유 증가, 락 대기, GC/스레드 덤프에 대기열

잘못된 EAGER: 불필요 조인 폭증, 직렬화 시 순환참조 이슈

잘못된 전파: REQUIRES_NEW 남용으로 커밋 순서/일관성 붕괴

락 경합/데드락: DB 대기 이벤트/Deadlock 로그, 재시도 로직 부재

대량 적재 OOM: 영속성 컨텍스트 누적(Flush/clear 미실시)


코드 스니펫(짧게)

// N+1 방지: 조회 전용 DTO
@Query("""
select new com.example.PostView(p.id, p.title, a.name)
from Post p
join p.author a
where p.createdAt >= :since
""")
List<PostView> findRecent(@Param("since") LocalDateTime since);

// 배치 저장: flush/clear 주기화
@Transactional
public void bulkSave(List<Entity> items) {
    for (int i = 0; i < items.size(); i++) {
        em.persist(items.get(i));
        if (i % 1000 == 0) { em.flush(); em.clear(); }
    }
}

// 낙관적 락: 재시도 템플릿
@Retryable(value = OptimisticLockException.class, maxAttempts = 3, backoff = @Backoff(delay = 50))
@Transactional
public void decreaseStock(Long id, int qty) { ... }

평가 루브릭(5점 척도)

5: 위 모든 축을 사례/수치와 함께 설명하고, 진단–해결–재발방지 사이클 제시

4: 주요 축을 이해하고 실무 튜닝 경험 다수 보유, 일부 미세 조정 미흡

3: 개념 이해는 충분하나 재현/수치 기반 검증 부족

2: ORM 사용 위주, 트랜잭션·락·배치 개념 단편적

1: 기본 개념 혼재, EAGER 남발·서비스 외부 트랜잭션 등 위험 패턴 다수

LIST

+ Recent posts