Как да стартирам Analytics на MySQL?
MySQL е страхотна база данни за натоварвания за онлайн обработка на транзакции (OLTP). За някои компании това беше повече от достатъчно за дълго време. Времената се промениха и бизнес изискванията заедно с тях. Тъй като предприятията се стремят да бъдат повече управлявани от данни, все повече и повече данни се съхраняват за по-нататъшен анализ; поведение на клиентите, модели на производителност, мрежов трафик, регистрационни файлове и т.н. Без значение в коя индустрия се намирате, е много вероятно да има данни, които искате да запазите и анализирате, за да разберете по-добре какво се случва и как да подобрите бизнеса си. За съжаление, за съхраняване и запитване на голямо количество данни, MySQL не е най-добрият вариант. Разбира се, той може да го направи и има инструменти, които помагат за настаняването на големи количества данни (например компресиране на InnoDB), но използването на специално решение за обработка на онлайн анализи (OLAP) най-вероятно значително ще подобри способността ви да съхранявате и отправяте заявки за голямо количество от данни.
Един от начините за справяне с този проблем ще бъде използването на специална база данни за извършване на анализи. Обикновено искате да използвате колонно хранилище за данни за такива задачи - те са по-подходящи за обработка на големи количества данни:данните, съхранявани в колони, обикновено са по-лесни за компресиране, също така е по-лесен за достъп на база на колона - обикновено искате някои данни, съхранявани в няколко колони – възможността да се извличат само тези колони, вместо да се четат всички редове и да се филтрират ненужните данни, прави достъпът до данните по-бърз.
Как да репликирате данни от MySQL към ClickHouse?
Пример за колонно хранилище за данни, което е подходящо за анализ, е ClickHouse, хранилище за колони с отворен код. Едно предизвикателство е да се гарантира, че данните в ClickHouse са в синхрон с данните в MySQL. Разбира се, винаги е възможно да настроите някакъв тръбопровод за данни и да извършите автоматизирано пакетно зареждане в ClickHouse. Но стига да можете да живеете с някои ограничения, има по-добър начин да настроите репликация в почти реално време от MySQL в ClickHouse. В тази публикация в блога ще разгледаме как може да се направи.
Инсталиране на ClickHouse
Преди всичко трябва да инсталираме ClickHouse. Ще използваме бързия старт от уебсайта на ClickHouse.
sudo apt-get install dirmngr # optional
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E0C56BD4 # optional
echo "deb http://repo.yandex.ru/clickhouse/deb/stable/ main/" | sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
sudo service clickhouse-server start
След като това е направено, трябва да намерим средство за прехвърляне на данните от MySQL в ClickHouse. Едно от възможните решения е да използвате clickhouse-mysql-data-reader на Altinity. На първо място, трябва да инсталираме pip3 (python3-pip в Ubuntu), тъй като се изисква Python във версия поне 3.4. След това можем да използваме pip3, за да инсталираме някои от необходимите модули на Python:
pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver
След като това стане, трябва да клонираме хранилището. За Centos 7 също са налични RPM, също така е възможно да го инсталирате с помощта на pip3 (пакет clickhouse-mysql), но открихме, че версията, достъпна чрез pip, не съдържа най-новите актуализации и искаме да използваме главен клон от хранилището на git:
git clone https://github.com/Altinity/clickhouse-mysql-data-reader
След това можем да го инсталираме с помощта на pip:
pip3 install -e /path/to/clickhouse-mysql-data-reader/
Следващата стъпка ще бъде да създадете потребители на MySQL, необходими от clickhouse-mysql-data-reader за достъп до MySQL данни:
mysql> CREATE USER 'chreader'@'%' IDENTIFIED BY 'pass';
Query OK, 0 rows affected (0.02 sec)
mysql> CREATE USER 'chreader'@'127.0.0.1' IDENTIFIED BY 'pass';
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE USER 'chreader'@'localhost' IDENTIFIED BY 'pass';
Query OK, 0 rows affected (0.02 sec)
mysql> GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE, SUPER ON *.* TO 'chreader'@'%';
Query OK, 0 rows affected (0.01 sec)
mysql> GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE, SUPER ON *.* TO 'chreader'@'127.0.0.1';
Query OK, 0 rows affected (0.00 sec)
mysql> GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE, SUPER ON *.* TO 'chreader'@'localhost';
Query OK, 0 rows affected, 1 warning (0.01 sec)
Трябва също да прегледате конфигурацията на MySQL, за да се уверите, че сте активирали двоични регистрационни файлове, max_binlog_size е настроен на 768M, binlogs са във формат „ред“ и че инструментът може да се свърже с MySQL. По-долу е дадена извадка от документацията:
[mysqld]
# mandatory
server-id = 1
log_bin = /var/lib/mysql/bin.log
binlog-format = row # very important if you want to receive write, update and delete row events
# optional
expire_logs_days = 30
max_binlog_size = 768M
# setup listen address
bind-address = 0.0.0.0
Импортиране на данните
Когато всичко е готово, можете да импортирате данните в ClickHouse. В идеалния случай бихте стартирали импортирането на хост със заключени таблици, така че да няма промяна по време на процеса. Можете да използвате подчинен като източник на данните. Командата за изпълнение ще бъде:
clickhouse-mysql --src-server-id=1 --src-wait --nice-pause=1 --src-host=10.0.0.142 --src-user=chreader --src-password=pass --src-tables=wiki.pageviews --dst-host=127.0.0.1 --dst-create-table --migrate-table
Той ще се свърже с MySQL на хост 10.0.0.142, използвайки дадени идентификационни данни, ще копира таблицата „pageviews“ в схемата „wiki“ към ClickHouse, работещ на локалния хост (127.0.0.1). Таблицата ще бъде създадена автоматично и данните ще бъдат мигрирани.
За целите на този блог импортирахме приблизително 50 милиона реда от набор от данни „показвания на страници“, предоставен от Фондация Wikimedia. Схемата на таблицата в MySQL е:
mysql> SHOW CREATE TABLE wiki.pageviews\G
*************************** 1. row ***************************
Table: pageviews
Create Table: CREATE TABLE `pageviews` (
`date` date NOT NULL,
`hour` tinyint(4) NOT NULL,
`code` varbinary(255) NOT NULL,
`title` varbinary(1000) NOT NULL,
`monthly` bigint(20) DEFAULT NULL,
`hourly` bigint(20) DEFAULT NULL,
PRIMARY KEY (`date`,`hour`,`code`,`title`)
) ENGINE=InnoDB DEFAULT CHARSET=binary
1 row in set (0.00 sec)
Инструментът преведе това в следната схема на ClickHouse:
vagrant.vm :) SHOW CREATE TABLE wiki.pageviews\G
SHOW CREATE TABLE wiki.pageviews
Row 1:
──────
statement: CREATE TABLE wiki.pageviews ( date Date, hour Int8, code String, title String, monthly Nullable(Int64), hourly Nullable(Int64)) ENGINE = MergeTree(date, (date, hour, code, title), 8192)
1 rows in set. Elapsed: 0.060 sec.
След като импортирането приключи, можем да сравним съдържанието на MySQL:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)
и в ClickHouse:
vagrant.vm :) SELECT COUNT(*) FROM wiki.pageviews\G
SELECT COUNT(*)
FROM wiki.pageviews
Row 1:
──────
COUNT(): 50986914
1 rows in set. Elapsed: 0.014 sec. Processed 50.99 million rows, 50.99 MB (3.60 billion rows/s., 3.60 GB/s.)
Дори в такава малка таблица можете ясно да видите, че MySQL изисква повече време за сканиране, отколкото ClickHouse.
Когато стартирате процеса за гледане на двоичния дневник за събития, в идеалния случай бихте предали информацията за двоичния регистрационен файл и позицията, откъдето инструментът трябва да започне да слуша. Можете лесно да проверите това на подчинения след приключване на първоначалното импортиране.
clickhouse-mysql --src-server-id=1 --src-resume --src-binlog-file='binlog.000016' --src-binlog-position=194 --src-wait --nice-pause=1 --src-host=10.0.0.142 --src-user=chreader --src-password=pass --src-tables=wiki.pageviews --dst-host=127.0.0.1 --pump-data --csvpool
Ако не го предадете, той просто ще започне да слуша за всичко, което идва:
clickhouse-mysql --src-server-id=1 --src-resume --src-wait --nice-pause=1 --src-host=10.0.0.142 --src-user=chreader --src-password=pass --src-tables=wiki.pageviews --dst-host=127.0.0.1 --pump-data --csvpool
Нека заредим още данни и да видим как ще се справим при нас. Можем да видим, че всичко изглежда наред, като погледнем регистрационните файлове на clickhouse-mysql-data-reader:
2019-02-11 15:21:29,705/1549898489.705732:INFO:['wiki.pageviews']
2019-02-11 15:21:29,706/1549898489.706199:DEBUG:class:<class 'clickhouse_mysql.writer.poolwriter.PoolWriter'> insert
2019-02-11 15:21:29,706/1549898489.706682:DEBUG:Next event binlog pos: binlog.000016.42066434
2019-02-11 15:21:29,707/1549898489.707067:DEBUG:WriteRowsEvent #224892 rows: 1
2019-02-11 15:21:29,707/1549898489.707483:INFO:['wiki.pageviews']
2019-02-11 15:21:29,707/1549898489.707899:DEBUG:class:<class 'clickhouse_mysql.writer.poolwriter.PoolWriter'> insert
2019-02-11 15:21:29,708/1549898489.708083:DEBUG:Next event binlog pos: binlog.000016.42066595
2019-02-11 15:21:29,708/1549898489.708659:DEBUG:WriteRowsEvent #224893 rows: 1
Това, което трябва да имаме предвид, са ограниченията на инструмента. Най-големият е, че поддържа само INSERT. Няма поддръжка за DELETE или UPDATE. Също така няма поддръжка за DDL, следователно всички несъвместими промени в схемата, изпълнени на MySQL, ще нарушат репликацията на MySQL към ClickHouse.
Заслужава да се отбележи и фактът, че разработчиците на скрипта препоръчват да се използва pypy за подобряване на производителността на инструмента. Нека преминем през някои стъпки, необходими, за да настроим това.
Първо трябва да изтеглите и декомпресирате pypy:
wget https://bitbucket.org/squeaky/portable-pypy/downloads/pypy3.5-7.0.0-linux_x86_64-portable.tar.bz2
tar jxf pypy3.5-7.0.0-linux_x86_64-portable.tar.bz2
cd pypy3.5-7.0.0-linux_x86_64-portable
След това трябва да инсталираме pip и всички изисквания за clickhouse-mysql-data-reader - точно същите неща, които разгледахме по-рано, докато описваме редовна настройка:
./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient
Последната стъпка ще бъде да инсталирате clickhouse-mysql-data-reader от хранилището на github (предполагаме, че вече е клонирано):
./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/
Това е всичко. Започвайки от сега, трябва да изпълнявате всички команди, като използвате средата, създадена за pypy:
./bin/pypy ./bin/clickhouse-mysql
Тестове
Данните са заредени, можем да проверим дали всичко е минало гладко, като сравним размера на таблицата:
MySQL:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 204899465
1 row in set (1 min 40.12 sec)
ClickHouse:
vagrant.vm :) SELECT COUNT(*) FROM wiki.pageviews\G
SELECT COUNT(*)
FROM wiki.pageviews
Row 1:
──────
COUNT(): 204899465
1 rows in set. Elapsed: 0.100 sec. Processed 204.90 million rows, 204.90 MB (2.04 billion rows/s., 2.04 GB/s.)
Всичко изглежда правилно. Нека изпълним някои заявки, за да видим как се държи ClickHouse. Моля, имайте предвид, че цялата тази настройка е далеч от производствения клас. Използвахме две малки VM, 4GB памет, по един vCPU всяка. Следователно, въпреки че наборът от данни не беше голям, беше достатъчно да се види разликата. Поради малката извадка е доста трудно да се направи „истински“ анализ, но все пак можем да изпратим някои произволни заявки.
Нека проверим от кои дни от седмицата имаме данни и колко страници са били прегледани на ден в нашите примерни данни:
vagrant.vm :) SELECT count(*), toDayOfWeek(date) AS day FROM wiki.pageviews GROUP BY day ORDER BY day ASC;
SELECT
count(*),
toDayOfWeek(date) AS day
FROM wiki.pageviews
GROUP BY day
ORDER BY day ASC
┌───count()─┬─day─┐
│ 50986896 │ 2 │
│ 153912569 │ 3 │
└───────────┴─────┘
2 rows in set. Elapsed: 2.457 sec. Processed 204.90 million rows, 409.80 MB (83.41 million rows/s., 166.82 MB/s.)
В случай на MySQL тази заявка изглежда по-долу:
mysql> SELECT COUNT(*), DAYOFWEEK(date) AS day FROM wiki.pageviews GROUP BY day ORDER BY day;
+-----------+------+
| COUNT(*) | day |
+-----------+------+
| 50986896 | 3 |
| 153912569 | 4 |
+-----------+------+
2 rows in set (3 min 35.88 sec)
Както можете да видите, MySQL се нуждаеше от 3,5 минути, за да извърши пълно сканиране на таблицата.
Сега нека видим колко страници имат месечна стойност, по-голяма от 100:
vagrant.vm :) SELECT count(*), toDayOfWeek(date) AS day FROM wiki.pageviews WHERE monthly > 100 GROUP BY day;
SELECT
count(*),
toDayOfWeek(date) AS day
FROM wiki.pageviews
WHERE monthly > 100
GROUP BY day
┌─count()─┬─day─┐
│ 83574 │ 2 │
│ 246237 │ 3 │
└─────────┴─────┘
2 rows in set. Elapsed: 1.362 sec. Processed 204.90 million rows, 1.84 GB (150.41 million rows/s., 1.35 GB/s.)
В случай на MySQL отново са 3,5 минути:
mysql> SELECT COUNT(*), DAYOFWEEK(date) AS day FROM wiki.pageviews WHERE YEAR(date) = 2018 AND monthly > 100 GROUP BY day;
^@^@+----------+------+
| COUNT(*) | day |
+----------+------+
| 83574 | 3 |
| 246237 | 4 |
+----------+------+
2 rows in set (3 min 3.48 sec)
Друга заявка, само търсене въз основа на някои стойности на низове:
vagrant.vm :) select * from wiki.pageviews where title LIKE 'Main_Page' AND code LIKE 'de.m' AND hour=6;
SELECT *
FROM wiki.pageviews
WHERE (title LIKE 'Main_Page') AND (code LIKE 'de.m') AND (hour = 6)
┌───────date─┬─hour─┬─code─┬─title─────┬─monthly─┬─hourly─┐
│ 2018-05-01 │ 6 │ de.m │ Main_Page │ 8 │ 0 │
└────────────┴──────┴──────┴───────────┴─────────┴────────┘
┌───────date─┬─hour─┬─code─┬─title─────┬─monthly─┬─hourly─┐
│ 2018-05-02 │ 6 │ de.m │ Main_Page │ 17 │ 0 │
└────────────┴──────┴──────┴───────────┴─────────┴────────┘
2 rows in set. Elapsed: 0.015 sec. Processed 66.70 thousand rows, 4.20 MB (4.48 million rows/s., 281.53 MB/s.)
Друга заявка, правеща някои търсения в низа и условие въз основа на колоната „месечна“:
vagrant.vm :) select title from wiki.pageviews where title LIKE 'United%Nations%' AND code LIKE 'en.m' AND monthly>100 group by title;
SELECT title
FROM wiki.pageviews
WHERE (title LIKE 'United%Nations%') AND (code LIKE 'en.m') AND (monthly > 100)
GROUP BY title
┌─title───────────────────────────┐
│ United_Nations │
│ United_Nations_Security_Council │
└─────────────────────────────────┘
2 rows in set. Elapsed: 0.083 sec. Processed 1.61 million rows, 14.62 MB (19.37 million rows/s., 175.34 MB/s.)
В случай на MySQL изглежда по-долу:
mysql> SELECT * FROM wiki.pageviews WHERE title LIKE 'Main_Page' AND code LIKE 'de.m' AND hour=6;
+------------+------+------+-----------+---------+--------+
| date | hour | code | title | monthly | hourly |
+------------+------+------+-----------+---------+--------+
| 2018-05-01 | 6 | de.m | Main_Page | 8 | 0 |
| 2018-05-02 | 6 | de.m | Main_Page | 17 | 0 |
+------------+------+------+-----------+---------+--------+
2 rows in set (2 min 45.83 sec)
И така, почти 3 минути. Втората заявка е същата:
mysql> select title from wiki.pageviews where title LIKE 'United%Nations%' AND code LIKE 'en.m' AND monthly>100 group by title;
+---------------------------------+
| title |
+---------------------------------+
| United_Nations |
| United_Nations_Security_Council |
+---------------------------------+
2 rows in set (2 min 40.91 sec)
Разбира се, може да се твърди, че можете да добавите повече индекси, за да подобрите производителността на заявката, но факт е, че добавянето на индекси ще изисква допълнителни данни, които да се съхраняват на диска. Индексите изискват дисково пространство и представляват оперативни предизвикателства - ако говорим за набори от OLAP данни в реалния свят, говорим за терабайти данни. Отнема много време и изисква добре дефиниран и тестван процес за изпълнение на промени в схемата в такава среда. Ето защо специализираните колонни хранилища за данни могат да бъдат много удобни и да помогнат изключително много за получаване на по-добра представа за всички аналитични данни, които всеки съхранява.