이번에 회사에서 DB 서버를 AWS RDS로 단계적으로 이전하던 도중에 프로덕션 데이터가 소실되는 상황이 발생하였고 다행히 바이너리 로그를 보관하는 기간안이어서 MySQL 바이너리 로그로 복구를 하였는데 이때 학승하였던것을 기록차 남긴다.

  1. 프로덕션에서 Binlog 있는지 확인
SHOW BINARY LOGS;
+---------------+-----------+
| Log_name      | File_size |
+---------------+-----------+
| binlog.000015 |    724935 |
| binlog.000016 |    733481 |
+---------------+-----------+
  1. Binlog 덤프진행
    mysqlbinlog --read-from-remote-server --host=<프로덕션호스트> --raw binlog.000001 -u<계정> -p
  2. Binlog 에서 INSERT 및 UPDATE 추출하기 위해 파일 파싱
    mysqlbinlog binlog.000001 -d <DB명> --base64-output=AUTO --verbose >> output.txt
  3. 분석 및 SQL 문으로 수기로 변환하고 INSERT 와 UPDATE 실행

다음에 진행한다면 다르게 했을것

  1. 추후에는 같은 이슈 발생시 GitHub - danfengcao/binlog2sql: Parse MySQL binlog to SQL you want 사용가능해보인다. DDL 과 DML 쿼리문을 바로 사용할수 있게 파싱해서 사용해준다
  2. Binlog 를 사실 Point-in-time recovery 로 binlog를 바로 사용하고 시간대를 지정하여 지원하는데 복구가 가능한데 수동으로 쿼리문을 파싱하지 않아도 복구가 가능하다

복구 진행시 참조글

  1. https://dev.mysql.com/doc/refman/8.0/en/mysqlbinlog.html
  2. Accessing MySQL binary logs - Amazon Relational Database Service
  3. https://www.percona.com/blog/binlog2sql-binlog-to-raw-sql-conversion-and-point-in-time-recovery/

이미 현업에서는 자주 사용하지만 따로 정리하지는 않아서 이번기회에 정식 레퍼런스를 보면서 내용을 찾아보았다

스프링 트랜잭션 처리

https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-declarative-attransactional-settings

 

Data Access

The Data Access Object (DAO) support in Spring is aimed at making it easy to work with data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This lets you switch between the aforementioned persistence technologies fairly easily, a

docs.spring.io

The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics (for example, "start a brand new read-only transaction when this method is invoked, suspending any existing transaction"). The default @Transactional settings are as follows:

 

  • The propagation setting is PROPAGATION_REQUIRED.
  • The isolation level is ISOLATION_DEFAULT.
  • The transaction is read-write.
  • The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
  • Any RuntimeException triggers rollback, and any checked Exception does not

스프링 트랜잭션 처리중에 예외가 발생했을때 명시적으로 아래의 옵션들을 사용해서 롤백 여부를 결정할수 있다

rollbackFor 롤백을 실행시키는 예외 클래스 목록들
rollbackForClassName 롤백을 실행시키는 예외 클래스 이름들
noRollbackFor 롤백을 실행시키지 않는 예외 클랙스 목록들
noRollbackForClassName 롤백을 실행시키지 않는 예외 클래스 이름들

 

자바 vs 스프링 트랙잭션 처리에 대한 오해

구글에서 자바 트랜잭션 처리로 검색해 보면 Checked vs Unchecekd exception 에 대해 비교하고 unchecekd exception 에 대해 rollback 을 시행한다고 적혀있는 글을 많이 볼수있다.

 

이는 염언히 틀린말이다. 자바에서는 기존벅으로 트랜잭션에 대한 기본적인 처리 메커니즘을 제공하지 않으므로 Checked 이든 Unchecked 이든 프로그래머가 어떻게 프로그램을 짜느냐에 달려있다. 즉 Unchecked 에 롤백하는 메커니즘은 스프링 프레임워크를 사용할때 적용되는 기본 세팅이지 다른 프레임워크 혹은 프레임워크를 사용하지 않고 순수 자바 SDK 에서 제공하는 인터페이스로만 DB처리를 할경우 맞지 않는 말이된다. 

 

이는 자바=스프링을 동일시하는데 발생해서 불러오는 자바 개발자들 사이에서 불러오는 흔한 오해라고 생각한다.

하이버네이트는 내부적으로 두종류의 캐쉬를 지원하는데 하나는 First-level cache 이고 다른 한종류는 Second-level Cache 이다

 

First-level cache?

대부분의 ORM (Object Relational Mapping) 프레임워크가 지원하는것처럼 하이버네이트또한 일차적 캐쉬를 지원한다. 

일차캐쉬는 Hibernate 의 Session 단계에서 지원하는 캐쉬로 가장 비싼 연산작업중 하나인 데이터베이스와의 대화를 줄여주기 위해 존재한다. Session 안에서 동작하는 캐쉬이기 때문에 Session 이 종료되면 캐쉬도 같이 사라지게 된다.

 

Second-level cache?

First level cache 이 세션 단계에서의 캐쉬라면 second level cache 는 session factory 단계에서 지원하는 캐쉬로 session factory 에서 생성되는 session 간에 공유가 된다. 

 

 

표로 다시 정리하면

  First Level  Second Level
범위 Session  Session Factory (all sessions) 
기본 활성화 O X
설정 따른 설정 필요없음 설정필요
캐쉬 백엔드에 따라 다른 설정 추가 필요

 

동시성 캐쉬 전략

이름  
READ_ONLY 읽기에 대한 캐쉬를 생성한다. 설정값 같이 어플리케이션이 시작되고 변화하지 않는 값에 대해 사용
NONSTRICT_READ_WRITE READ_WRITE 와 비슷하지만 때때로 데이터를 읽는 경우에 최신의 데이터를 가지고 오지 않을수도 있늠점에 유의해햐한다. 
어플리케이션이 같은 데이터에 대해 접근하는 일이 많이 없고 강한 트랜잭션 격리가 필요없는 경우 사용
READ_WRITE 읽기와 쓰기에 대해 캐쉬를 생성한다. Seriazliable 트랜잭션 격리수준은 적용되지 않는다
TRANSACTIONAL
트랜잭션 대한 캐쉬를 지원한다. Seriazliable 트랜잭션 격리.
TA Transaction Provider 와 같이 사용하여 분산 트랙잭션을 사용하는 경우 사용하면 좋을 거 같다 

 

Entity 에 대해 캐쉬 사용할때 샘플코드

@Entity(name = "Company")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public static class Company {

}

 

참조: 

1. https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#caching

'Java > JPA' 카테고리의 다른 글

JPA Cascade Types  (0) 2021.12.19

JPA를 사용해서 엔티티간의 관계를 설정할때 아래와 어노테이션을 작성하는 일이 많은데 어떤 의미를 가지고 있는지 알아보자

@OneToMany(cascade={CascadeType.REFRESH, CascadeType.MERGE}, fetch = FetchType.LAZY)

 

먼저 각각의 CaseType에 들어가기전에 영속성 컨텍스트와 JPA의 상태에 대한 선행지식이 필요하다

 

Persistence Conxtet (영속성 컨텍스트)

공식문서의 정의를 참조해보자 

 

EntityManager (Java(TM) EE 7 Specification APIs)

Interface used to interact with the persistence context. An EntityManager instance is associated with a persistence context. A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. W

docs.oracle.com

A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed. The 
EntityManager
 API is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities.

어렵게 정의되어 있지만 간단하게 요약하자면 영속성 컨텍스트가 영속성 컨텍스트안에 있는 엔티티들의 변화를 추적하고 관리한다는 이야기다.

 

엔티티 객체의 상태

상태 설명
Transient 엔티티 객체가 데이터베이스에 아직 저장되기 전의 상태 
Persistent 엔티티 객체가 데이터베이스에 저장된 상태
Detached Persistent 상태였던  엔티티 객체가 더이상 영속성 컨텍스트에 속해있지 않는 상태

JPA의 동작들

동작 설명 특징
Save 하이버네이트 구현체에만 있는 기능 Persist 와 같은 기능.  Persist 는 생성된 ID 를 돌려주지 않으나 Save 는 돌려준다
Persist 엔티티를 영속성 컨텍스트에 포함시킨다 1. 엔티티가 Transient 상태라면 엔티티는 Persistent 상태가 된고 관련 동작들을 전파한다. (PERSIST, ALL로 설정된 경우)
2. 엔티티가 이미 Persistent 상태라면 엔티티에 직접적인 영향은 없다. 그러나 관련 동작들은 여전히 자식으로 전파된다
3. 엔티티가 Detached 상태라면 에러를 발생시킨다
Merge Persistent 상태의 엔티티 객체를 Deatched 상태의 객체의 값들로 업데이트한다  
Update 하이버네이트에만 존재하는 동작으로 Merge 와 같은 동작을 수행한다  
SaveOrUpdate 하이버네이트에만 존재하는 동작으로   

Cascade 종류

 

 

Hibernate ORM 5.6.3.Final User Guide

Fetching, essentially, is the process of grabbing data from the database and making it available to the application. Tuning how an application does fetching is one of the biggest factors in determining how an application will perform. Fetching too much dat

docs.jboss.org

JPA 표준에 사용하면 아래와 같이 6가지 Cascade Type 있다

종류 특징
ALL 아래에 기술된 모든 동장들을 자식 엔티티에게 전파한다
PERSIST JPA의 Persist 동작 (save, persist )을 부모 엔티티에서 자식 엔티티에게 전파된다
MERGE 부모 엔티티가 업데이트 될때 자식 엔티티들도 업데이트 된다
REMOVE 자식 엔티티들을 부모 엔티티 삭제시 동시에 삭제한다
REFRESH 데이터베이스로부터 데이터르 다시 읽어 들이는 refresh 동작을 부모에서 자식 엔티티로 전파
DETACH Detach 를 부모에서 자식 엔티티로 전파한다

하이버네이트에만 존재하는 CaseCade Type

종류 특징
REPLICATE Replicate 를 사용할떄 자식엔티티에게도 같은 동작을 전파한다.
**자동생성되는 ID 사용하지 않고 엔티티를 복제할 필요가 있을때 사용하면 좋다.
SAVE_UPDATE 하이버네이트 구현체의 save, update, saveOrUpdate 동작을 수행시에 자식 엔티티에게 같은 동작을전파한다
LOCK 이미 Detached 된 부모 엔티티 객체를 다시 영속성 객체에 추가시에 자식엔티티도 같이 추가된다

 

참조:

1. https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html

2. https://stackoverflow.com/questions/161224/what-are-the-differences-between-the-different-saving-methods-in-hibernate

'Java > JPA' 카테고리의 다른 글

JPA & Hibernate Cache  (0) 2022.01.19

어플리케이션을 구동하기 위해 사용되는 설정값들을 어플리케이션이 돌아가는 플랫폼안에서 보통 환경변수 혹은 파일안에 저장해두고 쓰는데 어플리케이션 설정값을 Zookeepr, AWS Secrets 과 같은 외부 서비스에서 불러들이거나 보안을 위해 설정값들을 메모리 안에서만 저장이 필요한 경우에 활용 가능한 방법이다.

Spring boot 에서 만 활용 가능한 방법

EnvironmentPostProcessor 를 활용

public class PriceCalculationEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, 
      SpringApplication application) {
        PropertySource<?> system = environment.getPropertySources()
          .get(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);

        Map<String, Object> prefixed = names.stream()
          .collect(Collectors.toMap(this::rename, system::getProperty));
        environment.getPropertySources()
          .addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new MapPropertySource("prefixer", prefixed));
    }

}

Spring boot 와 Spring boot를 사용하지 않는 환경에서 활용 가능한 방법

ApplicationContextInitializer 를 활용한 방법

먼저 아래와 같이 프로퍼티 값을 오버라이드 하는 코드를 작성해준다.

public class PropertyOverrideContextInitializer
  implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableWebApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        MyConfigProps configProps = Binder.get(environment).bind("my-config", MyConfigProps);
        System.out.println(configProps.getHomekey());
    }
  }

여기서 네가지 방법으로 어플리케이션에 적용 가능하다

1. web.xml 안에 있는 contextInitializerClasses 에 추가 혹은 상응하는 Java Config (Spring MVC)

    <context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>com.xxxx.PropertyOverrideContextInitializer</param-value>
    </context-param>

 

자바로 설정시에 

 

@ContextConfiguration(initializers = PropertyOverrideContextInitializer.class)
public class AppConfig {

}

스프링만 사용시 

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MyAppContextInitializer initializer = new MyAppContextInitializer();
initializer.initialize( ctx );

ctx.register( com.my.classpath.StackOverflowConfiguration.class );
ctx.refresh()

JobLauncher launcher = context.getBean(JobLauncher.class);

 

2. META-INF/spring.factories 추가하여 자동 설정 추가 (Spring Boot) 

org.springframework.context.ApplicationContextInitializer=
org.springframework.boot.context.PropertyOverrideContextInitializer

3. 어플리케이션 추가 실행코드에 추가코드에 추가하는 방법 (Spring boot)

application.addInitializers(PropertyOverrideContextInitializer.class);
application.run(args);

혹은

new SpringApplicationBuilder(YourApp.class)
    .initializers(PropertyOverrideContextInitializer.class);
    .run(args);

4. context.initializer.classes 프라퍼티 설정 (Spring boot)

context.initializer.classes=com.xxxx.PropertyOverrideContextInitializer

참조

  1. https://www.baeldung.com/spring-tests-override-properties
  2. https://stackoverflow.com/questions/35217354/how-to-add-custom-applicationcontextinitializer-to-a-spring-boot-application
  3. https://stackoverflow.com/questions/35048164/spring-applicationcontextinitializer-and-properties
  4. https://stackoverflow.com/questions/13288841/applicationcontextinitializer-in-a-non-web-spring-context

사용자가 많아지고 트래픽이 많아질고 시스템 스케일링이 필요해지는 시점이 오면 데이터베이스 캐싱에 대한 고민을 하게된다.

이번 포스팅에서는 데이터베이스 캐싱 전략에 대해 알아보고자 한다

 

 

먼저 들어가기에 앞서 Cache Hit을 알아보자

 

Cache hit 이란?

찾으려는 데이터가 캐쉬에 저장되어 있어 데이터베이스를 거치지 하고 캐쉬에서 바로 데이터를 가져오는 형태.

Cache hit이 많이 발생할수록 데이터 베이스에 대한 부담이 줄어든다.

 

Cache-Asdie

일반 적인 형태의 캐쉬로 어플리케이션이 캐쉬업데이트 업데이트 담당.

캐쉬에 먼저 쿼리문을 보내고 캐쉬에 데이터가 없을시 데이터베이스에서 데이터를 가져온후 캐쉬에 데이터를 업데이트 한다

 

 

장점

1. 데이터베이스 데이터 모델과 캐쉬의 데이터 모델이 다르게 저장 가능

2. 캐쉬 서버가 다운되도 데이터베이스에서 데이터를 가져올수있다.

3. Lazy Loading 

 

단점:

1. 데이터와 캐쉬간의 데이터 불일치 발생 가능성. (Time to live 값을 설정하거나 캐쉬를 무효화하는 전략 수립 필요)

2. 어플리케이션이 캐쉬관리 담당

 

Read-Through (동기적 처리)

캐쉬에 데이터가 없으면 원본 데이터 소스에 접근해서 데이터를 가져오고 캐쉬에 저장

 

장점:

1. Lazy Loading 

2. 어플리케이션이 캐쉬를 관리하지 않음

 

단점:

1. 최초로 데이터를 요청한 사람의 응답속도가 느림.

 

 

Write-Through (동기적 처리)

캐쉬가 데이터베이스와 어플리케이션 사이에 위치하고 있고 캐쉬에 데이터를 저장하고 데이터베이스 이어서 바로 저장.

Read through 전략과 같이 이용되면 Cache Hit 확률을 높일수 있다.

 

장점:

1. Write-Behind 보다 데이터 유실 위험 적음

단점

1. 캐쉬와 데이터베이스에 동시에 저장하기때문에 느린 쓰기 속도

 

 

Write-Behind (비동기적 처리)

캐쉬에만 데데이터를 저장하고,  데이터 소스에는 별도의로 주기적으로 저장하는 방식

장점

1. Write-Through 보다 향상된 쓰기 속도

2. 데이터베이스에 대한 부담을 줄일

 

단점

1. Cache 가 다운되면 데이터 유실 위험

 

 

 

 

ES6 module 시스템이 등장하며 Typescript Namespace 가 현격히 줄어들었지면 Typescript가  ES6 module 등장전에 어떻게 Typescript 코드 재사용을 이루었는지 이해하기 위해 알아보았다.

 

 

사용법

먼저 아래와 같은 폴더구조를 가진다고 하자. 

 

- base-component.ts

- input-component.ts

- utils.ts

 

base-component.ts 코드

namespace App {
	export abstract class Component { }
}

 

utils.ts 코드

namespace CommonUtils {
	export class StringUtils { }
}

 

위의 코드를 보면 Component 클래스는 같은 네임스페이스안에 Util 클래스는 다른 네임스페이스 안에 설정된것을 볼수 있다.

* 참고로 같은 Namespace에 선언되어있더라도 다른파일에서 사용하기 위해서는 내보내는 변수, 클래스, 함수등에 export 키워드를 붙여줘야한다

 

input-component.ts 코드

/// <reference path="base-component.ts" />
/// <reference path="utils.ts" />

namespace App {

   // 같은 네임스페이스내에 선언된 클래스 사용
   class InputComponent extends Component { }
   
   // 다른 내임스페이스에 선언된 클래스 사용
   CommonUtils.StringUtils.method();
}

사용법은 간단한데 /// <reference path="모듈위치.ts" />

 

이렇게만 하고 Typescript 기본설정으로 하면 파일들이 제각기 따로 생성되기때문에 각각의 파일들을 따로 불러오지 않는 이상 브라우저에서는 사용할수 없다. 그런 경우애 편의를 위해 한파일로 된 파일을 생성할수 있는데 tsconfig.json 에 outFile 옵션을 활성화 한다. outFile 옵션은 amd 혹은 system 모듈일 경우에만 선택가능하니 용도에 맞게 선택한다

{
    "outFile": "./dist/bundle.js",
    "module": "amd"
}

 

그리고 HTML 파일에서는 아래 한파일만 불러오면 된다

<script src="dist/bundle.js"></script>

 

Namespace 의 단점

1. 의존성들이 불러오지 않더라도 오류 메세지를 발생시키지 않고 정상적으로 Javascript 로 Transpile 하는 경우가 생긴다. 이점은 규모가 큰 프론트 엔드 프로젝트가 될수록 치명적 단점으로 작용한다. 

 

 

그렇다면 Namespace 와 Modules 중에 어떤것을 선택해서 선택해야 될까? 이점은 Typescript 팀에서 명확히 가이드 라인을 제시해주었는데 아래 공식문서에 따르면 모던 코드들에 대해 ES Module 을 사용할 것을 권장하고 있다.

It is also worth noting that, for Node.js applications, modules are the default and we recommended modules over namespaces in modern code.
 

Documentation - Namespaces and Modules

How to organize code in TypeScript via modules or namespaces

www.typescriptlang.org

 

운영환경에서 장애가 발생시에 서버내로 직접 접속해서 어플리케이션 로그를 확인해야하는 경우가 생긴다. 물론 정상적인(?) 환경이라면 이미 ELK Stack 같은 Log management tool 이미 설치되어 거기에서 애플리케이션 로그를 확인하면된다. 하지만 간혹해서 서버에 직접 접속하여 로그를 확인하는 경우가 생기는데 그런경우에 유용한툴이 multitail 소개하고자한다.

 

보통 다중서버에서 애플리케이션 로그를 확인시에 아래와 같은 과정을 거친다

 

1. ssh sever1

2. tail -f <로그파일 위치> 

 

다중 터미널 띄어놓고 서버 별로 1, 2번과정 무한반복

 

이러한 과정을 한번에 해결하는 툴이 multitail 이다. 명령어는 아래와 같다

multitail -l "ssh server1 tail -f 로그 파일 위치위치" -l "ssh server2 tail -f 로그 파일 위치위치"

 

추가 서버가있을때마다 -l 을 추가해서 계속해서 연결해 주면 된다.

 

로컬내에서 서로 다른 파일을 확인하고자 하는 경우에는 더 간단한데 아래와 같이 설정해주면된다

multitail 로그파일위치1 로그파일위치2

 

리눅스 시스템 운영하는 경우에 간혹 서버의 하드웨어 용량 사용량을 수동으로 체크하는 경우가 생기는데 (물론 이에 대해 Monitoring 툴을 설치해서 특정 용량이상이 넘거나면 자동으로 경고메세지를 설정하는게 가장좋다) 가끔 사용하기에 잊어먹는 명령어들을 저장한다.

 

 

1. 시스템내 Mount (or 파티션) 별로 용량 보기

df -h

2. 현재 있는 폴더 및 하위 폴더내 파일들 크기

du -h

VCS (Version Control System) 은 과거부터 소스코드 관리를 위해 자주 사용하는 툴이다.

그중 VCS 의 한갈래중 하나인 DVCS (Distributed Version Control System) 에서 가장 대중적으로 사용하는 Git 에서 가장 자주 사용하는 커맨드들을 따로 성리했다

 

 

1. git commit --amend

바로 이전 커밋 메세지를 수정하는 경우에 사용

 

2. git rebase <target_branch>

현재 브랜치를 타켓 브랜치 위로 이동하기 위한 명령어

 

3. git rebase -i <starting_point>

어느 특정순간부터 현재 커밋시점까지의 GIt 커밋 기록을 수정하고 싶을떄 사용

 

4. git config --global rerere.enabled true

통일 프로젝트에서 여러 팀원들이 동시에 작업하는 경우에 mergre conflict 가 발생하는 경우가 있다. 이 옵션을 키면 과거에 conflict 를 해결하는 결과물을 git 에서 기억하고 같은 conflict 에 대해 동일하게 적용하여 반복해서 같은 conflict 를 해결해야하는 상황을 피하게 해준다 

 

 

'Tools > GIT' 카테고리의 다른 글

GIT 자주쓰는 명령어 정리  (0) 2016.03.30

+ Recent posts