H2 DB (공식문서)

주요 특징

  • 매우 빠른 데이터베이스 엔진
  • 오픈 소스
  • 자바로 작성
  • 표준 SQL, JDBC API 지원
  • 임베디드 및 서버 모드, 클러스터링 지원
  • 강력한 보안 기능
  • PostgreSQL ODBC 드라이버를 사용.
  • 다중 버전 동시성

사용에 대한 고찰

 외부에 물리DB를 생성하기 어려울 때나, 사이드 프로젝트를 하고 싶은데 물리 DB를 생성하기 부담 스러울때 간단히 내부 DB를 사용하고자 인메모리DB인 h2 를 사용한다.


  

Spring Boot에 H2 DB 사용법

H2 DB는 두가지 모드인 Embedded Mode 와 Server Mode가 존재하며 각 특징으로는 

  • Embedded mode(내장모드) : Application 서버 실행 종료시 데이터 모두 휘발
    • H2를 Application과 동일한 JVM을 이용하여 구동, Application이 종료되면 Data가 휘발
  • Server mode(서버모드) : Application 서버 종료시에도 데이터가 휘발 되지 않는다.
    • 별도의 JVM을 이용하여 구동
    • 여러 Application이 해당 H2에 동시 접근 가능

두 가지 모드 중 어떤 걸 선택하느냐에 따라 SpringBoot의 application.yml 의 설정이 다르며 아래는 Embedded Mode에서의 사용 방법에 대해 서술하는 내용이다.

bulid.gradle에 JPA 와 h2에 관한 의존성을 추가한다. JPA 의존성을 추가하는 이유는  H2:1.4.198 버전부터 보안상의 문제로 자동 데이터베이스 생성을 막아 두어 DB 파일이 생성되지 않고 있기 때문에 더미 Entity 생성으로 DB 생성을 유도해야하기 때문이다. 또는 h2의 버전을 1.4.198 보다 낮은 버전으로 설정해야한다.

// build.gradle
dependencies {
	...
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
//    runtimeOnly 'com.h2database:h2:1.4.197'
	...
}
# application.yml
  datasource:
    hikari: 
      jdbc-url: jdbc:h2:mem:testdb
      username: sa	# 필수로 입력해야만 한다. 
      password:
      driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
  jpa:
    hibernate:
      ddl-auto: create-drop # 시작할 때 새로 생성 하고 세션을 마칠 때 스키마를 소멸 시킵니다.
    properties:
      hibernate:
        show_sql: true
        format_sql: true

application.yml에서 해당 h2.console.enabled: true 설정한 후 console창을 보게 되면

H2 console available at 이라는 문구를 확인할 수 있으며

localhost:8080/h2-console 로 접속하게 된다면 아래와 같은 화면을 볼 수 있다.

반응형

docker restart {service} 를 통해 서비스를 재시작 하게 된다면 docker-compose의 수정한 내용이 반영이 안된다 이 경우

docker-compose down -> up 을 통해 도커 컨테이너를 재시작 해야 변경 점이 적용된다. 

 

docker-compose -f docker-compose-admin up --build -d 

build 란 이미지를 새롭게 빌드해서 컨테이너에 올리는 작업

 

docker-compose -f docker-compose-admin up --build --force-recreate

-> --force-recreate 란 ENTRYPOINT 자체가 컨테이너를 수정할  --build 컨테이너를 다시 만들지 않습니다. 즉, 변경된 컨테이너에서 두 번째 실행이 이루어집니다. --force-recreate 새로운 컨테이너에서 실행하는 데 사용합니다

 

./gradlew clean build --refresh-dependencies

 

docker 에는 buildx 라는 명령어가 존재하는데 docker 자체가 올라가는 호스트가 amd 계열인지 arm계열인지에 따라 빌드가 실패가 될수 있기에 docker image를 만들때 amd, arm 계열로 만든다는 의미이며 하나의 태그에 buildx로 여러 운영체제를 적용하게 된다면 해당 이미지로 컨테이너를 올릴때 호스트 운영체제에 맞는 이미지로 빌드하게 된다. 

반응형

'Docker' 카테고리의 다른 글

docker image 삭제 명령어  (0) 2024.02.10

파티션 테이블은 일반적인 테이블에서 사용하는 인덱스로는 사용이 힘들다. 파티션 테이블의 특성상 새로운 세그먼트를 계속해서 생성하여 테이블을 계속 나뉘어야 하지만 인덱스는 나눌 수가 없기 때문이다. 때문에 파티션 데이블에는 다른 방식으로 인덱스를 걸어줘야 한다. 오라클에서는 파티션 데이블에 인덱스를 거는 두 가지 방식이 있다. 바로 로컬 인덱스와 글로벌 인덱스이다. 

 

로컬 인덱스

파티션 테이블에서 사용하는 가장 일반적인 INDEX 생성 방법으로 테이블과 인덱스가 동일하게 파티션 된 경우를 말한다. 로컬 인덱스는 테이블 파티션 키 컬럼을 똑같이 인덱스로 구성한다. 예를 들어 파티션이 10개라면 인덱스도 똑같이 10개가 된다. 로컬 파티션 인덱스의 경우에 테이블의 파티션 구조가 바뀐다거나 파티션이 삭제가 된다고 하더라도 인덱스 재생성이 필요가 없어 오라클에서 알아서 자동으로 관리를 해주기 때문에 관리측면에서 유리한 측면이 있다. 

 

글로벌 인덱스

글로벌 인덱스는 파티션 테이블로 이루어져 있는 테이블에 하나의 인덱스가 여러 개의 테이블 파티션과 매핑된다. 글로벌 인덱스는 파티션 키 컬럼을 인덱스로 지정해주어야 하는 로컬 인덱스와는 달리 다른 칼럼들도 자유롭게 인덱스를 걸 수 있다는 장점이 있다.

 


파티션 테이블의 인덱스를 구성할 수 있는 두가지 방법인 로컬 인덱스, 글로벌 인덱스 위 두 가지 방식 중 로컬 파티션 인덱스를 압도적으로 많이 사용한다. 그 이유는 로컬 인덱스가 관리가 편하기 때문이다. 예를 들어 로그를 저장하는 테이블을 파티션 테이블로 만들었다고 했을 때, 시간이 지나 로그의 보관 의무시간이 종료되어 필요 없는 데이터들을 제거했다고 하며. 이때 파티션 DROP을 수행하게 될 텐데 글로벌 인덱스로 구성되어 있다면 필요 없는 테이블 파티션을 DROP 한 뒤에 인덱스가 깨진다. 이렇게 인덱스가 깨져버리면 Invaild 상태가 되고 이 인덱스를 다시 사용하기 위해서는 인덱스 리빌딩 작업을 다시 해줘야 하는 등 번거로움이 존재한다. 하지만 로컬 인덱스로 구축하였다면 전혀 문제가 발생하지 않는다. 로컬 인덱스 각각의 파티션마다 하나씩 걸려 있기 때문에 파티션을 DROP 할 때 함께 제거되고 남아있는 파티션에는 전혀 지장이 없기 때문이다. 

반응형

'DB' 카테고리의 다른 글

클러스터 인덱스 vs 넌클러스터 인덱스  (0) 2022.12.19
옵티마이저  (0) 2022.12.19

인덱스


데이터를 빠르게 검색할 수 있게 해주는 객체. 컬럼을 정렬한 후에 데이터를 빠르게 찾을 수 있도록 도와주는 역할을 한다. 책으로 비유하자면 색인을 의미.

 

인덱스를 생성하면 인덱스를 위한 디스크 공간이 필요, 인덱스를 가진 테이블에 DML 작업을 할 경우 더 많은 비용과 시간이 필요하다. 때문에 인덱스를 생성 시 해당 테이블의 요구를 정확하게 파악한 후에 상황에 맞게 적절한 칼럼으로 Clustered Index와 Non Clustered Index를 구성해야 합니다.

 

인덱스의 종류

  1. 클러스터형 인덱스(Clustered Index)
  2. 넌 클러스터형 인덱스(Nonclustered Index)

 

클러스터 인덱스

  • 테이블당 1개씩만 허용된다.
  • 물리적으로 행을 재배열한다.
  • PK설정 시 그 칼럼은 자동으로 클러스터드 인덱스가 만들어진다.
  • 인덱스 자체의 리프 페이지가 곧 데이터이다. 즉 테이블 자체가 인덱스이다. (따로 인덱스 페이지를 만들지 않는다.)
  • 데이터 입력, 수정, 삭제 시 항상 정렬 상태를 유지한다.
  • 비 클러스형 인덱스보다 검색 속도는 더 빠르다. 하지만 데이터의 입력. 수정, 삭제는 느리다.
  • 30% 이내에서 사용해야 좋은 선택도를 가진다.

 

넌 클러스터 인덱스

  • 테이블당 약 240개의 인덱스를 만들 수 있다.
  • 인덱스 페이지는 로그파일에 저장된다.
  • 레코드의 원본은 정렬되지 않고, 인덱스 페이지만 정렬된다.
  • 인덱스 자체의 리프 페이지는 데이터가 아니라 데이터가 위치하는 포인터(RID)이기 때문에 클러스터형보다 검색 속도는 더 느리지만 데이터의 입력, 수정, 삭제는 더 빠르다.
  • 인덱스를 생성할 때 데이터 페이지는 그냥 둔 상태에서 별도의 인덱스 페이지를 따로 만들기 때문에 용량을 더 차지한다
  • 3% 이내에서 사용해야 좋은 선택도를 가진다.

쉽게 책에 비유하자면 클러스터 인덱스는 페이지를 알기 때문에 바로 그 페이지를 펴는 것이고, 넌 클러스터 인덱스는 뒤에 목차에서 찾고자 하는 내용의 페이지를 찾고 그 페이지로 이동하는 것과 같다. 테이블 스캔은 처음부터 한 장씩 넘기면서 내용을 찾는 것과 같다.

 

결론

클러스터 인덱스는 데이터 위치를 바로 알기 때문에 그 데이터로 바로 접근할 수 있고, 넌 클러스터 인덱스는 인덱스 페이지를 한번 거쳐서 데이터에 접근하는 방식이다.

 

 

반응형

'DB' 카테고리의 다른 글

로컬 인덱스와 글로벌 인덱스  (0) 2022.12.19
옵티마이저  (0) 2022.12.19

 옵티마이저는 가장 효율적인 방법으로 SQL을 수행할 최적의 처리 경로를 생성해주는 DBMS의 핵심 엔진을 뜻함. 컴퓨터의 두뇌가 CPU인 것 처럼 DBMS의 두뇌는 옵티마이저라고 할 수 있다. 

 개발자가 SQL을 작성하고 실행하면 소프트웨어 실행파일처럼 즉시 실행되는 것이 아니라 옵티마이저라는 곳에서 여러가지 실행계획을 세운다. 

옵티마이저의 종류

옵티마이저는 실행 계획을 세우는 방식에 따라 규칙 기반 옵티마이저와 비용 기반 옵티마이저로 나뉜다.

항목 규칙 기반 옵티마이저 비용 기반 옵티마이저
개념 사전에 정의된 규칙 기반 최소비용 계산 실행계획 수립
기준 실행우선 순위(Ranking) 액세스 비용(Cost)
인덱스 인덱스 존재 시 가장 우선시 사용 Cost에 의한 결정
성능 사용자 SQL 작성 숙련도 옵티마이저 예측 성능
장점 판단이 매우 규칙적 실행 예상 가능 통계 정보를 통한 현실 요소 적용
단점 예측 통계정보 요소 무시 최소 성능 보장 계획의 예측 제어 어려움
사례 AND 중심 양쪽 ‘=’ 시 Index Merge 사용 AND 중심 양쪽 ‘=’ 시 분포도별 Index 선택

 규칙 기반 옵티마이저(RBO - Rule Base Optimizer)

오라클 8 이하의 버전에서 기본으로 설정된 옵티마이저가 바로 규칙기반 옵티마이저다. 규칙 기반 옵티마이저는  말 그대로 실행 속도가 빠른 순으로 규칙을 먼저 세워두고 우선순위가 앞서는 방법을 채택하는 것이다. 과거에는 옵티마이저의 비용을 예측하는 능력이 그다지 좋지 않아 이러한 방식을 사용하였다. 규칙의 우선순위는 아래와 같다.

순위 설명
1 ROWID를 사용한 단일 행인 경우
2 클러스터 조인에 의한 단일 행인 경우
3 유일하거나 기본키(Primary Key)를 가진 해시 클러스터 키에 의한 단일 행인경우
4 유일하거나 기본키(Primary Key)에 의한 단일 행인 경우
5 클러스터 조인인 경우
6 해시 클러스터 조인인 경우
7 인덱스 클러스터 키인 경우
8 복합 칼럼 인덱스인 경우
9 단일 컬럼 인덱스인 경우
10 인덱스가 구성된 칼럼에서 제한된 범위를 검색하는 경우
11 인덱스가 구성된 칼럼에서 무제한 범위를 검색하는 경우
12 정렬-병합(Sort-Merge) 조인인 경우
13 인덱스가 구성된 칼럼에서 MAX 혹은 MIN을 구하는 경우
14 인덱스가 구성된 칼럼에서 ORDER BY를 실행하는 경우
15 전체 테이블을 스캔(FULL TABLE SCAN)하는 경우

 

비용 기반 옵티마이저

 최근에 많이 사용되고 있는 옵티마이저 방식이며 오라클 10 이후 버전부터는 공식적으로 비용 기반 옵티마이저만 사용한다. 비용 기반 옵티마이저는 옵티마이저에서 실행 계획을 세운 뒤 (최대 2천개까지) 비용이 최소한으로 나온 실행 계획을 수행한다. 비용기반 옵티마이저는 이뵹을 예측하기 위해서 규칙 기반 옵티마이저가 사용하지 않는 테이블, 인텍스, 칼럼 등의 다양한 객체 통계정보와 시스템 통계정보를 이용한다. 통계정보가 없는 경우 비효율적인 샐행계획을 생성할 수 있으므로, 정확한 통계정보를 유지하는 것이 중요하다. 

 

비용 기반 옵티마이저 모드

비용 기반 옵티마이저는 여러 가지 모드가 있다. 이 모드에 따라 최적의 비용을 구하는 방식이 조금씩 달라진다.

choose: 현재는 잘 사용하지 않지만 SQL이 실행되는 환경에서 통계 정보를 가져올 수 있으면 비용 기반 옵티마이저로 아니라면 규칙 기반 옵티마이저로 작동시키는 모드.

 

first_rows: 옵티마이저가 처리 결과 중 첫 건을 출력하는데 걸리는 시간을 최소화 할 수 있는 실행 계획을 세우는 모드.

 

first_rows_n: SQL의 실행 결과를 출력하는데 까지 걸리는 응답속도를 최적화하는 모드.

 

all_rows: SQL 실행 결과 전체를 빠르게 처리하는데 최적화 된 실행계획을 세우는 모드이다. 마지막으로 출력될 행까지 최소한의 자원을 사용하여 최대한 빨리 가져오게 하며 오라클 10g 이후로는 이 모드가 기본값으로 설정.

 

옵티마이저 동작 방식

Parser : SQL문장을 분석하여 문법 검사와 구성요소를 파악하고 이를 파싱 해서 파싱 트리를 만듭니다.

Query Transformer : 파싱된 SQL을 보고 같은 결과를 도출하되, 좀 더 나은 실행 계획을 갖는 SQL로 변환이 가능한지를 판단하여 변환 작업을 수행한다. 

Estimator : 시스템 통계정보를 딕셔너리로부터 수집하여 SQL을 실행할 때 소요되는 총비용을 계산한다.

Plan Generator : Estimator를 통해 계산된 값들을 토대로 후보군이 되는 실행계획을 도출한다. 

Row-Source Generator : 옵티마이저가 생성한 실행계획을 SQL 엔진이 실제 실행할 수 있는 코드나 프로시저 형태로 포맷팅 한다.

SQL Engine : SQL을 실행한다.

반응형

'DB' 카테고리의 다른 글

로컬 인덱스와 글로벌 인덱스  (0) 2022.12.19
클러스터 인덱스 vs 넌클러스터 인덱스  (0) 2022.12.19

MSA는 Micro Service Architecture의 약어로써 과거 Monolitic Architecture로 서비스되고 있는 IT기업의 서비스 규모가 현재에는 비교도 할 수 없을만큼 커졌기에, 과거 소규모의 프로젝트에서의 Monolithic 형태는 간단하며, 유지보수가 편하기 때문에 선호되었지만 일정 규모이상으로 넘어가면 다양한 기술 부채와, 너무 복잡한 로직 속에 개발이 더뎌지며, CI/CD는 꿈도 못 꾸게 된다. 때문에 Monolitic Architecture의 대안으로 MSA라는 개념이 대두되기 시작했다. 때문에 MSA가 장단점을 설명하기 전에 기존의 모놀리식 아키텍처의 장단점에 대해 먼저 설명하고 이후 MSA의 장단점에 대해 설명하고자 한다.

 

Monolithic Architecture


한 덩어리로 뭉쳐진 단일 서비스 개발 형식, 하나의 프로젝트로 구성되어 있으며 단일 패키지로 배포하는 아키텍처

 

장점

  • MSA에 비해 상대적으로 운영이 용이하다. 하나의 코드 베이스를 유지하기에, 코드 관리가 쉽고 서버의 수도 적기 때문에 장애 관리, 로그 관리, 모니터링 등도 상대적으로 용이하다.
  • MSA에 비해 트랜잭션 관리가 용이하다 Monolithic의 트랜잭션 관리 수준으로 MSA에 적용할 수 있는 방법이 없다.
  • 단일 모듈을 배포하기에 MSA에 비해 배포가 간단하다.

 

단점

  • 하나의 코드베이스를 유지하기에, 누군가가 어떤 기능을 변경하였을 시, 다른 기능에 어떤 영향을 주는지 파악하기 힘들다.
  • 개발 언어가 종속적이다.
  • 단일 모듈이기에 선택적으로 확장이 불가능하며 굉장히 작은 수정을 해도 전체 시스템을 빌드하고 배포해야만 한다. ( 지속적인 통합 및 지속적인 배포가 어렵다)
  • 하나의 서비스가 다른 모든 서비스에 영향을 준다. (기능들 간의 결합도가 높다)
  • Scale-Out을 하려고 해도 전체 시스템을 확장해야 하는 비효율적인 부분도 존재한다.

MSA(Microservice Architecture)


애플리케이션을 느슨히 결합된 서비스의 모임으로 구조화하는 서비스 지향 아키텍처 스타일의 일종인 소프트 웨어 개발 기법이다. 

 

장점

  • 멀티 모듈로 서비스가 구성되어 있기에, 탄력적이고 선택적인 확장을 할 수 있다.
  • 서비스 단위로 자율적인 배포가 가능하다.
  • 다양한 언어로 개발이 가능하다 - Polyglot Architecture
  • 마이크로 서비스 아키텍처 덕분에 팀이 자율적으로 움직일 수 있다.
  • 결함 격리가 잘된다.

단점

  •  서비스 단위로 개발하기에 배포가 어렵고 서비스 간 통신방법이 필요하며, 서비스를 나눠서 데이터 중복이 발생할 수 있고 정합성을 보장하기 어렵다. 
  •  마이크로 서비스 응용테스트도 모놀리식에 비해 훨씬 더 복잡하다.

MSA도 Mono 도 하나의 아키텍처 종류

현재 MSA가 중요한 화두가 된 이유는 MSA는 독립적인 기능들로 이루어져 있고, 각 기능은 다양한 기술들로 개발될 수 있다. 때문에 비즈니스 환경은 점점 빠르게 급변하고 점점 더 많은 비즈니스들이 IT 기술에 의존하기에 IT 기술의 진보 없이는 비즈니스의 빠른 변화에 대처할 수 없고, 그러한 IT기술의 발전을 적용시키는 기술로는 MSA가 딱 들어맞는 아키텍처이었기에 MSA가 대두가 되었던 거지 무조건 모놀리식이 나쁘고 쓰지 말아야 할 아키텍처라고 생각하는 것은 지양되어야 한다.

반응형

Java에서의 Optional <T>는 nullable 한 'T'타입을 감싸는 Wrapper 클래스공식 API문서에서 Note 형식으로

Optional은 기본적으로 "결과 없음"을 분명히 나타내야 할 필요가 있고 사용으로 인해 null 오류가 발생할 가능성이 있는 메서드 반환 유형으로 사용하기 위한 것입니다.

라고 쓰여있지만 사실 이 글만 봤을 때에 어떻게 써야 맞는 건지 혹은 내가 쓰고 있는 방식이 제대로 쓰고 있는 방식인지에 대해 의문점이 들기에 여러 자료 글들을 찾아봤으며 '26 Reasons Why Using Optional Correctly Is Not Optional' 라는 글을 찾게 되었으며 이를 공유하고 또 정리하고자 이 글을 쓰게 되었다.

// 1. Optional값의 초기값에 null을 할당하지 마라 

// AVOID
public Optional<Cart> fetchCart() {
    
    Optional<Cart> emptyCart = null;
    ...
}

// PREFER
public Optional<Cart> fetchCart() {

    Optional<Cart> emptyCart = Optional.empty();
    ...
}  

// 2. Optional.get()을 호출하기 전에 값을 가지고 있는지 먼저 체크해라

// AVOID
Optional<Cart> cart = ... ; // this is prone to be empty
...
// if "cart"is empty then this code will throw a java.util.NoSuchElementException
Cart myCart = cart.get();

// PREFER
if (cart.isPresent()) {
    Cart myCart = cart.get();
    ... // do something with "myCart"
} else {
    ... // do something that doesn't call cart.get()
}

// 3. 값이 없으면 Optional.orElse()를 통해 이미 생성된 기본 객체를 Set/Return한다

// AVOID
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

    if (status.isPresent()) {
        return status.get();
    } else {
        return USER_STATUS;
    }
}

// PREFER
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

    return status.orElse(USER_STATUS);
}

....
반응형

+ Recent posts