🧑‍🤝‍🧑

【MySQL】リードレプリカのDB環境構築体験

2021/12/13に公開

はじめに

案件によって様々な環境に触れる機会があるかと思います。
システムを使用するユーザー数やモジュール数、要求される要件によって多種多様な環境が求められており、何もかも同じ条件や環境の案件などほぼ存在しないでしょう。

私が最近経験した案件でMySQLで

  • マスター/スレーブ方式
  • リードレプリカ構成

を採用し、運用していました。
実際にローカル環境に準備して、色々操作してみて知見を深めてみたので
その内容を記載します。

用語解説

DBに関する用語になります。

マスター/スレーブ方式

その名前の通りで主人と奴隷のような関係からこのような用語になりました。

  • マスター(Master)
    基本的にメインで使用されるDB。
    常時稼働しており、システムとメインでやりとりを行う。

  • スレーブ(Slave)
    マスターの複製(レプリケーション)として使用されるDB。
    マスターでの障害発生時や負荷分散を目的としている。

この構成によるメリットとしては
・MySQLの標準機能のため、特別な準備等は必要ない。
・DBサーバーで障害が起こった際、バックアップを行えている。
・非同期で複製を行っているが、ラグはほとんどない。

※注意
「マスター/スレーブ」という用語は世間では差別用語として嫌われ、使わないようにしている企業や国などがあります。
その場合は「メイン/スタンバイ」、「マスター/レプリカ」などと言い換えられていますが内容としては変わりません。

リードレプリカ構成

こちらもその名前の通りで、システムが処理として行う参照(SELECT処理=Read)をスレーブDBでのみ行い、マスターDBの負荷を分散させる構成になります。

一般的にWEBやデスクトップアプリなどやバッチなどのシステムにおいて参照処理は更新処理よりはるかに多いこともあり、参照と更新を分けるだけで負荷を分散しマシンへの負担を軽減させることができます。
また、マスター対スレーブは1:nの関係が成り立つのでユーザー数や処理が多い場合はさらにスレーブDBを増やして負荷分散を行うことが可能となります。

やってみよう

簡単にですが、自分のローカル上にDBサーバーを2つ構築し実際に「マスター/スレーブ構成」はどのようなものか体験してみます。

  • スタート:マスター/スレーブって何か知る
  • ゴール:マスター/スレーブ構成をローカルに完成させる

マスター/スレーブ環境を使ったリードレプリカ構成に関しては実際に自分でやってみてください。

前提条件
・Dockerが使用できること(Dockerで環境作ります。)
・MySQLに関して知識があること

環境の準備

Dockerを使用してMySQLのコンテナを2つ作成します。
まずはこのようなディレクトリ構成を用意します。

mysql-docker [D]
├docker-compose.yml [F]
├db1 [D]
│ ├my.cnf [F]
│ └DDL [D]
└db2 [D]
  ├my.cnf [F]
  └DDL [D]
  
[D]:ディレクトリ
[F]:ファイル

〇docker-compose.yml ↓

version: '3.3'
services:
  db1:
    container_name: "db1"
    restart: always
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: homestead
      MYSQL_USER: homestead
      MYSQL_PASSWORD: secret
      TZ: 'Asia/Tokyo'
    ports:
      - 33061:3306
    command: 'mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci'
    volumes:
      - db-store1:/var/lib/mysql
      - ./db1/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./db1/sql:/docker-entrypoint-initdb.d
      - ./db1/DDL:/etc/ddl
      - ./logs/db1:/var/log/mysql

  db2:
    container_name: "db2"
    restart: always
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: homestead
      MYSQL_USER: homestead
      MYSQL_PASSWORD: secret
      TZ: 'Asia/Tokyo'
    ports:
      - 33062:3306
    command: 'mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci'
    volumes:
      - db-store2:/var/lib/mysql
      - ./db2/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./db2/sql:/docker-entrypoint-initdb.d
      - ./db2/DDL:/etc/ddl
      - ./logs/db2:/var/log/mysql

  phpmyadmin1:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOSTS=mysql
      - PMA_USER=homestead
      - PMA_PASSWORD=secret
    links:
      - db1:mysql
    ports:
      - "8081:80"

  phpmyadmin2:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOSTS=mysql
      - PMA_USER=homestead
      - PMA_PASSWORD=secret
    links:
      - db2:mysql
    ports:
      - "8082:80"
      
volumes:
  db-store1:
  db-store2:

※中身確認用にphpadminを2つ用意してます。
 必要なければ削除してください。

〇db1/my.cnf ↓

[mysqld]
server-id=101
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
explicit-defaults-for-timestamp=1
general-log=1
general-log-file=/var/log/mysql/mysqld.log
[client]
default-character-set=utf8mb4

〇db2/my.cnf ↓

[mysqld]
server-id=102
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
explicit-defaults-for-timestamp=1
general-log=1
general-log-file=/var/log/mysql/mysqld.log
read_only
[client]
default-character-set=utf8mb4

環境の構築

1. 起動します。

docker-compose up -d

2. 起動確認します。

$ docker-compose ps
           Name                         Command               State                 Ports
--------------------------------------------------------------------------------------------------------
db1                          docker-entrypoint.sh mysql ...   Up      0.0.0.0:33061->3306/tcp, 33060/tcp
db2                          docker-entrypoint.sh mysql ...   Up      0.0.0.0:33062->3306/tcp, 33060/tcp
mysql-docker_phpmyadmin1_1   /docker-entrypoint.sh apac ...   Up      0.0.0.0:8081->80/tcp
mysql-docker_phpmyadmin2_1   /docker-entrypoint.sh apac ...   Up      0.0.0.0:8082->80/tcp

3. MySQLへ接続してみます。

DB1へ

docker-compose exec db1 bash -c 'mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DATABASE}'
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.26 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

DB2へ

docker-compose exec db2 bash -c 'mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DATABASE}'
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.26 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

PHPADMINで

DB1→
http://localhost:8081/

DB2→
http://localhost:8082/

※「db1コンテナ内からdb2内のMySQLへの接続」「db2コンテナ内からdb1内のMySQLへの接続」確認などもしておくとGood

MySQLの設定

1. レプリケーション用のユーザーを作成します。

マスタ側へ接続

docker-compose exec db1 bash -c 'mysql -u root -p${MYSQL_ROOT_PASSWORD} '

ユーザー作成

CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

※本来は'%'はホストを指定する方が良い

2. マスターDBのダンプの取得

docker-compose exec db1 bash -c 'mysqldump -u root -proot --all-databases --flush-logs --single-transaction --master-data > /etc/ddl/master.db'

「mysql-docker\db1\DDL」にmaster.dbが出力されていることを確認する。

3. スレーブDBへダンプのリストア

ダンプファイルをコピー

cp db1/DDL/master.db db2/DDL/

リストアの実行

docker-compose exec db2 bash -c 'mysql -u root -p${MYSQL_ROOT_PASSWORD}  < /etc/ddl/master.db'

※ダンプファイルの1行目に

WARNING: --master-data is deprecated and will be removed in a future version. Use --source-data instead.

が存在する場合は削除するとダンプファイルをリストアできる

4. スレーブDBの設定

master.dbをエディタで開き、以下の文言で検索する。
CHANGE MASTER TO

検索した結果、以下の文言が見つかる
CHANGE MASTER TO MASTER_LOG_FILE='binlog.XXXXXX', MASTER_LOG_POS=YYY;

スレーブDBへ接続する

docker-compose exec db2 bash -c 'mysql -u root -p${MYSQL_ROOT_PASSWORD} '

以下のSQLに当てはめ、実行する

mysql> CHANGE MASTER TO
-> MASTER_HOST='db1',
-> MASTER_PORT=33061,
-> MASTER_USER='repl',
-> MASTER_PASSWORD='password',
-> MASTER_LOG_FILE='binlog.XXXXXX',
-> MASTER_LOG_POS=YYY;	

スレーブ(レプリケーション)の開始

mysql> START SLAVE;

スレーブのステータス確認

mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for source to send event
                  Master_Host: db1
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: binlog.000006
          Read_Master_Log_Pos: 1444
               Relay_Log_File: 74ee0d826a91-relay-bin.000002
                Relay_Log_Pos: 1609
        Relay_Master_Log_File: binlog.000006
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 1444
              Relay_Log_Space: 1825
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File:
           Master_SSL_CA_Path:
              Master_SSL_Cert:
            Master_SSL_Cipher:
               Master_SSL_Key:
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 101
                  Master_UUID: 3b7acad3-5bd1-11ec-bfc2-0242ac160002
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp:
               Master_SSL_Crl:
           Master_SSL_Crlpath:
           Retrieved_Gtid_Set:
            Executed_Gtid_Set:
                Auto_Position: 0
         Replicate_Rewrite_DB:
                 Channel_Name:
           Master_TLS_Version:
       Master_public_key_path:
        Get_master_public_key: 0
            Network_Namespace:
1 row in set, 1 warning (0.01 sec)

エラーが出ていないことを確認できればOK

レプリケーションの確認

マスタDB側へサンプルとして以下のSQLを実行する。

CREATE TABLE `test_table1` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `test_text` text COLLATE utf8_unicode_ci NOT NULL,
  `test_varchar` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `test_int` int(11) NOT NULL,
  `created_at` timestamp DEFAULT NULL,
  `updated_at` timestamp DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

DB1の結果(phpadmin)

DB2の結果(phpadmin)

DB2の方に「test_table1」が作成されていれば完了。

さいごに

普段開発しか行っていない人はあまりDBの構成等は考える機会は少ないかと思われます。
大企業になれば専用のインフラチームが存在し、その部隊が上記のような構成の発案から保守まで請け負うパターンが多いです。

少し前までだとサーバーを用意しなければこういった構成を構築し、個人として試すことはできず技術として自分にインプットできませんでした。
※コストも時間もかかり手を出しにくかった

今現在ではDockerのようなコンテナ技術(簡易的な仮想環境)が発展したことで誰でも簡単に試すことができるようになりました。
もし「Dockerを勉強してなかった」「Dockerを勉強する機会がなかった」などあればこの機会に勉強しても良いかもしれませんね。

Discussion