Master & Slave DB 構成する方法について調べる

# はじめに
レプリケーションとは、2台以上のDBMSにデータを分けて配置する構成であり、Master/Slaveの構成で成り立っています。  

- Master DBMS : データの登録、修正、削除要求時にバイナリログを生成し、Slaveサーバーに送信します。（高い同時性が求められるトランザクション部分を担当します）
- Slave DBMS : Master DBMSから受信したバイナリログを利用して、データに反映します。（データの要求時に使用され、読み取り専用です）




## 活用方法
- Master DBMSに障害が発生した場合、代わりにSlave DBMSを使用することが可能です（フェイルオーバー）。
- DBMSの負荷分散を目的とし、ユーザーの急増により1台のDBサーバーが対応しきれなくなった場合に使用します。

**注意事項**
- 互換性を確保するために、レプリケーションに使用するMySQLのバージョンは同一にしてください。
- バージョンが異なる場合、Slaveを上位バージョンで設定してください。
- レプリケーションの起動時には、MasterからSlaveの順で起動してください。

## Dockerを活用したMySQLレプリケーション
ディレクトリの構成は以下の通りです。


**master/Dockerfile**    
```
FROM mysql:5.7
ADD ./master/my.cnf /etc/mysql/my.cnf
```

**master/my.cnf**
```
[mysqld]
log_bin = mysql-bin
server_id = 10
default_authentication_plugin=mysql_native_password
```
**slave/Dockerfile**
```
FROM mysql:5.7
ADD ./slave/my.cnf /etc/mysql/my.cnf
```
**slave/my.cnf**
```
[mysqld]
log_bin = mysql-bin
server_id = 11
relay_log = /var/lib/mysql/mysql-relay-bin
log_slave_updates = 1
read_only = 1
default_authentication_plugin=mysql_native_password
```

**docker-compose.yml**

```
version: "3"
services:
  db-master:
    build: 
      context: ./
      dockerfile: master/Dockerfile
    restart: always
    environment:
      MYSQL_DATABASE: 'db'
      MYSQL_USER: 'user'
      MYSQL_PASSWORD: 'password'
      MYSQL_ROOT_PASSWORD: 'password'
    ports:
      - '3306:3306'
    # Where our data will be persisted
    volumes:
      - my-db-master:/var/lib/mysql
      - my-db-master:/var/lib/mysql-files
    networks:
      - net-mysql
  
  db-slave:
    build: 
      context: ./
      dockerfile: slave/Dockerfile
    restart: always
    environment:
      MYSQL_DATABASE: 'db'
      MYSQL_USER: 'user'
      MYSQL_PASSWORD: 'password'
      MYSQL_ROOT_PASSWORD: 'password'
    ports:
      - '3307:3306'
    # Where our data will be persisted
    volumes:
      - my-db-slave:/var/lib/mysql
      - my-db-slave:/var/lib/mysql-files
    networks:
      - net-mysql
  
# Names our volume
volumes:
  my-db-master:
  my-db-slave: 

networks: 
  net-mysql:
    driver: bridge

```



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824062/f23649d1-ccdf-6bea-8524-75ba74c644c3.png)

## 実行手順
1. master-slaveの2つのサーバーを起動します。
~~~bash
docker-compose up -d
~~~
2. internal IPを探します。
- まず、ネットワークIDを見つけます。
~~~bash
❯ docker network ls
NETWORK ID     NAME                    DRIVER    SCOPE
c936b9b7cc42   repli_net-mysql         bridge    local
~~~
- docker inspect {NETWORK ID}を使用して、IPv4Addressに該当する値を確認します。

~~~bash
❯ docker inspect c936b9b7cc42

[
    {
        "Name": "repli_net-mysql",
        "Id": "c936b9b7cc4214b978aa64d7397d218c0fe62dd76389971ef2134ea8a8560765",
        "Created": "2022-01-11T14:36:21.9461848Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "31b2a62702a6249c666076d7182a34c30ac3e2939ea6924b84b237fb6a57e915": {
                "Name": "repli_db-slave_1",
                "EndpointID": "afbd9ee57c687c50fab1b39be1b97919229fff04fc418269612c058cbff06554",
                "MacAddress": "02:42:ac:13:00:03",
                "IPv4Address": "172.19.0.3/16",
                "IPv6Address": ""
            },
            "43bb2e42864eaa10c730ae53c01ab9817a49c5c34db2eba62ce3e14f92779cdb": {
                "Name": "repli_db-master_1",
                "EndpointID": "6e2d8cc63e3fc5d6322ab2de3d6bf414ee49a885ded7e26d66b4afb9188e6090",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "net-mysql",
            "com.docker.compose.project": "repli",
            "com.docker.compose.version": "1.29.0"
        }
    }
]
~~~
3. SlaveからMasterに接続します。

実際、この部分で多少苦労しました。直面したエラーは「Error 1236 - 'Could not find first log file name in binary log index file'」というものでしたが、slaveサーバーでMASTER_LOG_FILEを設定する際に、masterサーバーに存在するログファイルを基準に設定する必要がありました。

これを確認するためには、masterサーバーで以下のコマンドを入力します。
~~~sql
mysql> SHOW BINARY LOGS;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       177 |
| mysql-bin.000002 |   3072319 |
| mysql-bin.000003 |       177 |
| mysql-bin.000004 |       154 |
+------------------+-----------+
~~~

存在しないログファイルインデックスを選択すると、エラーが発生します。  
その後、以下のように設定を合わせました。結論として、Slave_IO_Running: Yes / Slave_SQL_Running: Yesと表示されれば同期が完了したことになります。

~~~sql
mysql> CHANGE MASTER TO MASTER_HOST='172.19.0.3', MASTER_USER='root', MASTER_PASSWORD='password', MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=0;
Query OK, 0 rows affected, 2 warnings (0.00 sec)

mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)

mysql> start slave;
Query OK, 0 rows affected (0.00 sec)

mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.19.0.3
                  Master_User: root
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000004
          Read_Master_Log_Pos: 154
               Relay_Log_File: mysql-relay-bin.000004
                Relay_Log_Pos: 367
        Relay_Master_Log_File: mysql-bin.000004
             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: 154
              Relay_Log_Space: 787
              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: 10
                  Master_UUID: fd513db6-72eb-11ec-890b-0242ac130002
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave 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: 
1 row in set (0.00 sec)
~~~

# テスト
最終的にテストを行うために、DBとテーブルにデータを直接投入してみました。

## Master DB
~~~sql
mysql> use bootex;
Database changed

mysql> create table Member (ID int not null primary key, name varchar

(100));
Query OK, 0 rows affected (0.01 sec)

mysql> insert into Member(id, name)  values (1,"song");
Query OK, 1 row affected (0.01 sec)
~~~

## Slave DB
~~~sql
mysql> show tables;
+------------------+
| Tables_in_bootex |
+------------------+
| Member           |
+------------------+
1 row in set (0.00 sec)

mysql> select * from Member;
+----+------+
| ID | name |
+----+------+
|  1 | song |
+----+------+
1 row in set (0.00 sec)
~~~