포스트

2022-03-12-TIL

Today I Learned

Microservice

마이크로서비스 아키텍처는 하나의 큰 애플리케이션(모놀리스)을 업무 기능 단위의 작은 서비스로 나누고, 각 서비스가 독립적으로 배포되며 API나 메시지로 통신하게 하는 구조다. 서비스별로 배포와 확장을 따로 할 수 있고 장애가 한 서비스에 갇히는 장점이 있지만, 네트워크 호출, 분산 트랜잭션, 서비스 간 데이터 일관성, 모니터링과 배포 자동화 같은 운영 부담이 함께 따라온다.

  • https://blog.lgcns.com/1278

Stream

Java 8의 Stream API는 컬렉션 같은 데이터 소스를 filter, map, sorted 같은 중간 연산과 collect, forEach, count 같은 최종 연산의 파이프라인으로 처리한다. 중간 연산은 지연 실행되어 최종 연산이 호출될 때 한 번에 수행되고, 스트림은 한 번 소비하면 재사용할 수 없다.

  • https://tecoble.techcourse.co.kr/post/2021-05-23-stream-api-basic/

Count 쿼리 성능

MySQL 문서에 따르면 InnoDB는 동시에 실행되는 트랜잭션마다 보이는 행 수가 다를 수 있어서 테이블의 행 수를 따로 저장하지 않는다. 그래서 SELECT COUNT(*)는 현재 트랜잭션에 보이는 행을 실제로 세며, 가장 작은 보조 인덱스를 훑는다(없으면 클러스터드 인덱스). 정확한 행 수를 저장해 두는 MyISAM과 다른 점이다.

  • COUNT(*)와 COUNT(1)은 InnoDB에서 똑같이 처리되며 성능 차이가 없다.
  • COUNT(컬럼)은 그 컬럼이 NULL이 아닌 행만 센다. 의미가 다르므로 행 수를 원하면 COUNT(*)를 쓴다.

큰 테이블에서 페이징용 전체 개수 쿼리가 느리다면 조건에 맞는 인덱스를 두거나, 개수를 캐시하거나, 정확한 개수가 꼭 필요한지 화면 설계부터 다시 보는 방법이 있다.

  • https://yjh5369.tistory.com/206
  • https://okky.kr/article/295940
  • https://www.phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=77484

Using Serializable Entities

JPA 명세는 엔티티 인스턴스를 원격 인터페이스 등으로 값 전달(detached 상태로 직렬화)해야 할 때 엔티티가 Serializable을 구현해야 한다고 정한다. 또 복합 키 클래스(@IdClass, @EmbeddedId)는 Serializable이어야 한다. 그 외의 일반적인 엔티티는 필수가 아니다. 엔티티를 HTTP 세션에 저장하거나 캐시에 직렬화해서 넣는 경우가 아니면, 직렬화가 필요한 곳에는 엔티티 대신 DTO를 쓰는 편이 낫다.

  • https://jongminlee0.github.io/2020/03/15/serializable/
  • https://derekpark.tistory.com/113
  • https://docs.oracle.com/cd/E17277_02/html/collections/tutorial/SerializableEntity.html
  • https://jihogrammer.tistory.com/76?category=958760
  • https://www.inflearn.com/questions/17117
  • https://www.inflearn.com/questions/16570
  • https://www.baeldung.com/jpa-entities-serializable
  • https://developmentnotepad.tistory.com/entry/Spring-%EC%9E%90%EB%B0%94-%EC%A7%81%EB%A0%AC%ED%99%94-Serializable?category=805012

웹 사이트에서 업로드할 파일 사이즈를 제한한다면 얼마가 좋을까?

정답은 없고, 어떤 파일을 받는지에 따라 정한다. 링크된 UX 질문의 답변들은 실제 사용자가 올리는 파일 크기 분포, 서버 저장 비용, 업로드 시간을 기준으로 정하라고 한다. 프로필 사진처럼 웹에 표시할 이미지라면 업로드 후 리사이즈하는 전제로 수 MB 정도로 제한하고, 동영상이라면 별도의 업로드 방식(분할 업로드, 스토리지 직접 업로드)을 고려한다.

Spring Boot에서는 spring.servlet.multipart.max-file-size(파일 하나)와 max-request-size(요청 전체)로 제한하며, 기본값은 각각 1MB와 10MB다. 앞단에 NGINX가 있다면 client_max_body_size도 함께 맞춰야 한다.

  • https://help.pro.photo/troubleshooting/servers/upload-file-size/
  • https://ux.stackexchange.com/questions/95196/how-can-we-go-about-deciding-an-appropriate-filesize-upload-limit
  • https://flothemes.com/flothemes-image-sizes/
  • https://www.foregroundweb.com/image-size/

@DynamicUpdate와 부분 업데이트

Hibernate는 기본적으로 엔티티의 모든 컬럼을 갱신하는 UPDATE 문을 미리 만들어 재사용한다. 필드 하나만 바꿔도 전체 컬럼이 SET 절에 들어간다. @DynamicUpdate를 엔티티에 붙이면 변경된 컬럼만 포함한 SQL을 실행 시점에 생성한다. 컬럼이 아주 많거나 큰 컬럼이 있는 테이블에서 유리하지만, SQL을 매번 만들어야 하고 미리 준비된 문장을 재사용하지 못하는 비용이 있다.

API에서 일부 필드만 수정하는 부분 업데이트(PATCH)는 이와 별개다. 요청에 없는 필드를 null로 덮어쓰지 않도록, 엔티티를 조회한 뒤 요청에 들어온 값만 반영하고 변경 감지에 맡기는 방식이 일반적이다.

  • https://velog.io/@freddiey/JPA%EC%9D%98-DynamicUpdate
  • https://www.baeldung.com/spring-data-partial-update
  • https://www.baeldung.com/spring-data-jpa-dynamicupdate
  • https://thorben-janssen.com/dynamic-inserts-and-updates-with-spring-data-jpa/
  • https://stackoverflow.com/questions/64500153/jpa-only-update-in-table-and-not-insert

JPA에서 Entity에 반드시 테이블의 모든 컬럼을 매팽해야하는가?

반드시 모두 매핑할 필요는 없다. JPA로 관리할 엔티티에 필요한 필드만 추가하면 된다. 단, 데이터베이스 내에서 필수값으로 설정되어있는 경우, 해당 값이 없이 save()같은 메서드를 호출하면 제약조건 위반 에러가 날 수 있다. 변경하고자 하는 필드만 가진 엔티티를 만들면 save() 호출 시 해당 필드에 대해서만 변경되니까 @DynamicUpdate를 사용할 필요도 없어진다.

다만 ddl-auto로 스키마를 자동 생성하거나 검증(validate)하는 설정을 쓰면, 매핑하지 않은 컬럼은 생성되지 않거나 검증 대상에서 빠진다는 점은 알고 있어야 한다.

  • https://stackoverflow.com/questions/19713594/can-an-entity-class-work-without-having-a-definition-for-all-columns-of-the-tabl

JPA update and insert (upsert)

Spring Data JPA의 save()는 엔티티가 새것이면(보통 ID가 null) persist, 아니면 merge를 호출한다. merge는 먼저 SELECT로 행을 찾고 있으면 갱신, 없으면 삽입하므로 결과적으로 upsert처럼 동작하지만 쿼리가 두 번 나간다. 한 번의 쿼리로 처리하려면 MySQL의 INSERT ... ON DUPLICATE KEY UPDATE 같은 DB 고유 문법을 네이티브 쿼리로 쓴다.

JPQL 벌크 UPDATE는 영속성 컨텍스트를 거치지 않고 DB에 바로 반영되므로, 실행 후 같은 트랜잭션에서 엔티티를 다시 읽으면 옛 값이 보일 수 있다. @Modifying(clearAutomatically = true)로 영속성 컨텍스트를 비워 주는 이유다.

  • https://jessyt.tistory.com/42
  • https://velog.io/@roro/JPA-JPQL-update-%EC%BF%BC%EB%A6%AC%EB%B2%8C%ED%81%AC%EC%99%80-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8

@Data 애너테이션을 지양하자

Lombok 문서에 따르면 @Data는 @ToString, @EqualsAndHashCode, 모든 필드의 @Getter, final이 아닌 필드의 @Setter, @RequiredArgsConstructor를 한 번에 붙이는 단축 애너테이션이다. 엔티티에 쓰면 모든 필드에 setter가 열려 불변성이 깨지고, 양방향 연관관계가 있으면 toString과 hashCode가 서로를 호출해 무한 재귀에 빠질 수 있으며, 지연 로딩 필드를 건드려 의도치 않은 쿼리가 나간다. 필요한 것만 @Getter, @ToString(exclude = ...)처럼 골라 쓰는 편이 안전하다.

  • https://velog.io/@rosa/Lombok-%EC%A7%80%EC%96%91%ED%95%B4%EC%95%BC-%ED%95%A0-annotation
  • https://www.daleseo.com/lombok-popular-annotations/

@Query in JPA

Spring Data JPA에서 메서드 이름으로 쿼리를 만들기 어려울 때 @Query로 JPQL이나 네이티브 SQL(nativeQuery = true)을 직접 지정한다. 파라미터는 ?1 같은 위치 기반이나 :name 같은 이름 기반으로 바인딩하고, UPDATE나 DELETE는 @Modifying을 함께 붙여야 한다.

  • https://www.baeldung.com/spring-data-jpa-query

정적 팩토리 메서드

생성자 대신 of, from, valueOf 같은 정적 메서드로 객체를 만드는 방식이다. 이름으로 생성 의도를 드러낼 수 있고, 호출할 때마다 새 객체를 만들 필요가 없으며(캐싱), 반환 타입의 하위 타입 객체를 돌려줄 수 있다. 대신 상속을 위해 public 생성자가 필요하다면 함께 제공해야 하고, 생성자에 비해 API 문서에서 찾기 어렵다는 단점이 있다.

  • https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/

DTO 깔끔하게 관리하기

API마다 요청/응답 DTO를 만들다 보면 클래스 파일이 크게 늘어난다. 링크된 글은 관련된 DTO를 하나의 바깥 클래스 안에 static 중첩 클래스로 모아(MemberDto.Request, MemberDto.Response) 파일 수를 줄이고 도메인별로 찾기 쉽게 하는 방법을 소개한다.

  • https://velog.io/@p4rksh/Spring-Boot%EC%97%90%EC%84%9C-%EA%B9%94%EB%81%94%ED%95%98%EA%B2%8C-DTO-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0

DTO to Entity 변환

기본적으로 직접 정적 팩토리 메서드를 만들어서 변환하는 방법이 있다. 그외에 라이브러리를 사용하는 방법으로는 두 가지가 있다. ModelMapper를 사용하는 방법, MapStruct를 사용하는 방법이다.

ModelMapper는 런타임에 리플렉션으로 이름이 비슷한 필드를 찾아 매핑하므로 설정이 적지만, 매핑 오류를 실행해 봐야 알 수 있다. MapStruct는 컴파일 시점에 매핑 코드를 생성해 오류를 빌드 단계에서 잡고 리플렉션 비용이 없다. 직접 작성한 변환 메서드는 코드가 늘지만 가장 명시적이다.

  • https://jforj.tistory.com/93
  • https://donghyeon.dev/jpa/2020/07/12/JPA%EC%9D%98-DTO%EC%99%80-Entity/
  • https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
  • https://lob-dev.tistory.com/entry/%EA%B0%9D%EC%B2%B4-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0-%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%EB%A7%A4%ED%95%91-vs-MapStruct-vs-ModelMapper

DTO, VO, Entity

엔티티는 식별자(ID)로 구분되고 생명주기 동안 상태가 바뀔 수 있는 도메인 객체, VO는 식별자 없이 값으로 비교되는 불변 객체, DTO는 계층 사이에서 데이터를 옮기기만 하는 객체다. VO는 생성 시점에 유효성을 검증하고 equals/hashCode를 값 기준으로 재정의하는 것이 핵심이다.

  • https://parkadd.tistory.com/53
  • https://velog.io/@gillog/Entity-DTO-VO-%EB%B0%94%EB%A1%9C-%EC%95%8C%EA%B8%B0
  • https://tecoble.techcourse.co.kr/post/2020-06-11-value-object/

DTO를 사용하지 않고 Map을 사용하는 경우

Map<String, Object>로 데이터를 주고받으면 클래스를 만들 필요가 없어 빠르게 작성할 수 있지만, 어떤 키가 들어 있는지 코드만 봐서는 알 수 없고 오타나 타입 오류가 런타임에야 드러난다. IDE 자동 완성과 리팩토링 도구의 도움도 받을 수 없다. 키 구성이 정말 가변적인 경우가 아니라면 DTO를 쓰는 것이 유지 보수에 낫다.

  • https://okky.kr/article/370609
  • https://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040201&docId=71573791&qb=Vk8g7YG0656Y7Iqk&enc=utf8&section=kin&rank=1&search_sort=0&spq=0&pid=gpRtcF5Y7uCsstDvUh0ssc–514595&sid=TpTa0DjMlE4AADv9GoY

개발자 커뮤니티

질문하고 정보를 얻을 수 있는 개발자 커뮤니티를 모아 둔 글들이다. Stack Overflow, GitHub, DEV 같은 해외 커뮤니티와 국내 커뮤니티가 소개되어 있다.

  • https://www.freecodecamp.org/news/best-developer-communities-to-be-part-of-in-2020/
  • https://freemoa-blog.com/438
  • https://blog.naver.com/freemoa1/221610785501
참고한 자료외부 출처 4

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다