Сложилась ситуация, что участвую в проекте, который работает с достаточно большой нагрузкой. Как уже написал — 36 млн запросов в час. Я много чего прочитал и перепробовал за последний месяц, настраивая сервер; хотелось бы просто сжато и компактно выдать тезисно то, что работает хорошо в такой конфигурации.

Первое, что я заметил — множество советов как все настроить под большую нагрузку. Читайте их внимательно, обычно в тексте найдете, что речь про «высокую нагрузку» в 15-20 тысяч клиентов в сутки. У нас клиентов примерно миллион, активных, ежедневных.

У нас нет денег и мы все делаем за свой счет, поэтому экономим. Итог — весь миллион клиентов обслуживается на одном сервере, вот на таком — EX-60 на hetzner.

Мы случайно сделали себе аналог DDoS через своих клиентов и в результате настроек, когда было по 4000 php процессов, загрузка ОС так же под 4000, я успел перепробовать множество конфигураций и найти наиболее работающие. С ошибкой в софте справились, теперь эти 10-12 тысяч запросов в секунду обрабатываются с загрузкой load average: 3,92, 3,22, 2,85. Не единичка, конечно, но для одного сервера считаю хорошим результатом.

Операционка — CentOS 7.1, 64 бита. Минимальная инсталляция, плюс iptables, nginx, php-fpm, mysql. Ядро 4-й версии, из kernel-ml.

Тюнинг настроек ядра под большой напор tcp коннектов:

/etc/sysctl.conf
fs.file-max = 1000000
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.tcp_max_orphans = 65536
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_max_syn_backlog = 65536
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_mem = 50576 64768 98152
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_syncookies = 0
net.ipv4.netfilter.ip_conntrack_max = 1048576
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_congestion_control = htcp
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.route.flush=1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.lo.rp_filter = 1
net.ipv4.conf.eth0.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.lo.accept_source_route = 0
net.ipv4.conf.eth0.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv4.ip_forward = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.core.somaxconn = 262144
net.core.netdev_max_backlog = 1000
net.core.rmem_default=65536
net.core.wmem_default=65536
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

Тюнинг лимитов по файлам, так как ни каких юзеров на сервере нет, то особо не заморачиваемся:

/etc/security/limits.conf
* soft nproc 65535
* hard nproc 65535
* soft nofile 100000
* hard nofile 100000
root soft nofile unlimited
root hard nofile unlimited

Монстры знают, но я давно не администрировал и был не в курсе, что * не работает на root, и его надо отдельно тюнинговать.

На этом с ядром всё.

Настройки мускула. Установлена percona-56.

Выбор был сделан в итоге на InnoDB, пробовали TokuDb, но на больших объемах постоянных инсертов, а их у нас 95% из 36 млн в час. InnoDB ведет себя лучше, тесты самой перконы говорят о том же.

Настройки mysql:

/etc/my.cnf
[mysql]
port = 3306
socket = /var/lib/mysql/mysql.sock
[mysqld]
user = mysql
default-storage-engine = InnoDB
socket = /var/lib/mysql/mysql.sock
pid-file = /var/lib/mysql/mysql.pid
key-buffer-size = 32M
myisam-recover = FORCE,BACKUP
max-allowed-packet = 16M
max-connect-errors = 1000000
skip-name-resolve
datadir = /var/lib/mysql/
tmp-table-size = 32M
max-heap-table-size = 32M
query-cache-type = 0
query-cache-size = 0
max-connections = 15000
thread-cache-size = 5000
open-files-limit = 150000
table-definition-cache = 1024
table-open-cache = 50000
innodb-flush-method = O_DIRECT
innodb-log-files-in-group = 2
innodb-log-file-size = 2G
innodb-file-per-table = 1
innodb-buffer-pool-size = 10G
innodb_flush_log_at_trx_commit = 0
log-error = /var/log/mysql/mysql-error.log
log-queries-not-using-indexes = 0
slow-query-log = 1
slow-query-log-file = /var/log/mysql/mysql-slow.log

Обязательно отключаем при такой нагрузке query-cache. Он будет реально тормозить всю систему. Впрочем, поиграйтесь, возможно в вашем случае нет, но во многих тестах и текстах встречал этот момент, проверил у себя — так и есть, с отключенным работает быстрее.

skip-name-resolve тоже дает хороший прирост.

Дополнительные настройки по отношению к стандартным для nginx:

fastcgi_params
fastcgi_param REDIRECT_STATUS 200;
fastcgi_buffer_size 4K;
fastcgi_buffers 64 4k;

nginx тюним под наши нужды:

nginx.conf
user nginx;
worker_processes 8;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

worker_rlimit_nofile 150000;

events {
worker_connections 8000;
multi_accept on;
use epoll;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr — $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

gzip off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
reset_timedout_connection on;
server_tokens off;
client_body_buffer_size 128k;

include /etc/nginx/conf.d/*.conf;

}

Ядер 8, поэтому и worker-процессов 8, по 8000 на брата, все-равно больше 64к не обслужить за раз. Будет небольшая очередь, если будет больше одновременных коннектов.

В сайте с php-fpm общаемся через сокеты:
/etc/nginx/conf.d/site.conf
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_send_timeout 180s;
fastcgi_read_timeout 180s;

Основное по конфигурации php-fpm:

/etc/php-fpm.d/www.conf
listen = /var/run/php-fpm/php-fpm.sock
pm = ondemand
pm.max_children = 4000
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_requests = 0

ondemand мало где описан, но он лучше чем dynamic под большой нагрузкой. А static — это, конечно, убийство для сервера, не понравилось сильно.

ondemand начинает с 5, при необходимости наращивается, но в отличии от dynamic с уменьшением нагрузки не убивает процессы, чтобы потом опять наращивать, а просто на пике фиксирует значение и переводит ненужный в режим ожидания. И если вдруг нагрузка опять растет — процессы уже готовы, никого не надо запускать с нуля.

pm.max_requests = 0 помогает боротся с утечками памяти, в стороннем софте.

Собственно, так мы и обслуживаем 36 млн в час, из которых 95 процентов — передача к нам данных и запись их в БД. На 2.8 миллиарда запросов у нас сейчас от 10 до 16 slow_query, каждый не больше 10 секунд, причем все они — селекты с джойнами по многим полям и таблицам. Остальные запросы отрабатывают моментально.

Вместо php-fpm компилировал и использовал hhvm одно время, действительно работает шикарно, значительно быстрее php-fpm, но есть беда — каждые 30-40 минут падает, причем наглухо.

В git разработчикам написал, пока ничем не смогли помочь, причин не знают. В итоге сидим на php-fpm, версия 5.6.

Весь софт ставится через yum, ни каких билдов с сорцов с мегатюнингом не используется.

Думаю, кому-то будет полезна эта информация о настройках вся в одном месте.

Комментарии (101)


  1. youROCK
    14.07.2015 19:46
    +10

    rusage каждого из запросов меньше 1мс? Из чего же состоят ваши скрипты? Просто mysql_query без авторизации?


    1. RUnnerTomsk Автор
      15.07.2015 07:36

      Авторизация по каждому запросу, затем update в БД, затем insert данных с replace on duplicate


      1. gandjustas
        15.07.2015 11:44

        БД от такого количества запросов не падает? Одно дело принять по сети 10К запросов в секунду, другое дело на диск их записать.


        1. doom369
          15.07.2015 11:50

          В среднем приходит от 200 до 500 байт данных


          Это в джейсоне. То есть самих данных <200 байт в запросе. 10к рек-сек * 200 байт == 2МБ сек — скорость записи на диск. То есть даже любой самый хиленький hdd справится.


          1. neolink
            15.07.2015 12:15

            это если индексов нет


            1. RUnnerTomsk Автор
              15.07.2015 12:22

              5-ть индексов, 3 по одному полю, один по двум полям, один по трем полям.


        1. BupycNet
          15.07.2015 18:19

          Мне кажется, или в случае чего, можно ведь в redis эти однородные инсерты запихивать, и раз в несколько секунд делать один большой запрос к БД на обработку.


          1. doom369
            15.07.2015 18:55

            Если в редис, то оперативка закончится за 1 день


            1. aleks_raiden
              15.07.2015 19:12

              Можно заюзать ssdb, и писать с той же скоростью и тем же протоколом, что и редис, но пока есть место на диске (я пробовал на уровне 40+Гб где-то)


              1. doom369
                15.07.2015 19:24
                +1

                Можно заюзать ssdb


                А можно и MySQL, что и сделал автор. Зачем ssdb? Зачем редис?


  1. homm
    14.07.2015 19:52
    +3

    Расскажите еще про pm.max_requests = 0. Это после каждого запроса интерпретатор перезапускается (т.е. всего 10000 раз в секунду)? Это же невозможно. Если наоборот, отключает перезагрузку интерпретатора, то как это «помогает бороться с утечками памяти»?

    Ну pm.max_children = 4000 — это что-то невероятное. Если каждый процесс кушает по 20 мегабайт, то это уже 80 гигабайт памяти.


    1. blind_oracle
      14.07.2015 20:23
      +4

      Расскажите еще про pm.max_requests = 0. Это после каждого запроса интерпретатор перезапускается (т.е. всего 10000 раз в секунду)?

      Там наоборот, процессы не перезапускаются вообще:
      The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries. For endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. Default value: 0.

      Так что у автора никакой борьбы с утечками нет :)


      1. RUnnerTomsk Автор
        15.07.2015 07:43
        +1

        Ага, сейчас нет, тут прогнал маленько. Пока боролись — было было 500. Сейчас тьфу тьфу тьфу, все стало хорошо и поставил 0 в текущем конфиге.


    1. RUnnerTomsk Автор
      15.07.2015 07:35

      4000 держит, но всё упирается вы этом случае в CPU, не справляется.
      Сейчас хватает максимум 400 процессов.


  1. doom369
    14.07.2015 20:21
    +15

    Инфы мало. Что за запросы? Какой протокол? Какой средний размер запроса? Каждый ли реквест пишется в базу? Как в базу пишете? Один поток? Много потоков? Какого рода запросы? Инсерты? Инсерты с селектом? Еще что-то? Сколько данных в сек пишется на диск? Сколько читается? Какой трафифик на сервер туда-сюда? Сколько самих реквестов в базу? Как деплоймент делаете при такой нагрузке? Отрубаете сервер?

    Вполне может оказатся что после ответов 10к рек-сек это мало для такого сервака =).


    1. FrostMoon
      14.07.2015 21:53
      +5

      Подпишусь под вопросом.
      Какова логика скриптов? Какие объемы данных? Какие индексы? Количество записей?
      Мы все были бы очень признательны за чуть больший объем инфы.


    1. RUnnerTomsk Автор
      15.07.2015 07:42

      Запросы входят через http в json формате. В среднем приходит от 200 до 500 байт данных, парсится, и уже в нужном виде вставляется в БД.
      Каждый пишется в БД, сначала идет select для проверки токена авторизации, если все ок — делается update для выставления флага удаления для потерявших актуальность записей, затем делается insert в БД с replace в случае если такой ключ уже имеется.
      Соответственно есть еще один демон, на джаве, который висит на отдельном порту, слушает сокет, и клиент сначала в него кидает пару логин/пароль, получает токен авторизации, и уже с ним через http в json формате сливает данные на сервер.
      Коряво, но когда-то было сделано такое решение, пока его не меняем.


      1. FrostMoon
        15.07.2015 15:13

        Однажды на практике убедились, что если часть запросов из бекенда перенести на сторону nginx ( например модули на perl исполняемые nginx-ом) можно получить почти на порядок большее qps к БД. А лучше, наверное, модули на Lua. Правда все упирается в специфику данных и саму базу. Но мне кажется, вам стоит попробовать. Если интересно могу в личку скинуть наш пример. Нашей задачей был сбор статистических метрик с распаковкой данных и по возможности быстрой записью в БД.

        Сижу на PgDay, пишу с телефона, прошу прощения за опечатки.


  1. blind_oracle
    14.07.2015 20:24
    +1

    А как же отказоустойчивость?


    1. RUnnerTomsk Автор
      15.07.2015 07:43
      +1

      Хромает. Думаем над этим. Пока не сбоит, но понятно что это не может быть вечным — один сервак у хетцера уже сожгли — винты вышли из строя.


      1. mlogarithm
        21.07.2015 11:38

        Винты — это болезнь хецнера, по опыту (парк в 30+ серверов в течении пары лет) — самая большая. Хотя у нас архитектура позволяла потерять несколько серверов, но в итоге пришли к тому, что заказывали новые винты и мониторили состояние работающих через smartctl. Это позволяло отловить умирающие винты заранее и принять превентивные меры.


  1. AlexeyShurygin
    14.07.2015 20:32
    +1

    «Итог — весь миллион клиентов обслуживается на одном сервере»
    Учитывая что Хетцнер использует ненадежное железо десктоп класса, которое периодически падает — хороший выбор, успехов)

    Касательно нагрузки — действительно 400 инсертов в секунду делаете на одном DB сервере?


    1. alexkrash
      14.07.2015 22:22
      +1

      Честно говоря, 400 инсертов в секунду для одного сервера MySQL это ничто :) Другое дело, что тут всё на одной железяке.


    1. neolink
      14.07.2015 22:29
      +3

      а как вы посчитали что 400?! 10к запросов в секунду из которых 95% это инсерты, то есть 9500 инсертов же


    1. RUnnerTomsk Автор
      15.07.2015 08:09

      12k qps видел, выше — не попадал в тот момент на сервер ) Я редко на него захожу.
      По железу — один сервак сожгли, винты посыпались, через 40 минут хетцер дал новый, переехали быстро.
      Есть там и enterprise решения, но дорого для нас пока что.


      1. AlexeyShurygin
        15.07.2015 17:55

        Спасибо за уточнение.
        По хостингу как я и говорил оказалось.


  1. inside22
    14.07.2015 20:58
    +7

    Подскажите пожалуйста, а каким образом вы подсчитали количество запросов в час?


    1. RUnnerTomsk Автор
      15.07.2015 07:44
      +2

      mysql ведет статистику запросов. Берем количество, делим на время.


      1. DoctorChaos
        15.07.2015 11:15

        Так 36 млн. HTTP-запросов? Или в MySQL?


        1. RUnnerTomsk Автор
          15.07.2015 11:22

          В мускул. Но они все приходят через веб. Так что можно уровнять я думаю.


          1. ffriend
            15.07.2015 14:54
            +2

            Авторизация по каждому запросу, затем update в БД, затем insert данных с replace on duplicate

            Если даже принять, что авторизация на сервер, а не в базу, то по вашим же словам на каждый запрос на веб-сервер приходится 2 запроса в БД. Соответсвенно, количество запросов к веб-серверу в 2 раза меньше. Правильно я понимаю?


            1. DoctorChaos
              15.07.2015 20:09

              Кроме того, там могут выполняться SET NAMES и что-то еще. Тогда соотношение HTTP и SQL запросов будет еще меньше.


            1. RUnnerTomsk Автор
              20.07.2015 20:22

              Не совсем. Клиент перед отправкой данных сначала запрашивает версию API на сервера, это ему nginx отдает, затем запрашивает контрольную сумму клиента на сервере, это ему так же nginx отдает.
              Если что-то не совпало — автообновляется. А если совпало — то идет передача данных. Так что разница реальная по отношению к SQL запросам где-то меньше процентов на 20-30%.


  1. AterCattus
    14.07.2015 23:54

    А всякие nginx_mysql_module не рассматривались, раз в php почти ничего кроме запросов к базе и нет?


    1. RUnnerTomsk Автор
      15.07.2015 07:45

      Да, это следующий шаг, сейчас изучаем как писать модули под nginx, думаем написать модуль и вообще уйти от php.


      1. Roxis
        15.07.2015 09:33
        -11

        Node.js


        1. DLag
          15.07.2015 10:07
          +2

          У него HTTP-сервер по скорости сильно хромает.


          1. Roxis
            15.07.2015 10:59
            +1

            Прошу пруфы. Сам не нашёл, только тесты подтверждающие обратное:
            centminmod.com/siegebenchmarks/2013/020313
            www.prahladyeri.com/blog/2014/06/php-fpm-vs-node-js-the-real-performance-battle.html


            1. DLag
              15.07.2015 18:35

              Первый тест — Virtualbox, да и логике данное сранение не подается.
              Второй тест — сранение вебсервера + PHP-FPM с вебсервером внутри nodejs


            1. DLag
              15.07.2015 18:39
              +1

              А раз вы любите синтетику, то вот вам примеры:
              ufocoder.com/ru/blog/2015/benchmark-prostogo-http-servera-na-golang-i-nodejs#.VaPzF3uaCiA.twitter
              gist.github.com/msoap/7060974


          1. gonzazoid
            15.07.2015 18:06

            голую ноду в мир никто не выставляет, для этого есть nginx.


        1. Fesor
          15.07.2015 11:17

          ReactPHP даст тот же профит примерно. Или PHP-PM. Полный отказ от прослойки дополнительной профита даст явно больше.


          1. DjOnline
            15.07.2015 22:30

            Скорее всего там не используются тяжелые фреймворки, да и фрейморки вообще, поэтому отличие будет небольшим. Но зато можно будет кэшировать prepared statements и set names.


      1. AterCattus
        15.07.2015 15:15

        Если вам только запросы в mysql, то достаточно самого nginx_mysql_module. Если все же какая-то логика, то почему бы не lua-nginx-module + lua-resty-mysql?


  1. aivus
    15.07.2015 00:23
    +6

    в секунду обрабатываются с загрузкой load average: 3,92, 3,22, 2,85. Не единичка, конечно, но для одного сервера считаю хорошим результатом.

    Единичка и не нужна, на 8-ядерной машине 100% загрузка всех ядер — это 8.

    ondemand мало где описан

    ondemand описан как минимум в комментариях www.conf:
    ondemand — no children are created at startup. Children will be forked when new requests will connect.


    но он лучше чем dynamic под большой нагрузкой..

    Может быть все дело в том, что в режиме ondemand php смотрит на параметры pm.max_children и pm.process_idle_timeout. Параметры pm.start_servers, pm.min_spare_servers используются только в режиме dynamic.

    но в отличии от dynamic с уменьшением нагрузки не убивает процессы,

    pm.process_idle_timeout — The number of seconds after which an idle process will be killed.

    А static — это, конечно, убийство для сервера, не понравилось сильно

    Правильно, потому что он запускает конкретное количество инстансов. И с вашим количеством в 4000 инстансов серверу потребуется около 80 гигов памяти.
    Такая же ситуация, в принципе, будет и с текущими настройками в пике (если пхп запустит все 4000 инстансов, указанных в pm.max_children).

    pm.max_requests = 0 помогает боротся с утечками памяти, в стороннем софте.

    Как уже написали выше, pm.max_requests = 0 значит совсем противоположное.


    1. RUnnerTomsk Автор
      15.07.2015 08:12

      Ага, спасибо. Я давно отошел от админства, со времен freebsd 3-й мало что админил из юниксов. Пришлось вспоминать, изучать методом тыка.
      По таймауту процессы и убиваются — сейчас у меня 30 секунд стоит.
      Про max_request выше писал, прогнал, — было в конфиге 500 когда боролись с утечками. Как победили — стало 0, держит стабильно, память не исчезает в никуда.


  1. Rathil
    15.07.2015 01:20
    +1

    Имхо на таком кол. запросов нужно логи вырубать, ну или не на HDD их сбрасывать.
    Не знаю какие у вас там запросы, но если есть запросы, как вы сказали, только на запись, советую для этих запросов дополнительно глянуть сюда php.net/manual/en/function.fastcgi-finish-request.php это поможет немного легче жить nginx-у.
    Ещё как вариант, возможно, имеет смысл посмотреть на модуля для того же nginx-а, которые умеют писать напрямую в DB, с пост обработкой на php.


    1. Rathil
      15.07.2015 01:21

      О логах — я о логах запросов, а не ошибок.


    1. RUnnerTomsk Автор
      15.07.2015 07:57

      Да, это решение мы на позапрошлой неделе сделали. Делаем финиш и дальше уже все работы по обработке данных и вставке их в БД.
      И модуль да, хотим написать свой модуль под nginx чтобы уйти от php вообще.


      1. Rathil
        15.07.2015 08:57

        Зачем тогда писать модуль для nginx-а, что ПОЛНОСТЬЮ уйти от php? Перепишите тогда полностью на на C++ и все…


        1. RUnnerTomsk Автор
          15.07.2015 09:01

          Полностью уйти на приеме данных.
          Веб-морда для пользователей остаётся, она вообще не symfony сделана.
          Была идея по типу демона авторизации, что на java сделан, сделать и обработку входящих данных так же.
          Но пока всё это под вопросом.


          1. AlexGx
            15.07.2015 13:37

            посмотрите тогда в сторону nginx + lua, возможно это то, что вам нужно


      1. AlexGx
        15.07.2015 13:36
        +1

        del


  1. AlexGx
    15.07.2015 03:50
    +3

    Зачем так много?
    fastcgi_send_timeout 180s;
    fastcgi_read_timeout 180s;


    1. RUnnerTomsk Автор
      15.07.2015 07:53

      Когда подсчитывается статистика и удаляются записи устаревшие, в этот момент дорогой мускул лочит таблицу, и если в этот момент, ночной, кто-то юзает веб-морду сервиса — чуть чуть ждет и получает результат, вместо ошибки «что-то пошло не так на нашем сервисе».
      Корявый момент, пока так его обошли.
      Ночью делаем delete from where del_flag=1, а затем alter table чтобы уменьшить объем данных и облегчить процесс впихивания всей этой беды по максимуму в кэш InnoDb, а то база разрастается до 20 млн и выше записей, в каждой записей по 20 полей, и они активно используются в достаточно больших select-ах с несколькими джойнами, на веб-морде, и мускул начинает подтупливать.


      1. FractalizeR
        15.07.2015 11:03

        Ночью делаем delete from where del_flag=1, а затем alter table чтобы уменьшить объем данных и облегчить процесс впихивания всей этой беды по максимуму в кэш InnoDb

        Партиционирование таблиц не рассматривали? ALTER TABLE достаточно дорогая операция…


        1. RUnnerTomsk Автор
          15.07.2015 11:09

          Пока не придумали как применить. Хотя слова эти витают в воздухе постоянно )


          1. FractalizeR
            15.07.2015 11:45

            А как вы конкретно уменьшаете размер таблицы? Не выходит сделать так, чтобы лишние данные попадали в другую партицию?


            1. RUnnerTomsk Автор
              15.07.2015 12:23

              Удаляем данные, делаем alter — оно дефрагментирует, так как InnoDb.


        1. script88
          15.07.2015 11:49
          +1

          для ALTER TABLE на больших объемах, советую посмотреть в сторону www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html


          1. RUnnerTomsk Автор
            15.07.2015 12:23

            Спасибо!!!
            Будем смотреть!


  1. TheRaven
    15.07.2015 10:24
    +1

    innodb_flush_log_at_trx_commit=0

    Аварийно сервер пробовали отключать и смотреть что с базой после этого?


    1. RUnnerTomsk Автор
      15.07.2015 11:21

      Если сильно сильно не повезет — потеряем пару секунд транзакций, для нас это не критично — данные от клиента прийдут заново очень быстро.


  1. simpleadmin
    15.07.2015 10:44

    Из статьи не понятна структура таблиц/запросов. Но вероятнее всего под такой нагрузкой хранятся пары вида «ключ, значение».
    Если так, то не лучше ли перейти на решения а-ля redis?
    Если по каким-то причинам no-sql решения не подходят, а структура запросов INSERT одинакова гоните их в mysql через fifo pipe напрямую.


    1. RUnnerTomsk Автор
      15.07.2015 11:07

      Вкратце — таблица пользователей, с их данными.
      Вторая таблица, в которую идут основные инсерты — ИД юзера, плюс ИД записи данных этого юзера, и 20 полей данных этой записи.
      У каждого юзера в среднем 18-20 записей данных.
      Плюс вспомогательные таблицы для расчета некоторых полей, часть полей приходит от клиента, часть — калькулируется и затем вставляется в БД в виде записи окончательно.


      1. simpleadmin
        15.07.2015 11:46

        Данные вносятся полагаю через form input? Если да, то

        в которую идут основные инсерты — ИД юзера, плюс ИД записи данных этого юзера
        навскидку сохраняя структуру, логику, не уходя от mysql и не писав собственного модуля для nginx:

        — добавляем в nginx модуль HttpFormInputModule от taobao

        — делаем
        log_format mylog 'INSERT IGNORE INTO `log`.`access` (`user`, `data1`, `data2`) VALUES ("$post_user","$post_data1","$post_data2");\n';
        логируем в нужном локейшине

        — создаём fifo-шку
        # mkfifo /var/log/nginx/fifo.mylog && chmod 666 /var/log/nginx/fifo.mylog

        — пайпим данные напрямую без php-шной обработки
        # tail -F /var/log/nginx/fifo.mylog | mysql -pMYSQL_PASS

        Вероятнее всего, для того чтобы данные полились придётся рестратануть (НЕ reload) nginx, чтобы он нашёл fifo.
        С таким потоком данных по поводу flush'a буферов можно не заморачиваться.
        Тем самым input-переменные не требующие дополнительной обработки льются напрямую без всякого php.


        1. aleks_raiden
          15.07.2015 11:51

          Интересное решение. Что с безопасностью и фильтрацией вводимых данных напрямую в базу? $post_data2 = '1");\nDROP DATABASE;\n'; — ну так навскид :)


          1. simpleadmin
            15.07.2015 11:56

            Код модуля смотрел очень давно, не помню. Но если обработки в нём и нет, то добавить её не так сложно.
            Самим модулем пользовался на нагрузке около 100k per sec, падений не было.


        1. RUnnerTomsk Автор
          15.07.2015 12:20

          Нет, данные идут из клиентов, сразу POST запрос в json формате по нужному урлу.


          1. simpleadmin
            15.07.2015 12:34

            Логики вышеописанного это не меняет, запросы всё-равно попадут в ngx_http_form_input_post_read(ngx_http_request_t *r).
            Только разбор JSON опять же придётся отдать на откуп этому модулю.


            1. RUnnerTomsk Автор
              15.07.2015 12:36

              Спасибо, очень интересно! У нас есть идея разделить данные на те что надо дообрабатывать, всмысле сделать рассчеты по ним, и те что не надо, возможно это решение будет в тему для тех данных что нужно просто залить и сохранить.


              1. simpleadmin
                15.07.2015 12:54

                Собственно, здесь можно обойтись и без пайпинга, а посмотреть в сторону github.com/openresty/drizzle-nginx-module#drizzle_query
                Тут же для борьбы с инъекциями (о которых подымал вопрос aleks_raiden)можно использовать github.com/openresty/set-misc-nginx-module#set_quote_sql_str


  1. isden
    15.07.2015 10:49

    > hhvm

    По нашим тестам оно помогает заметно только при PHP <= 5.3, емнип.
    На >= 5.5 лучше всего идет обычный PHP-FPM с OPCache.


    1. RUnnerTomsk Автор
      15.07.2015 11:08

      По ощущениям — похоже так и есть.


  1. onthefly
    15.07.2015 12:23

    use epoll;


    Nginx уже несколько лет как умеет самостоятельно выбирать оптимальный метод. Есть ли реальная необходимость в этой строке, чем руководствовались при её добавлении?


    1. RUnnerTomsk Автор
      15.07.2015 12:30

      чтобы наверняка )


    1. xandr0s
      15.07.2015 12:59

      Ну раз уж на то пошло,

      worker_processes 8;

      тут тоже можно заменить на auto где-то с 1.2 ветки. Но никто не запрещает их описывать. как и кучу других параметров, неплохо настроенных искаропки. Хотя бы, чтобы быть уверенным, что у тебя они верно выставлены.


  1. ErgoZru
    15.07.2015 12:40
    +1

    Вместо php-fpm компилировал и использовал hhvm одно время, действительно работает шикарно, значительно быстрее php-fpm, но есть беда — каждые 30-40 минут падает, причем наглухо.

    Та же самая проблема, побороть так и не смог, хотя производительность мне понравилась.


  1. zapimir
    15.07.2015 14:58
    +1

    А HandlerSocket для запросов к MySQL не пробовали использовать?


  1. ServerClub
    15.07.2015 15:39

    А можете рассказать, что за проект описывается в статье?


  1. Iforgot
    15.07.2015 15:44

    Экономия должна быть экономной. Почему вы юзаете 1-у железку? 48Gb mem %) Я бы смотрел в сторону использования нескольких железяк, но с распаралелливанием нагрузки. За 50Е можно взять несколько VPS. Опять же — отказоустойчивость на 0. То есть весь ваш будущий рост будет только в наращивании мощности железа 1-ой машины, не логичнее ли планировать рост и масштабизацию нагрузки на кластер?


    1. doom369
      15.07.2015 15:51

      Потому что 1 железка — самый простой вариант. А параллелить — это сразу лоад балансер, код перепиливать и тд…


  1. mihmig
    15.07.2015 19:58

    Пользуясь случаем спрошу — а у связки PHP+Mysql нет решения типа «пула коннектов»?
    Понимаю, что коннект к базе занимает миллисекунды, но может можно как-то оптимизировать.Зачем 36 млн. раз коннектиться к базе?


    1. aktuba
      15.07.2015 22:16

      pconnect?


      1. mihmig
        16.07.2015 06:51

        Да, но я тут по «правильному» работаю — через PDO…


        1. Fesor
          16.07.2015 10:47

          PDO::ATTR_PERSISTENT => true


  1. stychos
    16.07.2015 00:03

    Вам огромное, огроменнейшее спасибо! А то, действительно, в сети миллионы статей про «хайлоад», но по сути везде крохи.


  1. LeonidZ
    16.07.2015 02:41
    +1

    У нас всего 10 млн запросов в час на nginx, требующих достаточно непростой обработки через nginx + php-fpm, локальный кеш, глобальный кеш и api «мозгового центра», который достает нужную инфу после авторизации подключившегося клиента из базы.
    Специально потратил полтора часа времени и на практике проверил все предложенные вами настройки (за исключением бессмысленных вроде выставления значений по-умолчанию и безумных вроде max_children 4000). Проверял не просто так — реально надеялся, что кто-то где-то упустил и смогу снизить нарузку/необходимое количество инстансов. После каждой значимой манипуляции рестартовал испытуемый сервер и сравнивал показатели с другими. Увы, не смог добиться положительного статистически значимого результата, хотя рост количества обработанных запросов даже на 2% посчитал бы значимым.


    1. BupycNet
      16.07.2015 08:55

      opcache настроен надеюсь?
      Кстати автор в статье про него тоже ничего не сказал. Вообще никакие настройки PHP не показал.


      1. LeonidZ
        16.07.2015 09:07

        естественно )


      1. RUnnerTomsk Автор
        20.07.2015 20:25

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


        1. Fesor
          21.07.2015 00:14

          ни каких опкэшей не использовать, чтобы для начала все хорошо отладить.

          А можно как-то этот момент аргументировать? Как по мне это кажется чуть более чем странным (учетом того что морда на симфони).


          1. LeonidZ
            21.07.2015 04:36
            +1

            Разработчик очень странный у вас. Он кодит и тестирует прямо на продакшене? Т.к. если нет, то вообще смысл отключения opcache не понятен. Его включение снизит нагрузку примерно раза в 4.


  1. chabapok
    16.07.2015 10:52

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


    1. Fesor
      16.07.2015 11:18
      +1

      у PHP есть persistent connection. То есть вместо того что бы закрывать соединение по окончанию обработки запроса, оно остается висеть и может быть реюзано следующим скриптом, который использует те же креденшелы. Естественно что это чуть сложнее, так как по окончанию работы скрипта вам обязательно надо завершить все транзакции и убрать локи, иначе это все перетянется на следующий запрос.


  1. inkvizitor68sl
    16.07.2015 13:36
    +1

    Такое ощущение, что настройки надерганы из разных мест наугад без «примерок».
    Хотя бы вот:
    my.cnf — open-files-limit = 150000
    limits.conf — * hard nofile 100000
    С nginx тоже самое.

    pm.max_requests = 0 помогает боротся с утечками памяти, в стороннем софте.
    Наоборот, мешает бороться с утечками, при том в своём коде — не рестартит fpm-процессы никогда (ну если шедуллер не решит их освободить и убить).

    И в итоге у вас там XtraDB или MyISAM? Что вы тюнили в конфиге mysql — совсем неясно.


    1. RUnnerTomsk Автор
      20.07.2015 20:26

      InnoDb
      Про max_request я выше отвечал — было 500 пока боролись. Потом стало 0.
      Лимиты файлов — подрезал, а то излишне раздул. И не везде уменьшил, это есть немного, да.


  1. yojick
    18.07.2015 20:23

    Присоединюсь к вопросу про HandlerSocket. Все условия для такого перехода есть: MySQL, InnoDB, множество быстрых однотипных операций вставки. Почему бы нет?


    1. RUnnerTomsk Автор
      20.07.2015 20:27

      Получил тут множество ценнейших советов. Будем копать во все стороны, в том числе и в эту.


  1. Garr
    21.07.2015 17:12

    Рекомендую попробовать поменять дефолтовый уровень изоляции мускуля с repeatable-read на read-commited. Может очень существенно поднять производительность (тут конечно все от задач). А вот держать на нуле innodb_flush_log_at_trx_commit — стремно имо. 2 хотя бы…
    Так же в зависимости от железок и системы с O_DIRECT. Может иметь обратный эффект.


  1. datacompboy
    12.08.2015 11:09
    +1

    Расскажите, а что будете делать, когда сервер ляжет?
    Сколько времени должен быть простой, чтобы стоимость второго сервера вылетела в трубу?