sql >> Databasteknik >  >> RDS >> Mysql

Hybrid OLTP/Analytics Databas Workloads:Replikera MySQL-data till ClickHouse

Hur kör man Analytics på MySQL?

MySQL är en fantastisk databas för OLTP-arbetsbelastningar (Online Transaction Processing). För vissa företag brukade det vara mer än tillräckligt länge. Tiderna har förändrats och affärskraven tillsammans med dem. När företag strävar efter att bli mer datadrivna, lagras mer och mer data för vidare analys; kundbeteende, prestationsmönster, nätverkstrafik, loggar, etc. Oavsett vilken bransch du är i är det mycket troligt att det finns data som du vill behålla och analysera för att bättre förstå vad som pågår och hur du kan förbättra din verksamhet. Tyvärr är MySQL inte det bästa alternativet för att lagra och fråga efter den stora mängden data. Visst, det kan göra det och det har verktyg som hjälper till att ta emot stora mängder data (t.ex. InnoDB-komprimering), men att använda en dedikerad lösning för Online Analytics Processing (OLAP) kommer sannolikt att avsevärt förbättra din förmåga att lagra och fråga en stor kvantitet av data.

Ett sätt att lösa detta problem är att använda en dedikerad databas för att köra analyser. Vanligtvis vill du använda en kolumnär datalagring för sådana uppgifter - de är mer lämpade för att hantera stora mängder data:data som lagras i kolumner är vanligtvis lättare att komprimera, det är också lättare att komma åt per kolumn - vanligtvis ber du om några data lagras i ett par kolumner - en förmåga att hämta bara dessa kolumner istället för att läsa alla rader och filtrera bort onödig data gör att data nås snabbare.

Hur replikerar man data från MySQL till ClickHouse?

Ett exempel på den kolumnära databutiken som är lämplig för analys är ClickHouse, en kolumnbutik med öppen källkod. En utmaning är att se till att data i ClickHouse är synkroniserade med data i MySQL. Visst, det är alltid möjligt att sätta upp en datapipeline av något slag och utföra automatisk batchladdning i ClickHouse. Men så länge du kan leva med vissa begränsningar finns det ett bättre sätt att ställa in nästan realtidsreplikering från MySQL till ClickHouse. I det här blogginlägget ska vi ta en titt på hur det kan göras.

ClickHouse Installation

Först och främst måste vi installera ClickHouse. Vi använder snabbstarten från ClickHouse-webbplatsen.

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

När detta är gjort måste vi hitta ett sätt att överföra data från MySQL till ClickHouse. En av de möjliga lösningarna är att använda Altinitys clickhouse-mysql-data-läsare. Först och främst måste vi installera pip3 (python3-pip i Ubuntu) eftersom Python i version minst 3.4 krävs. Sedan kan vi använda pip3 för att installera några av de nödvändiga Python-modulerna:

pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver

När detta är gjort måste vi klona förvaret. För Centos 7 är RPM:s också tillgängliga, det är också möjligt att installera det med pip3 (clickhouse-mysql-paketet) men vi upptäckte att versionen som är tillgänglig via pip inte innehåller de senaste uppdateringarna och vi vill använda master branch från git repository:

git clone https://github.com/Altinity/clickhouse-mysql-data-reader

Sedan kan vi installera det med pip:

pip3 install -e /path/to/clickhouse-mysql-data-reader/

Nästa steg blir att skapa MySQL-användare som krävs av clickhouse-mysql-data-reader för att komma åt MySQL-data:

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)

Du bör också granska din MySQL-konfiguration för att säkerställa att du har binära loggar aktiverade, max_binlog_size är inställd på 768M, binlogs är i "rad"-format och att verktyget kan ansluta till MySQL. Nedan är ett utdrag ur dokumentationen:

[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

Importera data

När allt är klart kan du importera data till ClickHouse. Helst skulle du köra importen på en värd med tabeller låsta så att ingen förändring kommer att ske under processen. Du kan använda en slav som datakälla. Kommandot att köra kommer att vara:

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

Den kommer att ansluta till MySQL på värd 10.0.0.142 med givna referenser, den kommer att kopiera tabellen "sidvisningar" i schemat "wiki" till ett ClickHouse som körs på den lokala värden (127.0.0.1). Tabell skapas automatiskt och data kommer att migreras.

För syftet med den här bloggen importerade vi ungefär 50 miljoner rader från datauppsättningen "sidvisningar" som gjorts tillgänglig av Wikimedia Foundation. Tabellschemat i MySQL är:

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)

Verktyget översatte detta till följande ClickHouse-schema:

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.

När importen är klar kan vi jämföra innehållet i MySQL:

mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)

och i 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.)

Även i en så liten tabell kan du tydligt se att MySQL krävde mer tid för att skanna igenom den än ClickHouse.

När du startar processen för att titta på den binära loggen för händelser, skulle du helst skicka informationen om den binära loggfilen och positionen varifrån verktyget ska börja lyssna. Du kan enkelt kontrollera det på slaven efter att den första importen är klar.

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

Om du inte klarar det börjar den bara lyssna efter allt som kommer in:

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

Låt oss ladda lite mer data och se hur det kommer att fungera för oss. Vi kan se att allt verkar ok genom att titta på loggarna för 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

Det vi måste tänka på är verktygets begränsningar. Den största är att den endast stöder INSERT. Det finns inget stöd för DELETE eller UPDATE. Det finns heller inget stöd för DDL, därför kommer inkompatibla schemaändringar som körs på MySQL att bryta MySQL till ClickHouse-replikeringen.

Värt att notera är också det faktum att utvecklarna av skriptet rekommenderar att använda pypy för att förbättra verktygets prestanda. Låt oss gå igenom några steg som krävs för att konfigurera detta.

Först måste du ladda ner och dekomprimera 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

Därefter måste vi installera pip och alla krav för clickhouse-mysql-data-reader - exakt samma saker som vi tog upp tidigare, samtidigt som vi beskrev vanliga inställningar:

./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient

Sista steget kommer att vara att installera clickhouse-mysql-data-reader från github-förvaret (vi antar att det redan har klonats):

./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/

Det är allt. Från och med nu bör du köra alla kommandon med den miljö som skapats för pypy:

./bin/pypy ./bin/clickhouse-mysql

Tester

Data har laddats, vi kan verifiera att allt gick smidigt genom att jämföra storleken på tabellen:

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.)

Allt ser korrekt ut. Låt oss köra några frågor för att se hur ClickHouse beter sig. Vänligen kom ihåg att all denna installation är långt ifrån produktionsklass. Vi använde två små virtuella datorer, 4 GB minne, en vCPU vardera. Därför räckte det även om datasetet inte var stort för att se skillnaden. På grund av litet urval är det ganska svårt att göra "riktiga" analyser men vi kan fortfarande skicka några slumpmässiga frågor.

Låt oss kontrollera vilka dagar i veckan vi har data från och hur många sidor som har visats per dag i vår exempeldata:

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.)

När det gäller MySQL ser denna fråga ut som nedan:

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)

Som du kan se behövde MySQL 3,5 minuter för att göra en genomsökning av hela tabellen.

Nu ska vi se hur många sidor som har ett månatligt värde över 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.)

I fall av MySQL är det igen 3,5 minuter:

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)

En annan fråga, bara en uppslagning baserad på några strängvärden:

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.)

En annan fråga, gör några uppslagningar i strängen och ett villkor baserat på "månadsvis" kolumn:

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.)

I fall av MySQL ser det ut som nedan:

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)

Alltså nästan 3 minuter. Den andra frågan är densamma:

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)

Naturligtvis kan man hävda att du kan lägga till fler index för att förbättra frågeprestanda, men faktum är att lägga till index kräver att ytterligare data lagras på disken. Index kräver diskutrymme och de utgör också operativa utmaningar - om vi pratar om verkliga OLAP-datauppsättningar, talar vi om terabyte med data. Det tar mycket tid och kräver en väldefinierad och testad process för att köra schemaändringar i en sådan miljö. Det är därför dedikerade kolumnära databutiker kan vara mycket praktiska och hjälpa enormt att få bättre insikt i all analysdata som alla lagrar.


  1. Skillnad mellan text och varchar (tecken varierar)

  2. Motsvarar strftid i Postgres

  3. SQLite CASE

  4. Partitioneringsförbättringar i PostgreSQL 11