├── .env.example
├── .gitignore
├── README.md
├── database
├── PDOConnector.php
├── database-connection.php
└── public
│ └── index.php
├── docker-compose.yml
└── docker
├── .gitignore
├── mysql
├── Dockerfile
├── dumps
│ └── .gitignore
├── primary
│ ├── logs
│ │ └── .gitignore
│ └── my.conf
├── replica-1
│ ├── logs
│ │ └── .gitignore
│ └── my.conf
└── replica-2
│ ├── logs
│ └── .gitignore
│ └── my.conf
├── nginx
├── Dockerfile
└── default.conf
└── php
├── Dockerfile
└── php.ini
/.env.example:
--------------------------------------------------------------------------------
1 | PRIMARY_MYSQL_DATABASE=main_db
2 | PRIMARY_MYSQL_ROOT_PASSWORD=secret
3 | PRIMARY_MYSQL_USER=user
4 | PRIMARY_MYSQL_PASSWORD=secret
5 | PRIMARY_MYSQL_PORT=3307
6 |
7 | REPLICA_1_MYSQL_DATABASE=replica_db
8 | REPLICA_1_MYSQL_ROOT_PASSWORD=secret
9 | REPLICA_1_MYSQL_USER=user
10 | REPLICA_1_MYSQL_PASSWORD=secret
11 | REPLICA_1_MYSQL_PORT=3308
12 |
13 | REPLICA_2_MYSQL_DATABASE=replica_db
14 | REPLICA_2_MYSQL_ROOT_PASSWORD=secret
15 | REPLICA_2_MYSQL_USER=user
16 | REPLICA_2_MYSQL_PASSWORD=secret
17 | REPLICA_2_MYSQL_PORT=3309
18 |
19 | PHP_PORT=9000
20 | NGINX_PORT=80
21 |
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | docker/nginx/logs/*
3 | .idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Database Replication Setup
2 |
3 | After doing some study on highly available systems, I saw how database replication can be used
4 | to seperate read and write queries to speed them up respectively.
5 |
6 | The main DB is optimized to support faster writes (no indexes setup and ideally no read requests)
7 | and used for writes only, while the replica DB is optimized (properly indexed) for faster reads
8 | and no writes.
9 |
10 | This Repo contains a containerized implementation of database replication with one main db for writes
11 | and two replica DBs for reads, you can have as many replicas as possible, you just need to add more
12 | replica mysql containers and follow the steps to listen for the replica events, more on that later.
13 |
14 |
15 | ## Setup Containers
16 |
17 | To run this setup, you will need to have `Docker` installed and running on your system.
18 |
19 | Open your terminal in the project folder and run `cp .env.example .env` to copy the env file.
20 |
21 | Run `docker-compose up -d` and wait for the services to finish build and be available.
22 |
23 |
24 | ## Setup Replication
25 |
26 | *Assumption: You have a terminal access open at this project folder*
27 |
28 |
29 |
30 | The needed config for the WRITE and READ DBs are already setup and can be found inside the
31 | `docker/mysql` folder.
32 |
33 | ### WRITE DATABASE SETUP
34 | We will start with setting up the WRITE DB.
35 |
36 | Open the terminal for your WRITE DB and run:
37 |
38 | ```mysql
39 | docker-compose exec primary-sql bash
40 | ```
41 |
42 | This will open up a bash command line interface for the primary-sql container, then you
43 | login to mysql, using:
44 |
45 | ```shell
46 | mysql -u root -p
47 | ```
48 |
49 | It will prompt for your password and you provide it, if you're sticking to the env defaults, that would be the word `secret`
50 |
51 | After gaining access to the MySQL interface, copy the codes below and run it in there.
52 |
53 | ```mysql
54 | create user ‘replica’@’%’ identified by ‘password’;
55 | grant replication slave on *.* to ‘replica’@’%’;
56 | flush privileges;
57 | ```
58 |
59 | This sets up a user that will be in charge of replication, and grants replication abilities to it.
60 |
61 | Next, you need to grab the binary log details for the WRITE database which will be used later to provision the READ database
62 |
63 | Still in the WRITE database mysql terminal, run:
64 |
65 | ```mysql
66 | use main_db;
67 | show master status;
68 | ```
69 | This will bring out a table containing the binary log file and the position, copy this details to somewhere safe.
70 |
71 | Now exit the MySQL terminal and then exit the WRITE database terminal too entirely. We are done with the setup
72 |
73 | ### READ DATABASE SETUP
74 |
75 | Open the terminal for your WRITE DB and run:
76 |
77 | ```mysql
78 | docker-compose exec replica-sql-1 bash
79 | ```
80 |
81 | This will open up a bash command line interface for the primary-sql container, then you
82 | login to mysql, using:
83 |
84 | ```shell
85 | mysql -u root -p
86 | ```
87 |
88 | It will prompt for your password and you provide it, if you're sticking to the env defaults, that would be the word `secret`
89 |
90 | After gaining access to the MySQL interface, copy the codes below and run it in there.
91 |
92 | *Before running the command below, change `MASTER_LOG_FILE = 'mysql-bin.000001', MASTER_LOG_POS = 107` values to the values you got from
93 | the master setup.*
94 |
95 | ```mysql
96 | stop slave;
97 | CHANGE MASTER TO MASTER_HOST = 'primary-sql', MASTER_USER = 'replica', MASTER_PASSWORD = 'password', MASTER_LOG_FILE = 'mysql-bin.000001', MASTER_LOG_POS = 107;
98 | start slave;
99 | ```
100 | Then run `show slave status\G;` and look out for the following parameters:
101 |
102 | ```mysql
103 | Slave_IO_State: Waiting for master to send event
104 |
105 | Master_Host: primary-sql
106 | Slave_IO_Running: Yes
107 | Slave_SQL_Running: Yes
108 | ```
109 |
110 | If the last two parameters are not running then there is a setup error and you might need to either look for where you missed a step
111 | or check in with ChatGPT with the specific error code.
112 |
113 | That is all the setup, now the replica DB will be picking up commands made on the primary DB
114 |
115 | You can repeat this steps on the `replica-sql-2` database and as many databases you want.
116 |
117 | ## Testing
118 |
119 | Run a command on your master mysql terminal, and then check that it is ran too on your replica db.
120 | An easy example would be creating a table on the READ database and checking that same table exists on the replica table.
121 |
122 | ## The Docker File
123 |
124 | The Docker file contains 3 services which are instances of the mysql image:
125 | - primary-sql
126 | - replica-sql-1
127 | - replica-sql-2
128 |
129 | The `primary-sql` instance acts as the WRITE DB while the `replica-sql-1` and `replica-sql-2`
130 | acts as the READ DB.
131 |
132 | Each respective mysql container has a folder in the `docker/mysql` folder and contains the config
133 | files and logs folder. You can update it to suit your custom needs but the default details there are
134 | just right to get us going.
135 |
136 |
137 | Implementation Resource Aid: [MySQL DB Replication.](https://thilinamad.medium.com/mysql-db-replication-63786ac8241e) and ChatGPT
138 |
139 |
140 | ### Todo
141 |
142 | [] Add a webserver to visualize the replication
--------------------------------------------------------------------------------
/database/PDOConnector.php:
--------------------------------------------------------------------------------
1 | host = $host;
13 |
14 | return $this;
15 | }
16 |
17 | public function setUserName(string $username)
18 | {
19 | $this->username = $username;
20 |
21 | return $this;
22 | }
23 |
24 | public function setPassword(string $password)
25 | {
26 | $this->password = $password;
27 |
28 | return $this;
29 | }
30 |
31 | public function setDBName(string $database)
32 | {
33 | $this->database = $database;
34 |
35 | return $this;
36 | }
37 |
38 | public function connect(array $options = [])
39 | {
40 | try {
41 | return new PDO(
42 | "mysql:host=$this->host;dbname=$this->database",
43 | $this->username,
44 | $this->password,
45 | $options
46 | );
47 | } catch(PDOException $e) {
48 | echo "Error!:". $e->getMessage() . "
";
49 | die();
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/database/database-connection.php:
--------------------------------------------------------------------------------
1 | setHostName('primary-sql')
6 | ->setUserName('root')
7 | ->setPassword('secret')
8 | ->setDBName('main_db')
9 | ->connect();
10 |
11 | $firstReplicaDBConnection = (new PDOConnector)
12 | ->setHostName('replica-sql-1')
13 | ->setUserName('user')
14 | ->setPassword('secret')
15 | ->setDBName('replica_db')
16 | ->connect();
17 |
18 | $secondReplicaDBConnection = (new PDOConnector)
19 | ->setHostName('replica-sql-2')
20 | ->setUserName('user')
21 | ->setPassword('secret')
22 | ->setDBName('replica_db')
23 | ->connect();
24 |
--------------------------------------------------------------------------------
/database/public/index.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |