2022-04-13-TIL
Today I Learned
Content-Type in HTTP GET Reqeust
Content-Type은 메시지에 담긴 바디(payload)의 미디어 타입을 알려 주는 헤더다. RFC 7231은 바디를 담은 메시지를 보내는 쪽이 Content-Type을 보내야 한다(SHOULD)고 하고, 같은 문서의 4.3.1절은 GET 요청의 바디에는 정의된 의미가 없다고 한다. 따라서 바디가 없는 GET 요청에는 Content-Type이 필요 없다. 클라이언트가 받고 싶은 응답 형식을 알리려면 Content-Type이 아니라 Accept 헤더를 쓴다. 일부 서버나 프록시는 GET 바디를 무시하거나 거부하므로, 검색 조건은 쿼리 파라미터로 보내는 것이 안전하다.
- https://stackoverflow.com/questions/5661596/do-i-need-a-content-type-header-for-http-get-requests
HikariCP Maximum Pool Size
커넥션 풀은 클수록 좋을 것 같지만, HikariCP 위키의 About Pool Sizing은 반대로 작은 풀을 권한다. CPU 코어 수보다 많은 쿼리를 동시에 실행해도 컨텍스트 스위칭만 늘어나기 때문이다. 위키가 출발점으로 제시하는 공식은 PostgreSQL 프로젝트에서 나온 connections = ((core_count * 2) + effective_spindle_count)이고, 이 값을 기준으로 실제 부하를 주며 조정하라고 한다.
링크된 글은 다른 측면, 즉 풀 크기가 너무 작아서 생기는 데드락을 다룬다. 스레드 하나가 커넥션을 잡은 채로 또 다른 커넥션을 요청하면(예: 한 트랜잭션 안에서 REQUIRES_NEW로 새 트랜잭션을 여는 경우), 모든 스레드가 첫 커넥션을 잡고 두 번째를 기다리며 멈출 수 있다. 위키는 이를 피하는 최소 크기를 pool size = Tn x (Cm - 1) + 1로 제시한다. Tn은 최대 스레드 수, Cm은 스레드 하나가 동시에 잡는 최대 커넥션 수다. 다만 위키는 풀을 키우기 전에 애플리케이션 수준에서 이런 구조를 고칠 수 있는지 먼저 보라고 권한다.
- https://jaehun2841.github.io/2020/01/27/2020-01-27-hikaricp-maximum-pool-size-tuning/#%EC%9D%B4-%EA%B8%80%EC%9D%98-%EC%98%88%EC%83%81-%EB%8F%85%EC%9E%90
Spring Boot Unit Testing
Spring Boot 애플리케이션의 테스트 모범 사례를 다룬 자료들이다. 공통된 조언은 스프링 컨텍스트 없이 돌릴 수 있는 로직은 순수 단위 테스트로 빠르게 검증하고, 컨텍스트가 필요한 부분은 @WebMvcTest, @DataJpaTest 같은 슬라이스 테스트로 범위를 좁히며, 전체 통합 테스트는 핵심 흐름에만 쓰라는 것이다. phauer의 글은 given-when-then 구조, 테스트마다 필요한 값만 드러내는 헬퍼 함수, 구현 대신 동작을 검증하기를 강조한다.
- https://www.youtube.com/watch?v=fLVUlJ-i_Qs
- https://www.baeldung.com/java-unit-testing-best-practices
- https://phauer.com/2019/modern-best-practices-testing-java/
- https://www.diffblue.com/blog/testing/tutorials/the-best-unit-testing-tutorials-for-spring-and-spring-boot/
- https://medium.com/personal-capital-tech-blog/testing-best-practices-for-java-spring-apps-762e9fde39ec
DataBase
Readings in Database Systems는 흔히 “Red Book”이라고 불리는 데이터베이스 논문 선집이다. 5판은 Peter Bailis, Joseph M. Hellerstein, Michael Stonebraker가 엮었고, 전통적인 RDBMS 구조, 모두 알아야 할 기법, 새로운 DBMS 아키텍처, 대규모 데이터플로 엔진, 약한 격리와 분산, 쿼리 최적화 같은 주제별로 고전 논문과 엮은이의 해설을 웹에서 무료로 공개한다.
- http://www.redbook.io/
Pagination
가장 흔한 LIMIT ... OFFSET ... 페이징은 뒤쪽 페이지로 갈수록 느려진다. DB가 OFFSET만큼의 행을 읽고 버린 뒤에 결과를 돌려주기 때문이다. 이를 피하는 방법이 키셋(keyset, no offset) 페이징이다. 직전 페이지의 마지막 키를 기억해 두고 WHERE id < :lastId ORDER BY id DESC LIMIT 20처럼 인덱스로 바로 시작 위치를 찾는다. 대신 임의의 페이지 번호로 바로 이동할 수 없어서 무한 스크롤이나 “더 보기”에 잘 맞는다.
페이징의 또 다른 비용은 전체 개수를 세는 COUNT 쿼리다. 링크된 jojoldu의 글은 검색 버튼을 눌렀을 때만 개수를 다시 세고 페이지 이동 시에는 이전 개수를 재사용하는 방식을, Brent Ozar의 글은 SQL Server에서 COUNT 쿼리를 빠르게 하는 방법들을 다룬다. 사용자가 실제로 필요한 것이 정확한 전체 개수인지, “다음 페이지가 있는지”인지부터 따져 보는 것이 출발점이다.
- https://jojoldu.tistory.com/530
- https://alwayspr.tistory.com/45
- https://ykh6242.tistory.com/108
- https://itnext.io/the-best-database-pagination-technique-is-530abf2aab51
- https://stackoverflow.com/questions/34336462/is-there-really-any-performance-benefit-in-using-pagination-in-web-pages
- https://www.brentozar.com/archive/2019/12/how-to-make-select-count-queries-crazy-fast/
QueryDSL Distinct
일대다 컬렉션을 fetch join하면 SQL 결과가 자식 행 수만큼 늘어나서, 부모 엔티티가 결과 리스트에 중복으로 들어간다. QueryDSL에서 .distinct()를 붙이면 SQL에 DISTINCT가 추가되고, Hibernate가 같은 엔티티 인스턴스를 메모리에서 한 번 더 걸러 중복을 없앤다. 다만 SQL의 DISTINCT는 모든 컬럼이 같아야 제거하므로 DB 쪽에서는 효과가 없고, 큰 결과에서 정렬 비용만 늘어 느려질 수 있다. 링크된 Stack Overflow 질문이 다루는 상황이다. 또 컬렉션 fetch join과 페이징을 함께 쓰면 Hibernate가 모든 결과를 메모리에 올려 페이징하므로, 컬렉션은 @BatchSize나 default_batch_fetch_size로 따로 가져오는 편이 낫다.
- https://blog.leocat.kr/notes/2020/01/13/querydsl-duplication-problem-on-fetchjoin-with-onetomany
- https://stackoverflow.com/questions/62557762/querydsl-distinct-query-too-slow
QueryDSL
링크된 글은 우아한테크콘서트 2020의 Querydsl 강의를 정리한 것이다. 조건 메서드가 BooleanExpression을 반환하게 해 동적 쿼리를 조합하는 방법, 존재 여부 확인에 count 대신 selectOne().fetchFirst()를 쓰는 방법, 커버링 인덱스를 활용한 페이징, 필요한 컬럼만 조회하기 같은 성능 팁을 다룬다.
- https://kkambi.tistory.com/193
QueryDSL GroupBy
jojoldu의 글은 MySQL에서 GROUP BY가 정렬을 함께 수행하는 문제를 다룬다. MySQL 5.7까지는 GROUP BY 컬럼 기준으로 암묵적인 정렬이 일어나 실행 계획에 filesort가 생길 수 있었고, 정렬이 필요 없으면 ORDER BY NULL을 붙여 이를 없앴다. QueryDSL에는 ORDER BY NULL을 표현하는 방법이 없어서 글은 이를 위한 OrderSpecifier를 직접 만들어 쓴다. MySQL 8.0부터는 암묵적 정렬이 없어졌다. 다른 링크는 조회 결과를 자바 Stream의 Collectors.groupingBy로 메모리에서 그룹화하는 예시다.
- https://jojoldu.tistory.com/477
- https://gem1n1.tistory.com/159
댓글
아직 댓글이 없습니다