Производительность полусинхронной репликации MySQL

медленная штука

Сегодня попалась интересная статья на английском.

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

Возможно, статья окажется полезной при принятии решения какой вид репликации лучше выбрать.

Что такое полусинхронная репликация?

Если совсем коротко, то это то что слейв должен дать подтверждение того что получен бинарный лог от мастера перед тем как выполнять запрос. Эффект который можно от этого получить — при полном сбое мастера слейв может пропустить максимум 1 коммит. Но это не улучшает производительности репликации самой по себе и не защищает от девиации данных.

Как там производительность?

Полусинхронная репликация подразумевает, что запись на мастере приостанавливается пока не будет получено подтверждение того, что слейв получил байнарный лог.

В локальной сети с мизерной задержкой это не должно дать особых проблем с производительностью.

Но если сервера находятся удаленно друг от друга.Скажем, один в Вирджинии, а другой в Калифорнии. Задержка между ними составляет порядка 85 миллисекунд.

Отсюда вытекает моя гипотеза — при такой задержке мы не можем развить скорость больше чем 11 запросов (Insert, Update, Delete) в секунду — 1000ms/85ms = 11.7.

Проверим предположение.

Я подготовил одинаковые инстансы БД на EC2 расположенные в us-east-1 и в us-west-1 на Ubuntu 12.04 и установил Percona сервер из репозитария.

gpg --keyserver  hkp://keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A
gpg -a --export CD2EFD2A | apt-key add -
echo deb http://repo.percona.com/apt precise main > /etc/apt/sources.list.d/percona.list
apt-get update
apt-get install percona-server-server-5.5 libmysqlclient-dev

Так же думаю не лишним будет привести конфигурацию MySQL. My.cnf взят с дистрибутива support-files/my-huge.cnf

[mysqld]
port          = 3306
socket        = /var/run/mysql/mysql.sock
skip-external-locking
key_buffer_size = 384M
max_allowed_packet = 1M
table_open_cache = 512
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 8M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
query_cache_size = 32M
thread_concurrency = 8
server-id     = 1
log-bin=mysql-bin
binlog_format=mixed
innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:2000M;ibdata2:10M:autoextend
innodb_log_group_home_dir = /var/lib/mysql
innodb_buffer_pool_size = 384M
innodb_additional_mem_pool_size = 20M
innodb_log_file_size = 100M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 0
innodb_lock_wait_timeout = 50

После написал маленький скрипт на Ruby, позволяющий делать 10К вставок в таблицу.

apt-get install rubygems
gem install sequel mysql2 --no-rdoc --no-ri
#!/usr/bin/env ruby
# insertperf.rb
require 'logger'
require 'rubygems'
require 'sequel'
logger = Logger.new(STDOUT)
localdb = "inserttest"
db = Sequel.connect( :database = localdb,
                   :adapter =  'mysql2',
                   :user    =  'root',
                   :logger  = logger )
db["DROP DATABASE IF EXISTS #{localdb}"].all
db["CREATE DATABASE #{localdb}"].all
db["CREATE TABLE IF NOT EXISTS #{localdb}.foo (
id int unsigned AUTO_INCREMENT PRIMARY KEY,
text VARCHAR(8)
) ENGINE=InnoDB"].all
 
n = 10000
t1 = Time.new
n.times do
value = (0...8).map{65.+(rand(25)).chr}.join
db["INSERT INTO #{localdb}.foo (text) VALUES (?)",
value].insert
end
t2 = Time.new
elapsed = t2-t1
logger.info "Elapsed: #{elapsed} seconds. #{n/elapsed} qps"

Тест 1

Теперь можно запустить тест для us-east-1, для которого не указаны слейвы.

# w/ no slaves
...
INFO -- : (0.000179s) INSERT INTO test.foo (text) VALUES ('FKGDLOWD')
...
INFO -- : Elapsed: 9.37364 seconds. 1066.82142689499 qps

Получилось приблизительно 1000 запросов в секунду.

Тест 2

Далее сконфигурировал обычный асинхронный slave на us-west-1 и запустил тест еще раз:

# w/ no slaves
# w/ traditional replication
...
INFO -- : (0.000237s) INSERT INTO test.foo (text) VALUES ('CVGAMLXA')
...
INFO -- : Elapsed: 10.601943 seconds. 943.223331798709 qps

производительность немного упала — примерно 950 запросов в секунду.

Тест 3

Ну и последний тест — полусинхронная репликация. Перед началом теста, я замерил задержку:

# ping -c
1 184.72.189.235
PING 184.72.189.235 (184.72.189.235) 56(84) bytes of data.
64 bytes from 184.72.189.235: icmp_req=1 ttl=52 time=85.5 ms

Задержка составляет 85 ms. Это, как я думаю, означает 11 запросов в секунду. Также это значит, что скрипт будет выполняться 15 минут (до этого время было 10 секунд).

Тест итоговый

Настраивал полусинхронную репликацию следующим образом:

master> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
master> SET GLOBAL rpl_semi_sync_master_enabled = 1;
slave> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
slave> SET GLOBAL rpl_semi_sync_slave_enabled = 1;
slave> STOP SLAVE; START SLAVE;

После этого я запустил скрипт вставки. И как ожидал, каждая вставка выполнялась около 85ms.

Чтобы тест прошел быстрее я уменьшил количество вставок с 10К до 1К. Скрипт занял порядка 90 секунд.

# w/ semi-sync replication
...
INFO -- : (0.086301s) INSERT INTO test.foo (text) VALUES ('JKIJTUDO')
...
INFO -- : Elapsed: 86.889529 seconds. 11.5088666207409 qps

Как я и предполагал — 11 вставок в секунду!


Выводы

при текущей реализации полусинхронную репликацию лучше не использовать. Особенно справедливо на проектах между серверами на большом расстоянии или в сетях с большой задержкой.

оригинал статьи

comments powered by Disqus