금융서비스 MSA 전환기 - DB 분리(1편)

김형래
finda 기술 블로그
13 min readJul 17, 2023

--

안녕하세요, FINDA 현금그로스 PG 자산/신용관리 PT 백엔드 개발자 김형래 입니다.

이번 글에서는 자산/신용관리 PT 에서 기존 및 신규 서비스를 MSA(Micro Service Architecture)로 전환하는 첫 단추를 끼우는 작업에 대해 알아보도록 할게요!

왜 기존 서비스를 마이크로 서비스(MSA) 로 전환해야 할까요?

Why 를 팀원 들과 논의하게 된 시점은 KCB 의 금융명의보호 서비스를 개발하면서 시작되었어요. 금융명의보호 서비스(이하 금명보 서비스)는 아래와 같아요.

금융명의보호 서비스

이 서비스에 가입해 두면 누군가가 자신 몰래 카드를 신규 발급받거나 카드론 등을 신청할 경우 곧바로 ‘알림 정보’가 옵니다. 아예 금융사가 자신의 신용정보를 조회하지 못하도록 ‘차단’ 기능을 설정할 수도 있습니다. 피해 고객이 아니어도 누구나 신청할 수 있습니다.

금명보 서비스를 FINDA 앱에 안전하게 서비스로 릴리즈 하기위해 대출비교 서비스와 같이 사용하는 DB 에서 분리하기로 했어요. 이유는 대출비교 서비스에 부하를 줄이는 게 가장 큰 목적이었어요. 대출비교 서비스(메인 서비스) 에 여신관리 서비스가 부하를 증가 시킨다면, 메인 서비스의 전면 및 일부 장애로 이어져 회사 수익에 손실이 발생할 수 있기 때문이죠. 또한, 여신관리 서비스가 타 서비스와 의존성이 너무나 큰 강결합 형태여서 유지 보수 및 대량 트래픽 이슈 처리를 위해 MSA 구조를 선택했습니다.

기존 Database 구조(개선 전)

FINDA 백엔드 서비스의 Legacy 로 유관부서의 영향도 체크하는 커뮤니케이션 비용이 정말 컸어요. 유관부서의 리드 또는 팀원 들과 소통을 해야했어요. 우리 팀이 쏘아올린 공에 유관부서의 작업이 많이 증가해서 협조를 많이 구했던 작업이었어요. 아래는 MSA 를 위해 선행 작업 Timeline 을 나타낸 것이에요.

선행 작업 Timeline

MSA 로 전환 하기

선행 작업 분석

사내의 모든 유관 부서와 협업 하면서 여신관리 DB 가 이관 될 경우, Side Effect를 조사하기 시작했습니다. 유관부서와 전환 전에 사전 협의 및 논의를 진행하면서 Side Effect 가 얼마나 클지 미리 검토하고 사전 시나리오를 작성해봤어요. 그 중에서 가장 까다로운 부분은 FINDA 앱에서 사용 되는 알림 발송 처리 부분이었어요. 좀 더 자세한 내용은 DB 이관 테이블 분석에서 살펴볼게요.

Domain Driven Design(DDD) 로 설계를 시작해 볼게요.

  • Domain 은 사전적 의미로 ‘영역', ‘집합' 입니다.
  • Domain 은 비즈니스 Domain 으로 유사한 업무의 집합으로 표현됩니다.
  • Application 은 비즈니스 Domain 별로 나누어 설계 및 개발을 합니다.
  • DDD 의 목표는 “Loosely coupling”, “High cohesion” 입니다.
  • DDD 는 Strategic Design 와 Tactical Design 으로 나뉩니다. Strategic Design 은 개념 설계이고, Tactical Design 은 개발하기 위한 구체적 설계입니다.

위에 설계 방식을 따라 Domain Event 를 정의하기 위해 비즈니스 Domain 내에 발생하는 모든 이벤트를 과거형으로 서술하면서 Domain Event 정보를 정의했습니다. 이번 글에서는 여신관리의 일부 정보만 한정해서 기술했습니다.

Domain Event 정의

도출된 이벤트로 도메인의 업무 흐름을 이해하고 토론하여 프로세스 별로 그룹핑해서 Bounded Context 를 정의했습니다. 또한, 아래에 색상 별 항목을 정의 하면서 진행했습니다. 결과적으로 여신정보가 아닌 부분을 도출해서 적합한 팀으로 이관하게 되었습니다.

  • 초록색 : Event
  • 하늘색 : Command
  • 분홍색 : External System
  • 하늘색 : Actor
  • 노란색 : Bounded Context
  • 주황색 : Aggregate Root
Bounded Context 정의
Aggregate Root 선정

AS-IS

- 여신관리 DB 와 대출비교 DB 간의 의존성 존재(JOIN)

- Bounded Context 기준으로 타 도메인 로직 존재(상품 정보, 두낫콜 정보)

TO-DO

- 여신, 수신, 자산, 배치 관리 DB 와 대출비교 DB 간 의존성 분리

- Bounded Context 기준으로 타 도메인 로직 이관(상품 정보, 두낫콜 정보)

공통 모듈 제거

전사 공통적으로 사용하는 모듈(logging, user 유효성 체크 등)을 제거해서 의존성을 낮추는 작업을 진행했습니다. 공통으로 사용하는 모듈은 이미 주인이 없는 소스 코드라 사실상 관리가 제대로 되지 않고 있어서 계속 함께하기엔 유지보수 부담스러운 모듈이었습니다. 또한, 공통 모듈을 제거하면서 빌드 툴도 Maven 에서 Gradle 로 변경했습니다.

전사 영향도 체크

Backend / Frontend / Data 서비스 별로 영향도 체크를 위해 모든 유관부서와 협업하면서 진행하다보니, 많은 커뮤니케이션 비용이 들었습니다. 덕분에 FINDA 서비스를 전반적으로 살펴보고 분석하는 계기가 되었고, 비즈니스 파악에도 도움이 많이 되었습니다.

DB 이관 테이블 분석

가장 큰 작업으로 DB 이관 테이블을 분석 및 여신관리 서비스용 DB 테이블을 생성했어요. DBA 분들이 외래키 제약조건을 여러 이유로 최대한 지양 (성능 저하, DDL/DML시 관리포인트 증가) 하는 제안을 받았습니다. 서비스의 성능을 높이고 관리포인트 감소를 위해, 이번 작업에서 여신관리 DB의 외래키를 제거한 설계를 진행했습니다. 또한, Bounded Context 도출된 결과로 Entity 설계에 반영했습니다.

병렬 스레드 갯수와 무관하게 외래키 제약조건이 존재할 경우,

→ QPS 감소, Latency 증가

MSA 로 여신관리 서비스를 전환하면서, 대출비교 DB 에 있는 Push 알림 발송 테이블과 여신관리 DB 테이블이 서로 접근이 어려웠어요. 해결방안으로 FINDA 의 Data Lake 를 관리하고 담당하는 Data Platform 팀에서 Databricks 로 처리하는 프로세스로 변경해서 알림 발송을 일괄 처리하도록 개선 되었습니다.

Multi Datasource 구성

Application 에서 아래의 그림과 같이 DB read/write 를 각각 처리하는 구조로 변경하면서 Repository Layer 를 CQRS 적용이 편리한 구조인 Multi Datasource 로 만들었습니다.

부가적으로 여신관리 서비스에 맞지 않는 API 들도 이번 작업에서 제 주인을 찾아가면서 Domain Driven Design(DDD)을 위한 Bounded Context 도 잘 이루어졌습니다. 😉

아래에서 CQRS 를 적용한 코드를 보면서 이야기를 나눠 보겠습니다.

한 개의 DB 만 사용할 경우, application.yml 설정으로 auto configuration 으로 DB 커넥션이 자동으로 생성됩니다. 그러나, 2개 이상 DB 를 접근할 경우, Configuration class 를 생성합니다. 우선, EntityManagerFactory class 로 아래와 같이 Bean 을 생성합니다.

application.yml 파일을 아래와 같이 설정합니다.

spring:
...
datasource:
lms-read:
maximum-pool-size: 20
max-lifetime: 540000
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/lms?useUnicode=true&characterEncoding=utf8
username: test
password: testpw
lms-write:
maximum-pool-size: 20
max-lifetime: 540000
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/lms?useUnicode=true&characterEncoding=utf8
username: test
password: testpw

먼저 각 Datasource 에서 사용할 entity package와 repository package 를 분리합니다.

package 구조

DB configuration class 로 application.yml 파일에 설정한 datasource properties 를 가져옵니다. LmsReadDataSourceConfig 파일을 설정해서 entityManager 와 transactionManager 를 Bean 으로 생성합니다. LmsWriteDataSourceConfig 파일도 LmsReadDataSourceConfig 와 유사합니다.

@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "lmsReadEntityManager",
transactionManagerRef = "lmsReadTransactionManager",
basePackages = {"com.finda.lms.db.lmsdb.credit.read.*", "com.finda.lms.db.lmsdb.lms.read.*"}
)
public class LmsReadDataSourceConfig {

private final EntityManagerFactory entityManagerFactory;

private static final String[] LMS_READ_PACKAGE_NAME =
{"com.finda.lms.db.lmsdb.lms.entity", "com.finda.lms.db.lmsdb.credit.entity"};


@Bean
public LocalContainerEntityManagerFactoryBean lmsReadEntityManager(
@Qualifier("lmsReadDataSource") DataSource dataSource, ConfigurableListableBeanFactory beanFactory
) {
LocalContainerEntityManagerFactoryBean build = entityManagerFactory.getEntityManger(dataSource, LMS_READ_PACKAGE_NAME);
build.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
return build;
}

@Bean
public PlatformTransactionManager lmsReadTransactionManager(
@Qualifier("lmsReadEntityManager") LocalContainerEntityManagerFactoryBean entityManager
) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(Objects.requireNonNull(entityManager.getObject()));
return transactionManager;
}
}

여기서 @EnableJpaRepositories 가 핵심이며, 설정을 통해 Datasource 를 JPA Repository 로 접근 가능하게 합니다. 여러개의 Datasource 를 참조하는 상황에서는 반드시 명시적으로 entitiyManager, transactionManager 를 사용해야 합니다.

QA 및 테스트

DB Config, Repository, Service, Controller Layer 별 모든 소스코드가 변경 및 추가되어 전체 테스트로 리소스가 많이 들었습니다. QA 테스트는 전수 테스트에서 금융데이터를 실제 금융권에서 받아서 처리하는 부분이 테스트가 원활하지 않아서 Mockup Server 에서 Mock 데이터를 생성해 처리 하면서 테스트 시간을 단축 시킬 수 있었습니다.

기존 서버에서 신규 서버로 트래픽 전환

기존 서버의 트래픽을 신규 서버로 전환하는 작업은 Ingress 에서 prefix 를 redirect 처리 하면서 진행했습니다. 또한, 전환 전에 전사 유관 부서에 공지하면서 진행하다보니, 커뮤니케이션 비용도 많이 들었습니다. MSA 로 전환 작업을 하면서 팀원들이 기존 레거시 소스코드를 뜯어내고 재조립하는 과정에서 정해진 일정 내에 완료하기 위해서 팀원들과 QA 분들이 많은 고생을 했던 기억이 아직도 생생합니다. 이래서 Legacy 개선은 어렵습니다. 😱

개선사항 정리 및 결과 분석하기

현재 Database 구조(개선 후)

개선사항 정리

위에 그림은 현재 Database 구조로 개선 후 모습입니다. 처음에는 여신관리 서비스가 대출비교 DB 에서 독립 했고, 이후에는 수신, 자산, 배치관리 DB 가 독자적인 DB 를 갖는 MSA 구조로 변모하게 되었습니다. 지금까지 개선한 사항들을 정리해 보면 아래와 같습니다.

  • 전사 공통모듈 제거
  • Build Tool 변경(Maven to Gradle)
  • Multi Datasource(CQRS) 적용
  • Repository 분리(Command, Query)
  • Service 분리(Command, Query)
  • Controller 분리(Command, Query)
  • 외래키 및 미사용 테이블 제거
  • Character Sets(utf8 to utf8mb4) 및 Collations 변경
  • Push 알림 발송 관련 작업은 Databricks 로 이관
  • 기존 서비스(여신관리) DB 이관
  • 신규 서비스(수신, 자산, 배치관리) DB 분리

개선 후, 결과 분석

여신관리 DB 이관 후, 대출비교 DB 모니터링 결과는 아래와 같이 긍정적으로 나타났습니다.

  • 대출비교 DB CPU Workload 10% 정도 감소
  • 대출비교 DB QPS 평일 기준 20% 감소
CPU Workload
QPS

여기까지 긴 글을 읽어주셔서 감사합니다.

다음 글은 금융서비스 MSA 전환기- BFF 와 CircuitBreaker 적용(2편)으로 알아보겠습니다.

--

--