Możliwość kształtowania ruchu trafiającego do bazy danych jest jedną z najważniejszych. W minionych dniach nie miałeś nad tym zbyt dużej kontroli - aplikacje wysyłały ruch do bazy danych i tyle. HAProxy, który był powszechnie używany, również nie ma możliwości precyzyjnej kontroli ruchu. Wraz z wprowadzeniem serwerów proxy obsługujących SQL, takich jak ProxySQL, administratorzy baz danych zyskali więcej możliwości. Przyjrzyjmy się możliwościom obsługi połączeń i ograniczania przepustowości w ProxySQL.
Obsługa połączeń w ProxySQL
Jak być może wiesz, sposób działania ProxySQL opiera się na regułach zapytań. Jest to lista reguł, według których testowane jest każde zapytanie i które dokładnie określają, w jaki sposób ProxySQL obsłuży zapytanie. Aplikacja od początku łączy się z ProxySQL. Uwierzytelni się w ProxySQL (dlatego ProxySQL musi przechowywać wszystkich użytkowników i skróty haseł), a następnie ProxySQL przeprowadzi go przez reguły zapytań, aby określić, do której grupy hostów zapytanie powinno zostać wysłane.
ProxySQL otwiera pulę połączeń z serwerami zaplecza. Nie jest to mapowanie 1-do-1, domyślnie próbuje ponownie wykorzystać jedno połączenie backendowe dla jak największej liczby połączeń frontendowych. Nazywa się to multipleksowaniem połączeń. Szczegóły zależą od dokładnego ruchu, który ma obsłużyć. Każda otwarta transakcja musi być obsługiwana w ramach tego samego połączenia. Jeśli jest zdefiniowana jakaś zmienna lokalna, to połączenie nie może być ponownie użyte. Możliwość ponownego wykorzystania pojedynczego połączenia zaplecza przez wiele połączeń frontonu znacznie zmniejsza obciążenie bazy danych zaplecza.
Po nawiązaniu połączenia z ProxySQL, jak wspomnieliśmy wcześniej, zostanie ono przetworzone zgodnie z regułami zapytania. Tutaj może mieć miejsce kształtowanie ruchu. Rzućmy okiem na opcje
Ograniczanie połączeń w ProxySQL
Najpierw odrzućmy wszystkie SELECTy. Naszą „aplikację”, Sysbench, uruchamiamy w następujący sposób:
[email protected]:~# sysbench /root/sysbench/src/lua/oltp_read_only.lua --threads=4 --events=200 --time=0 --mysql-host=10.0.0.101 --mysql-user=sbtest --mysql-password=sbtest --mysql-port=6033 --tables=32 --report-interval=1 --skip-trx=on --table-size=100000 --db-ps-mode=disable --rate=10 run
sysbench 1.1.0-bbee5d5 (using bundled LuaJIT 2.1.0-beta3)
Running the test with following options:
Number of threads: 4
Target transaction rate: 10/sec
Report intermediate results every 1 second(s)
Initializing random number generator from current time
Initializing worker threads...
Threads started!
[ 1s ] thds: 4 tps: 5.97 qps: 103.49 (r/w/o: 103.49/0.00/0.00) lat (ms,95%): 244.38 err/s: 0.00 reconn/s: 0.00
[ 1s ] queue length: 0, concurrency: 4
[ 2s ] thds: 4 tps: 13.02 qps: 181.32 (r/w/o: 181.32/0.00/0.00) lat (ms,95%): 580.02 err/s: 0.00 reconn/s: 0.00
[ 2s ] queue length: 5, concurrency: 4
[ 3s ] thds: 4 tps: 14.99 qps: 228.81 (r/w/o: 228.81/0.00/0.00) lat (ms,95%): 669.89 err/s: 0.00 reconn/s: 0.00
[ 3s ] queue length: 1, concurrency: 4
[ 4s ] thds: 4 tps: 16.99 qps: 232.88 (r/w/o: 232.88/0.00/0.00) lat (ms,95%): 350.33 err/s: 0.00 reconn/s: 0.00
[ 4s ] queue length: 0, concurrency: 3
[ 5s ] thds: 4 tps: 8.99 qps: 99.91 (r/w/o: 99.91/0.00/0.00) lat (ms,95%): 369.77 err/s: 0.00 reconn/s: 0.00
[ 5s ] queue length: 0, concurrency: 1
[ 6s ] thds: 4 tps: 3.99 qps: 55.81 (r/w/o: 55.81/0.00/0.00) lat (ms,95%): 147.61 err/s: 0.00 reconn/s: 0.00
[ 6s ] queue length: 0, concurrency: 1
[ 7s ] thds: 4 tps: 11.06 qps: 162.89 (r/w/o: 162.89/0.00/0.00) lat (ms,95%): 173.58 err/s: 0.00 reconn/s: 0.00
[ 7s ] queue length: 0, concurrency: 2
[ 8s ] thds: 4 tps: 7.99 qps: 112.88 (r/w/o: 112.88/0.00/0.00) lat (ms,95%): 200.47 err/s: 0.00 reconn/s: 0.00
[ 8s ] queue length: 0, concurrency: 2
[ 9s ] thds: 4 tps: 9.01 qps: 110.09 (r/w/o: 110.09/0.00/0.00) lat (ms,95%): 71.83 err/s: 0.00 reconn/s: 0.00
[ 9s ] queue length: 0, concurrency: 0
[ 10s ] thds: 4 tps: 9.99 qps: 143.87 (r/w/o: 143.87/0.00/0.00) lat (ms,95%): 153.02 err/s: 0.00 reconn/s: 0.00
[ 10s ] queue length: 0, concurrency: 1
[ 11s ] thds: 4 tps: 12.02 qps: 177.28 (r/w/o: 177.28/0.00/0.00) lat (ms,95%): 170.48 err/s: 0.00 reconn/s: 0.00
[ 11s ] queue length: 0, concurrency: 1
[ 12s ] thds: 4 tps: 5.00 qps: 70.95 (r/w/o: 70.95/0.00/0.00) lat (ms,95%): 231.53 err/s: 0.00 reconn/s: 0.00
[ 12s ] queue length: 0, concurrency: 2
[ 13s ] thds: 4 tps: 10.00 qps: 137.01 (r/w/o: 137.01/0.00/0.00) lat (ms,95%): 223.34 err/s: 0.00 reconn/s: 0.00
[ 13s ] queue length: 0, concurrency: 1
[ 14s ] thds: 4 tps: 11.01 qps: 143.14 (r/w/o: 143.14/0.00/0.00) lat (ms,95%): 130.13 err/s: 0.00 reconn/s: 0.00
[ 14s ] queue length: 0, concurrency: 0
[ 15s ] thds: 4 tps: 5.00 qps: 100.99 (r/w/o: 100.99/0.00/0.00) lat (ms,95%): 297.92 err/s: 0.00 reconn/s: 0.00
[ 15s ] queue length: 0, concurrency: 4
[ 16s ] thds: 4 tps: 10.98 qps: 122.82 (r/w/o: 122.82/0.00/0.00) lat (ms,95%): 344.08 err/s: 0.00 reconn/s: 0.00
[ 16s ] queue length: 0, concurrency: 0
[ 17s ] thds: 4 tps: 3.00 qps: 59.01 (r/w/o: 59.01/0.00/0.00) lat (ms,95%): 287.38 err/s: 0.00 reconn/s: 0.00
[ 17s ] queue length: 0, concurrency: 2
[ 18s ] thds: 4 tps: 13.01 qps: 165.14 (r/w/o: 165.14/0.00/0.00) lat (ms,95%): 173.58 err/s: 0.00 reconn/s: 0.00
[ 18s ] queue length: 0, concurrency: 0
[ 19s ] thds: 4 tps: 6.99 qps: 98.79 (r/w/o: 98.79/0.00/0.00) lat (ms,95%): 253.35 err/s: 0.00 reconn/s: 0.00
[ 19s ] queue length: 0, concurrency: 1
[ 20s ] thds: 4 tps: 9.98 qps: 164.60 (r/w/o: 164.60/0.00/0.00) lat (ms,95%): 590.56 err/s: 0.00 reconn/s: 0.00
[ 20s ] queue length: 0, concurrency: 3
SQL statistics:
queries performed:
read: 2800
write: 0
other: 0
total: 2800
transactions: 200 (9.64 per sec.)
queries: 2800 (134.89 per sec.)
ignored errors: 0 (0.00 per sec.)
reconnects: 0 (0.00 per sec.)
Throughput:
events/s (eps): 9.6352
time elapsed: 20.7573s
total number of events: 200
Latency (ms):
min: 44.36
avg: 202.66
max: 726.59
95th percentile: 590.56
sum: 40531.73
Threads fairness:
events (avg/stddev): 50.0000/0.71
execution time (avg/stddev): 10.1329/0.05
Jest to ruch w pełni tylko do odczytu, powinien wynosić średnio 10 transakcji (140 zapytań) na sekundę. Ponieważ są to tylko SELECTy, możemy łatwo zmodyfikować jedną z istniejących reguł zapytań i zablokować ruch:
Spowoduje to następujący błąd po stronie aplikacji:
[email protected]:~# sysbench /root/sysbench/src/lua/oltp_read_only.lua --threads=4 --events=200 --time=0 --mysql-host=10.0.0.101 --mysql-user=sbtest --mysql-password=sbtest --mysql-port=6033 --tables=32 --report-interval=1 --skip-trx=on --table-size=100000 --db-ps-mode=disable --rate=10 run
sysbench 1.1.0-bbee5d5 (using bundled LuaJIT 2.1.0-beta3)
Running the test with following options:
Number of threads: 4
Target transaction rate: 10/sec
Report intermediate results every 1 second(s)
Initializing random number generator from current time
Initializing worker threads...
Threads started!
FATAL: mysql_drv_query() returned error 1148 (SELECT queries are not allowed!!!) for query 'SELECT c FROM sbtest25 WHERE id=83384'
FATAL: `thread_run' function failed: /usr/local/share/sysbench/oltp_common.lua:426: SQL error, errno = 1148, state = '42000': SELECT queries are not allowed!!!
Teraz jest to oczywiście trudne. Możemy być bardziej uprzejmi i po prostu zwiększyć opóźnienie dla zapytań SELECT.
To oczywiście wpływa na wydajność zapytań, ponieważ dodaje się 10 milisekund do każdego wykonywanego SELECT.
SQL statistics:
queries performed:
read: 2800
write: 0
other: 0
total: 2800
transactions: 200 (5.60 per sec.)
queries: 2800 (78.44 per sec.)
ignored errors: 0 (0.00 per sec.)
reconnects: 0 (0.00 per sec.)
Throughput:
events/s (eps): 5.6030
time elapsed: 35.6952s
total number of events: 200
Latency (ms):
min: 622.04
avg: 7957.01
max: 18808.60
95th percentile: 15934.78
sum: 1591401.12
Threads fairness:
events (avg/stddev): 50.0000/36.01
execution time (avg/stddev): 397.8503/271.50
Konfigurujemy opóźnienia dla każdego zapytania SELECT, co niekoniecznie ma jakikolwiek sens poza pokazaniem, że możesz to zrobić. Zazwyczaj chciałbyś użyć opóźnienia w przypadku niektórych obraźliwych zapytań. Załóżmy, że mamy zapytanie, które jest bardzo ciężkie i znacznie obciąża procesor bazy danych. Co gorsza, został on wprowadzony przez niedawną zmianę kodu i pochodzi od wszystkich hostów aplikacji. Jasne, możesz poczekać, aż programiści cofną zmianę lub wprowadzą poprawkę, ale dzięki ProxySQL możesz przejąć kontrolę w swoje ręce i po prostu zablokować zapytanie lub zmniejszyć jego wpływ nawet dość znacząco.
Załóżmy, że nasza baza danych dobrze się porusza, gdy zaczynają dzwonić dzwonki alarmowe.
Szybkie spojrzenie na metryki mówi nam, że liczba zapytań wykonanych przez ProxySQL spada, a wykorzystanie procesora rośnie. Możemy spojrzeć na Najpopularniejsze zapytania w ProxySQL, aby zobaczyć, czy zauważyliśmy coś niezwykłego.
To rzeczywiście niezwykłe - nowe zapytanie, które nie jest częścią regularny mix zapytań, który zaobserwowaliśmy w naszym systemie. Możemy użyć opcji, aby utworzyć regułę zapytania.
Dodamy 50-sekundowe opóźnienie do zapytania, ustawiając Opóźnienie na 50000 ms.
Możemy potwierdzić, że reguła zapytania jest używana i zapytania do niej trafiają .
Po krótkiej chwili możemy również zauważyć, że spada obciążenie i liczba wykonanych zapytań ponownie znajduje się w oczekiwanym zakresie. Oczywiście zamiast dodawać opóźnienie do zapytania możemy je po prostu zablokować. Byłoby to dla nas jeszcze łatwiejsze, ale całkowite zablokowanie zapytania może mieć znaczący wpływ na aplikację.
Mamy nadzieję, że ten krótki wpis na blogu da ci wgląd w to, jak ProxySQL może pomóc w kształtowaniu ruchu i zmniejszeniu spadku wydajności powodowanego przez niekontrolowane zapytania.