본문 바로가기

카테고리 없음

5.21일 코드 리뷰에 대한 제 답변

✅ SQL Batch 설정: 왜 해야 하며, 무엇을 해야 하는가? :이형진님

더보기

1. ✅ 왜 SQL Batch를 설정해야 하는가?

🔹 1) 성능 향상

  • 데이터 INSERT/UPDATE 시, 하나씩 SQL을 실행하면 네트워크 + DB 처리 비용이 큼
  • Batch는 여러 SQL을 한 번에 처리하므로 성능이 최대 수십 배 향상
처리 방식 특징 예시
일반 방식 SQL 1개씩 전송 insert into ... (100번 반복)
배치 방식 SQL 여러 개를 모아서 한 번에 전송 insert into ... values (...), (...), ...

🔹 2) 대량 데이터 처리에 적합

  • 로그, 검색 기록, 대량 마이그레이션, 초기 데이터 세팅 등에 효율적
  • Spring Batch나 로그 수집기에서 필수로 사용

2. ✅ SQL Batch 방식의 종류

🟢 A. JPA + Hibernate 배치

  • JPA에서 Hibernate를 사용할 경우 아래와 같은 설정과 조건이 필요

❗ 주의할 점

  • spring.jpa.hibernate에 설정하면 절대 적용되지 않음
  • 반드시 spring.jpa.properties.hibernate.xxx 위치에 설정
  • IDE 자동완성 기능을 믿지 말 것 (오히려 잘못된 경로 유도)

🟢 B. JDBC + Spring Batch (JdbcBatchItemWriter)

  • JPA를 사용하지 않고, JDBC 기반으로 batch 처리하는 방식
  • 성능이 가장 좋음
  • IDENTITY 전략 등에도 제약이 없음

🟡 C. JPA 수동 배치 처리 (EntityManager.persist + flush/clear)

  • saveAll()보다 더 세밀하게 제어하고 싶을 때 사용
  • 아래 조건을 지켜야만 batch insert가 작동

✔ 조건

  1. @GeneratedValue(strategy = GenerationType.IDENTITY) ❌ 사용 금지
  2. flush()와 clear() 주기적으로 호출
  3. Hibernate 배치 설정 적용돼 있어야 함

4. ✅ 쓰기 지연 효과

03. 쿼리 코드 만들기 (JpaRepository) 문서에 정리되어 있음 - 한번 흝고 지나값니다.

5. 결과

출처 : https://techblog.woowahan.com/2695/

 

추가 공부 중............. (놓친 부분입니다)

🔍 검색 시 굳이 count를 늘리지 않아도 되는 이유 : 분리를 할 필요가 없어요-김현우님

더보기

1. 사례 기반: 파맛첵스 투표 사건

  • 사건 개요: 2004년, 파맛첵스 출시를 위한 국민 투표에서 유저들이 장난삼아 ‘파맛’을 대량 투표하며 결과를 왜곡시킴.
  • 시사점: 단순한 입력(예: 검색어 입력, 투표 클릭)을 신뢰만으로 수치화할 경우, 실제 의도와 다른 결과가 나올 수 있다.
  • 대응 방식: 이후 유사 시스템에서는 IP 추적, 세션 기반 체크, 쿠키 검사, 로그인 사용자 구분 등으로 중복/비정상 입력을 방지하거나 필터링하는 기능을 추가하게 되었다.

💡 2. 경험 기반의 판단

  • 실제 서비스를 운영해 보면 단순히 검색된 횟수만으로 인기 검색어를 판단하면 비정상적인 트래픽이나 자동화된 요청(bot) 등에 의해 결과가 왜곡될 수 있다는 걸 알 수 있다.
  • 이 때문에 많은 서비스는 단순한 검색 횟수 증가가 아닌, 일정 기준을 만족할 때만 카운트를 증가시키는 로직을 두게 된다. 예:
    • 동일 IP에서의 연속 검색은 무시
    • 너무 짧은 시간 간격으로 검색된 동일 키워드는 무시(핵심)
    • 로그인 사용자당 하루 1회만 count 허용 등

🔁 3. Count를 분리하지 않는 것이 오히려 좋을 수 있다고 생각한 이유

  • 검색과 count를 분리하면 두 로직 간의 동기화 문제가 생길 수 있다.
    • 예: 검색은 됐는데 count는 실패하거나, 반대로 count만 증가한 경우
  • 반면, 검색 자체가 count 증가와 묶여 있으면 의도치 않은 중복 증가나 이상 탐지가 더 쉬워지고, 필터링 기준을 하나의 흐름 안에서 통합적으로 관리할 수 있다.
  • 또한, 데이터를 조작하려는 의도가 있는 사용자들이 count API를 별도로 공략하는 것보다, 하나의 흐름에서 통합적으로 제어하면 보안·유지보수 측면에서 유리하다.
  • 실시간 인기 검색어와 같이 빠르게 반영되는 기능에서는, 분리된 처리보다 함께 묶인 단일 흐름에서 필터링 및 집계하는 것이 오히려 더 직관적이고 효율적이다.

🧠 4. 정리

  • 단순한 "검색 발생 = count 증가"는 오히려 데이터의 신뢰도를 낮출 수 있다.
  • 실 사용 사례와 경험을 바탕으로 보면, 신뢰할 수 있는 사용자 기반 필터링이 필요하다.
  • 또한, 검색과 count를 인위적으로 분리하지 않고 하나의 흐름에서 관리하는 것이 오히려 시스템의 일관성과 보안성, 유지보수성 측면에서 더 나을 수 있다.
  • 따라서, 검색 시 무조건 count를 늘리지 않는 것뿐만 아니라, 그 구조를 어떻게 설계하느냐가 더욱 중요하다.

📌 캐시는 왜 Page가 안 될까?- 유세영님

핵심)로컬 캐시는 되는데, 글로벌 캐시는 왜 안 될까?

더보기

1. ✋ 먼저, Page 처리가 뭘까?

우리가 흔히 말하는 페이지네이션(Pagination)은 이렇게 생긴 API를 말해요:

  • 전체 데이터 중 2번째 페이지, 즉 11~20번째 데이터만 잘라서 클라이언트에 내려주는 것.
  • 리스트가 있다면 subList(offset, offset + size) 로 쉽게 잘라낼 수 있죠.

2. ✅ 로컬 캐시는 왜 Page가 될까?

로컬 캐시는 JVM 메모리에 데이터가 올라와 있어요

  • 이처럼, 전체 데이터를 메모리에서 꺼내서 원하는 부분만 잘라서 리턴할 수 있어요.
  • 접근 속도도 빠르고, 로직도 단순해서 page 처리에 매우 적합합니다.

👉 한마디로,

"내 손 안에 있는 데이터를 자르는 건 너무 쉽다."

3. ❌ 글로벌 캐시는 왜 Page가 안 될까?

글로벌 캐시는 Redis 같은 외부 시스템이에요.

 

문제는?

  • 데이터를 꺼낼 때마다 네트워크 왕복이 발생하고
  • 전체 데이터를 꺼내야 subList가 가능하고
  • Redis는 기본적으로 정렬이나 조건 검색이 안 됨 (단순 Key-Value 저장소)

즉, 페이지 처리를 하려면:

  • 전체 데이터를 꺼내고
  • 애플리케이션에서 정렬하고
  • 잘라야 함

그럼 속도는? 🤯 …당연히 느려요. 그러니 안되죠

 

4. 🤔 Redis에서 Page가 안된다고? 그냥 Page 객체 만들면 된다

많은 사람들이 "Redis는 페이징이 안 돼요"라고 하는데,
사실은 Page<T> 같은 커스텀 객체 하나 만들어서 정보를 같이 묶어 보내면 끝입니다.

예를 들어, 이렇게 생긴 객체를 하나 정의해요:

public class PageResult<T> {
    private List<T> content;      // 현재 페이지에 들어갈 데이터
    private int page;             // 현재 페이지 번호
    private int size;             // 한 페이지 당 사이즈
    private long totalElements;   // 전체 데이터 개수
    private int totalPages;       // 전체 페이지 수

    public PageResult(List<T> content, int page, int size, long totalElements) {
        this.content = content;
        this.page = page;
        this.size = size;
        this.totalElements = totalElements;
        this.totalPages = (int) Math.ceil((double) totalElements / size);
    }

    // getter 생략
}

💡 요점 정리

  • Redis가 Page를 못 만드는 게 아니라,
  • 우리가 직접 Page 객체 만들어서 넘겨주면 끝이에요.
  • 정렬 기준만 잘 설계하고, ZSET 쓰면 DB 같은 느낌 낼 수 있음.

5. 🔚 그래서 결론은?

항목로컬 캐시 (In-memory)글로벌 캐시 (Redis 등)
위치 JVM 내부 외부 네트워크 (서버 간 공유)
접근 속도 매우 빠름 (메소드 수준) 네트워크 I/O 포함 → 상대적으로 느림
페이징 처리 List.subList() 등으로 아주 쉬움 ✅ 직접 ZSET + 슬라이싱 + 커스텀 Page 객체 필요 ✅
조건 필터/정렬 Java Stream, Comparator 등 자유도 높음 ✅ Redis 단에서 복잡한 조건/정렬은 불가 ❌
공유 여부 한 인스턴스에서만 사용 가능 ❌ 여러 인스턴스 간 데이터 공유 가능 ✅
활용 적합도 리스트 조회, 페이징 결과 캐시 등에 적합 ✅ 인기 검색어, 랭킹, 로그인 세션 등 공유용에 적합 ✅

💬 최종 요약

🔸 "Page 처리는 로컬 캐시가 편하다"
→ JVM 안에 있으니 subList()로 바로 잘라서 끝!

🔸 "Redis도 Page 된다!"
→ ZSET + ZRANGE + total count + Page 객체 만들면
 DB 못지않은 페이징 응답 구현 가능 💡

🔸 결국 중요한 건 데이터 구조 설계와 캐시 전략 분리다.
→ "조회만 빠르면 된다"가 아니라,
 "내가 원하는 구조로 가공해서 보낼 수 있느냐"가 핵심!

출차: https://developer-nyong.tistory.com/78?utm_source=chatgpt.com

 

[Spring] 로컬 캐시(Local Cache) 도입으로 성능 개선하기

들어가며 집사의 고민 서비스의 기능 개발이 거의 마무리되면서 기존 코드 또는 기능의 퀄리티를 올리기 위한 작업 중 캐시를 적용하자는 의견이 나왔습니다. 이번 글에서는 집사의 고민 팀에

developer-nyong.tistory.com