티스토리 뷰

Sharded MySQL Cluster 도입 배경과 개발기 (부제: 우당탕탕 좌충우돌 개발기)

안녕하세요, 지마켓 Item Engineering 팀입니다. 저희는 지마켓 & 옥션의 상품 등록/관리와 관련된 플랫폼을 담당하고 있습니다!

저희 팀은 대량의 상품 데이터와 대규모 트래픽을 처리해야 하며 로직이 (꽤) 복잡한 부분이 있는 상품 데이터들을 운영하고 있습니다. MSSQL을 주로 사용하지만 이번에 샤딩 기법을 사용한 MySQL 클러스터를 도입하게 되었습니다!

이름하야 '루미큐브' 프로젝트!

하지만 해당 프로젝트를 진행하며 많은 시행착오가 있었는데요..

왜 MySQL을 도입하게 되었는지, 샤딩 기법을 사용하고 있는데 샤딩이란 무엇인지, 샤딩을 쉽게 처리하기 위해 사용한 '샤딩스피어'란 무엇인지,

그리고 저희의 시행착오 일부도

알아봅시다!


 

주요 내용

  1. 왜 MySQL Cluster인가
  2. 실시간 데이터 복제
  3. 샤딩이란 무엇인가
  4. ShardingSphere
  5. ShardingSphere 적용기 (부제: 첫 번째 시련)
  6. Data Migration
  7. 운영 반영기 (부제: 두 번째 시련)
  8. 마치며

왜 MySQL Cluster 인가?

하나의 RDBMS로 구성되어 있는 상품 DB를 Write DB와 Read DB를 분리하여 CQRS(Command Query Responsibility Segregation) Pattern을 적용해 본다면, 각각의 독립적인 인스턴스를 유연하게 운영할 수 있고, 장애 전파를 미연에 방지할 수 있지 않을까?라는 생각이 들었습니다.

현재 지마켓은 하나의 상품 DB에서 수많은 Write 작업과 Read 작업이 이루어지고 있습니다.

특히 상품은 오픈마켓의 근간이 되는 데이터로 여러 도메인에서 동시다발적으로 DB를 사용하면서 가용 리소스의 한계를 가져오고, DB에 이슈가 생기면 지마켓 전체 장애로 확산될 수도 있습니다.

지마켓은 코어 데이터베이스로 Microsoft SQL Server(MSSQL)를 사용하고 있기 때문에 여러 방식을 생각해 볼 수 있습니다.

  1. 별도의 조회 DB를 구성
    • Microsoft SQL Server Replacation 기능을 이용해 복제 DB에 데이터 동기화
  2. Trigger를 이용한 Sync 어플리케이션을 구현하여 데이터 동기화

실제로 지마켓에서는 두 가지 방법 모두 사용 중입니다.

그렇다면, 왜 기존 방식을 사용하여 익숙한 Microsoft SQL Server에 Read DB를 구성하는 대신 MySQL Cluster를 사용하였을까요?

첫 번째, 상대적으로 저렴한 비용을 들 수 있습니다.

상용 버전을 사용해도 license 비용이 저렴합니다.

두 번째, 수평적 확장(scale-out)이 가능합니다.

수직적 확장(scale-up)을 통한 성능 확장은 한계가 있고, 이미 지마켓 코어 데이터베이스 서버의 하드웨어 스펙은 업계 최고임에도 불구하고 가용 리소스의 한계를 가지고 있습니다. 성능 확장이 필요한 경우, 노드를 증설하거나 DB 인스턴스 클러스터 추가 등의 scale-out을 통해 유연한 대처가 가능합니다.

세 번째, 급격히 증가하는 상품 데이터의 샤딩(Sharding)을 통해 데이터베이스 처리 성능을 향상할 수 있습니다.

하나의 DB에 존재하는 수억 건의 대용량 상품 데이터를 여러 개의 DB에 나누어 저장하는 샤딩(Sharding)을 통해, 데이터베이스 처리 작업을 여러 대의 서버에 분산 처리할 수 있어 처리 능력을 향상할 수 있습니다.

MySQL Cluster 도입을 결정했다면, Source DB로부터 Target DB로 실시간 데이터 동기화는 어떻게 처리하였는지 살펴봅시다.


실시간 데이터 복제

DB to DB 작업(특히 지금과 같이 Source DB는 Microsoft SQL Server이고 Target DB는 MySQL인 이기종 간 데이터 복제)에는 CDC(Change Data Capture)를 활용하여 실시간 데이터 동기화를 할 수 있습니다.

CDC(Change Data Capture)는 데이터베이스 내 데이터 변경을 식별하여 외부 저장소에 전달하는 기술을 의미하는데, 데이터 실시간 동기화나 데이터 변경 이벤트를 외부 시스템에 보내는 등 폭넓게 활용하고 있습니다.

대표적으로 오픈 소스 Debezium을 들 수 있는데, Debezium은 카프카 커넥터(Kafka Connect)의 Source 커넥터의 집합으로 다양한 Source 커넥터를 제공합니다.
Kafka Connect는 Kafka로 레코드를 보내는 Debezium과 같은 소스 커넥터(Source connector)와 Kafka 토픽에서 다른 시스템으로 레코드를 전파하는 싱크 커넥터(Sink connector)로 구성되어 있습니다.

Debezium Architecture

그러나, CDC를 이용하여 실시간 데이터 동기화를 할 수가 없었습니다. 이미 지마켓 코어 데이터베이스는 Replication과 Trigger를 사용 중이고, 가용 리소스가 많지 않아 현실적으로 CDC를 활성화하기 힘들기 때문입니다.
CDC 사용이 어렵다면 Debezium Architecture에서 착안하여 소스 커넥터와 싱크 커넥터를 Application으로 구현하여 이를 활용해보는 것으로 계획을 수정하였습니다.

Architecture

소스 커넥터 역할을 하는 Application(이하 DBStreaming)

Spark Streaming을 이용하여 SQLServer에서 변경되는 데이터를 주기적으로 읽어와 Kafka 토픽으로 전송합니다.

싱크 커넥터 역할을 하는 Application(이하 Rummikub-Consumer)

Spring Kafka의 Listener를 이용하여 해당 토픽에서 레코드를 가져와 MySQL에 반영합니다.

데이터 분할

지마켓 상품은 수억 건의 대용량 데이터로 매우 거대하고 많은 정보를 포함하고 있어, 수직적이든 수평적이든 분할(파티셔닝, Partitioning)이 필요합니다.

이에 동일한 테이블 스키마로 여러 개의 DB에 나누어 저장하는 Sharding(수평적 파티셔닝)을 적용, Sharding을 사용함으로써 데이터를 분산시켜 저장하는 Application, 분산된 데이터베이스에서 조회하는 Application에서는 Sharding 처리가 가능해야 합니다.

물론 직접 Sharding 처리를 구현해볼 수 있지만 Application의 복잡도가 증가하는 것에 비해, Sharding Middleware를 사용한다면 개발 편의성과 안정성이 강화됩니다. Sharding Middleware 선택에도 고려해야 할 사항이 많지만, 신속한 개발과 시스템의 안정성을 위해 Open Source Sharding Middleware인 ShardingSphere를 사용하게 되었습니다.


샤딩이란 무엇인가?

일단 ShardingSphere(샤딩스피어) 설명을 진행하기에 앞서 샤딩에 대해서 간단하게 이해합시다!

한 개의 DB에 많은 데이터가 쌓이다 보면 용량도 커지지만, 데이터 처리 성능이 큰 영향을 받게 됩니다. 이러한 부분을 해결하기 위해서 샤딩을 사용합니다. 샤딩을 사용하면 DB 트래픽이 분산되어 큰 효과를 볼 수 있습니다. 여기서, 샤딩의 핵심은 데이터를 효율적으로 분산시키는 것입니다.

분산이 잘 되지 않는다면 한쪽으로 Data 요청이 몰리게 되고, 결국 성능이 느려지게 됩니다. 샤드 키를 통해 데이터를 균일하게 분산시키는 것이 샤딩의 필수 조건입니다.

샤딩의 종류

위 사진을 보다시피 크게 두 가지로 나뉩니다.

  1. 수직 분할 (좌측)
  2. 수평 분할 (우측)

보편적으로 대부분 수평 분할을 사용합니다.

1. 수직 분할

한 스키마에 저장되어 있는 데이터를 특정 칼럼 단위로 잘라내어 분할 저장합니다. 수직 파티셔닝은 각 스키마를 나누고 데이터가 따라 옮겨갑니다.

결국 논리적 엔티티들을 다른 물리 엔티티들로 나누는 것을 의미합니다.

2. 수평 분할

한 스키마에 저장되어 있는 데이터를 특정 알고리즘을 통해 행 단위로 잘라내어 분할 저장합니다. 수평 분할은 하나로 구성된 스키마를 동일한 구성의 여러 개의 스키마로 분리한 후, 각 스키마에 어떤 데이터가 저장될지를 샤드키를 기준으로 분리합니다.

제일 보편적으로 많이 사용하고, 애플리케이션 서버 레벨에서 진행할 수도 있습니다. 또한 분할을 시키기 위한 샤딩 알고리즘을 필요로 합니다.


샤딩 알고리즘

수평 분할을 통한 샤딩은 샤딩 알고리즘이 필요합니다. 기준이 되는 샤드키를 통해 어느 스키마에 접근하여 데이터를 핸들링할 것인지 정해야 하기 때문입니다. 한마디로 라우팅을 위한 알고리즘입니다.

크게 두 가지 샤딩 방법이 있습니다.

  1. Modular Sharding
  2. Range Sharding

1. Modular Sharding

Modular 샤딩은 샤드키를 모듈러 연산한 결과로 DB를 선택하는 방식입니다.

  • 장점: Range 샤딩에 비해 데이터가 균일하게 분산됩니다.
  • 단점: DB를 추가 증설하게 된다면 이미 적재된 데이터들의 재정렬이 필요합니다.
    • (ex) (DB는 2개, 상품번호가 0~10까지 있을 경우) 상품번호 % 2 모듈러 연산 시 DB1에는 0,2,4,6,8,10 상품, DB2에는 1,3,5,7,9 상품이 들어가게 됩니다.

Modular 샤딩은 데이터량이 일정 수준에서 유지될 것으로 예상되는 데이터 성격을 가진 곳에 적용할 때 어울리는 방식입니다.

실제적으로 상품 데이터를 적재하고 있으나, 해당 데이터는 장기적으로 미사용 할 경우 다른 디비에 옮겨 보관하기 때문에 적합합니다. 데이터가 꾸준히 늘어나더라도 적재속도가 그리 빠르지 않다면 문제없다고 합니다.

일단 데이터가 균일하게 분산된다는 점 자체가 트래픽을 안전하게 소화하면서도 DB 리소스를 최대한 활용할 수 있기 때문입니다.

2. Range Sharding

Range 샤딩은 샤드키의 범위를 기준으로 DB를 선택하는 방식입니다.

  • 장점: Modular 샤딩에 비해 증설에 재정렬 비용이 들지 않습니다.
  • 단점: 일부 DB에 데이터가 몰릴 수 있습니다.

Range 샤딩은 증설작업에 드는 비용이 크지 않습니다. Modular의 경우 증설작업이 진행될 경우 기존 데이터들도 모두 재정렬을 해야 합니다. 이런 부분에서 편합니다.

하지만 많이 접근하는 데이터가 있는 DB 쪽으로 트래픽이나 데이터량이 몰릴 수 있습니다. 결국 샤딩을 했더라도 동일한 현상이 나타난다면 또 부하 분산을 통해 DB를 쪼개 재정렬하는 작업이 필요하고, 반대로 트래픽이 저조한 DB는 통합 작업을 통해 유지비용을 아끼도록 관리해야 합니다.

그래서

샤딩은 데이터가 많을 경우 효율적으로 데이터를 나누어 부하를 줄일 수 있습니다.

하지만 운영 복잡도가 높아지기 때문에 최대한 샤딩을 피하여 개선할 수 있는 방법을 찾고 시도해 본 후 진행하는 것이 좋습니다만.. 저희 팀은 이미 많은 고난을 헤쳐 왔기 때문에 더없이 문제없었습니다.

샤딩 잘못쓰면 위 짤처럼 되어버립니다

저희는 리소스 사용 balance가 좋은 'Modular Sharding'을 이용하여 분산 처리의 효과를 극대화하여 사용하기로 했습니다. 확장이 필요할 경우 그 배수로 확장을 하기로 하고 MySQL Cluster를 구성하였습니다.


ShardingSphere

ShardingSphere의 가장 큰 장점은 위 그림처럼 분산된 테이블을 하나의 테이블처럼 사용할 수 있다는 점입니다.
ShardingSphere는 Sharding-JDBC, Sharding-Proxy, Sharding-Sidecar(TODO) 독립된 제품으로 구성되어 있고, 각 제품군의 특징을 정리하면 다음과 같습니다.

Sharding-JDBC

shardingsphere-jdbc

ShardingSphere-JDBC는 Java의 JDBC 계층에서 추가 서비스를 제공하는 경량 Java 프레임워크입니다. 클라이언트가 데이터베이스에 직접 연결하면 jar 형식으로 서비스를 제공하며 추가 배포 및 종속성이 필요하지 않습니다. JDBC 및 모든 종류의 ORM 프레임워크와 완벽하게 호환되는 JDBC 드라이버의 향상된 버전으로 생각할 수 있습니다.

  • JDBC 기반 ORM 프레임워크(JPA, Hibernate, Mybatis, Spring JDBC 등)에 적용 가능
  • DBCP, C3P0, BoneCp, HikariCP 등의 서드파티 데이터베이스 연결 풀 지원
  • 모든 종류의 JDBC 표준 데이터베이스 지원(MySQL, PostgreSQL, Oracle, SQLServer 등)

Sharding-Proxy

shardingsphere-proxy

ShardingSphere-Proxy는 이기종 언어를 지원하기 위해 데이터베이스 프로토콜을 캡슐화하여 데이터베이스 서버를 제공하는 프록시입니다. MySQL과 PostgreSQL 프로토콜이 제공되며, MySQL/PostgreSQL 프로토콜과 호환되는 모든 종류의 터미널을 사용하여 운영할 수 있어 DBA에게 더 친숙합니다.

  • MySQL/PostgreSQL 서버같이 직접 사용 가능
  • MariaDB와 같은 MySQL 기반 데이터베이스 및 openGauss와 같은 PostgreSQL 기반 데이터베이스와 호환
  • MySQL Command Client, MySQL Workbench 등 MySQL/PostgreSQL 프로토콜과 호환되는 모든 종류의 클라이언트에 적용 가능
  ShardingSphere-JDBC ShardingSphere-Proxy
데이터베이스 Any MySQL/PostgreSQL
연결 수 비용 많음 적음
지원되는 언어 Java만 Any
성능 손실이 적음 비교적 손실이 높음
분산 가능 불가
정적 항목 불가 가능

Spring Boot를 사용하여 개발 중이고, 손실이 적고 자바 어플리케이션 위에서 JDBC 형태로 동작하는 ShardingSphere-JDBC 방식을 사용해 봅니다.

Architecture


ShardingSphere 적용기 (부제: 첫 번째 시련)

ShardingSphere-JDBC는 총 3가지 버전이 존재합니다.

  1. Java API 버전
  2. YML Configuration 버전
  3. SpringBoot Starter 버전

YML Configuration과 SpringBoot Starter를 이용한다면 손쉽게 ShardingSphere DataSource를 설정할 수 있습니다.

하지만 사내 보안정책에 따라 DB 주소 및 계정정보를 노출시키지 않기 위해 만들어진 사내 라이브러리인 DCM(Database Connection String Management)을 사용하기 위해서는 Spring Boot Starter의 JNDI 혹은 Java API를 사용해 직접 DataSource를 주입해야 했습니다.

그리하여 ShardingSphere DataSource 생성 시에 DCM DataSource를 직접 주입하기 위해서 Java API버전을 선택하게 되었고, 지마켓용으로 직접 YML Configuration 버전을 구현하였습니다.

기존의 Java API 버전을 사용한 설정 적용

일반적인 샤딩스피어에서 제공하는 'Java API' 버전을 이용하여 샤딩 스피어 기본 세팅을 한 코드입니다. 아래처럼 기나긴 코드와 알아보기 힘든 로직들로 인하여 가독성이 매우 떨어졌습니다.

    //보기 힘든 영어들의 향연
    private Collection<RuleConfiguration> getRuleConfigurations(){
        ShardingRuleConfiguration ruleConfiguration = new ShardingRuleConfiguration();

        // Sharding tables rules
        Collection<ShardingTableRuleConfiguration> tableRulesConfList = new ArrayList<>();
        ShardingStrategyConfiguration strategyConfiguration = new StandardShargindStrategyConfiguration("샤드키", "generalModShardingAlgorithm");
        ShardingTableRuleConfiguration tableRuleConfiguration = new ShardingTableRuleConfiguration("테이블", "ds${0...1}.goods");
        goodsTableRuleConfiguration.setDatabaseShardingStrategy(strategyConfiguration);
        tableRuleConfList.add(goodsTableRuleConfiguration);

        ruleConfiguration.setTables(tableRulesConfList);


        // Algorithm rules
        Properties generalModShardingAlgorithmProperties = new Properties();
        generalModShardingAlgorithmProperties.setProperty("sharding-count", "4");

        ShardingSphereAlgorithmConfiguration shardingSphereAlgorithmConfiguration = new ShardingSphereAlgorithmConfiguration("MOD", generalModShardingAlgorithmProperties);
        Map<String, ShardingSphereAlgorithmConfiguration> shardingSphereAlgorithmConfigurationMap = new HashMap<>();
        shardingSphereAlgorithmConfigurationMap.put("generalModShardingAlgorithm", shardingSphereAlgorithmConfiguration);


        ruleConfiguration.setShardingAlgorithm(shardingSphereAlgorithmConfigurationMap);

        Collection<RuleConfiguration> ruleConfigs = new ArrayList<>();
        ruleConfigs.add(ruleConfiguration);

        return ruleConfigs;
    }

지마켓용 YML Configuration 버전을 사용한 설정 적용

기본 Java API 버전을 사용할 경우 유지보수를 어렵게 만들기 때문에 Java API 기반으로 지마켓용 세팅을 개발하여 YML로 편하게 세팅이 가능하도록 했습니다.
샤딩스피어 공식 문서를 보고 쉽게 세팅할 수 있도록 샤딩스피어에서 제공하는 SpringBoot Starter 버전과 거의 유사한 로직으로 설정하도록 했습니다.

  ...

gmarket:
  shardingsphere:
    datasources:
      ds0: ...
      ds1: ...

    rules:
      tables:
        테이블명:
          actual-data-nodes: ds$->{0..1}.테이블명
          database-strategy:
            sharding-column: 샤드키
            sharding-algorithm-name: 샤드 모드명
      sharding-algorithms:
        모드명:
          type: ...
          props:
            sharding-count: 샤드 mod 값
      sharding:
        broadcast-tables: #샤딩하지 않고, 모든 데이터베이스에 저장할 경우
            - 브로드캐스트 테이블명

    ...

이러한 과정들을 거쳐 드디어 ShardingSphere를 사용할 수 있게 되었습니다.


Data Migration

메인 로직들은 모두 SQLServer를 통해 처리되어 있으니 해당 DB에 저장되어 있던 데이터를 MySQL로 옮기는 데이터 마이그레이션 과정이 필요합니다. 마이그레이션 또한 Sharding을 사용하여 데이터를 저장해야 하므로 Application으로 구현해야 합니다.

애초에는 대용량 데이터를 빠르게 마이그레이션 하는 것을 목표로 로직을 최소화하여 insert 하도록 구현하였습니다. Export를 수행한 시점 이후에 SQLServer에서 발생하는 데이터 변경 건은 MySQL에 반영되지 않으므로 마이그레이션 작업 시간을 최소한으로 하고, Export를 수행한 시점 이후 데이터 변경 건을 빠르게 복제해 가는 것을 계획하였습니다.

그러나, 실제 운영에서 마이그레이션을 진행해보니 MySQL Primary 인스턴스에서 Secondary 인스턴스로 복제 지연이 발생하였습니다.

마이그레이션 작업 시간 최소화가 힘들어진 이상 당초 계획을 수정하여, Rummikub-Consumer(싱크 커넥터 역할을 하는 Application)와 마이그레이션 Application이 같이 실행되어도 데이터 정합성에 문제가 없도록 Upsert 처리로 변경되었습니다.

Architecture


 

운영 반영기 (부제: 두 번째 시련)

드디어 운영 서버에 반영을 하는 날이 되었습니다. 해당 프로젝트는 새로운 프로젝트라 기존 트래픽이 없었고, Real 운영 배포를 진행하기 위해 서버에 반영하고 테스트를 하던 와중..

요청을 보내면 무려 3초 뒤에 응답이 오는 획기적인 서비스가 되었습니다.

해당 현상은 개발서버에서는 전혀 발견되지 않았고 운영 서버에서만 발견되었던 현상이라 매우 당황스러웠습니다.

여러 가지 방법 시도

프로그램을 첫 실행한 후, 첫 번째 요청건이 굉장히 느려지는 현상이었는데요. 현재 서비스를 쿠버네티스로 관리하고 있는 현재 상황상, 각 Pod마다 첫 요청건이 느려지는 문제였습니다. 많은 트래픽이 들어오는 상황의 3초 동안의 latency는 굉장히 큰 부분이며, 차후 서비스를 새로 배포할 때마다 해당 현상을 겪어야 한다는 것이 문제였습니다.

개발서버에서 발견하지 못했던 이유가 Pod 1개로 테스트를 하고 있었기 때문에 눈에 크게 띄지 못했던 것이었습니다. 프로파일링을 통해 디테일하게 확인해보니 샤딩스피어쪽 + DB 연결 쪽 문제가 확인되었는데요. 저희가 시도해본 방법은 아래와 같습니다.

  • Hikari Connection Config 세팅 변경
  • ShardingSphere 사용하지 않는 API 구현하여 확인
  • 사내 DCM 세팅 확인
  • jvm 속성 변경
  • ShardingSphere 버전 변경
  • 그 외 등등..

위 방식들은 모두 실패하였습니다.

도대체 무엇이 문제일까 싶어 ShardingSphere 공식 문서를 정독하고 github repository의 issue 탭과 Release history를 확인했습니다.

Warm Up 코드를 통한 해결

그러던 중 closed issue에서 동일한 현상을 호소하는 글을 발견합니다. (Issue Reporter 외에도 꽤나 다수 고통받고 있었습니다.)

(질문자): 샤딩스피어 버전 x.xx 버전 사용하고 있는데 너무 느립니다.
  프로그램 시작하고 첫번째 실행구문이 너무 느린데 이게 5~6초 걸려요.
  공식 홈페이지에서 이 문제를 해결할 마땅한 세팅방법을 찾지 못했어요.

(다른사람-1,2,3...): 저두요..

(메인터넌스 답변): SQL은 처음 수신되었을때 AST로 구문 분석이 되며 캐시에 저장됩니다.
  SQL 웜업 기능을 사용하면 애플리케이션을 시작하기 전에 사용자가 SQL을 신속하게 사용할 수 있을겁니다.
  저희는 해당 기능에 대해 원하는 사람이 있다면 논의해서 설계할 수 있습니다.

실제적으로 Spring에서의 WarmUp 로직을 실행시키도록 하면 해결되는 문제였습니다. 실제 ShardingSphere + DataDog의 환장의 콜라보로 지연시간이 발생한 것으로 추가적으로 확인이 되었습니다.

일단 이러한 웜업 코드를 작성하기 전 JVM의 기본 아키텍처를 한번 짚고 넘어갑시다.

JVM 기본 아키텍처

새로운 JVM 프로세스가 시작될 때마다 ClassLoader 인스턴스에 인해 필요한 모든 클래스가 메모리에 로드됩니다. 이 프로세스는 3단계로 이루어집니다.

  1. Bootstrap Class Loading
    • Java 코드와 java.lang과 같은 필수 Java 클래스를 메모리에 로드합니다.
  2. Extension Class Loading
    • java.ext.dirs 경로에 있는 모든 JAR 파일을 로드합니다. 개발자가 수동으로 Jar을 추가하거나 maven과 gradle 기반이 아닌 응용프로그램에선 이런 모든 클래스가 이 단계에서 로드됩니다.
  3. Application Class Loading
    • 애플리케이션 클래스 경로에 있는 모든 클래스를 로드합니다.

이 초기화 프로세스는 Lazy Loading으로 동작됩니다.

결국 이러한 로딩 방식에 따라 중요 클래스는 프로세스 시작과 동시에 JVM 캐시로 로드되어 빠르게 접근이 가능하지만, 다른 클래스들은 프로세스가 실행 중일 때 실제 요청을 받아야만 로드되는 방식입니다.

Spring에서의 Warm Up

이러한 부분을 바탕으로 저희는 Spring에서의 Warm Up 코드를 작성하였습니다. 필요한 클래스들을 미리 Warm Up 처리하려면 스프링 부트 애플리케이션이 정상적으로 실행된 이후에 Warm Up을 수행해야 합니다. 이때 스프링 부트에서는 간단하게 ApplicationListener의 구현체를 통해 프로세스를 만들 수 있습니다.

사실 내부적으로 모든 클래스를 분석하여 캐싱하는 것은 굉장히 어렵기 때문에 내부적으로 하나하나 분석해서 처리하기보다는 외부에서 처리하는 방식을 사용하였습니다.

클라이언트가 서버로 요청하는 상황을 가정하여 진행되는 프로세스들을 캐싱하는 부분이니 웹 애플리케이션의 Warm Up에 알맞다고 판단되었습니다. 외부에서 처리하는 방식으로는 서비스 내부에서 Http 요청을 보내는 방식이 제일 간편하나, 문제 되는 부분이 샤딩스피어 + DataDog인 부분으로 파악되어 자체적으로 Service를 실행하도록 하는 부분으로 처리하였습니다. (혹시나 다른 좋은 방법이 있다면 꼭 알려주세요!)

@Slf4j
@Component
@RequiredArgsConstructor
public class ApplicationWarmUp {
    private final GmarketService service;

    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReadyEvent(ApplicationReadyEvent event) {
        try {
            service.excute(List.of("0", "1", "2", "3"));

            log.info("WarmUp 완료 - excute 사용");
        } catch (Exception e) {
            log.error("WarmUp 실패 - {}", e.getMessage(), e);
        }
    }
}

해당 프로젝트는 각 샤딩키마다 접근할 수 있는 DB가 정해져 있고 전체 접근 DB는 4곳이기 때문에 각 DB를 예열할 수 있는 샤딩키를 전달함으로써 Warm Up 처리를 완료했습니다!

Warm Up 코드를 통해 첫 요청건이라도 아래와 같이 매우 준수한 응답 속도를 낼 수 있었습니다.

추가적으로 해당 프로젝트를 진행하며 DataDog을 통해 한눈에 볼 수 있는 모니터링 시스템도 구축하게 되었습니다.


 

마치며

할 수 이따. 우리는 어른이니까...

이런저런 시행착오를 겪으며 결국 Sharded MySQL Cluster를 도입하여 대량의 상품 데이터 대상으로 대규모 트래픽을 빠르게 처리하는 상품 조회 플랫폼을 성공적으로 구축하게 되었습니다.

CDC를 활성화할 수 없어 Kafka Connect Echo System을 이용할 수 없었다는 점, Target DB가 Sharding 된 MySQL Cluster이라는 점 때문에 개발자의 역할과 책임이 커지게 되었고 그 과정에서 다소 생산성이 떨어지게 되었습니다. 또한, 오픈 소스를 이용하여 개발하면서 개발 편의성의 이점과 동시에 경험의 부족으로 이슈 트래킹에 어려움도 겪었습니다.

시스템 고도화와 자동화 숙제는 남아있지만, 경험을 바탕으로 Sharded MySQL Cluster 사용이 활발해지는 날을 기대해 봅니다.

긴 글 읽어주셔서 감사합니다.


Reference

https://debezium.io/

https://shardingsphere.apache.org/

https://www.baeldung.com/java-jvm-warmup

https://levelup.gitconnected.com/what-is-database-sharding-and-how-is-it-done-f36b9cb653e8

댓글