ローカル環境で DB のレプリケーション構築してみた

# ディレクトリ構造

```
.
├── docker-compose.yml
├── master
│   ├── Dockerfile
│   ├── initdb.d
│   │   └── init.sql
│   ├── master.cnf
│   └── master.env
└── slave
    ├── Dockerfile
    ├── initdb.d
    │   └── init.sql
    ├── slave.cnf
    └── slave.env
```

# 環境変数

```bash
# master.env
TZ='Asia/Tokyo'
MYSQL_ALLOW_EMPTY_PASSWORD='yes'
```

```bash
# slave.env
TZ='Asia/Tokyo'
MYSQL_ALLOW_EMPTY_PASSWORD='yes'
```

# cnf ファイル

```bash
# master.cnf
[mysqld]
character-set-server=utf8mb4
character-set-database=utf8mb4

# binlog
# 指定された値がファイル名になりプレフィックスに6桁の連番がついたファイルとなる
# ex) mysql-bin.000001
# SHOW BINARY LOGS でバイナリログ一覧を表示
log-bin=mysql-bin

# server id. unique id in replication servers
server-id=1

# enable Global Transaction ID
# https://dev.mysql.com/doc/refman/5.6/ja/replication-options-gtids.html
gtid-mode=ON

# トランザクションで使用できるステートメントのみログを記録するようにする
# https://dev.mysql.com/doc/refman/5.6/ja/replication-options-gtids.html#sysvar_enforce_gtid_consistency
enforce-gtid-consistency=on

[client]
default-character-set=utf8mb4
```

```bash
# slave.cnf
[mysqld]
character-set-server=utf8mb4
# character-set-database は 5.7からDeperecatedになっている
# Assigning a value to the session character_set_database and 
# collation_database system variables is deprecated in MySQL 5.7 and 
# assignments produce a warning. You should expect the session variables 
# to become read only in a future version of MySQL and assignments 
# to produce an error, while remaining possible to access the session variables
# to determine the database character set and collation for the default database.
# character-set-database=utf8mb4

# binlog
# 将来昇格するかもしれないslaveに必要
log-bin=/var/log/mysql/bin-log

# --log-bin optionを指定している必要がある
# マスターから受け取った更新をバイナリログに書き込ませる
log_slave_updates

# relay log file name
relay-log=slave

# server id. unique id in replication servers
server-id=2

gtid-mode=ON
enforce-gtid-consistency=on

[client]
default-character-set=utf8mb4
```

# Dockerfile

```bash
FROM mysql:5.7

RUN apt update \
    && apt install -y locales \
    && sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen \
    && locale-gen && update-locale LANG=ja_JP.UTF-8

# ビルド時のコンテキストは、docker buildコマンドを実行した場所
# しかし、docker-compose.ymlでビルドコンテキストを ~/prog/mysql/replication_sample にしている
# そのため、 ./initdb.d/ ではなく、./master/initdb.d/ のように指定しないといけない
# docker-compose.yml のcontext を ./master にしてあげれば、 ./initdb.d でもいける
COPY ./master/initdb.d/ /docker-entrypoint-initdb.d/
COPY ./master/master.cnf /etc/mysql/conf.d
```

デフォルトでは、日本語が扱えない

```bash
root@7b450e42fce3:/# locale -a
C
C.UTF-8
POSIX
```

`locales` をインストールし、生成された `/etc/locale.gen` ファイル内に記載されている言語のうち使用したい言語のコメントを外してあげる

その後、 `locale-gen` コマンドを実行することで、その言語用の処理がコンパイルされ、`update-locale LANG=ja_JP.UTF-8` を実行することで デフォル言語を設定できる。

```bash
root@7b450e42fce3:/# locale
LANG=ja_JP.UTF-8
LANGUAGE=
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=ja_JP.UTF-8
```

なお、 LANGやLC_ALL についてはこちら

[https://eng-entrance.com/linux-localization-lang](https://eng-entrance.com/linux-localization-lang)

[https://linuxfan.info/locale-variables](https://linuxfan.info/locale-variables)

# Docker Compose

**Using a custom MySQL configuration file(**[https://hub.docker.com/_/mysql](https://hub.docker.com/_/mysql))

では、カスタム設定ファイルを適用させるなら `/etc/mysql/conf.d` にマウントさせろと書いている。別に my.cnfでも良い気はするが 公式に倣う。

```yaml
version: '3.7'

services:
  master:
    container_name: master
    build:
      context: . # https://qiita.com/sam8helloworld/items/e7fffa9afc82aea68a7a
      dockerfile: ./master/Dockerfile
    env_file:
		  - master.env
    ports:
      - '3307:3306'
```

### build contextについて

[https://qiita.com/sam8helloworld/items/e7fffa9afc82aea68a7a](https://qiita.com/sam8helloworld/items/e7fffa9afc82aea68a7a)

# /docker-entrypoint-initdb.d

コンテナ起動時、/docker-entrypoint-initdb.d 内に存在する `.sh` `.sql` `.sql.gz` ファイルがアルファベット順に実行される。

[https://hub.docker.com/_/mysql](https://hub.docker.com/_/mysql) > **Initializing a fresh instance**

## init.sql (Master)

```sql
# % と _ を指定できる
CREATE USER 'replication'@'%' IDENTIFIED BY 'password';
# *.* の部分は クォーテーション などで囲うとシンタックスエラーになる
GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%';
```

# slaveからmasterへのレプリケーション開始

```ruby
# slave server
# レプリケーションの設定
CHANGE MASTER TO
	MASTER_HOST='master'
	MASTER_PORT='3306'
	MASTER_AUTO_POSITION=1 # GTID mode version
	MASTER_HEARTBEAT_PERIOD=30

# SHOW SLAVE STATUS\G でこれらの値を確認できる

# レプリケーション開始
START SLAVE USER = 'replication' PASSWORD = 'password';
```

### MASTER_HEARTBEAT_PERIOD

接続が良好でも、更新内容ががないとslaveはmasterとの接続が悪いのかな？ってなる

このオプションに指定された時間が立つとslaveに接続が悪いと思わせないために、heartbeat signalをslaveに送る。

default値は [slave_net_timeout](https://dev.mysql.com/doc/refman/5.7/en/replication-options-replica.html#sysvar_slave_net_timeout) の半分。

slave_net_timeout はコネクションが断絶したと判断する期間の値

## Masterでの更新内容をSlaveに反映されていることを確認

```ruby
CREATE DATABASE test;
use test
CREATE TALBE sample (id int, name varchar(256));
```