✅ 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가 작동
✔ 조건
- @GeneratedValue(strategy = GenerationType.IDENTITY) ❌ 사용 금지
- flush()와 clear() 주기적으로 호출
- 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. 🔚 그래서 결론은?
위치 | 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