company_banner

ProxySQL — средство для демультиплексирования соединений

    Здравствуйте, меня зовут Александр Яковлев, я работаю в компании Ситимобил и занимаюсь эксплуатацией. Сегодня я расскажу про очень интересный продукт ProxySQL — это высокопроизводительный MySQL Proxy, который умеет очень много — отлавливать и убивать запросы по маске, с помощью него можно искать sql injection, дублировать нагрузку и много другое. Я расскажу о нашем опыте работы с ним.
    С описанной ниже ситуацией рано или поздно сталкивается любой крупный IT-проект, развитие которого начиналось с пары серверов. Представим, что в проекте сначала была только одна база данных — мастер-сервер. Постепенно к нему добавили кучу слейвов. Потом внедрили шардинг.

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

    Рассмотрим на примере одного мастера. Допустим, у вас 50 web серверов и на каждом 200 php-fpm процессов. Тогда в мастер прилетит 50*200 коннектов, при этом в каждый слейв придет 50*200/количество слейвов (если, конечно же, в haproxy настроен roundrobin) — смотрите картинку ниже. Конечно, 10 тыс. коннектов в мастер — это много, но еще терпимо, а если будет 200 вебов, то количество коннектов будет еще больше, а один коннект = один тред.
    Именно в это бутылочное горлышко мы уперлись.

    image

    Дальше мы стали рассуждать: коннект в мастер устанавливается в коде всегда, но нужен ли он всем fpm процессам? Скорее всего, нет. Мы заметили, что большое количество persist-коннектов в мастер просто висят в слипах. И решили, что нам нужно демультиплексирование.

    image

    Для этого мы обратили внимание на продукт под названием ProxySQL. Он работает как обычный reverse proxy: к нему устанавливаются подключения, и он перераспределяет трафик по определенным правилам, указанным в конфигурации.

    Мы установили ProxySQL на всех наших веб-серверах, а в конфигурации приложения прописали, что обращение в мастер-базу выполняется по адресу 127.0.0.1. Если раньше 200 FPM-воркеров на каждом веб-сервере означали 200 подключений к мастер-базе от этой машины, то теперь ситуация изменилась. Эти 200 подключений приходят в ProxySQL, а наружу в разное время выходят 50-70. То есть ProxySQL умеет многократно использовать уже установленные подключения.

    Благодаря демультиплексированию мы на всех мастерах сократили количество подключений в 3-10 раз, график current connections одного из мастеров смотрите ниже.

    image

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

    Как это будет реализовано? Приложение идет в ProxySQL, а тот отправляет трафик по двум маршрутам: в боевые базы данных, чтобы приложение функционировало, и в тестовую среду для проверки новых фич под нагрузкой.

    Особенность ProxySQL в том, что есть его конфиг, который в нашем случае выкатывается через puppet (для puppet есть модуль ProxySQL), но еще есть понятие зоны runtime, когда для того, чтобы внести изменение (добавить сервер, добавить пользователя, удалить сервер и пользователя), не нужен привычный рестарт/релоад., Все делается через консоль ProxySQL, например так.

    mysql -ulogin -ppassword -h 127.0.0.1 -P6032 -e "INSERT INTO mysql_users(username,password,default_hostgroup) VALUES ('sm_username','pass',1);;LOAD MYSQL USERS TO RUNTIME;SAVE MYSQL USERS TO DISK;"

    Более подробно конечно же в официальной документации proxysql.com/documentation

    Спасибо за внимание.

    Наш конфиг, которым мы решили описанную выше задачу смотрите ниже.

    Наш конфиг

    datadir="/var/lib/proxysql"
    
    admin_variables=
    {
        admin_credentials="user:pass"
        mysql_ifaces="0.0.0.0:6032"
        refresh_interval=2000
        web_enabled=true
        web_port=6080
        stats_credentials="stats:admin"
    }
    
    mysql_variables =
    {
        threads = 1000
        max_connections = 2000
        default_query_delay= 0
        default_query_timeout=1
        have_compress=true
        poll_timeout=2000
        interfaces="0.0.0.0:6033;/tmp/proxysql.sock"
        default_schema="information_schema"
        stacksize=1048576
        server_version="5.7.22"
        connect_timeout_server=10000
        monitor_history=60000
        monitor_connect_interval=200000
        monitor_ping_interval=200000
        ping_interval_server_msec=5000
        ping_timeout_server=200
        commands_stats=true
        sessions_sort=true
        monitor_username="root"
        monitor_password="password"
        monitor_galera_healthcheck_interval=200
        monitor_galera_healthcheck_timeout=80
    }
    
    mysql_servers =
    (
      {
        address = "ip_real_mysql_server",
        port = 3306,
        max_connections = 10000,
        host_group = 1,
      })
    mysql_users =
    (
      {
        username = "user",
        password = "pass",
        default_hostgroup = 1,
        transaction_persistent = 0,
        active = 1,
      })
    
    Ситимобил
    Компания

    Комментарии 8

      +4
      Попробуйте ходить из приложения не через 127.0.0.1, а через сокет /tmp/proxysql.sock будет быстрее.
        0
        По конфигу два коментария.
        monitor_ping_interval у вас стоит в 200000ms (200 секунд), это означает что среднее время между падением сервера и обнаружением этого ProxySQL — 100 секунд, а в плохом случае ProxySQL будет ломиться на нерабочий сервер больше трех минут. Лучше поставить это значение хотя бы в дефолтное (8 секунд), а лучше раз в секунду, проверка легкая, сервер не тормозит.
        max_connections у вас стоит в 10 000. Если каждый клиент откроет в пике 10к коннектов, базе может стать очень нехорошо. Лучше поставить в сотню, тем более что вы сами посчитали, что в среднем у вас 50-80 активных подключений с бокса, что честно говоря очень много и это значит что нужно смотреть на незакрытые транзакции и медленные запросы.
          +1
          первое согласен — спасибо!
          второе осталось на всякий случай, 10к коннектов норм. мы боролись когда было 35к коннектов, из-за чего пришлось выключить много вебов.
          +1

          А вы не пробовали использовать его функционал по кэшированию запросов?
          На тестовой нагрузке работает, а вот как он поведёт себя на >20k запросов в секунду — неизвестно.

            0
            В целом кеширование по TTL не самая умная штука. Под нагрузкой работает, но если нужно существенную часть данных отдавать из кеша, лучше научить приложение ходить вместо базы в memcache / редис / etc
          0
          Интересное совпадение — заговорили про кэширование, и как раз всплыло. Написал комментарий — его не видно…

          Продублирую ещё раз в таком случае.
          Не могу не согласиться — конечно решать на уровне приложения правильней. Но сейчас как-то часто случается что или разработчики заняты, или их уже нет. А нагрузка есть и с ней нужно что-то делать.
          С микрокэшированием на уровне nginx-а я уже обжёгся. Теперь вот хочу набить шишек уровнем ниже.
            0
            Попробовал проксировать запросы через ProxySQL (v2.0.12 из исходников). Удивительно, но подключений к базе стало больше. Может этот multiplexing нужно как-то отдельно включать? Например в табличке mysql_query_rules есть поле 'multiplex'.

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

            Самое читаемое