2023-12-15-TIL
2023-12-15-TIL
Today I Learned
JPA saveAll OOM
대량의 엔티티를 saveAll()로 저장하다 OutOfMemoryError가 나는 원인은 영속성 컨텍스트(Hibernate 세션)다. artofcode 글은 백만 건 정도를 넣다가 메모리가 부족해진 사례를 분석한다. 메모리 프로파일러로 보니 새로 저장한 엔티티 객체를 Hibernate 세션이 계속 붙잡고 있었다. 글에 따르면 Hibernate는 새로 넣은 엔티티를 모두 세션 수준 캐시에 두고, 같은 세션을 계속 쓰는 동안에는 이를 비우지 않는다.
해결책은 Hibernate 문서의 배치 삽입 절이 안내하는 대로 일정 개수(청크)마다 flush()로 SQL을 내보내고 clear()로 영속성 컨텍스트를 비우는 것이다.
1
2
3
4
5
6
7
for (int i = 0; i < items.size(); i++) {
entityManager.persist(items.get(i));
if (i > 0 && i % batchSize == 0) {
entityManager.flush();
entityManager.clear();
}
}
hibernate.jdbc.batch_size를 같은 크기로 맞추면 INSERT가 JDBC 배치로 묶여 나간다. 다만 ID 생성 전략이 IDENTITY이면 Hibernate가 INSERT 배치를 하지 않으므로, 대량 삽입이 잦다면 JDBC batchUpdate처럼 JPA를 거치지 않는 방법을 함께 검토한다. 조회 쪽도 마찬가지로, 큰 결과를 한 번에 읽지 말고 페이지나 스트림 단위로 읽어야 한다는 점을 codete 글이 다룬다.
- https://stackoverflow.com/questions/58348016/jpa-batch-insert-doesnt-free-up-memory
- https://artofcode.wordpress.com/2020/03/16/out-of-memory-when-inserting-lots-of-records/
- https://persistencelayer.wixsite.com/springboot-hibernate/post/the-best-way-to-batch-inserts-via-saveall-iterable-s-entities
- https://codete.com/blog/jpa-pagination-pitfalls
- https://okky.kr/questions/804849
- https://medium.com/javarevisited/spring-data-jpa-how-to-bulk-insert-data-5b9602008a08
JPA N+1 Problem
N+1 문제는 엔티티 목록을 조회하는 쿼리 1번 뒤에, 각 엔티티의 연관 엔티티를 가져오는 쿼리가 N번 더 나가는 현상이다. 지연 로딩(lazy loading)인 연관 관계를 반복문에서 하나씩 접근할 때 주로 생긴다. 대표적인 해결책은 필요한 연관 엔티티를 한 번에 가져오는 fetch join이나 @EntityGraph, 그리고 연관 엔티티를 IN 절로 묶어 가져오는 배치 크기 설정(@BatchSize, hibernate.default_batch_fetch_size)이다. 실제로 쿼리가 몇 번 나가는지는 SQL 로그로 확인해야 하고, 실제 DB와 같은 환경에서 확인하려고 Testcontainers를 함께 본다.
- https://github.com/testcontainers
- https://velog.io/@hyunjong96/JPA-N1-%EB%AC%B8%EC%A0%9C-%EB%B0%8F-%ED%95%B4%EA%B2%B0%EB%B0%A9%EC%95%88
Testcontainers for Java
Testcontainers는 테스트 코드에서 MySQL, Redis, Kafka 같은 의존 서비스를 도커 컨테이너로 띄워 쓰게 해 주는 라이브러리다. 지원 환경 문서에 따르면 도커 API와 호환되는 컨테이너 런타임이 필요하다. 로컬에 도커를 설치하거나 Testcontainers Cloud를 쓰고, Colima 같은 대체 런타임은 문서에 나온 환경 변수를 설정해 연결한다.
- https://java.testcontainers.org/supported_docker_environment/
Port Check
NHN Cloud 글은 ACL이나 보안 그룹 때문에 포트가 막혔는지 확인하는 방법을 정리한다. ping은 ICMP라서 서버가 살아 있는지만 알려 주고, SSH나 HTTP 포트가 열려 있는지는 알 수 없다. 그래서 TCP 연결을 직접 시도하는 도구를 쓴다. TCP SYN 패킷을 보내 포트 개방 여부와 왕복 시간을 보여 주는 tcpping, 그리고 telnet <IP> <port>가 소개된다.
- https://meetup.nhncloud.com/posts/204
554 5.7.1 Client host rejected: Access denied
SMTP 서버가 메일 전달(relay)을 거부할 때 나는 오류다. Zimbra 포럼 글의 상황은 인증을 지원하지 않는 프로그램이 Postfix 기반 메일 서버로 메일을 보내려다 거부된 경우다. Postfix의 smtpd_relay_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination은 SMTP 인증을 통과했거나, 신뢰하는 네트워크(mynetworks)에 속한 호스트만 외부 도메인으로 메일을 보내게 하고 나머지는 거부한다. 그래서 보내는 서버의 IP를 mynetworks에 추가하거나, 클라이언트가 SMTP 인증을 하도록 바꿔야 한다. 포럼 답변은 먼저 메일 서버 로그를 확인하라고 권한다.
- https://forums.zimbra.org/viewtopic.php?t=62349
- https://forum.osticket.com/d/91966-unable-to-email-via-smtpclient-host-rejected-access-denied
@Transactional
Spring 레퍼런스에서 다시 확인한 기본 동작 두 가지다. 기본 프록시 모드에서는 프록시를 거쳐 들어오는 외부 호출만 가로채므로, 같은 객체 안에서 다른 메서드를 부르는 자기 호출에는 트랜잭션이 적용되지 않는다. 그리고 기본 설정에서는 RuntimeException과 Error에서만 롤백하고, 체크 예외는 롤백하지 않는다.
Today I Studied
The Twelve-Factor App
The Twelve-Factor App의 세 번째 원칙은 “설정은 환경에 저장하라”다. 배포 환경마다 달라지는 값(DB 주소, 자격 증명, 외부 서비스 주소)을 코드에서 분리해야 같은 빌드 결과물을 여러 환경에 그대로 배포할 수 있다. 아래 설정 관리 도구들은 모두 이 원칙을 어떻게 구현하느냐의 문제다.
- https://12factor.net/
Spring Cloud Config
Spring Cloud Config는 분산 시스템에서 외부 설정을 서버와 클라이언트 양쪽으로 지원한다. Config Server가 모든 환경의 설정을 한곳에서 관리하고, 기본 전략은 Git 저장소를 복제해 설정을 읽는 것이다. 클라이언트는 /{application}/{profile}[/{label}] 형태의 경로로 자기 설정을 받는다.
쿠버네티스를 쓴다면 ConfigMap과 겹친다. Joshgun의 비교 글은 Spring Cloud Config는 설정이 바뀐 사실을 클라이언트에 따로 알려야 하고 그렇지 않으면 재시작해야 한다는 점, ConfigMap은 Spring의 Kubernetes 클라이언트 구현으로 별도 구성 요소 없이 다시 읽을 수 있다는 점을 비교한다. 글은 유행이라는 이유로 기술을 고르지 말고 현재 시스템 상황에 맞춰 판단하라고 강조한다.
- https://docs.spring.io/spring-cloud-config/docs/current/reference/html/
- https://velog.io/@borab/Spring-Cloud-vs-ConfigMap
- https://joshgunh.medium.com/spring-cloud-config-vs-kubernetes-configmap-detailed-comparison-bce64b594af8
Rancher Desktop
Rancher Desktop은 데스크톱에서 컨테이너와 쿠버네티스를 다루는 데 필요한 것을 묶어 제공하는 오픈소스 애플리케이션이다. macOS, Windows, Linux를 지원하고, 로컬에서 쿠버네티스 클러스터를 띄워 ConfigMap 같은 기능을 실험해 보기 좋다.
- https://rancherdesktop.io/
Kubernetes ConfigMap
ConfigMap을 지우지 않고 파일 내용으로 갱신하는 방법을 Stack Overflow 답변이 정리한다. kubectl create로 YAML만 만들고(--dry-run=client -o yaml) 그 결과를 kubectl apply로 넘기는 방식이다. apply는 새로 만들 때와 이미 있을 때 모두 동작한다.
1
kubectl create configmap foo --from-file foo.properties -o yaml --dry-run=client | kubectl apply -f -
- https://stackoverflow.com/questions/38216278/update-k8s-configmap-or-secret-without-deleting-the-existing-one
Vault HashiCorp
HashiCorp Vault는 자격 증명, 암호화 키, 인증서 같은 비밀(secret)을 중앙에서 관리하는 도구다. Vault 문서는 애플리케이션이 동작하는 데 필요한 민감한 정보를 비밀로 정의하고, 이를 한곳에 모아 접근을 감사 가능하게 관리하는 것을 목표로 든다. ConfigMap이나 Config Server는 일반 설정에, Vault는 비밀번호와 키 같은 민감한 값에 쓰는 식으로 역할을 나눈다.
- https://www.vaultproject.io/
Spring Boot Config Update Without Restart
Baeldung 글에 따르면 Actuator 자체에는 빈을 새로 고치는 엔드포인트가 없고, Spring Cloud가 /refresh 엔드포인트와 @RefreshScope를 추가한다. @RefreshScope가 붙은 빈은 refresh 이벤트가 오면 다시 만들어지면서 바뀐 설정 값을 읽는다. 그래서 Config Server와 함께 쓰면 설정 저장소를 바꾼 뒤 /actuator/refresh를 호출해 재시작 없이 반영할 수 있다. @RefreshScope가 없는 빈은 그대로 이전 값을 쥐고 있다는 점을 주의해야 한다.
- https://stackoverflow.com/questions/76839843/springboot-update-classes-without-restarting-tomcat-server
- https://hashnode.com/post/refresh-applicationproperties-in-spring-boot-without-restarting-tomcat-server-cl015nl7m0ae9l0nv3yfi10y3
- https://www.baeldung.com/spring-reloading-properties
- https://medium.com/@javadzone/reload-application-properties-in-spring-boot-f2b8ca955caf
Dynamic Configuration
Twitter 엔지니어링 블로그는 사내 동적 설정 시스템 ConfigBus를 소개한다. 초기에는 애플리케이션마다 ZooKeeper에 설정을 두고 직접 배포했지만, ConfigBus는 설정을 저장하는 DB(Git 저장소)와 이를 데이터센터의 모든 머신으로 밀어 넣는 파이프라인, 설정을 읽는 API로 구성된다. 개발자가 Git에 커밋하면 변경이 머신들로 퍼져 나가는 구조다.
이름이 비슷한 Spring의 @DynamicPropertySource는 운영 설정이 아니라 테스트용 기능이다. Testcontainers처럼 실행 시점에야 포트가 정해지는 컨테이너의 접속 정보를 테스트의 Spring Environment에 동적으로 등록할 때 쓴다.
- https://blog.twitter.com/engineering/en_us/topics/infrastructure/2018/dynamic-configuration-at-twitter
- https://www.baeldung.com/spring-dynamicpropertysource
- https://medium.com/@AlexanderObregon/dynamic-configuration-with-spring-cloud-config-a-comprehensive-guide-7cd825dd9867
댓글
아직 댓글이 없습니다