데이터베이스 샤딩 & 레플리카

코끼릭·2022년 10월 23일
0

IT

목록 보기
16/24
post-custom-banner

Sharding

서비스가 성장하게 되면 자연스럽게 많은 트래픽이 데이터베이스에 몰리면서 부하가 커지고 데이터베이스에 저장하는 양이 늘어나게 되면서 데이터베이스의 성능 향상을 위해 데이터를 분산저장할 수 있는 데이터베이스 샤딩을 하게 된다. 샤딩은 관리 측면에서 복잡하고 효율적인 성능을 내기 위해서는균일하게 저장해야 된다는 점에서 운영 시 난이도가 있는 데이터베이스 설계 방식이지만 잘 설계된 샤딩 데이터베이스를 통해 얻을 수 있는 장점은 크게 다음과 같다.

  • 샤딩 데이터베이스의 탐색 범위 단축으로 인한 응답 시간 개선
  • 샤드 단위의 데이터베이스 장애로 전체 서비스 중단 방지 가능
  • 데이터베이스 용량 확장 용이(Scale-Out)

Horizontal Partitions

  • 분산저장 시 하나의 테이블을 row 기준으로 나눠서 저장하는 방식
  • 데이터의 개수가 작아지면서 index의 개수도 작아지게 된다.
  • 읽기 과정에서 레이턴시가 증가하게 된다.
  • 파티션 하나가 고장나도 데이터 무결성이 깨진다.

Vertical Partitions

  • 분산저장 시 하나의 테이블을 column 기준으로 나눠서 저장하는 방식
  • 자주 사용하는 column을 분리시켜 성능을 향상 시킬 수 있다.

Replication

하나의 원본 데이터베이스를 그대로 복제해서 다른 데이터베이스에 저장하는 방법으로 갑자기 늘어나는 트래픽 부하를 줄일 수 있고 데이터베이스 백업의 역할까지 수행한다. 데이터를 복제하기 위해 슬레이브 데이터베이스는 마스터 데이터베이스의 변경 내역이 저장된 바이너리 로그를 전송받고 그대로 변경 내역을 수행해서 복제하게 되는데 MySQL에서는 아래의 3개의 스레드를 통해 수행된다.

  • Binary Log Dump Thread
    마스터 데이터베이스에서 레플리카 서버로 바이너리 로그를 전송할 때 작업을 수행한다.
  • Replication I/O Thread
    start slave를 통해 슬레이브 데이터베이스가 동작하게 되면 마스터 데이터베이스에서 업데이트된 내역이 있는지 요청한 후 새롭게 추가된 내용을 레플리카 relay log에 기록하는 작업을 수행한다.
  • Replication SQL Thread
    레플리카 relay log에 기록된 레코드 변경사항을 반영하기 위한 SQL 스레드로 트랜잭션 작업을 수행한다.

데이터베이스 샤딩과 레플리카 환경 자동 구축하기

docker에서 컨테이너 실행시 실행되는 쉘스크립트를 활용해 만든 docker 구성 파일의 경우 하나의 서버에 여러 개의 데이터베이스 컨테이너를 구동하게 되어있지만 실제 운영 환경에서는 각각의 컨테이너가 별도의 서버에서 동작하게 된다.

1. docker compose를 통한 컨테이너 환경 설정

docker compose를 통해 모든 컨테이너를 한 번에 실행시키는 것이 아닌 master_db > slave_db(master_db의 접속 설정까지 완료 확인 필요) > spider_db > redis1 > spring1 순서로 컨테이너를 차례대로 구동시켜야 한다.

  • spider_db
    데이터베이스 컨테이너로 애플리케이션이 접속하는 데이터베이스이다. docker-entrypoint.sh에 두 개의 파티션 데이터베이스 서버를 등록하도록 스크립트를 추가했다.

  • master_db1, master_db2
    각 파티션 데이터베이스 컨테이너이다. 슬레이브 컨테이너가 접속할 수 있도록 슬레이브 계정 권한을 생성하도록 docker-entrypoint.sh에 스크립트를 추가하고 접속 ip를 고정시켰다.(따로 설정 없으면 임의의 ip 주소를 할당한다.)

  • slave_db1, slave_db2
    파티션 데이터베이스의 슬레이브 데이터베이스 컨테이너이다. 마스터 데이터베이스에 동작을 복사할 수 있도록 슬레이브 설정을 수행한 후 마스터 컨테이너에 접속해 테이블을 생성하도록 docker-entrypoint.sh에 스크립트를 추가하고 슬레이브 동작을 시작하도록 설정했다.

    version: "3.7"
    services:
      spider_db:
        platform: ubuntu:latest
        image: mariadb:10.1
        restart: on-failure
        container_name: spider_db
        ports:
          - "3305:3306"
        networks:
          default_bridge:
            ipv4_address: 192.168.0.2        
        environment:
          - MYSQL_DATABASE=database
          - MYSQL_ROOT_PASSWORD=samho101
          - TZ=Asia/Seoul
        command:
          - --lower_case_table_names=1
          - --character-set-server=utf8mb4
          - --collation-server=utf8mb4_unicode_ci
          - --bind-address=0.0.0.0
        volumes:
          - /프로젝트 경로/database/shard_repl/spider_db/docker-entrypoint.sh:/docker-entrypoint.sh
          - /프로젝트 경로/database/shard_repl/spider_db/:/etc/mysql/conf.d
    
      master_db1:
        platform: ubuntu:latest
        image: mariadb:10.1
        restart: on-failure
        container_name: master_db1
        ports:
          - "3307:3306"
        networks:
          default_bridge:
            ipv4_address: 192.168.0.3
        environment:
          - MYSQL_DATABASE=database
          - MYSQL_ROOT_PASSWORD=samho101
          - TZ=Asia/Seoul
        command:
          - --lower_case_table_names=1
          - --character-set-server=utf8mb4
          - --collation-server=utf8mb4_unicode_ci
          - --bind-address=0.0.0.0
        volumes:
          - /프로젝트 경로/database/shard_repl/master_db1/docker-entrypoint.sh:/docker-entrypoint.sh
          - /프로젝트 경로/database/shard_repl/master_db1/:/etc/mysql/conf.d
    
      master_db2:
        platform: ubuntu:latest
        image: mariadb:10.1
        restart: on-failure
        container_name: master_db2
        ports:
          - "3308:3306"
        networks:
          default_bridge:
            ipv4_address: 192.168.0.4
        environment:
          - MYSQL_DATABASE=database
          - MYSQL_ROOT_PASSWORD=samho101
          - TZ=Asia/Seoul
        command:
          - --lower_case_table_names=1
          - --character-set-server=utf8mb4
          - --collation-server=utf8mb4_unicode_ci
          - --bind-address=0.0.0.0
        volumes:
          - /프로젝트 경로/database/shard_repl/master_db2/docker-entrypoint.sh:/docker-entrypoint.sh
          - /프로젝트 경로/database/shard_repl/master_db2/:/etc/mysql/conf.d
    
      slave_db1:
        platform: ubuntu:latest
        image: mariadb:10.1
        restart: on-failure
        container_name: slave_db1
        ports:
          - "3309:3306"
        networks:
          default_bridge:
            ipv4_address: 192.168.0.5
        environment:
          - MYSQL_DATABASE=database
          - MYSQL_ROOT_PASSWORD=samho101
          - TZ=Asia/Seoul
        command:
          - --lower_case_table_names=1
          - --character-set-server=utf8mb4
          - --collation-server=utf8mb4_unicode_ci
          - --bind-address=0.0.0.0
        volumes:
          - /프로젝트 경로/database/shard_repl/slave_db1/docker-entrypoint.sh:/docker-entrypoint.sh
          - /프로젝트 경로/database/shard_repl/slave_db1/restart_slave.sql:/docker-entrypoint-initdb.d/restart_slave.sql
          - /프로젝트 경로/database/shard_repl/slave_db1/:/etc/mysql/conf.d
    
      slave_db2:
        platform: ubuntu:latest
        image: mariadb:10.1
        restart: on-failure
        container_name: slave_db2
        ports:
          - "3310:3306"
        networks:
          default_bridge:
            ipv4_address: 192.168.0.6
        environment:
          - MYSQL_DATABASE=database
          - MYSQL_ROOT_PASSWORD=samho101
          - TZ=Asia/Seoul
        command:
          - --lower_case_table_names=1
          - --character-set-server=utf8mb4
          - --collation-server=utf8mb4_unicode_ci
          - --bind-address=0.0.0.0
        volumes:
          - /프로젝트 경로/database/shard_repl/slave_db2/docker-entrypoint.sh:/docker-entrypoint.sh
          - /프로젝트 경로/database/shard_repl/slave_db2/restart_slave.sql:/docker-entrypoint-initdb.d/restart_slave.sql
          - /프로젝트 경로/database/shard_repl/slave_db2/:/etc/mysql/conf.d
    
    networks:
      default_bridge:
        ipam:
          driver: default
          config:
            - subnet: 192.168.0.0/16

2. 노드 데이터베이스 설정 작업

spider_db의 데이터베이스로 접속해 파티션 설정을 한 테이블을 생성한다.

create table test(
    id  int auto_increment primary key,
    user_id varchar(50) null
)
engine=spider
comment='wrapper "mysql", table "test"'
partition by key(id) (
 partition sample1 comment = 'srv "master_db1"', 
 partition sample2 comment = 'srv "master_db2"' 
);

3. 조회 속도 비교

Full Scan Search에서의 속도 차이를 확인하기 위해 50만 개의 테스트 데이터를 넣고 인덱스 없는 테이블의 조회 환경을 확인해봤을 때 단일 데이터베이스 조회보다 속도가 빠른 것을 확인할 수 있다.

  • 단일 데이터베이스 조회 결과

    {
    	"status": "OK",
    	"data": {
      	"id": 500000,
      	"user_id": "0a916d8b"
    	},
    	"time": 1113,
    	"now": "2022-09-09T03:06:07.010807"
    }
  • 샤딩 + 레플리카 데이터베이스 조회 결과

    {
     	"status": "OK",
     	"data": {
        	"id": 500000,
        	"user_id": "0a916d8b"
     	},
     	"time": 11,
     	"now": "2022-09-09T11:37:02.08804"
    }

데이터베이스 샤딩이란 무엇인가요?
Database의 샤딩(Sharding)
Database의 파티셔닝(Partitioning)이란?
Replication Threads
샤딩 + 레플리카 환경 자동 구축 실습 프로젝트

profile
ㅇㅅㅇ
post-custom-banner

0개의 댓글