HighLoad_HomeWork/test/dz003/REPORT.md

13 KiB
Raw Blame History

1. Настраиваем асинхронную репликацию.

Я решил сделать сразу три хоста - один мастер и два слейва, чтобы к этому не возвращаться в 5-м пункте.

[mysqld]
skip-host-cache
skip-name-resolve
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = app
[mysqld]
skip-host-cache
skip-name-resolve
server-id = 2
log_bin = /var/log/mysql/mysql-bin.log
relay-log = /var/log/mysql/mysql-relay-bin.log
binlog_do_db = app
[mysqld]
skip-host-cache
skip-name-resolve
server-id = 3
log_bin = /var/log/mysql/mysql-bin.log
relay-log = /var/log/mysql/mysql-relay-bin.log
binlog_do_db = app
  • Инициализация кластера:

    • на мастере создаем базу, пользователя для работы приложения и пользователя для репликации:
      CREATE DATABASE IF NOT EXISTS app CHARACTER SET utf8 COLLATE utf8_general_ci;
      GRANT ALL ON app.* TO "app"@"%" IDENTIFIED BY "app";
      GRANT REPLICATION SLAVE ON *.* TO "mydb_slave_user"@"%" IDENTIFIED BY "mydb_slave_pwd";
      FLUSH PRIVILEGES;
      
    • на обоих слэйвах создаем базу и пользователя для работы приложения:
      CREATE DATABASE IF NOT EXISTS app CHARACTER SET utf8 COLLATE utf8_general_ci;
      GRANT ALL ON app.* TO "app"@"%" IDENTIFIED BY "app"; FLUSH PRIVILEGES;
      
    • определяем текущий IP адрес мастера:
      docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "mysql_master"
      
    • определяем текущий лог файл мастера и позицию в нем:
      mysql -u root -e "SHOW MASTER STATUS"' | grep mysq | awk '{print $1}'
      mysql -u root -e "SHOW MASTER STATUS"' | grep mysq | awk '{print $2}'
      
    • на обоих слейвах назначаем мастера и запускаем репликацию:
      CHANGE MASTER TO MASTER_HOST={IP_мастера},MASTER_USER='mydb_slave_user',MASTER_PASSWORD='mydb_slave_pwd',MASTER_LOG_FILE={текущий_лог},MASTER_LOG_POS={текущая_позиция};
      START SLAVE;
      

2. Выбираем 2 любых запроса на чтения (в идеале самых частых и тяжелых по логике работы сайта) и переносим их на чтение со слейва.

Я решил поэкспериментировать с теми же поисковыми запросами, над которыми мы экспериментировали в ДЗ002. Тем более, что все инструменты для этого уже есть в наличии (wrk).

  • Добавил в структуру конфига адреса мастера и двух слейвов:

    type DSN struct {
        Master	string
        Slave1	string
        Slave2	string
        Port	string
        User	string
        Pass	string
        Base	string
    }
    
  • В хэндлере страницы app:port/search создал селектор, который направляет запросы чтения на первый слейв, если он присутствует в конфигурации:

    db := app.DBMaster
    if app.Config.DSN.Slave1!="" {
        db = app.DBSlave1
    }
    

3. Делаем нагрузочный тест по странице, которую перевели на слейв до и после репликации. Замеряем нагрузку мастера (CPU, la, disc usage, memory usage).

  • Разворачиваем prom&grafana в docker настраиваем dashboard grafana на docker контейнеры

    sudo make prom-up
    
  • Запускаем приложение с подключением только к мастеру и с помощью wrk и lua скрипта нагружаем страницу app:port/search:

    sudo make app-up
    sudo docker run --rm -v /root/scripts:/scripts williamyeh/wrk -t1 -c10 -d5m --timeout 30s http://localhost:8080/search -s /scripts/post.lua -- debug true
    
  • Идем в графану и наблюдаем нагрузку на мастер.

  • Ждем пока wrk отработает:

    Running 5m test @ http://192.168.1.66:8080/search
    1 threads and 10 connections
    Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.83s     1.74s   13.29s    77.58%
    Req/Sec     3.57      4.74    30.00     82.50%
    511 requests in 5.00m, 18.69MB read
    Requests/sec:      1.70
    Transfer/sec:     63.79KB
    
  • Подключаем первый слэйв в приложении, добавлением в docker-compose.yml переменной окружения:

    APP_DSN_SLAVE1: mysql_slave1
    
  • Перезапускаем контейнер с приложением и нагружаем ту же страницу тем же запросом, с помощью wrk:

    sudo make app-reload
    sudo docker run --rm -v /root/scripts:/scripts williamyeh/wrk -t1 -c10 -d5m --timeout 30s http://localhost:8080/search -s /scripts/post.lua -- debug true
    
  • Идем в графану и наблюдаем нагрузку на слэйв.

  • Ждем пока wrk отработает:

    Running 5m test @ http://192.168.1.66:8080/search
    1 threads and 10 connections
    Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    32.12ms   31.59ms 561.62ms   90.65%
    Req/Sec   373.34    147.37   610.00     68.45%
    110834 requests in 5.00m, 433.90MB read
    Non-2xx or 3xx responses: 110834
    Requests/sec:    369.39
    Transfer/sec:      1.45MB
    
  • Получаем график нагрузки:

График нагрузки на контейнерах приложения

4. ОПЦИОНАЛЬНО: в качестве конфига, который хранит IP реплики сделать массив для легкого добавления реплики. Это не самый правильный способ балансирования нагрузки. Поэтому опционально.

Не очень понял, что именно нужно сделать. Если речь о конфиге приложения, я пока сделал отдельные переменные для каждого сервера БД. Если потребуется, заменю срезом. Пока пусть останется так.

5. Настроить 2 слейва и 1 мастер.

Пропускаю этот пункт, т.к. все уже было сделано в п.1

6. Включить row-based репликацию.

  • Добавляем в my.cnf мастера и обоих слейвов, строки:

    binlog_format=ROW
    binlog-checksum=crc32
    

7. Включить GTID.

  • Добавляем в my.cnf мастера, строки:

    gtid-mode=on
    enforce-gtid-consistency=true
    
  • Добавляем в my.cnf обоих слейвов, строки:

    gtid-mode=on
    enforce-gtid-consistency=true
    binlog-rows-query-log_events=1
    

8. Настроить полусинхронную репликацию.

  • Включаем динамическую загрузку модулей и полусинхронную репликацию с таймаутом 1с в my.cnf на мастере:

    loose-rpl_semi_sync_master_enabled=1
    loose-rpl_semi_sync_master_timeout=1000
    
  • Включаем динамическую загрузку модулей и полусинхронную репликацию в my.cnf на обоих слейвах:

    loose-rpl_semi_sync_slave_enabled=1
    
  • Устанавливаем semisync плагин на мастере:

    INSTALL PLUGIN rpl_semi_sync_master SONAME "semisync_master.so";
    
  • Устанавливаем semisync плагин на обоих слейвах:

    INSTALL PLUGIN rpl_semi_sync_slave SONAME "semisync_slave.so";
    
  • Проверяем на всех хостах, установлен ли плагин:

    SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE '%semi%';
    
  • Запускаем кластер и приложение:

    sudo make app-up
    

9. Создать нагрузку на запись в любую тестовую таблицу. На стороне, которой нагружаем считать, сколько строк мы успешно записали.

  • Запускаем кластер:

    sudo make db-up
    
  • Запускаем контейнер с mysql-client:

    sudo make client-up
    
  • Заходим внутрь контейнера и запускаем скрипт:

    /scripts/dz003_2.sh
    

10. С помощью kill -9 убиваем мастер MySQL.

  • Т.к. образ mysql:5.7 не содержит команды ps, не мудрим и грохаем контейнер с мастером:

    sudo docker kill mysql_master
    

11. Заканчиваем нагрузку на запись.

  • Возвращаемся в контейнер клиента и запоминаем, знаение счетчика строк успешно записанных в таблицу мастера

    В нашем случае, скрипт тормазнул после добавления 10233 строк

12. Выбираем самый свежий слейв. Промоутим его до мастера. Переключаем на него второй слейв.

  • Определяем свежайшую реплику:

    sudo docker exec mysql_slave1 sh -c "export MYSQL_PWD=root; mysql -u root -e 'SHOW SLAVE STATUS\G' | grep Master_Log_File"
    sudo docker exec mysql_slave2 sh -c "export MYSQL_PWD=root; mysql -u root -e 'SHOW SLAVE STATUS\G' | grep Master_Log_File"
    

    В нашем случае оба слейва остановились на mysql-bin.000003, поэтому мастером будем промоутить mysql_slave1

    З.Ы. Еще можно было бы поиграть с отложенной репликацией, но не в рамках данного эксперимента

  • Останавливаем на всех слейвах потоки получения обновлений бинарного лога:

    sudo docker exec mysql_slave1 sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE IO_THREAD; SHOW PROCESSLIST;'"
    sudo docker exec mysql_slave2 sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE IO_THREAD; SHOW PROCESSLIST;'"
    
  • Полностью останавливаем слэйв который будем промоутить до мастера и сбрасываем его бинлог:

    sudo docker exec mysql_slave1 sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE; RESET MASTER;'"
    
  • Оставшийся слэйв переключаем на новый мастер:

    sudo docker exec {slave_to_promote} sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE; CHANGE MASTER TO MASTER_HOST='{slave_to_promote}'; START SLAVE;'"
    

13. Проверяем, есть ли потери транзакций:

  • Проверяем количество строк в тестовой таблице на новом мастере:

    sudo docker exec {slave_to_promote} sh -c "export MYSQL_PWD=root; mysql -u root -e 'USE app; SELECT count(*) from test;'"
    
    В нашем случае, потерь транзакций не наблюдается. В таблице нового мастера те же 10233 строки.