AWS: RDS Proxy – обзор, запуск, тестирование

Thank you for reading this post, don't forget to subscribe!

AWS RDS Proxy – сер­вис от AWS, поз­во­ля­ю­щий раз­гру­зить сер­ве­ра баз дан­ных AWS RDS, в первую оче­редь за счёт пере­ис­поль­зо­ва­ния суще­ству­ю­щих под­клю­че­ний вме­сто откры­тия новых для выпол­не­ния запро­сов от клиентов.

Кро­ме того, RDS Proxy улуч­ша­ет failover при пере­клю­че­нии упав­ше­го инстан­са на резерв­ный, напри­мер – когда AWS RDS Aurora выпол­ня­ет пере­клю­че­ние read-replica на роль master, если master ушёл в ребут.

Как работает RDS Proxy?

См. RDS Proxy concepts and terminology

В целом, из все­го, что нагуг­лил за вре­мя зна­ком­ства с сер­ви­сом – это ана­лог Proxy SQL для MySQL и pgproxy для PostgreSQL.

Итак, у нас есть сер­вер баз дан­ных, в этом при­ме­ре гово­рим толь­ко о MySQL. На каж­дое новое под­клю­че­ние для выпол­не­ния запро­са сна­ча­ла уста­нав­ли­ва­ет­ся TCP-соеди­не­ние меж­ду хоста­ми кли­ен­та и сер­ве­ра баз дан­ных, затем выпол­ня­ет­ся handshake, и толь­ко потом начи­на­ет­ся пере­да­ча и непо­сред­ствен­но выпол­не­ние SQL-запро­са. См. Connection Phase.

Все эти фазы отни­ма­ют и вре­мя CPU, и RAM для под­дер­жа­ния соеди­не­ний. Кро­ме того, Proxy избав­ля­ет из необ­хо­ди­мо­сти про­ду­мы­вать логи­ку в самом при­ло­же­нии для пере­со­зда­ния под­клю­че­ния в слу­чае про­блем с ним.

Для демон­стра­ции того, насколь­ко реаль­но это вли­я­ет на ресур­сы сер­ве­ра – давай­те запу­стим mysqlslap, кото­рый будет откры­вать 30 одно­вре­мен­ных под­клю­че­ний, выпол­нять самый про­стой запрос типа select version(), отклю­чать­ся, и повто­рять всё зано­во, и так 1000 раз:

mysqlslap -h proxy-test-rds-aurora.dev.bttrm.local -u rds-proxy-user -pxie0AhN5bee9 --detach=1 --concurrency=30 --iterations=1000 --query 'select version();'
И гра­фи­ки CPU и памяти:

RDS Proxy и “Too many connections

Самое инте­рес­ное слу­ча­ет­ся, когда коли­че­ство под­клю­че­ний к RDS Proxy пре­вы­ша­ет его “capacity” (поз­же уви­дим, где оно настра­и­ва­ет­ся): в отли­чии от под­клю­че­ния к RDS напря­мую, кото­рый в таком слу­чае нач­нёт отбра­сы­вать новые под­клю­че­ния с ошиб­кой “Too many connections” – RDS Proxy нач­нёт ста­вить под­клю­че­ния и запро­сы в оче­редь. Это при­ве­дёт к дОльше­му выпол­не­нию запро­сов, но изба­вит имен­но от оши­бок под­клю­че­ния, что очень при­ят­но для при­ло­же­ния, и для кли­ен­тов, кото­рые этим при­ло­же­ни­ем пользуются.

Инстан­сы RDS Proxy рас­по­ла­га­ют­ся в несколь­ких Avalability Zones, что обес­пе­чи­ва­ет их отка­зо­у­той­чи­вость, и исполь­зу­ют свои CPU и RAM, не затра­ги­вая таким обра­зом ресур­сы само­го сер­ве­ра баз данных.

Connection pooling и multiplexing

Вме­сто того, что бы под­клю­чать при­ло­же­ние напря­мую к сер­ве­ру баз дан­ных – мы настра­и­ва­ем RDS Proxy и его Target Group (бекенд), к кото­ро­му RDS Proxy откры­ва­ет пул под­клю­че­ний (connection pool) через свои соб­ствен­ные ендпоинты.

Кли­ен­ты под­клю­ча­ют­ся к RDS Proxy, и их запро­сы отправ­ля­ют­ся через пул кон­нек­тов само­го Proxy к сер­ве­ру баз дан­ных. При этом часть запро­сов могут выпол­нять­ся через уже уста­нов­лен­ное соеди­не­ние к бекен­ду вме­сто откры­тия ново­го – multiplexing.

Failover

Failover – одна из мно­гих при­ят­ных вещей в AWS RDS Aurora, когда при выхо­де из строя или пере­за­груз­ке Aurora-кла­стер сам пере­клю­ча­ет енд­по­ин­ты, направ­ляя таким обра­зом новые под­клю­че­ния на новый инстанс, напри­мер – когда Мастер ста­но­вит­ся Слейвом.

В обыч­ном слу­чае мы зави­сим от DNS и его вре­ме­ни обнов­ле­ния, что в луч­шем слу­чае зай­мёт 10-20 секунд, за вре­мя кото­рых кли­ен­ты могут пытать­ся под­клю­чать­ся на инстанс сер­ве­ра баз дан­ных, кото­рый уже недо­сту­пен (и мы часто стал­ки­ва­ем­ся со слу­ча­я­ми, когда при­ло­же­ние пыта­ет­ся выпол­нить какой-то UPDATE на инстан­се, кото­рый уже стал слейвом).

RDS Proxy сам мони­то­рит состо­я­ние сер­ве­ров в сво­ей тар­гет-груп­пе, и при необ­хо­ди­мо­сти быст­ро пере­клю­ча­ет пул кон­нек­тов на новый инстанс сер­ве­ра баз данных.

Надо будет про­те­сти­ро­вать – очень любо­пыт­ная вещь.

См. Improving application availability with Amazon RDS ProxyFailover и A First Look at Amazon RDS Proxy.

Где пригодится RDS Proxy?

См. Planning where to use RDS Proxy.

Да мно­го где. К при­ме­ру, есть сер­ве­ра баз дан­ных Dev-окру­же­ния, где срав­ни­тель­но “тон­кие” кли­ен­ты, но где QA-коман­да любит запус­кать раз­лич­ные нагру­зоч­ные тесты. В таком слу­чае перед нагру­зоч­ным нам при­хо­дит­ся уве­ли­чи­вать типы сер­ве­ров, ина­че тести­ров­щи­ки стал­ки­ва­ют­ся с ошиб­ка­ми “Too many connections“.

Так­же, RDS Proxy при­го­дит­ся для сер­ве­ров, кото­рые обслу­жи­ва­ют мно­же­ство корот­ко­жи­ву­щих запро­сов типа mysql_ping или для AWS Lambda functions, кото­рые обыч­но выпол­ня­ют­ся срав­ни­тель­но часто и быстро.

Полез­но при исполь­зо­ва­нии язы­ков, кото­рые не уме­ют в пулы кон­нек­тов, такие как PHP и Ruby.

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

Ограничения RDS Proxy

Всё зву­чит очень вкус­но, даже слиш­ком, поэто­му крат­ко рас­смот­рим огра­ни­че­ния, с кото­ры­ми можем столк­нуть­ся при исполь­зо­ва­нии AWS RDS Proxy. Так­же, см. ссыл­ки в кон­це поста – там пара любо­пыт­ных исто­рий о про­бле­мах, кото­рые могут воз­ник­нуть при исполь­зо­ва­нии SQL Proxy.

См. Quotas and limitations for RDS Proxy.

  • RDS Proxy на дан­ный момент досту­пен не во всех регионах
  • RDS Proxy досту­пен толь­ко для MySQL и PostgreSQL
  • RDS Proxy недо­сту­пен для кла­сте­ров Aurora Serverless
  • RDS Proxy дол­жен быть в той же VPC, где и сервер(а) баз дан­ных, и не может быть досту­пен из мира (хотя сер­ве­ра могут быть publicity accessible)

Стоимость RDS Proxy

Тут всё очень про­зрач­но: опла­чи­ва­ем за коли­че­ство доступ­ных vCPU ($0.015 в us-east-1) на сервере/ах баз дан­ных, кото­рые вхо­дят в Target Group наше­го Proxy.

К при­ме­ру, у нас инстанс Авро­ры db.t3.medium с дву­мя vCPU. Сле­до­ва­тель­но за его Прок­си мы будем платить:

echo 0.015*2*24*30 | bc
21.600

См. Amazon RDS Proxy pricing.

Чем тестировать?

Для про­вер­ки того, когда сер­вер нач­нёт отбра­сы­вать под­клю­че­ния с “Too many connections” и для того, что бы отсле­жи­вать вре­мя выпол­не­ния запро­сов – набро­сал скрипт на Golang.

гофер из меня так себе – но скрипт рабочий:

 

Созда­ёт под­клю­че­ние, потом в цик­ле выпол­ня­ет запро­сы, в про­цес­се выпол­не­ния выво­дит коли­че­ство актив­ных тре­дов в MySQL (читай – под­клю­че­ний), плюс вре­мя выпол­не­ния каж­до­го, и теку­щую итерацию.

Даль­ше уже для нагру­зоч­но­го тести­ро­ва­ния – исполь­зу­ем mysqlslap, а ещё слу­чай­но уви­дел ути­ли­ту mysqltest, но не пользовался.

Подготовка – сеть, EC2, RDS Aurora

Настройка сети

Для тести­ро­ва­ния – созда­дим отдель­ную VPC, под­се­ти, неболь­шой кла­стер AWS RDS Aurora и тесто­вый ЕС2, с кото­ро­го будем подключаться.

Созда­ём VPC:

Созда­ём три под­се­ти – одна пуб­лич­ная, в eu-west-2a, и две при­ват­ных – в eu-west-2b и eu-west-2c:

Созда­ём Internet Gateway для пуб­лич­ной подсети:

Под­клю­ча­ем к VPC:

Нахо­дим Route Table пуб­лич­ной посе­ти, добав­ля­ем марш­рут в 0.0.0.0/0 через создан­ный выше IGW:

Тестовый EC2

Запус­ка­ем тесто­вый EC2 в пуб­лич­ной под­се­ти, под­клю­ча­ем ему пуб­лич­ный IP:

Про­ве­ря­ем подключение:

ssh -i rds-proxy-test-ec2-eu-west-2-rsa.pem ubuntu@13.40.69.72
root@ip-10-0-0-97:/home/ubuntu#
Уста­нав­ли­ва­ем mysql-tools и Golang:
root@ip-10-0-0-97:/home/ubuntu# apt update && apt -y install mysql-client golang

Тестовый RDS Aurora

Пере­хо­дим в RDS > Subnet Groups, созда­ём новую груп­пу, в кото­рую вклю­ча­ем наши при­ват­ные под­се­ти из Avvailability Zones eu-west-2b и eu-west-2c:

Созда­ём кла­стер Aurora:

Для гене­ра­ции паро­лей я обыч­но исполь­зую кон­соль­ную ути­лит­ку pwgen:

pwgen 12 -1
Lohn1aiceiPh
Ука­зы­ва­ем имя кла­сте­ра, руто­во­го поль­зо­ва­те­ля и его пароль:
Исполь­зу­ем наи­мень­ший доступ­ный тип инстан­са, доба­вим Multi-AZ:
Настра­и­ва­ем сеть – выби­ра­ем VPC, нашу Subnet Group, пуб­лич­ный доступ не вклю­ча­ем, созда­ём новую Security Group rds-proxy-test-aurora-cluster-sg:
Запус­ка­ем созда­ние кла­сте­ра, пока он созда­ёт­ся – нахо­дим создан­ную Security Group:
И добав­ля­ем в неё Security Group наше­го инстан­са EC2, что бы полу­чить доступ для тестов:

Вклад­ку с Security Group для Aurora мож­но оста­вить откры­той – она нам ещё при­го­дит­ся во вре­мя настрой­ки RDS Proxy.

Про­ве­ря­ем под­клю­че­ние с EC2:

root@ip-10-0-0-97:/home/ubuntu# mysqladmin -u admin -p -h rds-proxy-test-aurora-cluster.cluster-ci9hdkrgdpwy.eu-west-2.rds.amazonaws.com status
Enter password:
Uptime: 175  Threads: 5  Questions: 1176  Slow queries: 0  Opens: 140  Flush tables: 1  Open tables: 121  Queries per second avg: 6.720
Созда­ём тесто­вую базу, пользователя:
mysql> create database `rds-proxy-db`;
Query OK, 1 row affected (0.01 sec)
mysql> create user 'rds-proxy-user'@'%' identified by 'xie0AhN5bee9';
Query OK, 0 rows affected (0.01 sec)
mysql> grant all privileges on `rds-proxy-db`.* to 'rds-proxy-user'@'%';
Query OK, 0 rows affected (0.00 sec)
Про­ве­ря­ем доступ ещё раз:

Тестирование количества подключений #1: на RDS Aurora

Сна­ча­ла про­ве­рим на каком коли­че­стве актив­ных под­клю­че­ний мы нач­нём полу­чать “Too many connections” от само­го кла­сте­ра Aurora, без RDS PRoxy.

Про­ве­ря­ем макс клиентов:

Гото­вим скрипт (не умею я в новую систе­му моду­лей, делаю по-старинке):

ubuntu@ip-10-0-0-97:~$ export GO111MODULE=off
ubuntu@ip-10-0-0-97:~$ go get github.com/go-sql-driver/mysql
Зада­ём переменные:
export RDS_HOST=proxy-test-rds-aurora.dev.bttrm.local
export RDS_USER=rds-proxy-user
export RDS_PASS=xie0AhN5bee9
export RDS_DB=rds-proxy-db
export RDS_ITERATIONS=100
Домен proxy-test-rds-aurora.dev.bttrm.local направ­лен на мастер-инстанс Aurora. Для Proxy потом доба­вим домен proxy-test-rds-proxy.dev.bttrm.local
Запус­ка­ем скрипт:

Отлич­но – на 45+ кон­нек­тах мы нача­ли отва­ли­вать­ся, как и ожидалось.

Создание RDS Proxy

Ну и нако­нец-то при­сту­па­ем к RDS Proxy.

AWS Secret Manager

Сна­ча­ла созда­ём сек­рет – пере­хо­дим в Secrets Manager, кли­ка­ем Store a new secret, выби­ра­ем тип Credentials for Amazon RDS database.

Ука­зы­ва­ем логин и пароль, вни­зу выби­ра­ем кла­стер Авро­ры, к кото­ро­му сек­рет применяется:

Зада­ём имя сек­ре­та, сохраняем:

RDS Proxy Security Group

С Security Group есть два вари­ан­та: исполь­зо­вать Security Group, кото­рую дела­ли для нашей Авро­ры – тогда в ней надо доба­вить пра­ви­ло MySQL и в source ука­зать ID этой же груп­пы, т.е. раз­ре­шить ей “ходить саму на себя”.

Дру­гой вари­ант – сде­лать новую Security Group для инстан­са RDS Proxy, а в Security Group кла­сте­ра Авро­ры раз­ре­шить доступ с Security Group наше­го Proxy.

Луч­ше, думаю, отдель­ную – мало ли, как в буду­щем будем менять пра­ви­ла в груп­пе сер­ве­ра баз дан­ных, а мы тут ста­ра­ем­ся всё-таки что око­ло-про­дак­ше­нов­ское постро­ить, поэто­му будем делать сра­зу пра­виль­но, где можно.

Копи­ру­ем группу:

Сохра­ним как rds-proxy-test-proxy-sg:

Копи­ру­ем ID новой группы:

Редак­ти­ру­ем Security Group Авро­ры, добав­ля­ем в ней доcтуп из новой Security Group, кото­рая для Proxy:

И толь­ко теперь пере­хо­дим в RDS > Proxies, и начи­на­ем созда­вать сам RDS Proxy.

Создание RDS Proxy

Кли­ка­ем Create proxy, зада­ём имя, тип MySQL, TLS пока не трогаем:

Настра­и­ва­ем Target Group – выби­ра­ем кла­стер Aurora, в пуле кон­нек­тов ука­жем 100% – это будет 45 кон­нек­тов пула само­го RDS Proxy, 100% от капа­си­ти инстан­са RDS Aurora:

Отме­ча­ем Include reader – RDS Proxy опре­де­лит, что у нашей Aurora есть slave-инстанс со сво­им read-only енд­по­ин­том, и создаст такой же енд­по­инт у себя. Тогда при­ло­же­ния, кото­рые исполь­зу­ют мастер для всех write/update опе­ра­ций будут по-преж­не­му ходить через один енд­по­инт, а все запро­сы типа SELECT будут как и рань­ше ходить на слейв-инстансы.

В Additional target group configuration мож­но настро­ить пин­нинг – пока про­пу­стим, см. Avoiding pinning.

Настра­и­ва­ем Connectivity – выби­ра­ем сек­рет из Secrets Manager, остав­ля­ем Create IAM Role, IAM-аутен­ти­фи­ка­цию тут не исполь­зу­ем, выби­ра­ем теже при­ват­ные под­се­ти, кото­рые исполь­зо­ва­ли в Subnet Group при созда­нии Aurora-кластера.

В Additional connectivity configuration выби­ра­ем Security Group, кото­рую созда­ва­ли для RDS Proxy:

Ждём ста­тус Available:

Ста­тус тар­гет-груп­пы мож­но про­ве­рить с AWS CLI:

Минут через 5 RDS Proxy пишет, что готов:

Но его енд­по­ин­ты и под­клю­че­ние к тар­гет-груп­пам ещё настраиваются.

В целом про­цесс занял око­ло 10-15 минут:

В AWS Route53 созда­ём запись proxy-test-rds-proxy.dev.bttrm.local с типом CNAME на адрес мастер-енд­по­ин­та Proxy – rds-proxy-test-proxy.proxy-ci9hdkrgdpwy.eu-west-2.rds.amazonaws.com.

С тесто­во­го ЕС2 про­ве­ря­ем мастер-енд­по­инт RDS Proxy, исполь­зуя логин-пароль от Aurora:

ubuntu@ip-10-0-0-97:~$ mysqladmin -h proxy-test-rds-proxy.dev.bttrm.local -u rds-proxy-user -p status
Enter password:
Uptime: 13645  Threads: 8  Questions: 1003139  Slow queries: 0  Opens: 118  Flush tables: 1  Open tables: 111  Queries per second avg: 73.516

В слу­чае про­блем с под­клю­че­ни­ем – мож­но загля­нуть на стра­нич­ку Troubleshooting for RDS Proxy.

Тестирование количества подключений #2: через RDS Proxy

Обнов­ля­ем пере­мен­ную для скрип­та – ука­зы­ва­ем адрес RDS PRoxy:

ubuntu@ip-10-0-0-97:~$ export RDS_HOST=proxy-test-rds-proxy.dev.bttrm.local
Запус­ка­ем повтор­ный тест – в про­шлый раз мы полу­чи­ли “Too many connections” на 40-ой ите­ра­ции, а сейчас:

И что мы видим?
  1. не при­шло ни одно­го сооб­ще­ния “Too many connections” – RDS Proxy ста­вил запро­сы в оче­редь на выполнение
  2. неко­то­рые запро­сы выпол­ня­лись не ~500ms, как зада­но в time.Sleep(500 * time.Millisecond) меж­ду ите­ра­ци­я­ми, а почти две мину­ты – RDS Proxy ждал, пока закон­чит­ся один из преды­ду­щих запро­сов SELECT sleep(100), 100 секунд, после чего запус­кал выпол­не­ние следующего

Т.е. новые запро­сы ожи­да­ли сво­бод­ных кон­нек­ше­нов в пуле Proxy, что бы начать выполнение.

Latency (вре­мя отве­та) места­ми  вырос­ло – но это намно­го луч­ше, чем ловить на API-бекен­де мобиль­но­го при­ло­же­ния 500-ые ошиб­ки, правда?

Нагрузочное тестирование

Ну и самые инте­рес­ные резуль­та­ты полу­ча­ют­ся уже с исполь­зо­ва­ни­ем mysqlslap.

Тут сто­ит учи­ты­вать, что тесты весь­ма син­те­ти­че­ские-искус­ствен­ные, и реаль­ной кар­ти­ны для реаль­но­го при­ло­же­ния не пока­жут – там надо про­во­дить свои тесты, отсле­жи­вать ошиб­ки, вре­мя отве­та и так далее.

Пер­вый прогон.

Исполь­зу­ем:

  • хост: напря­мую на Авро­ру, proxy-test-rds-aurora.dev.bttrm.local
  • --detach=1: выпол­ня­ем новое под­клю­че­ние после каж­до­го запроса
  • --concurrency=45: 45 одно­вре­мен­ных под­клю­че­ний, наш лимит сер­ве­ра  из max_connected_threads
  • --iterations=10: каж­дый кли­ент выпол­ня­ет 10 запросов
  • --query 'select version();': и про­стой запрос на полу­че­ние вер­сии MySQL

Запус­ка­ем – и сра­зу ловим “Too many connections“:

Умень­ша­ем кли­ен­тов до 30:

Вре­мя – 0.762 seconds.

А теперь – тоже самое, но через RDS Proxy – меня­ем --host на proxy-test-rds-proxy.dev.bttrm.local, те же 30 клиентов:

Вре­мя – 0.264 seconds.

Попро­бу­ем уве­ли­чить кли­ен­тов до 45:

Вре­мя – 0.723 секунд.

И жах­нем 200 клиентов:

 

Вре­мя – 1.796, но по-преж­не­му – “Ни еди­но­го раз­ры­ва“!