메인으로 가기

목차

    집약형 아키텍처

    • 대형 컴퓨터를 이용해서 모든 업무 처리 (집중형)
    • 장점
      - 한 대의 대형 컴퓨터만 있으면 되므로 구성이 간단
    • 단점
      - 대형 컴퓨터의 도입 비용과 유지 비용이 비쌈
      - 확장성에 한계

    분할형 아키텍처

    • 여러 대의 컴퓨터를 조합해서 하나의 시스템을 구축
    • 장점
      - 낮은 비용으로 시스템을 구축
    • 단점
      • 대수가 늘어나면 관리 구조 복잡
      • 한 대가 망가지면 영향 범위를 최소하기 위한 구조 검토

    .

    서버란?
    물리서버와 논리 서버로 구성
    컴퓨터 자체(하드웨어)를 가리크는 물리 서버
    컴퓨터에서 동작하고 있는 소프트웨어 논리 서버 (웹서버, DB서버)

    분할형 아키텍처는 역할 분담에 따라 구분

    • 수직 분할형 아키텍처
    • 수평 분할형 아키텍처

    수직 분할형 아키텍처

    • 역할에 따라 위 또는 아래 계층으로 나뉨
    • 2가지 존재
      - 클라이언트-서버형 아키텍처
      - 3계층형 아키텍처

    클라이언트-서버형 아키텍처

    • 서버 1개에서 모든 클라이언트 처리를 접수한다.
    • 클라이언트 측에 전용 소프트웨어를 설치해야 한다.
    • 서버 처리에 집중되면 확장성에 한계 발생
    • 이런 단점을 극복하고자 3계층형 아키텍처가 나옴

    3계층형 아키텍처

    • 클라이언트-서버형을 발전 시킴
    • 프레젠테이션 계층 / 애플리케이션 계층 / 데이터 계층 3층 구조로 분할하여 서버 부하 집중을 개선
    • 스프링의 MVC와 비슷
    • 프레젠테이션 계층 : 사용자의 요청을 받아서 화면에 표시(웹 서버)
    • 애플리케이션 계층 : 무엇을 할지 판단해서 필요한 경우 데이터 계층에 질의 (애플리케이션(AP)서버)
    • 데이터 계층 : 데이터 입출력을 담당한다.(DB서버)

    수평 분활형 아키텍처

    • 수직 분활형 아키텍처를 하나 더 늘려 확장성, 안정성을 향상시키는 것
    • 단순 수평 분할형 아키텍처 & 공유형 아키텍처로 나뉨

    단순 수평 분할형 아키텍처

    • 샤딩 또는 파티셔닝이라고 불린다.
    • 잘 안쓰임 (본사, 동탄 따로 서버쓰는 꼴) -> 공유형 아키텍처
    • 장점
      • 수평으로 서버를 늘리기 때문에 확장성이 향상된다.
      • 분할한 시스템이 독립적으로 운영되므로 서로 영향을 주지 않는다.
    • 단점
      • 데이터를 일원화해서 볼 수 없다.
      • 애플리케이션 업데이트는 양쪽을 동시에 해 주어야 한다.
      • 처리량이 균등하게 분할돼 있지 않으면 서버별 처리량에 치우침이 생긴다.

    공유형 아키텍처

    공유형은 단순형과 달리 일부 계층에서 상호 접속이 이루어진다.

    • 장점
      • 수평으로 서버를 늘리기 때문에 확장성이 향상된다.
      • 분할한 시스템이 서로 다른 시스템의 데이터를 참조할 수 있다.
    • 단점
      • 분할한 시스템 간 독립성이 낮아진다.
      • 공유한 계층의 확장성이 낮아진다.
    아키텍처 트랜드
    오픈화(분산) -> 가상화/클라우드(집중) -> 엣지 컴퓨팅(분산)
    * 엣지 컴퓨팅 : 최신 키워드, 지리적으로 가까운 위치에 있는 서버로 처리하고, 처리 결과만 중앙으로보내는 아키텍처
    엣지 컴퓨팅에서는 관리를 위한 수고를 줄이면서, 서버를 분산하는 것이 중요하다.

    지리 분할에 따른 아키텍처

    • 스탠바이형 아키텍처와 재해 대책형 아키텍처

    스탠바이형 아키텍처

    • 액티브 - 스탠바이로 구성
    • 액티브 측이 고장나면 스탠바이를 이용
    • 단점은 한 쪽이 계속 놀고 있는 상태가 되기 때문에 양쪽 서버로를 교차이용 하는 경우도 많음

    재해 대책형 아키텍처

    • DR (Disater Recovery) 구성으로 재해용 서버를 똑같이 구성함
    • 평상시에는 일반적인 데이터 서버를 이용하고 재해가 발생하였을대 부산 측 시스템을 이용

    'Back-end > Infra' 카테고리의 다른 글

    그림으로 공부하는 IT 인프라 구조  (0) 2022.08.10
    도커란?  (0) 2022.03.13
    그림으로 공부하는 IT 인프라 구조 중에 필요한 부분만 찍먹 해보자.
      공부 일시
    1.1 인프라란 무엇인가? 2022.08.10(수)
       

     

    'Back-end > Infra' 카테고리의 다른 글

    [그림으로 공부하는 IT 인프라 구조] 인프라란 무엇일까?  (0) 2022.08.10
    도커란?  (0) 2022.03.13

    목차

    JPA를 사용하는 데 가장 중요한 일은 엔티티와 테이블을 정확히 매핑하는 것이다.
    • 객체와 테이블 매핑 : @Entity, @Table
    • 기본 키 매핑 : @Id
    • 필드와 컬럼 매핑 : @Column
    • 연관관계 매핑 : @ManyToOne, @JoinColumn

    객체와 테이블 매핑

    @Entity

    • @Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
    • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수이다.
    • 주의
      • 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
      • final 클래스, enum, interface, inner 클래스 --> 사용x
      • 저장할 필드에 final 사용 x

    @Table, enumtype

    • @Table은 엔티티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.
    • createdDate, lastModifiedDate : 자바의 날짜 타입은 @Temporal을 사용해서 매핑한다.
    • roleType : 자바의 enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야한다.
    • 회원을 설명하는 필드는 길이 제한이 없다. 따라서 데이터 베이스의 VARCHAR 타입 대신 CLOB 타입으로 저장해야 한다. @Lob을 사용하면 CLOB, BLOB 타입을 매핑할 수 있다.
    package jpabook.start; 
    import javax.persistence.*; 
    import java.util.Date; 
    
    @Entity 
    @Table (name="MEMBER") 
    public class Member { 
    	
    	@Id 
    	@Column (name = "ID") 
    	private String id; 
    	
    	@Column (name - "NAME") 
    	private String username; 
    	
    	private Integer age; 
    
    	//== 추가 == 
    	@Enumerated (EnumType. STRING) 
    	private RoleType roleType; // 1
    
    	@Temporal (TemporalType. TIMESTAMP) 
    	private Date createdDate; // 2
    
    	@Temporal (TemporalType. TIMESTAMP)  
    	private Date lastModifiedDate; // 2
     
    	@Lob 
    	private String description; //3
    }
    
    //Getter, Setter 
    package jpabook.start; 
    
    public enum RoleType { 
    	ADMIN, USER
    }

    DDL 생성 기능

    • 제약조건 추가 : 회원 이름 필수, 10자 초과 X
    @Column(nullable = false, length =10)
    • 유니크 제약조건 추가
    @Table(uniqeConstraints = (@UniqueConstraint(name = "NAME_AGE_UNIQUE", columnNames ={"NAME", "AGE"})))
    • DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

    @Column

    옵션
    설명
    name 필드와 매핑할 테이블의 컬럼 이름
    insertable, updatable 등록, 변경 가능 여부 (기본 값 : TRUE)
    nullable(DDL) null 가능 여부, false 설정하면 DDL 생성 시에 Not Null 조건이 붙다.
    unique(DDL) @Table의 uniqueConstraints와 같지만 한 컬럼에 간단한 유니크 제약조건을 걸 때 사용한다.
    columnDefinition(DDL) 데이터베이스 컬럼 정보를 직접 줄 수 있다.
    ex) varchar(100) default 'EMPTY'
    length(DDL) 문자 길이 제약 조건, String 타입에만 사용 (기본 값 : 255)
    precision, scale(DDL) 소수의 자릿수 설정 (기본 값 : precision=19)

    @Enumerated

    • 자바 enum 타입을 매핑 할 때 사용
    • 주의 ORDINAL 사용 X
      • EnumType.ORDINAL : enum 순서를 데이터 베이스에 저장
        • enum의 순서대로 0,1,2,3... 저장 된다.(Integer값 저장)
          • 따라서 엄청난 혼란을 줄 수 있다. 예를 들면 ADMIN이 0번 이었는데, GUEST가 0번으로 바뀌면 이전에 저장 되어있던 0번 값(=ADMIN)과 새로 저장된 값 GUEST(=0번 값)이 헷갈리게 된다.
      • EnumType.STRING : enum 이름을 데이터베이스에 저장 (*항상 이렇게 사용)
        • enum 설정 값 이름 그대로를 저장한다.

    @Temporal

    날짜 타입(java.util.Date, java.util.Calendar) 매핑할 때 사용한다.

    • 참고 : LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)
    • TemporalType.DATE : 날짜, 데이터베이스, date 타입과 매핑 (예 : 2013-10-11)
    • TemporalType.TIME : 시간, 데이터베이스, time 타입과 매핑 (예 : 11:11:11)
    • TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스, timestamp 타입과 매핑(예 : 2013-10-11 11:11:11)

    아래와 같이 적으면 어노테이션이 필요없다!

    private LocalDate localDate;
    private LocalDateTime localDateTime;

    @Lob

    데이터베이스 BLOB, CLOB 타입과 매핑

    @Lob에는 지정할 수 있는 속성이 없고, 대신에 매핑하는 필드 타입이 문자면, CLOB으로 매핑하고 나머지는 BLOB로 매핑한다.

    @Transient

    객체 임시로 어떤 값을 넣고 싶을 때 사용하고 데이터베이스에는 반영이 안된다.

    데이터 베이스 스키마 자동 생성

    사실상 운영에선 사용하진 않고, 개인적으로 개발할때 정도 사용한다.
    • DDL을 애플리케이션 실행 시점에 자동 생성
    • 테이블 중심 -> 객체 중심
    • 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성

    xml 다음과 같이 입력하면 초기 실행 시 자동으로 테이블을 생성한다.

    <property name="hibernate.hbm2ddl.auto" value ="create"/>

    위의 value 속성은 개발 단계마다 다르게 생성할 수있다.

    • 개발 초기 단계에는 create 또는 update
    • 테스트 서버는 update 또는 validate(엔티티와 테이블이 정상 매핑되었는지만 확인)
    • 스테이징과 운영서버는 validate 또는 none
    • 운영 장비에는 절대 create, create-drop, update 사용하면 안된다.

    기본 키 매핑

    기본 키 매핑 어노테이션

    • @Id
    • @GeneratedValue
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    기본 키 매핑 방법

    데이터베이스마다 기본 키를 생성하는 방식이 서로 다르므로 이 문제를 해결하기는 쉽지 않다. JPA는 이런 문제들을 어떻게 해결하는지 알아보자
    • 직접 할당 : @Id만 사용
    • 자동 생성(@GeneratedValue)
      • IDENTITY : 데이터베이스에 위임(DB에 따라서 알아서(임의로) 해줌), MYSQL, PostgreSQL, SQL Server, DB2에서 사용
        • MySQL의 AUTO_INCREMENT
      • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE
        • @SequenceGenerator 필요
      • TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용
        • @TableGenerator 필요

    기본 키 직접 할당 전략

    @Id 적용 가능 자바 타입

    • 자바 기본형
    • 자바 래퍼wrapper형
    • String
    • java.util.Date
    • java.sql.Date
    • java.math.BigDecimal
    • java.math.BigInteger
    SEQUENCE 전략

    sequenceName 속성의 이름으로 BOARD_SEQ 를 지정했는데 JPA는 이 시퀸스 생성기를 실제 데이터베이스 BOARD_SEQ 시퀀스와 매핑한다. sequenceName을 따로 설정하지 않으면 hibernate_sequence와 같이 자동으로 설정된다.

    @Entity
    @SequenceGenerator(
    	name = "BOARD_SEQ_GENERATOR".
    	sequenceName = ”BOARD_SEQ”, //매핑할 데이터베이스 시퀀스 이름
    	initialvalue = 1, 
    	allocationsize = 1)
    public class Board {
    
    	@IdQGeneratedValue(
    	strategy = GenerationType.SEQUENCE,
    	generator = "BOARD_SEQ_GENERATOR")
    	private Long id;
    	...
    }

    call next value for MEMBER_SEQ란?
    영속성 컨텍스트에 의해 JPA는 항상 PK값을 알아야한다. 그래서 MEMBER_SEQ값에서 id값을 조회한다. 그러고 나서 em.persist를 해준다.

    commit 하는 시점에 insert쿼리가 날라간다.
    SquenceGenerator.allocationSize의 기본값이 50인 이유는 최적화 때문이다 allocationSize 값이 50이면 시퀀스를 한 번에 50 증가 시킨 다음에 1~50까지는 메모리에서 식별자를 할당한다. 이 최적화 방법은 시퀀스 값을 선점 하므로 여러 JVM이 동시에 동작 해도 기본 키 값이 충돌하지 않는 장점이 있다. 반면에 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두해 두어야 한다. 참고로 앞서 설명한 hibernate.id.new_generator_mappings 속성을 true로 설정해야 지금까지 설명한 최적화 방법이 적용된다.

    TABLE 전략(운영에서 잘 쓰이지 않음)

    TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다.
    • 장점 : 모든 데이터베이스에 적용 가능
    • 단점 : 성능
    @Entity
    @TableGenerator(
    	name = "BOARD_SEQ_GENERATOR",
    	table = ”MY_SEQUENCES",
    	pkColumnValue = ”BOARD_SEQ”, 
    	allocationsize = 1)
    public class Board {
    	@Id
    	@GeneratedValue(
    		strategy = GenerationType.TABLE,
    		generator = '' BOARD_SEQ_GENERATOR''
    	)
    	private Long id;
    	...
    }
    TABLE 전략과 최적화 TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용한다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스와 한번 더 통신하는 단점이 있다. TABLE 전략을 최적화하려면 TableGenerator.allocationSize를 사용하면 된다.

    권장하는 식별자 전략

    • 기본 키 제약 조건 : null 아님, 변하면 안된다.
    • 미래까지 이 조건에 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
    • 예를 들어 주민등록번호도 기본 키로 적절하지 않다.
    • 권장 : Long형 + 대체키 + 키 생성전략 사용

    AUTO 전략

    GenerationType.AUTO는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. AUTO를 사용할 때 SEQUENCETABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다.

    IDENTITY 전략

    기본 키 생성을 데이터베이스에 위임

    IDENTITY 전략은 지금 설명한 AUTO INCREMENT를 사용한 예제처럼 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.

    private static void logic (EntityManager em) { 
    	Board board = new Board(); em.persist (board); 
    	System.out.println("board.id = " + board.getId()); 
    }
    //출력: board.id = 1
    문제점 : IDENTITY 전략은 데이터를 데이터베이스에 INSERT한 후에 기본 키 값을 조회할 수 있다.
    티티가 영속 상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL 이 데이터베이스에 전달된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다. 왜냐하면 JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행합니다. 그리고 AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있습니다.

    'Back-end > JPA' 카테고리의 다른 글

    객체지향 쿼리 언어1 - 기본 문법  (0) 2022.09.24
    연관관계 매핑 기초  (0) 2022.08.22
    [JPA] JPA 영속성 컨텍스트  (0) 2022.05.05
    [JPA] Batch Insert  (0) 2022.05.05
    JPA 소개  (0) 2022.03.14

    목차

      결합 인덱스란?

      결합 인덱스란 두 개 이상의 컬럼을 합쳐서 인덱스를 만드는 것을 말합니다. 주로 단일 컬럼으로는 나쁜 분포도를 가지지만 여러 개의 컬럼을 합친다면 좋은 분포도를 가지고, Where절에서 AND 조건에 많이 사용되는 컬럼들을 결합 인덱스로 구성합니다.

      결합 인덱스 컬럼 선택

      1. where절에서 and 조건으로 자주 결합되어 사용되면서 각각의 분포도 보다 두 개 이상의 컬럼이 결합될 때 분포도가 좋아지는 컬럼들 

      2. 다른 테이블과 조인의 연결고리로 자주 사용되는 컬럼들

      3. order by에서 자주 사용되는 컬럼들

      4. 하나 이상의 키 컬럼 조건으로 같은 테이블의 컬럼들이 자주 조회될 때

      결합 인덱스의 컬럼 순서 결정

      결합 인덱스를 만들 때 결합 인덱스를 구성하는 컬럼들의 배열 순서는 아주 중요하기에 신중하게 결정하여야 합니다. 컬럼의 순서를 잘못 배열하면 결합 인덱스의 발동 확률이 매우 낮아질 수 있기 때문입니다. 만약 select 문의 where절에 결합 인덱스의 첫 번째 컬럼을 조건에 사용하였다면 그 질의문은 결합 인덱스를 사용할 수 있습니다. 하지만 개발자가 결합 인덱스의 두번째 컬럼만을 where 절에 조건으로 사용하고 결합 인덱스를 사용하고자 했다면 실행계획은 인덱스를 사용하지 못합니다. 따라서 쿼리문 작성 시 결합 인덱스를 사용하고자 한다면 반드시 결합 인덱스의 컬럼 중 선행하는 컬럼부터 조건에 지정하여 사용하여야 합니다. 조건은 컬럼 전체를 순서대로 사용할 수도 있고, 아니면 선행하는 일부 컬럼을 순서대로 사용할 수 있습니다. 

      결합 인덱스 컬럼의 설정 시 고려해야 할 우선순위

      1. where절 조건에 많이 사용되는 컬럼이 우선시

      2. Equal('=')로 사용되는 컬럼 우선

      3. 분포도가 좋은 컬럼을 우선

      4. 자주 이용되는 순서대로 결합 인덱스 컬럼의 순서 결정

       

      결합 인덱스 사용 예시

      결합 인덱스 생성

      create index emp_pay_idx on emp_pay(급여년월, 급여코드, 사원번호);

      emp_pay 테이블에서 급여년월, 급여코드, 사원번호 컬럼으로 emp_pay_idx라는 결합 인덱스를 생성하였습니다.

      select * from emp_pay where 급여년월 = '202107';
      select * from emp_pay where 급여년월 = '202107' and 급여코드 ='정기급여';
      select * from emp_pay where 급여년월 = '202107' and 급여코드 = '정기급여' and 사원번호 = '20210401';

      select 문장의 where 절에서는 다음과 같은 조건 조합에서 인덱스가 사용되게 됩니다.

      결합 인덱스의 효율성이 떨어지는 경우

      결합 인덱스도 일반적인 인덱스와 마찬가지로 데이터들이 정렬되어 보관되기 때문에 소수의 데이터를 빠르게 찾는 것에는 유리하지만 아래와 같이 스캔이 많이 생기게 된다면 효율성이 떨어지게 됩니다. 아래의 예시들은 emp_pay_idx 인덱스를 사용하기는 하지만 스캔이 많이 생기는 경우로 인덱스의 효율성이 떨어지는 경우들의 예시입니다.

      select * from emp_pay where 급여년월 LIKE '2021%' and 급여코드 = '정기급여';

      위 조건절의 경우 결합 인덱스의 첫 번째 컬럼인 급여년월의 조건이 있더라도 Equal(=)이 아닌 범위 연산자인 LIKE '2021%' 조건을 사용했으므로, 세개의 칼럼이 모두 필요한 emp_pay_idx 인덱스를 찾을 때 두번째 칼럼인 급여코드에 대한 조건을 B*Tree에서 쉽게 찾을수가 없게 됩니다. 이는 결합 인덱스가 각 칼럼별로 정렬이 되어 있는 것이 아니라 첫번째, 두번째, 세번째 칼럼이 결합이 되어 정렬이 되어있기 때문입니다. 이때 급여코드에 대한 조건은 인덱스를 찾아가는 검색조건이 아니라 인덱스 값이 조건에 맞는지 여부를 검증하는 체크 조건이 됩니다.

      select * from emp_pay where 급여년월 = '202107' and 사원번호 = '20210401';

      위 조건절의 경우는 결합 인덱스의 첫번째 칼럼인 급여년월의 조건이 equal(=)이더라도 두번째 컬럼인 급여코드에 대한 조건이 없으므로 세번째 칼럼인 사원번호 조건을 검색 조건이 아닌 체크 조건으로 밖에 사용할 수 없게 됩니다. 즉 결합 인덱스에서 급여년월인 모든 데이터를 찾아서 사원번호 조건에 맞는지 일일이 확인하는 풀 테이블 스캔이 일어나고 있는 셈입니다.

      'Back-end > DB' 카테고리의 다른 글

      [DB] Dababase Sharding 이란?  (0) 2022.05.05
      인덱스(Index) 란?  (0) 2022.05.05
      인덱스 란? ***  (0) 2022.02.18

      목차

        JPA에서 가장 중요한 2가지

        • 객체와 관계형 데이터 베이스 매핑하기 (Object Relational Mapping)
        • 영속성 컨텍스트

        영속성 컨텍스트

        • JPA를 이해하는데 가장 중요한 용어
        • "엔티티를 영구 저장하는 환경" 이라는 뜻
        • EntityManager.persist(entity);

        EntityManager.persist(entity)

        우리는 .persist를 해당 entity를 DB에 저장한다고 이해했다. 그렇지않고 우리는 entity를 DB에 저장하는 것이 아닌 영속성 컨텍스트에 저장하는 것이다.

        • 영속성 컨텍스트는 논리적인 개념이다.
        • 눈에 보이지 않는다.
        • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

        영속성 컨텍스트 생성 과정

        EntityManasger를 생성하면 위 그림 처럼 영속성 컨텍스트가 생성됩니다.

        엔티티의 생성주기

        • 비영속(new/transient)
          • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
        • 영속(managed)
          • 영속성 컨텍스트에 관리되는 상태
        • 준영속(detached)
          • 영속성 컨텍스트에 저장되었다가 분리된 상태
        • 삭제(removed)
          • 삭제된 상태

        비영속 상태(JPA랑 전혀 관계 없는 상태)

        영속

        DB에는 언제 저장하는가?

        em.persist(member);

        위의 상태에서는 DB에 저장한 것이 아닌 영속성 컨텍스트에 저장한 것이다. 그렇다면 언제 DB에 저장하는 것 인가?

        확인 코드 

        System.out.print("=== before ===");
        em.persist(member);
        System.out.print("=== after ===");

        결과 : before와 after사이에 insert문이 날라가지 않는다.

        이유

        tx.commit;

        트랜잭션 커밋 상태에서 결국 query가 날라가게 된다.

        준영속, 삭제

        회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태

        em.detach(member);

        객체를 삭제한 상태

        em.remove(member);

        영속성 컨텍스트의 이점

        바로 db에 저장하지 않고 중간과정을 거친다. 그래서 얻는 것은 다음과 같다.

        • 1차 캐시
        • 동일성 보장
        • 트랜잭션을 지원하는 쓰기 지연
        • 변경 감지
        • 지연 로딩

        엔티티 조회, 1차 캐시

        1차 캐시로 저장하고 db에 저장한다.

        어느 시점에? 트랜잭션

         데이터베이스에서 조회

        단, 한 트랜잭션안에서만 동작하기 때문에 큰 이점은 없다. 즉, 트랜잭션이 끝나면 휘발성으로 날라간다. 우리가 아는 캐싱을 적용하려면 2차캐싱을 적용해야한다. 

        영속 엔티티의 동일성 보장

        1차 캐시로 반복 가능한 읽기(Repeatable read) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공

        엔티티 등록 트랜잭션을 지원하는 쓰기 지연

        1차 캐시에 저장과 동시에 INSERT SQL 생성하여 쓰기 지연 SQL 저장소에 저장한다.

        그리고 transactio.commit();을 만나면 flush가 되면서 db에 날라가고 commit이 된다.

        • jpa batch 라는 것으로 size를 관리한다.
        • 약간 bulk insert 같은 느낌으로 버퍼를 모으는 느낌

        ★엔티티 수정 변경 감지(Drity Checking)★

        ​EntityManager em = emf.createENtityManager();
        EntityTransaction tr = em.getTransaction();
        tr.begin(); //트랜잭션 시작
        
        //영속 엔티티 조회
        Member memberA = em.find(Member.class, "memberA");
        //영속 엔티티 데이터 수정
        MemberA.setUserName("hi");
        MemberA.setAge(10);
        
        //em.update(member) 이런 코드가 있어야 하지 않을까?
        tr.commit(); //트랜잭션 커밋

        위의 코드를 보면 memberA라는 객체에 setName, setAge를 해주고 update 쿼리를 해주지 않았는데도 자동으로 update가 된다. 왜그럴까? 뭔가 set을 해주고 persist를 해줘야 반영이 될 것 같은데 말이다.

        1차 캐시안에는 @Id, Entity, 스냅샷이라는 것이 있다. 스냅샷은 최초로 영속성 컨텍스트에 들어온 값을 임시 저장하는 것이다. 그러고 Entity(새로 들어온 값) 스냅샷과 변경이 감지되면 update 쿼리를 쓰기 지연 SQL 저장소에 저장해버리고 DB에 반영한다.

        플러시

        영속성 컨텍스트의 변경내용을 데이터베이스에 반영

        플러시 발생

        • 변경 감지
        • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
        • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송
          • 등록, 수정, 삭제 쿼리

        영속성 컨텍스트를 플러시 하는 방법

        • em.flush() - 직접 호출 트랜잭션 커밋 전에 강제 느낌 (거의 사용x)
        • 트랜잭션 커밋 - 플러시 자동 호출
        • JPQL 쿼리 실행 - 플러시 자동 호출
        em.persist(memberA); 
        em.persist(memberB); 
        em.persist(memberC);
        // 중간에 JPQL 실행 
        query = em.createQuery("select m from Member m", member.class);
        List<Member> members = query.getResultList();
        • em.persist의 memberA,B,C는 아직 데이터베이스에 Insert 전이다.
        • 따라서 select 문에서 조회가 안될 것 같지만, JPQL은 flush가 자동으로 된다. 그래서 조회가 된다.
          • flush 여부 설정 가능
            • FlushModeType.AUTO... or COMMIT

        준영속 상태

        • 영속 -> 준영속
        • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
        • 영속성 컨텍스트가 제공하는 기능을 사용 못함
        • 거의 비영속 상태에 가깝다.
          영속성 컨텍스트가 관리하지 않으므로 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떤한 기능도 동작하지 않는다.
        • 식별자 값을 가지고 있다.
          비영속 상태는 식별자 값이 없을 수도 있지만 준영속 상태는 이미 한 번 영속상태였으므로 반드시 식별자 값을 가지고 있다
        • 지연 로딩을 할 수 없다.
        //1 : 떼어내기
        em.detach(member); 
        tx.commit();//commit을 해도 em은 반영되지 않는다.
        
        //2 1차 캐시를 조회함 그래서 같은 내용 또 조회해도 select 쿼리가 또 나감
        em.clear();
        
        //3. em.close() : 종료

         

        'Back-end > JPA' 카테고리의 다른 글

        연관관계 매핑 기초  (0) 2022.08.22
        엔티티 매핑  (0) 2022.08.06
        [JPA] Batch Insert  (0) 2022.05.05
        JPA 소개  (0) 2022.03.14
        자바 ORM 표준 JPA 프로그래밍  (0) 2022.03.14

        목차

          Dababase Sharding 이란?

          DB에 데이터가 늘어나면 용량 이슈와 함께 성능이 저하되고 DB 시스템 전체에 문제가 생길 가능성이 높아지게 된다. 이를 방지하기 위해 여러 DB 분산처리 기법이 있는데, 그 중 Sharding 기법에 대해 알아보려고 한다.

           

          Sharding은 같은 테이블 스키마를 가진 데이터들을 다수의 DB에 분산하여 저장하는 Horizontal Partitioning 방법으로 해당 테이블의 인덱스 크기를 줄이고, 작업 동시성을 늘리는 방법이다.

           

          다만 DB Sharding을 적용하면 프로그래밍 복잡도 및 운영 복잡도가 높아지기 때문에 다른 분산처리 방법을 먼저 고려한 후 대규모의 빅데이터 관리의 경우에만 사용하는 것이 좋다고 한다. 선고려할 분산처리 방법에는 다음 방법들이 있다.

          다른 DB 분산 처리 방법

          • 물리적 Scailing : DB 서버 및 Storage를 물리적으로 Scaling 한다. (=Scale Up)
          • Cache & DB Replication 적용 : Read가 많은 시스템일 경우 Cache 및 DB Replication 방법을 적용한다.
          • (DB Replication : DBMS를 Master/Slave 구조로 나누어, Master DB는 Insert, Update, Delete 기능을 수행하고, Slave DB는 실제 데이터를 복사하여 Select 문을 수행한다.)
            • Master(DB1) - insert, update, delete 수행 
            • Slave(IRDB) - select 전용 db
          • Vertical Partitioning : Table의 일부 컬럼만을 자주 사용할 경우.
          • Hot & Cold Data 분리 : 사진, 동영상, 메일 등 보관 기간은 길지만 자주 접근하여 사용하지 않는 Cold 데이터는 별도의 DB로 분리한다.

          - Hash Sharding : Shard Key를 Hashing 한 결과로 DB를 선택한다. Hash 방법으로 Modular 연산 등이 있으며, 데이터 형태에 따라 Hash 함수를 잘 설계하는 것이 필요하다.

          주로 데이터량이 일정 수준에서 유지될 것으로 예상될 때 적용한다.

          장점 단점
          • 데이터가 각 Shard에 균일하게 분산된다.
          • DB를 추가 증설하는 과정에서 Hash 함수가 바껴야하므로 이미 적재된 데이터의 재정렬이 필요하다.

          Dynamic (Range Based) Sharding

          Local Service를 이용해 Shard Key를 특정 범위 기준으로 분할한다. 데이터의 트래픽에 따라 기준을 동적으로 변경할 수 있다.

          장점 단점
          • 증설 시 Shard Key만 추가하면 되므로 재정렬 비용이 들지 않는다.
          • 일부 DB에 데이터가 몰릴 수 있다.
          • 데이터 분할 범위 기준을 명확하게 설정되어야 한다.
          • Locator의 의존성이 커져 Locator 장애 시 Shard 전체에 영향을 줄 수 있다.
          • 데이터 재배치 시 Locator의 Shard Key Table도 일치시켜줘야 한다.
          • 성능을 위해 Cache 및 Replication 실행 시 Location의 잘못된 Routing으로 데이터를 찾지 못해 에러가 발생할 수 있다.

          Entity Group

          테이블이 Key-Value 관계가 아닌 다양한 객체로 구성되어 있는 경우, 일정 관계에 있는 Entity들을 같은 Shard 내에 구성한다.

          장점 단점
          • 같은 물리적인 Shard 내에서 쿼리 진행 시 효율적.
          • 사용자 증가에 따른 확장성이 좋다.
          • 하나의 Shard 내에서 강한 응집도를 가진다.
          • 특정 파티션 간 쿼리 요구 시 비효율적일 수 있다.

          참고

          출처: https://seongyun-dev.tistory.com/8 [이거슨무슨블로그]

          'Back-end > DB' 카테고리의 다른 글

          [DB] 결합 인덱싱  (0) 2022.05.14
          인덱스(Index) 란?  (0) 2022.05.05
          인덱스 란? ***  (0) 2022.02.18

          목차

            Web API란?

            REST의 uniform interface를 지원하는 것은 쉽지 않기 때문에, 많은 서비스가 REST에서 바라는 것을 모두 지원하지 않고 API를 만들게 된다. REST API의 모든 스타일을 구현하지 못할 경우에는 Web API 혹은 Http API라고 부른다.

            Web API 디자인 가이드

            - URI는 정보의 자원을 표현해야 한다.

            - 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.

            웹 API의 이점

            Web API는 분산 시스템에서 서비스를 제공하는 조직에 도움이 됩니다. 다음은 웹 API의 몇 가지 이점입니다.

            • 비즈니스: Web API는 오픈 소스이므로 일관된 비즈니스 데이터를 유지하기 위해 논리 중앙 집중화에 대한 복잡성을 줄입니다. 저대역폭 데이터(JSON/XML)는 구문 분석이 쉽고 가벼우며 이상적인 데이터 교환 형식이므로 모든 언어와 통합할 수 있습니다. 또한 Web API는 ASP.NET 프레임워크의 필수적인 부분이므로 유지 관리 및 이해가 매우 간단합니다.
            • 기술: Web API의 주요 기술 이점 중 하나는 복잡한 구성이 필요하지 않다는 것입니다. 경량 아키텍처이기 때문에 대역폭이 제한된 장치(스마트폰)에 이상적입니다. OData(공개 데이터), 라우팅, 모델 바인딩 및 MVC와 유사한 유효성 검사를 지원합니다.

            Web API와 REST API의 4가지 주요 차이점

            1) 웹 API 대 REST API: 프로토콜

            Web API는 서비스가 웹을 통해 다양한 클라이언트에 도달할 수 있도록 하는 HTTP/s 프로토콜 및 URL 요청/응답 헤더에 대한 프로토콜을 지원합니다. 반면 REST API의 모든 통신은 HTTP 프로토콜을 통해서만 지원됩니다.

            2) 웹 API 대 REST API: 형식

            API는 동일한 작업을 수행하지만 Web API는 모든 통신 스타일에 유연성을 제공합니다. REST API는 통신을 위해 REST, SOAP 및 XML-RPC 를 사용할 수 있습니다.

            3) 웹 API 대 REST API: 디자인

            Web API는 경량 아키텍처이므로 스마트폰과 같은 장치에 제한된 가제트용으로 설계되었습니다. 이와 대조적으로 REST API는 시스템을 통해 데이터를 송수신하여 복잡한 아키텍처를 만듭니다.

            4) 웹 API 대 REST API: 지원

            Web API는 IIS(인터넷 정보 서비스) 또는 XML 및 JSON 요청을 지원하는 자체 에서만 호스팅될 수 있습니다 . 대조적으로 REST API는 표준화된 XML 요청을 지원하는 IIS에서만 호스팅될 수 있습니다.

            성능 향상을 위해서 Batch Insert를 도입하는 과정 중 JPA, Mysql 환경에서의 Batch Insert에 대한 방법과 제약사항들에 대해서 정리했습니다. 결과적으로는 다른 프레임워크를 도입해서 해결했으며 본 포스팅은 JPA Batch Insert의 정리와, 왜 다른 프레임워크를 도입을 했는지에 대해한 내용입니다.

            Batch Insert 란 ?

             
            # 단건 insert
            insert into payment_back (amount, order_id) values (?, ?)
            
            # 멀티 insert
            insert into payment_back (amount, order_id)
            values 
                   (1, 2),
                   (1, 2),
                   (1, 2),
                   (1, 2)

            insert rows 여러 개 연결해서 한 번에 입력하는 것을 Batch Insert라고 말합니다. 당연한 이야기이지만 Batch Insert는 하나의 트랜잭션으로 묶이게 됩니다.

            Batch Insert With JPA

            위 Batch Insert SQL이 간단해 보이지만 실제 로직으로 작성하려면 코드가 복잡해지고 실수하기 좋은 포인트들이 있어 유지 보수하기 어려운 코드가 되기 쉽습니다. 해당 포인트들은 아래 주석으로 작성했습니다. JPA를 사용하면 이러한 문제들을 정말 쉽게 해결이 가능합니다.

            // 문자열로 기반으로 SQL을 관리하기 때문에 변경 및 유지 보수에 좋지 않음
            val sql = "insert into payment_back (id, amount, order_id) values (?, ?, ?)"
            val statement = connection.prepareStatement(sql)!!
            
            fun addBatch(payment: Payment) = statement.apply {
                // code 바인딩 순서에 따라 오동작 가능성이 높음
                // 매번 자료형을 지정해서 값을 입력해야 함
                this.setLong(1, payment.id!!)
                this.setBigDecimal(2, payment.amount)
                this.setLong(3, payment.orderId)
                this.addBatch()
            }
            
            // connection & statement 객체를 직접 close 진행, 하지 않을 경우 문제 발생 가능성이 있음
            fun close() {
                if (statement.isClosed.not())
                    statement.close()
            }

            쓰기 지연 SQL 지원 이란 ?

            EntityMaanger em  = emf.createEnttiyManager();
            ENtityTranscation transaction = em.getTransaction();
            // 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
            
            transaction.begin();
            
            em.persist(memberA);
            em.persist(memberB);
            
            // 여기까지 Insert SQL을 데이터베이스에 보내지 않는다.
            // Commit을 하는 순간 데이터베이스에 Insert SQL을 보낸다
            transaction.commit();
             

            엔티티 매니저는 트랜잭션을 커밋 하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 트랜잭션을 커밋 할 때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연이라 한다.

            회원 A를 영속화했다. 영속성 컨텍스트는 1차 캐시에 회원 엔티티를 저장하면서 동시에 회원 엔티티 정보로 등록 쿼리를 만든다. 그리고 만들어진 등록 쿼리를 쓰기 지연 SQL 저장소에 보관한다.


            다음으로 회원 B를 영속화했다. 마찬가지로 회원 엔티티 정보로 등록 쿼리를 생성해서 쓰지 지연 SQL 저장소에 보관한다. 현재 쓰기 지연 SQL 저장소에는 등록 쿼리가 2건이 저장되어 있다.

            마지막으로 트랜잭션을 커밋 했다. 트랜잭션을 커밋 하면 엔티티 매니저는 우선 영속성 컨텍스트를 플러시 한다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데 이때 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다. 이러한 부분은 JPA 내부적으로 이루어지기 때문에 사용하는 코드에서는 코드의 변경 없이 이러한 작업들이 가능하다.

            JPA With Batch Insert Code

            spring:
                jpa:
                    database: mysql
                    properties:
                        hibernate.jdbc.batch_size: 50
                        hibernate.order_inserts: true
                        hibernate.order_updates: true
                        hibernate.dialect: org.hibernate.dialect.MySQL5InnoDBDialect
                        hibernate.show_sql: true
            
                datasource:
                    url: jdbc:mysql://localhost:3366/batch_study?useSSL=false&serverTimezone=UTC&autoReconnect=true&rewriteBatchedStatements=true
                    driver-class-name: com.mysql.cj.jdbc.Driver
             

            addBatch 구분을 사용하기 위해서는 rewriteBatchedStatements=true 속성을 지정해야 합니다. 기본 설정은 false이며, 해당 설정이 없으면 Batch Insert는 동작하지 않습니다. 정확한 내용은 공식 문서를 참고해 주세요.

            MySQL Connector/J 8.0 Developer Guide : 6.3.13 Performance Extensions
            Stops checking if every INSERT statement contains the “ON DUPLICATE KEY UPDATE” clause. As a side effect, obtaining the statement’s generated keys information will return a list where normally it wouldn’t. Also be aware that, in this case, the list of generated keys returned may not be accurate. The effect of this property is canceled if set simultaneously with ‘rewriteBatchedStatements=true’.

            hibernate.jdbc.batch_size: 50 Batch Insert의 size를 지정합니다. 해당 크기에 따라서 한 번에 insert 되는 rows가 결정됩니다. 자세한 내용은 아래에서 설명드리겠습니다.

            @Entity
            @Table(name = "payment_back")
            class PaymentBackJpa(
                @Column(name = "amount", nullable = false)
                var amount: BigDecimal,
            
                @Column(name = "order_id", nullable = false, updatable = false)
                val orderId: Long
            ){
                
                @Id
                @Column(name = "id", updatable = false) // @GeneratedValue를 지정하지 않았음
                var id: Long? = null
            }
            
            interface PaymentBackJpaRepository: JpaRepository<PaymentBackJpa, Long>
             

            엔티티 클래스는 간단합니다. 중요한 부분은 @GeneratedValue을 지정하지 않은 부분입니다.

            @SpringBootTest
            @TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
            internal class BulkInsertJobConfigurationTest(
                private val paymentBackJpaRepository: PaymentBackJpaRepository
            ) {
            
                @Test
                internal fun `jpa 기반 bulk insert`() {
                    (1..100).map {
                        PaymentBackJpa(
                            amount = it.toBigDecimal(),
                            orderId = it.toLong()
                        )
                            .apply {
                                this.id = it.toLong() // ID를 직접 지정
                            }
                    }.also {
                        paymentBackJpaRepository.saveAll(it)
                    }
                }
            }
             

            paymentBackJpaRepository.saveAll()를 이용해서 batch inset를 진행합니다. JPA 기반으로 Batch Insert를 진행할 때 별다른 코드가 필요 없습니다. 컬렉션 객체를 saveAll()으로 저장하는 것이 전부입니다. hibernate.show_sql: true으로 로킹 결고를 확인해보겠습니다.

            로그상으로는 Batch Insert가 진행되지 않은 것처럼 보입니다. 결론부터 말씀드리면 실제로는 Batch Insert가 진행됐지만 hibernate.show_sql: true 기반 로그에는 제대로 표시가 되지 않습니다. Mysql의 실제 로그로 확인해보겠습니다.

            show variables like 'general_log%'; # general_log 획인
            set global general_log = 'ON'; # `OFF` 경우 `ON` 으로 변경
             

            해당 로그 설정은 성능에 지장을 줄 수 있기 때문에 테스트, 개발 환경에서만 지정하는 것을 권장합니다. 해당 기능은 실시간으로 변경 가능하기 때문에 설정 완료 이후 /var/lib/mysql/0a651fe44d20.log 파일에 로그를 확인할 수 있습니다.

            batch size

            Query	SELECT @@session.transaction_read_only
            Query	insert into payment_back (amount, order_id, id) values (1, 1, 1),(2, 2, 2),(3, 3, 3),(4, 4, 4),(5, 5, 5),(6, 6, 6),(7, 7, 7),(8, 8, 8),(9, 9, 9),(10, 10, 10),(11, 11, 11),(12, 12, 12),(13, 13, 13),(14, 14, 14),(15, 15, 15),(16, 16, 16),(17, 17, 17),(18, 18, 18),(19, 19, 19),(20, 20, 20),(21, 21, 21),(22, 22, 22),(23, 23, 23),(24, 24, 24),(25, 25, 25),(26, 26, 26),(27, 27, 27),(28, 28, 28),(29, 29, 29),(30, 30, 30),(31, 31, 31),(32, 32, 32),(33, 33, 33),(34, 34, 34),(35, 35, 35),(36, 36, 36),(37, 37, 37),(38, 38, 38),(39, 39, 39),(40, 40, 40),(41, 41, 41),(42, 42, 42),(43, 43, 43),(44, 44, 44),(45, 45, 45),(46, 46, 46),(47, 47, 47),(48, 48, 48),(49, 49, 49),(50, 50, 50)
            Query	SELECT @@session.transaction_read_only
            Query	insert into payment_back (amount, order_id, id) values (51, 51, 51),(52, 52, 52),(53, 53, 53),(54, 54, 54),(55, 55, 55),(56, 56, 56),(57, 57, 57),(58, 58, 58),(59, 59, 59),(60, 60, 60),(61, 61, 61),(62, 62, 62),(63, 63, 63),(64, 64, 64),(65, 65, 65),(66, 66, 66),(67, 67, 67),(68, 68, 68),(69, 69, 69),(70, 70, 70),(71, 71, 71),(72, 72, 72),(73, 73, 73),(74, 74, 74),(75, 75, 75),(76, 76, 76),(77, 77, 77),(78, 78, 78),(79, 79, 79),(80, 80, 80),(81, 81, 81),(82, 82, 82),(83, 83, 83),(84, 84, 84),(85, 85, 85),(86, 86, 86),(87, 87, 87),(88, 88, 88),(89, 89, 89),(90, 90, 90),(91, 91, 91),(92, 92, 92),(93, 93, 93),(94, 94, 94),(95, 95, 95),(96, 96, 96),(97, 97, 97),(98, 98, 98),(99, 99, 99),(100, 100, 100)
            Query	commit
            Query	SET autocommit=1
             

            실제 mysql 로그에서는 Batch Insert를 확인할 수 있습니다. 그런데 왜 2번에 걸쳐서 Batch Insert가 진행되었을까요? hibernate.jdbc.batch_size: 50설정으로 Batch Insert에 대한 size를 50으로 지정했기 때문에 rows 100를 저장할 때 2번에 걸쳐 insert를 진행하는 것입니다. 만약 hibernate.jdbc.batch_size: 100이라면 1번의 insert로 저장됩니다.

             
             

            위 쿼리는 hibernate.jdbc.batch_size: 100으로 지정한 결과입니다. 그렇다면 왜 batch_size 옵션을 주어서 한 번에 insert 할 수 있는 데이터의 크기를 제한하는 것일까요? 아래 코드에서 해답을 찾을 수 있습니다.

            Hibernate User Guide: 12.2.1. Batch inserts

            When you make new objects persistent, employ methods flush() and clear() to the session regularly, to control the size of the first-level cache.

             

            하이버네이트 공식 가이드의 내용입니다. batchSize 값을 기준으로 flush();, clear();를 이용해서 영속성 컨텍스트를 초기화 작업을 진행하고 있습니다. batchSize에 대한 제한이 없으면 영속성 컨텍스트에 모든 엔티티가 올라가기 때문에 OutOfMemoryException 발생할 수 있고, 메모리 관리 측면에서도 효율적이지 않기 때문입니다. 하이버네이트의 공식 가이드에서도 해당 부분의 언급이 있습니다.

            Hibernate User Guide: 12.2. Session batching

            1. Hibernate caches all the newly inserted Customer instances in the session-level cache, so, when the transaction ends, 100 000 entities are managed by the persistence context. If the maximum memory allocated to the JVM is rather low, this example could fail with an OutOfMemoryException. The Java 1.8 JVM allocated either 1/4 of available RAM or 1Gb, which can easily accommodate 100 000 objects on the heap.
            2. long-running transactions can deplete a connection pool so other transactions don’t get a chance to proceed
            3. JDBC batching is not enabled by default, so every insert statement requires a database roundtrip. To enable JDBC batching, set the hibernate.jdbc.batch_size property to an integer between 10 and 50.

            쓰기 지연 SQL 제약 사항

            batchSize: 50 경우 PaymentBackJpa 객체를 50 단위로 Batch Insert 쿼리가 실행되지만, 중간에 다른 엔티티를 저장하는 경우 아래처럼 지금까지의 PaymentBackJpa에 대한 지정하기 때문에 최종적으로 batchSize: 50 단위로 저장되지 않습니다.

            em.persist(new PaymentBackJpa()); // 1
            em.persist(new PaymentBackJpa()); // 2
            em.persist(new PaymentBackJpa()); // 3
            em.persist(new PaymentBackJpa()); // 4
            em.persist(new Orders()); // 1-1, 다른 SQL이 추가 되었기 때문에  SQL 배치를 다시 시작 해야 한다.
            em.persist(new PaymentBackJpa()); // 1
            em.persist(new PaymentBackJpa()); // 2
             

            이러한 문제는 hibernate.order_updates: true, hibernate.order_inserts: true 값으로 해결 할 수 있습니다.

            JPA Batch Insert의 가장 큰 문제…

            위에서 설명했던 부분들은 Batch Insert에 필요한 properties 설정, 그리고 내부적으로 JPA에서 Batch Insert에 대한 동작 방식을 설명한 것입니다. 실제 Batch Insert를 진행하는 코드는 별다른 부분이 없고 컬렉션 객체를 saveAll() 메서드로 호출하는 것이 전부입니다. 이로써 JPA는 Batch Insert를 강력하게 지원해 주고 있습니다. 하지만 가장 큰 문제가 있습니다. @GeneratedValue(strategy = GenerationType.IDENTITY) 방식의 경우 Batch Insert를 지원하지 않습니다.

            Hibernate User Guide: 12.2. Session batching

            Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator.

            공식 문서에도 언급이 있듯이 @GeneratedValue(strategy = GenerationType.IDENTITY) 경우 Batch Insert를 지원하지 않습니다. 정확히 어떤 이유 때문인지에 대해서는 언급이 없고, 관련 내용을 잘 설명한 StackOverflow를 첨부합니다.

            제가 이해한 바로는 하이버네이트는 Transactional Write Behind 방식(마지막까지 영속성 컨텍스트에서 데이터를 가지고 있어 플러시를 연기하는 방식)을 사용하기 때문에 GenerationType.IDENTITY 방식의 경우 JDBC Batch Insert를 비활성화함. GenerationType.IDENTITY 방식이란 auto_increment으로 PK 값을 자동으로 증분 해서 생성하는 것으로 매우 효율적으로 관리할 수 있다.(heavyweight transactional course-grain locks 보다 효율적). 하지만 Insert를 실행하기 전까지는 ID에 할당된 값을 알 수 없기 때문에 Transactional Write Behind을 할 수 없고 결과적으로 Batch Insert를 진행할 수 없다.

            Mysql에서는 대부분 GenerationType.IDENTITY으로 사용하기 때문에 해당 문제는 치명적입니다. 우선 GenerationType.IDENTITY 으로 지정하고 다시 테스트 코드를 돌려 보겠습니다.

             
            @Entity
            @Table(name = "payment_back")
            class PaymentBackJpa(
                @Column(name = "amount", nullable = false)
                var amount: BigDecimal,
            
                @Column(name = "order_id", nullable = false, updatable = false)
                val orderId: Long
            ){
                @Id
                @GeneratedValue(strategy = GenerationType.IDENTITY) // GenerationType.IDENTITY 지정
                var id: Long? = null
            }
            
            internal class BulkInsertJobConfigurationTest(
                private val paymentBackJpaRepository: PaymentBackJpaRepository
            ) {
            
                @Test
                internal fun `jpa 기반 bulk insert`() {
                    (1..100).map {
                        PaymentBackJpa(
                            amount = it.toBigDecimal(),
                            orderId = it.toLong()
                        )
                            .apply {
            //                    this.id = it.toLong() // ID를 자동 증가로 변경 했기 때문에 코드 주석
                            }
                    }.also {
                        paymentBackJpaRepository.saveAll(it)
                    }
                }
            }
            Query	insert into payment_back (amount, order_id) values (1, 1)
            Query	insert into payment_back (amount, order_id) values (2, 2)
            Query	insert into payment_back (amount, order_id) values (3, 3)
            Query	insert into payment_back (amount, order_id) values (4, 4)
            Query	insert into payment_back (amount, order_id) values (5, 5)
            Query	insert into payment_back (amount, order_id) values (6, 6)
            Query	insert into payment_back (amount, order_id) values (7, 7)
            Query	insert into payment_back (amount, order_id) values (8, 8)
            Query	insert into payment_back (amount, order_id) values (9, 9)
            Query	insert into payment_back (amount, order_id) values (10, 10)
            Query	insert into payment_back (amount, order_id) values (11, 11)
            Query	insert into payment_back (amount, order_id) values (12, 12)
            ...
             

            GenerationType.IDENTITY의 경우에는 Batch Insert가 진행되지 않습니다. 그래서 다른 대안을 찾아야 했습니다. 이 부분부터는 다음 포스팅에서 이어가겠습니다.

            'Back-end > JPA' 카테고리의 다른 글

            엔티티 매핑  (0) 2022.08.06
            [JPA] JPA 영속성 컨텍스트  (0) 2022.05.05
            JPA 소개  (0) 2022.03.14
            자바 ORM 표준 JPA 프로그래밍  (0) 2022.03.14
            모든 연관관계는 지연로딩으로 설정하자. (N+1문제)  (0) 2022.03.13

            + Recent posts