Помощь зала

Читают ли меня знатоки Apache/PHP/Mysql/Drupal и подобного, имеющие опыт работы под нагрузкой?
Надеюсь, читают.
Нужны идеи.
При небольшой в сущности нагрузке в четыре тысячи человек в день Либрусек сжирает весь процессор и начинает безбожно тормозить. Виртуальный хостинг вываливал CPU-квоту, реальный не вываливает, но от этого не сильно легче. Сотня посетителей onlain - и loadaverage стабильно больше 1. Это плохо.
Поменять сервер на более мощный - это не решение. Оно должно в перспективе держать нагрузку в сто раз больше, таких серверов не бывает.
Кто чего подскажет?
Имеет смысл менять апача на что-то другое, как сделала Вебпланета?
Что крутить у мускула?
Куда вообще смотреть?

Стоит всё более-менее по умолчанию.

Комментарии

Я не большой спец по Linux-овым решениям...
Но, даже такому ламмеру как я, очень мало информации. Ты бы хоть версии выдал, или конфигурацию сервера...
Вот первая нарытая ссылка http://www.devside.net/articles/apache-performance-tuning может чем поможет...
Порталы под нагрузкой хорошо бы хостить на нескольких машинах... Но тогда - это уже не хостинг за пятачок, и денежки стоит не малой.. :-(
Воопщем - ежели чего то я готов помогать, за халяву... Могу даже немного собственных мощностей выделить :-)

Какой % времени CPU в целом занимают в отдельности MySQL и apache.
Полагаю самая тяжелая нагрузка из-за queries к MySQL.
В общем все по чего где-либо сортируешь или делаешь join должно быть с индексами... Наверное в DB части модулей друпала все с индексов нормально (хотя можно и проверить).
По моему надо смотреть в первую часть на таблицы связаны с либе, и на еффективность queries к ним. Поиски в текстовых полях /и сортировка по текстовых/ жрут наибольше. Наверное можно и логи запустить на MySQL чтобы увидеть какие queries тормозят.
А так со сторону более менее шустро бегает...

>Поменять сервер на более мощный - это не решение. Оно должно в перспективе держать нагрузку в сто раз больше, таких серверов не бывает.

Это тоже решение. А реально можно разнести Апач и Мускул на две машины в локальной сети - условно фронт-энд и бак-энд, какая должна быть лучше определяется на практике.
В своё время тот же Грибов на своей игрушке выделил 4 слоя - БД, скрипты, Апач и внешний прокси со статическими картинками. Как он распределял их по машинам не знаю, но оборудование менялось на более мощное уже минимум 2 раза, если не 3: вроде у него сейчас 2 четырёхпроцессорных сервера с 4 и 8 Гб памяти и RAID, в хостинг-центре. Причём он фактически для разделения нагрузки между платными и бесплатными игроками (у платников приоритет, у бесплатников остатки) сооружал хитрую комбинацию из двух адресов, двух Апачей, настроенных по-разному, и то ли одного то ли двух "исходящих" прокси. Вдобавок squid ему не нравился, он ставил что-то другое, и вдобавок в качестве ОС выбрал не linux или freeBSD, а Solaris.
При всём различии в ваших подходах (он с самого начала собирался на этой игрушке деньги заколачивать, и сейчас она как минимум себя окупила), его опыт по работе с системами массового обслуживания - и игрой, и библиотекой - у него накопился, именно такой какой вам нужен. Другое дело что он своё ноу-хау вам не даст и может даже и не продаст, во всяком случае задёшево - разве что за 100.000$ ;) Жадный он стал.

Конкретнее, у старого Апача 1.3 надо было смотреть занимаемую память, число субпроцессов, их память, число обрабатываемых запросов на субпроцесс, число ожидающих запросов... и эти параметры должны были находиться в определённых соотношениях - в обшем чтобы не было своппинга.
У Мускуля надо было смотреть какие запросы отнимают максимальное время, потом выяснять почему, и или менять формулировку (текст SQL; встроенный оптимизатор не всегда оптимален), или перестраивать индексы (или вводить новые).
Сейчас давно с этим не работал, более конкретно уже не могу. :(

Это не решение, потому как сейчас нагрузка в сто раз меньше планируемой.
Я могу в перспективе разнести на два сервера, на четыре, но не на сто же.
Можно взять машину, у которой в два раза больше память, в четыре - но не в сто, опять же.
И, прежде чем тратить деньги и силы, хочется понять узкие места. А уж когда из одного сервера выжмем всё - брать второй. Или не брать, если окупаться не будет. Мой уровень благотворительности тоже не безграничен. По-любому, тормозить, как вентиляционная библиотека не хочется.
Надеюсь, что кто-то более опытный сразу увидит то, что я искал бы долго.

С запросами мускуля непонятно, видимо из-за la>1
Один и тот же запрос отрабатывается сильно разное время. Разница - в десятки раз. Как с этим разбираться - пока непонятно.
Лог на slow_quereis включил, SQL_CACHE местами добавил. Не думаю, что из меня оптимизатор запросов лучше мускульного, к тому же сложных запросов очень мало, обычно join на пару таблиц, сгруппировать/проссумировать/отсортировать. Упростить запрос можно только ценой потери функциональности. Срезать функционал - путь очевидный, простой, но чем-то меня не радующий.
Проблема, видимо, в том, что книжек на порядок больше, чем в остальных библиотеках. Хотя для БД сто тысяч записей - не так много должно быть. Оно должно на миллионах жить по-идее.

Вся суть copyleft - что кто-то тратит годы на работу, а остальные её не повторяют, а идут вперёд. Поэтому платить - дёшево или дорого, не важно - я не буду. В конце концов, масса инфы есть в Сети, мне просто сейчас некогда рыться. Припрёт - пороюсь.
И результаты скрывать, естественно, не буду.
Где смотреть, какие соотношения должны быть?
Свопинг был, по совету в ЖЖ уменьшил MaxRequestsPerChild - своп уменьшился с 500К до 90К. Уже что-то.

Вот тот фильтр: http://lib.rus.ec/list сам перегружает несколько раз, пока вводится слово на поиск и отмечаются чекбоксы. В итоге когда нужен один search он междувременно делает 2-3 лишние.
Ето ненужно круто... и очень ресурсоемко в смысле CPU /текстовой поиск... практически скан а не индекс /.
Сделай его с кнопку... Т.е. ввел что надо, нажал кнопку - и получил результат. И, чтобы не выдавал все что есть в библиотеке если все поля пусты /так делает сейчас когда стираю чтобы поменять заказ/.

я его мерил. Он намного быстрее поиска.
Там именно индекс - он же ищет по началу слова, строго как в базе лежит - название, фамилия, etc.
И роботы на него не ходят - не любят они js. Всё зло от роботов :)
С поиском намного хуже - он как-раз без индекса. Пытался настроить full-text, но что-то не завелось, не помню уже деталей.
Но он нечасто попадается - http://lib.rus.ec/ge жрёт больше.

Выдать всё что есть в библиотеке - SELECT * ORDER ID LIMIT 50 - это вообще бесплатный запрос. Какие-то невразумеИ полезный - считай, новости получил. Подфильтровал как надо и пользуйся.
Попробовал в phpMyadmin - (50 total, Query took 0.0009 sec)
Куда это еще оптимизировать?

Значит здесь оптимизировать нельзя, все как надо.
Как я понимаю MaxRequestsPerChild помогло. 90к своп 1gb ram... Сколько у тебя весит сама база? Что показывает iostat
Наверное также стоит просто подождать и увидеть что показывает лог на slow queries

как оказалось, в мускуле было выключено кэширование. Включил - стало получше.
iostat говорит command not found
база небольшая -
[root@lib mysql]# du -s librusec_Drupal/ librusec_lib/
131476 librusec_Drupal/
110268 librusec_lib/

>Я могу в перспективе разнести на два сервера, на четыре, но не на сто же.
Тем не менее... разделить на два сервера вам всё равно придётся. А пока такое разделение - IMHO простейший путь выяснить, кто же - Апач или Мускул - больше нуждается в настройке. Впрочем, если нет интенсивного своппинга, а нагрузка проца превышает 2 (именно 2, длину очереди процессов от 1 до 2 ещё можно считать нормальной!), то можно прикинуть по процессорному времени - кто съедает больше в %, пара процессов Мускула или все подпроцессы Апача в сумме.

larin написал:
Один и тот же запрос отрабатывается сильно разное время. Разница - в десятки раз. Как с этим разбираться - пока непонятно.

В одних случаях данные из кэша берутся, в других -- делается запрос к базе. Рекомендуется увеличение памяти для кеширования результатов запросов -- query_cache_size.

larin написал:
Лог на slow_quereis включил, SQL_CACHE местами добавил. Не думаю, что из меня оптимизатор запросов лучше мускульного, к тому же сложных запросов очень мало, обычно join на пару таблиц, сгруппировать/проссумировать/отсортировать. Упростить запрос можно только ценой потери функциональности. Срезать функционал - путь очевидный, простой, но чем-то меня не радующий.

На частых запросах сведи к минимуму join-ы, особенно между толстыми таблицами. Если join используется только для получения некоторой статистики (например кол-во книг у автора) можно ввести избыточное поле (num_books в таблице авторов) -- усложнится логика (надо будет пересчитывать значения поля), но уйдут один-два join-а.

Для всех частых запросов проверь explain-ом чтобы не было type=ALL в плане запроса (особенно по толстым таблицам). Если есть -- добавь необходимые индексы.

Вот пока все, что навскидку пришло в голову по описанным симптомам.

hedgehog написал:

В одних случаях данные из кэша берутся, в других -- делается запрос к базе. Рекомендуется увеличение памяти для кеширования результатов запросов -- query_cache_size.

Чего-то его в моём my.cnf такой строчки нету. Сколько поставить?

Добавил query_cache_size - мускул перестал запускаться. Его куда сувать-то?

разобрался - оно стояло в 0.
Поставил 16М - посмотрим на результат.

Ok. Информацию по текущему состоянию с кэшированием можно посмотреть c помощью SHOW STATUS LIKE '%qcache%'. Наиболее интересные

Qcache_queries_in_cache -- сколько запросов (точнее их результатов) сейчас в кэше, Qcache_hits - сколь результатов было взято их кэша, Qcache_free_memory - сколько свободно памяти для кэша.

ЗЫ: Проверь состояние SQL_QUERY_CACHE_TYPE -- должно быть 1 или ON.

Я уже нашел.
16 маловато, поставил 50.

mysql> SHOW STATUS LIKE 'Qcache%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 87 |
| Qcache_free_memory | 28555544 |
| Qcache_hits | 60330 |
| Qcache_inserts | 30688 |
| Qcache_lowmem_prunes | 13505 |
| Qcache_not_cached | 241 |
| Qcache_queries_in_cache | 4450 |
| Qcache_total_blocks | 9091 |
+-------------------------+----------+
8 rows in set (0.00 sec)

Статистика в основном отражает ситуацию с 16М.

Здравствуйте:

Очень трудно давать советы по оптимизации не зная ни структуры базы данных, ни структуры и параметров запросов к ней, тем не менее, несколько соображений "на вскидку"

1. Если уменьшение MaxRequestsPerChild приводит к уменьшению своппинга, значит, скорее всего где-то под PHP происходит "утечка памяти" - имеет смысл перезапустить сервер и в течении некоторого времени понаблюдать за размерами процессов httpd утилитой top или просто ps с соответствующим ключом. Если размеры процессов будут монотонно расти - значит где-то что-то "течет" и с этим надо бороться. В стабильной системе MaxRequestsPerChild должен быть установлен в 0 или быть достаточно большим числом.

2. Насчет кеширования запросов в MySQL - оно хорошо работает, но по моим наблюдениям, параметры кеширования надо увеличивать раза в 2-3 по отношению к тому, что приводится в руководстве MySQL. Например, у меня на одном из серверов стоит:

query_cache_size = 64M
thread_concurrency = 4

Этот сервер работает под FreeBSD и эти параметры позволили дотянуть его производительность почти до уровня аналогичного сервера под Linux (MySQL под FreeBSD работает медленнее чем под Linux) и сейчас он обслуживает порядка 30 запросов в секунду при загрузке процессора около 0.15.

3. Наверное самое главное: каков тип таблиц в базе данных? Если таблицы типа InnoDB (MySQL по умолчанию создает таблицы именно типа InnoDB), то это прекрасно объясняет непредсказуемые задержки, десятикратную (и более!) разницу во времени обработки одного и того же запроса и прочие эффекты этого рода. Все это объясняется тем, что лшюбой запрос к таблице InnoDB целиком блокирует эту таблицу, т.е. если один пользователь выполняет SELECT * ORDER ID LIMIT 50 (пусть этот запрос выполняется всего за 0.0009 sec) остальные пользователи будут ждать. Если таких пользователей несколько сотен, то время ожидания растет экспоненциально (причем ожидает не только пользователь но и процесс Apache-PHP который занимает не мало мегабайт в оперативной памяти), как только количество ожидающих процессов достигает некоего предела, начинается своппинг, т.е. система коллапсирует. Мне приходилось видеть подобные Apache-PHP-MySQL в состоянии коллапса - оно не отвечает ни на что, причем загрузка процессора обычно была чуть больше единицы (процесс своппинга за процесс не считается). Метод лечения проблемы прост - таблицы надо переделать под InnoDB, причем параметры в my.cnf надо подобрать. Если Вы пойдете этим путем, то или нипишите мне в личку или ответьте на этом форуме - с удовольствием поделюсь опытом. Отмечу только, что с таблицами InnoDB, одна довольно корявая система Apache-PHP-MySQL обслуживает порядка 1.2 миллиона запросов в сутки и начинает "загибаться" (появляются задержки 5-10 сек.) при 1.9-2 миллионов запросов/сутки. Все работает под FreeBSD, на 4-х процессорной машине с 4 Г. памяти.

4. Насчет "Вот тот фильтр: http://lib.rus.ec/list сам перегружает несколько раз, пока вводится слово..." - dzver абсолютно прав - это круто и... ненужно. Как только нагрузка на систему вырастет, это возможность придется убирать именно из-за того, что эти почти бесплатные запросы будут чуть-чуть, но тормозить систему, при росте числа одновременно работающих пользователей, задержки будут расти экспоненциально и пользоваться системой станет невозможно - такой фильтр начнает разбражать уже при задержке в 1 секунду.

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

6. На самый крайний случай - очень хорошие результаты по ускорению дает memcached (http://www.danga.com/memcached), но чтобы им воспользоваться, придется несколько модифицировать исходные тексты программ и стартовые скрипты системы. Опять таки, если Вас это заинтересует - буду рад поделиться (не очень богатым) опытом.

7. Забыл написать сразу: разумеется, надо увеличить размеры всяческих буферов MySQL (в файле /etc/my.cnf), например, на одном из моих серверов:

max_connections = 450
key_buffer = 384M
table_cache = 512
sort_buffer_size = 4M
read_buffer_size = 2M
read_rnd_buffer_size = 8M
myisam_sort_buffer_size = 64M
thread_cache = 80
skip-locking
skip-name-resolve
skip-networking

Мне, правда, показалось, что увеличение размеров буферов не приводит к радикальным изменениям - во всяком случае в моих тестах увеличение производительности было процентов на 10-15, не "в разы". Но попробовать, конечно, стоит. Последние две команды skip-... несколько увеличивают производительность MySQL в случае если в таблицах доступа все хосты указаны IP адресами и если все сосредоточено на одном сервере.

Я буду рад, если Вам пригодится что-то из вышенаписанного, если что-то вызвало какой-то интерес - пишите в личку или здесь, на форуме или, просто, на email.

1. А как бороться? Я думал, что при закрытии php-скрипта память освобождается, и внутри особо не беспокоился. Или искать ошибки в самом апаче?

2. У меня стоит 50. Вроде хватает.
mysql> SHOW STATUS LIKE 'Qcache%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 11781 |
| Qcache_free_memory | 15080328 |
| Qcache_hits | 2539483 |
| Qcache_inserts | 1182144 |
| Qcache_lowmem_prunes | 530386 |
| Qcache_not_cached | 6128 |
| Qcache_queries_in_cache | 12066 |
| Qcache_total_blocks | 38912 |
+-------------------------+----------+

Большинство запросов в кэш попадает. Или добавить?

3. Всё по умолчанию. Переделывать честно говоря не хочется, но если припрёт - займусь. Как я понимаю, это mysqldump/drop db/create db/mysqlimport, или есть тонкости?

4.Придётся - уберу. Только поиск грузит намного больше - иногда запрос отрабатывается по несколько секунд.

5. Диск, к сожалению, всего один, и разумной возможности добавить второй пока нет. У текущего хостера проще добавить второй компьютер, дешевле обойдётся.

6. Хотелось бы пока обойтись без экстремизма.

7. Спасибо, попробую. Боюсь, на столь кардинальные увеличения у меня памяти не хватит - её всего гиг.

Попробую прокомментировать:

1. А как бороться? Я думал, что при закрытии php-скрипта память освобождается, и внутри особо не беспокоился. Или искать ошибки в самом апаче?

Теоретически, php должен память освобождать, но как оно происходит практически - бог его знает, возможно там есть ошибки и в самом php и в его библиотеках. Лучше всего понаблюдать за размером процессов Апача и потом уже решать. Если php работает с каким-либо ускорителем ("zen" чего-то там или e-accelerator), то вероятность наткнуться на ошибку выше. Я наблюдал как скрипты php с e-accelerator через несколько дней работы начинали работать очень медленно. перезапуск Апача и MySQL решал проблему еще на несколько дней. Лучше всего, все-таки, внимательно посмотреть на них под top. Ошибку в Апаче искать, наверное, смысла нет - Апач это очень "вылизанная" программа.

2. У меня стоит 50. Вроде хватает....

Здесь чем больше, тем лучше. К сожалению, по распечатке переменных не видно сколько всего запросов было получено и следственно непонятно сколько "пролетело мимо" кеша. Наверное, можно и увеличить - если памяти хватает.

3. Всё по умолчанию. Переделывать честно говоря не хочется, но если припрёт - займусь. Как я понимаю, это mysqldump/drop db/create db/mysqlimport, или есть тонкости?

По умолчанию - это значит InnoDB. Скорее всего, причина замедления именно в этом - в полной блокировке ВСЕЙ таблицы ЛЮБЫМ запросом. Честно говоря, я бы начал именно с перестроения таблиц под InnoDB. Перестроение (хотел написать "перестройку", но задумался :)) можно делать через mysqldump/drop db/create db/mysqlimport, но можно сделать и через MODIFY TABLE ... TYPE=InnoDB - это безопасно, но лучше делать в offline (и, конечно, сделать backup). Тонкостей особых нет, но не все то что поддерживается в InnoDB поддерживается в InnoDB, например INSERT ... DELAYED и full-text search под InnoDB не работают. Перед перестроеникем таблиц, лучше всего попрактиковаться на домашнем компьютере: переустановить БД с таблицами InnoDB и десятком записей и прогнать все запросы. Это не очень приятно, но по моему опыту, неизбежно - для серьезной системы с серьезным трафиком InnoDB практически не пригоден.

4. Придётся - уберу. Только поиск грузит намного больше - иногда запрос отрабатывается по несколько секунд.

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

В принципе, это можно проверить если в момент ожидания выполнить:

mysqladmin -u ... -p processlist

Есть еще полезная утилита mytop - http://jeremy.zawodny.com/mysql/mytop/

5. Диск, к сожалению, всего один, и разумной возможности добавить второй пока нет. ...

Это плохо. А как быть с резервным копированием?

6. Хотелось бы пока обойтись без экстремизма.

На самом деле добавить memcached не так уж сложно, особенно если структура программ хорошо известна.

7. Спасибо, попробую. Боюсь, на столь кардинальные увеличения у меня памяти не хватит - её всего гиг.

Один гиг - это крайне мало, особенно если переходить на InnoDB - там все просто: чем больше памяти, тем быстрее. Но если не переходить на InnoDB, то система рухнет при самом незначительно увеличении нагрузки. Даже если просто увеличить размеры буферов, то с учетом того, что процессы Апач-PHP постоянно сидят в памяти при незначительном росте числа одновременно пришедших запросов (на уровне воскресных флуктуацию) можно попасть в зону своппинга из которого система уже самостоятельно не выйдет.

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

2. Как же не видно.
Qcache_hits | 2539483 - взяли из кэша
Qcache_inserts | 1182144 - не нашли, добавили в кэш
Qcache_lowmem_prunes | 530386 - удалили редкоиспользуемое
Qcache_not_cached | 6128 - не смогли закэшировать по каким-либо причинам.
Qcache_queries_in_cache | 12066 - хранится в кэше
Итого из 2539483 + 1182144 + 6128 = 3.727.755 запросов 2539483 (68%) взяты из кэша. На мой взгляд неплохо. И серверу полегчало заметно. А главное - оный эффект достигнут изменением одной (одной!) переменной в конфигурации. Собственно, ради таких трюков я этот топик и затеял.

3. INSERT ... DELAYED и full-text search не использую. Надо глянуть в потроха друпала, но думаю что вряд ли, там всё просто. А имеет ли смысл при общей памяти в гиг? Выглядит несложным, можно и попробовать.

4. Причины тормозов поиска очевидны. Запрос like 'Иванов%' ищет в индексе, like '%Иванов%' перерывает всю базу. Имея под сотню тысяч книг и больше 20 тысяч авторов получаем тормоза. Логичного пути их преодоления я не вижу, кроме как вообще отказаться от поиска - посему и пихаю фильтр-список во все дыры. При всей своей внешней крутости он намного ближе к структуре базы и в силу этого работает несравнимо быстрее.
За mytop спасибо.
MySQL on localhost (5.0.27-log)
Queries: 4.8M qps: 59 Slow: 0.0 Se/In/Up/De(%): 61/00/00/00
qps now: 50 Slow qps: 0.0 Threads: 4 ( 1/ 0) 72/00/00/00
Key Efficiency: 99.2% Bps in/out: 0.1/ 11.3 Now in/out: 8.3/ 1.3k

Id User Host/IP DB Time Cmd Query or State
-- ---- ------- -- ---- --- ----------
105852 root localhost test 0 Query show full processlist
106969 librusec_ localhost librusec_D 0 Sleep
106972 librusec_ localhost librusec_l 0 Sleep
106967 librusec_ localhost librusec_l 4 Sleep
Это хорошо или плохо?

5. Идёт на совершенно другой сервер. Сейчас можно за $5 взять хостинг с 300-500G места, для работы он не годится из-за cpu-quota и невозможности настроек, типа обсуждаемых, а для бэкапа - идеален. Думаю, открою доступ к бэкапам всем желающим - а то постоянно просят выкачать всё.

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

7. Я понимаю, что мало. Задача на минимальных ресурсах заставить работать с текущей (небольшой на самом деле) посещаемостью - тогда по мере роста нагрузки можно будет тупо взять сервер помощнее, тем более что они дешевеют со страшной скоростью.
Понятно, что можно взять восьмиядерную машину с 64G рам и всё будет летать. Только как это потом масштабировать?
Хочется выжать максимум из существующего железа, а когда упрусь - тогда уже добавлять.

larin написал:
4. Причины тормозов поиска очевидны. Запрос like 'Иванов%' ищет в индексе, like '%Иванов%' перерывает всю базу. Имея под сотню тысяч книг и больше 20 тысяч авторов получаем тормоза. Логичного пути их преодоления я не вижу, кроме как вообще отказаться от поиска - посему и пихаю фильтр-список во все дыры. При всей своей внешней крутости он намного ближе к структуре базы и в силу этого работает несравнимо быстрее.

Да, от таких запросов надо категорически избавляться. Есть для такого случая решение. Создается таблица для специального инверсного индекса фрагментов имён (вроде full-text только похитрее). На практике получается так: таблицы author <-> author2keyword <-> auth_keyword. После чего, для автора например "Иванов, Сергей Петрович" в таблицу auth_keyword создаются и прилинковываются следующие "ключевые слова" (на самом деле фрагменты): "иванов", "ванов", "анов", "нов", "ов" (можно еще и "в" -- но вряд ли поиск по одной букве в любой части имени имеет серьезный смысл -- "найти всех авторов с буквой А в середине"). Тоже самое делается с именем и отчеством (если таковое есть). Когда нужно кого-то найти по фрагменту, то просто делаешь запрос "search%" к auth_keywords и join-ом получаешь авторов.

Выглядит логично.
Когда опять возникнет перегрузка - обдумаю.
Смущает размер таблицы.
Сотня строк на автора - 200 000 на всех, плюс еще миллион на названия книг, плюс сериалы, то-сё - поиск-то ищет по всему что только можно. А иначе толку с того поиска?
Большие таблицы будут жрать память и вымывать кэши...
Неприятно, что её придется переделывать при любых изменениях - переименовывании авторов, например. Усложняет логику. Не люблю.
Может быть, проще будет забить и искать только по целым словам.
Всё бы было хорошо, да только название книги бывает в кавычках, причём - разных. Это, впрочем, решаемо.

Так ключевое слово (фрагмент) вносится только один раз и таких фрагментов будет немного (например имена и отчества будут сплошь повторяться). В любом случае это предложение касается только авторов. Для названий книг и сериалов лучше использовать стандратный full-text search (а еще лучше Lucene - насколько я знаю Zend портировал его на РНР). Кстати, предложенное решение в некотором роде и есть full-text search, только с кастомной токенизацей (разбиенем на слова). А насчет работоспособности подобоного решения -- я участвовал в проекте, в котором сейчас более 40 млн. записей (правда там решение было несколько сложнее - более 1000 таблиц индексов) и ничего - работает.

Спасибо за Ваши комментарии, еще пара заметок:

1. Ошибки в php и его библиотеках безусловно есть. ... - конечно, целенаправленно искать эти ошибки большого смысла нет, надо просто убедиться, что в "своем" коде не происходит "утечек памяти". Мне говорили, что PHP по окончании скрипта все закрывает и все очищает, но мне все-таки кажется, что должны быть способы вызвать в нем "утечку памяти", например путем некоректного использования каких-либо библиотек. Такие, потенциально опасные места в коде лучше просто избегать, а если они уже есть, то тщательно протестировать и убедиться, что там действительно "не течет".

Итого из 2539483 + 1182144 + 6128 = 3.727.755 запросов 2539483 (68%) взяты из кэша. На мой взгляд неплохо. И серверу полегчало заметно.

Да, это неплохой результат. То что серверу заметно полегчало - это, скорей всего, при малой загрузке. По мере роста загрузки на сервер основные потери времени будут происходить не из-за поиска данных в таблицах или в кеше, а на порождение новых процессов, коммутацию существующих, своппинг и уборку за умершими процессами, т.е. по мере роста числа запросов к сайту, основная часть времени процессора и основной объем памяти будет уходить на "накладные расходы" операционной системы, MySQL и PHP.

А главное - оный эффект достигнут изменением одной (одной!) переменной в конфигурации.

:-) К сожалению, таких мест (где заметный эффект достигается изменением одного параметра) очень мало и они быстро кончаются. После этого приходится делать нудную, грязную и кропотливую работу чтобы добиться хоть какого-либо продвижения. Увы, в системном администрировании "серебряной пули" так же не существует, как нет ее и в программировании, как нет ее и в медицине.

А имеет ли смысл при общей памяти в гиг? Выглядит несложным, можно и попробовать. Надо анализировать причины торможения системы. Если система замедляется из-за блокировки таблиц при одновременном получении нескольких запросов, то смысл есть. Надо учитывать, что при росте числа обращений к сайту взаимная блокировка таблиц будет случаться все чаще и чаще, т.е. в самом недалеком будущем переделывать базу под InnoDB придется все равно. Как я уже писал, таблицы InnoDB для серьезной работы практически непригодны.

При переводе системы под InnoDB надо еще учитывать следующее:

1. В "тепличных условиях" (одиночный запрос на незанятой системе) InnoDB работает медленнее чем InnoDB.

2. Сразу после запуска MySQL, InnoDB работает еще медленнее - ему надо "насосать данных" в кеши.

3. При аварийном завершении MySQL, таблицы InnoDB восстановления, как правило, не требуют - и это очень хорошо. Однако, если в таблице InnoDB есть данные типа BLOB и длина записей составляет 300-500 Кбайт, то есть вероятность при аварийном завершении MySQL получить полностью испорченную базу данных - в этом случае портится ВСЯ база и восстановить ее можно ТОЛЬКО с резервной копии. Я регулярно сталкивался с такой ситуацией на одном сервере, где MySQL "сыпался" из-за ошибки в драйвере диска, надеюсь, что порча БД происходила тоже из=за этой ошибки, на других системах с InnoDB подобных ситуаций я не видел, там при перезагрузке сервера или "слете" MySQL БД просто восстанавливалась автоматически и никакого ручного вмешательства не требовалось.

4. InnoDB хранит все таблицы в одном файле неимоверно большого размера. Чтобы сделать резервную копию такой базы надо или останавливать MySQL и копировать файл "как файл", или пользоваться mysqldump, или покупать у разработчиков InnoDB программу "горячего" копирования или делать копирование своими средствами с использованием семантики данных. Возможен впрочем еще и вариант с репликацией БД на другой сервер и копирования БД с этого сервера с остановкой MySQL.

Несмотря на все эти минусы, "жить" с InnoDB вполне можно, надо просто учитывать все эти обстоятельства.

Причины тормозов поиска очевидны. Запрос like 'Иванов%' ищет в индексе, like '%Иванов%' перерывает всю базу. Имея под сотню тысяч книг и больше 20 тысяч авторов получаем тормоза. Логичного пути их преодоления я не вижу, кроме как вообще отказаться от поиска - посему и пихаю фильтр-список во все дыры. При всей своей внешней крутости он намного ближе к структуре базы и в силу этого работает несравнимо быстрее.

К сожалению, фильтр-список увеличивает количество запросов к БД примерно на порядок. Как я уже писал, на загруженной системе играет роль не то сколько времени надо, чтобы ответить на запрос, а играет роль само наличие или отсутствие запроса. Если мы на каждый "реальный" запрос (пользователь ищет автора по фамилии и знает фамилию целиком) делаем десять обращений к системе (предположим, средняя длина фамилии 10 символов), то это значит, что система способная обслужить 1000 пользователей одновременно, реально будет обслуживать всего 100 или, другими словами говоря, там где мы имеем 4000 человем в день, мы могли бы иметь 40000 (счастливых) человек в тот же день.

Id User Host/IP DB Time Cmd Query or State -- ---- ------- -- ---- --- ---------- 105852 root localhost test 0 Query show full processlist 106969 librusec_ localhost librusec_D 0 Sleep 106972 librusec_ localhost librusec_l 0 Sleep 106967 librusec_ localhost librusec_l 4 Sleep Это хорошо или плохо?

Это совершенно свободный, ничем не занятый MySQL :). Наверное, это распечатка сделана в тот момент когда система работала хорошо. На самом деле, подобные измерения надо делать тогда когда система "подвисает", причем мерять надо несколько раз и пытаться выявить какие-то закономерности.

Может быть имеет смысл поставить "mysqladmin ... processlist >some.log.file" на периодическое выполнение и оставить работать на сутки, потом проанализировать какие запросы обрабатывались медленнее всего в моменты "зависания" системы? Это, правда, потребует мучительного анализа длиннющего файла, но другого пути, наверное и нет ("серебряную пулю" еще не придумали :)). В моем случае, в этом протоколе, начиная с какого-то момента выстраивалась длиннющая очередь вполне безобидных запросов SELECT, и это безобразие начиналось с того момента когда время обработки одного запроса SELECT становилось сравнимым со временем до поступления следующего запроса, причем это могло произойти не только когда поступало много запросов, но и когда какой-то запрос обрабатывался чуть медленнее, например из-за свопинга или просто небольшого замедления системы, например во время резервного копирования.

Понятно, что можно взять восьмиядерную машину с 64G рам и всё будет летать. Только как это потом масштабировать?

:) Программисты шутя и играя способны "подвесить" любую машину, с любым количеством процессоров :). Я видел 16-ти процессорный сервер БД, который регулярно "зависал" из-за плохо построенных запросов MySQL: программист писавший эту часть системы убедился, что все отлично работает на его настольной машине сБД из сотни записей, но не подумал как оно будет работать при миллионах записей в базе данных. Проблема была решена путем незначительной оптимизации нескольких запросов выявленных путем анализа журнала slow queries.

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

Это правильный подход, трудно только понять, что является теоретически достижимым максимумом для существующего железа. Может быть четыре тысячи человек в день и есть тот самый максимум? А может быть этот максимум на самом деле гораздо выше, скажем 100 тысяч человек в день, а может быть он где-то посредине, скажем, 50 тысяч человек в день, но при условии, что одновременно к сайту обращаются не более 5 человек (условие заведомо не выполнимое).

Я бы, лично, InnoDB не рекомендовал... Во-первых, оно сильно тормознутое. Во-вторых, оно плохо живёт под реальными нагрузками. В третьих - оно несколько недоделанное...
В любом случае - до тех пор, пока не нужна поддержка транзакций - InnoDB в MySQL использовать не следует.
А то, что InnoDB не годится для больших нагрузок - так это просто не соответствует действительности. И то, что при запросе к InnoDB блокируется вся таблица - это тоже, хмм... - как правило, неверно. Как правило - таблица не блокируется вообще :-) Факт блокировки таблицы - он, конечно, допустим, но в нормально спроектированных системах это экстраординарное событие.

Но на самом деле - информации приведено недостаточно - нельзя судить, что происходит, и почему оно неправильно. Но, вот например, было сказано, что поиск "по тексту" есть, а система полнотекстового поиска - не включена. А это уже несерьёзно...

В общем, я полагаю, что проблемы - они не в MySQL, они в голове... Например, база данных криво спроектирована.

Спасибо, Stager, за Ваш очень интересный комментарий, не могу удержать себя, чтобы не вставить свои 5 коп:

Я бы, лично, InnoDB не рекомендовал... Во-первых, оно сильно тормознутое.

Да. я уже писал, что InnoDB работает медленнее чем InnoDB, я проводил измерения, если выделить MySQL с InnoDB достаточно памяти, то получается, что таблицы InnoDB при объемах где-то 500 тыс.записей работают примерно на 10% медленнее чем под InnoDB. В моем случае (система реального времени) было принято решение, что лучше замедлить на 10% обработку каждого запроса нежели "подвешивать" систему очередями из "вечноожидающих" запросов.

Во-вторых, оно плохо живёт под реальными нагрузками.

Здесь надо смотреть на реальную систему. Мне пришлось переводить под InnoDB одну систему FreeBSD-PHP-MySQL, под InnoDB система "зависала" (из-за катастрофического роста очередей ожидающих запросов) при 900 тыс.- 1 млн. запросов/сутки, после перевода под InnoDB, система стала замедляться при где-то 2 млн. запросов/сутки. Система находится в непрерывной эксплуатации, за 1.5 г. работы с InnoDB замечаний, собственно, к InnoDB не было. Сейчас у меня в работе находится другая система с распределенной БД на 6-ти серверах, нагрузки более чем реальные (если интересуют конкретные цифры, давайте обсудим по email или в личных сообщениях), замечаний к устойчивости и стабильности InnoDB тоже пока (пол-года эксплуатации) не было.

В третьих - оно несколько недоделанное...

Честно говоря, особой "недоделанности" я в InnoDB не заметил и я был бы очень благодарен, если бы Вы могли подробнее поделиться Вашими соображениями по этому поводу.

В любом случае - до тех пор, пока не нужна поддержка транзакций - InnoDB в MySQL использовать не следует.

Я не могу с этим полностью согласиться: когда у Вас незапланированно перезагружается сервер с таблицей InnoDB на несколько миллионов записей и MySQL полчаса занимается перестроением индексов (а Ваши пользователи отдыхают, а руководство обрывает телефоны однообразно повторяя "скоро? скоро?! скоро?!!"), некоторые преимущества InnoDB становятся более очевидными.

И то, что при запросе к InnoDB блокируется вся таблица - это тоже, хмм... - как правило, неверно.

Вообще-то, я это прочитал в руководстве пользователя по MySQL ("MySQL Reference Manual for version 4.0.12", раздел "5.3.2 Table Locking Issues"):

MySQL uses table locking (instead of row locking or column locking) on all table types, except InnoDB and BDB tables, to achieve a very high lock speed. ...

и далее (в свое время, я на это и купился):

Table locking enables many threads to read from a table at the same time,...

Но в реальности оказалось:

Table locking is, however, not very good under the following senario:

* A client issues a SELECT that takes a long time to run.
* Another client then issues an UPDATE on a used table. This client will wait until the SELECT is finished.
* Another client issues another SELECT statement on the same table. As UPDATE has higher priority than SELECT, this SELECT will wait for the UPDATE to finish. It will also wait for the first SELECT to finish!

причем, все впадало в ожидание даже без "Another client then issues an UPDATE", а так, ждало пока первый SELECT не выполнится.

Как правило - таблица не блокируется вообще :-)

Мне пришлось проверить это экспериментально. Схема проверки:

1. Берем таблицу InnoDB на 8-10 миллионов не очень длинных записей;

2. Из одной сессии MySQL (из-под монитора mysql) запускаем SELECT * WHERE НеИндексированноеПоле="чему-либо не существующему"; Такой запрос выполняется несколько минут;

3. Не дожидаясь завершения запроса 2, из другой сессии MySQL с той же базой выдаем запрос SELECT * WHERE ИндексированноеПоле="чему-либо существующему";

4. Убеждаемся, что запрос 3 выполнится только после завершения запроса 2;

5. Разнообразим проверку, "убивая" (mysqladmin) запрос 2 сразу после выдачи запроса 3, убеждаемся, что запрос 3 выполняется моментально после завершения запроса 2.

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

Факт блокировки таблицы - он, конечно, допустим, но в нормально спроектированных системах это экстраординарное событие.

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

Но, вот например, было сказано, что поиск "по тексту" есть, а система полнотекстового поиска - не включена. А это уже несерьёзно...

Мне кажется, тут произошла терминологическая путаница, уважаемый larin скорее всего имел в виду, что реализован поиск по тексту, но при этом не используется расширение full text search реализованное в MySQL.

В общем, я полагаю, что проблемы - они не в MySQL, они в голове...

Хорошее заключение, пригодное на все случаи жизни, к сожалению, это утверждение, как бы сказать, не конструктивно.

Например, база данных криво спроектирована.

В реальной жизни, не-криво-спроектированная БД бывает только в учебнике по программированию и еще ее можно найти в головах руководства во время, например, ежегодного отчета перед Вышестоящими. Во всех остальных случаях, нам приходится работать с криво спроектированными базами данных, потому что в реальной жизни у нас не бывает желания/времени/денег/возможностей (нужное подчеркнуть), чтобы взять уже работающую систему и перепроектировать БД (а затем переписать, проверить и отладить сотни тысяч строк исходного кода) только для того, чтобы сделать ее "не кривой" - причем, "не кривой" только по отношению к нескольким из множества взаимоисключающих требований предъявляемых к реально работающей системе.

я проводил измерения, если выделить MySQL с InnoDB достаточно памяти, то получается, что таблицы InnoDB при объемах где-то 500 тыс.записей работают примерно на 10% медленнее чем под InnoDB.

С использованием транзакций или без?

Честно говоря, особой "недоделанности" я в InnoDB не заметил и я был бы очень благодарен, если бы Вы могли подробнее поделиться Вашими соображениями по этому поводу.

Это Петя Зайцев на мастер-классе сказал. Но я, признаться, не вдавался.

когда у Вас незапланированно перезагружается сервер с таблицей InnoDB на несколько миллионов записей и MySQL полчаса занимается перестроением индексов...

Так это отличный результат! У меня тут дважды пришлось Oracle переустанавливать и базы по-новой импортировать из-за "незапланированной перегрузки сервера" :-)

Как правило - таблица не блокируется вообще :-) Мне пришлось проверить это экспериментально. Схема проверки:

Ну я и имел в виду, что не надо так делать. Запросов без использования индексов в нормальном случае быть не должно. Он действительно блокирует таблицу - как написано :-) Но да - я верю, что на чтение - не блокирует. Но не проверял. Может быть - проблемы в базовой операционной системе? В Win, скажем, очень много трудностей с многозадачностью...

Мне кажется, тут произошла терминологическая путаница, уважаемый larin скорее всего имел в виду, что реализован поиск по тексту, но при этом не используется расширение full text search реализованное в MySQL.

Ну дык опять же - не надо так делать. Это расширение для того и создано. И работает нормально.

В общем, я к чему - мне кажется, что сменить тип движка СУБД - это чрезмерно радикальное решение с неоднозначными последствиями. Думаю, что для начала нужно принять обычные меры - спрофилировать запросы, построить индексы. А если не поможет - вот тогда задуматься о радикальных мерах.
Впрочем - мы так и не знаем? Может, Larin это всё уже сделал?

Я пока ничего особо не делал, кроме уже описанного.
Гложат меня сомнения, что после включения кэша запросов мускул перестал быть узким местом.
Поисковые запросы - они конечно долгие, но их очень мало.
А всякие навороченные joinы теперь кэшируются с эффективностью 75-80%. Достаточно.
Теперь буду думать, как ускорить друпала.

Гложат меня сомнения, что после включения кэша запросов мускул перестал быть узким местом.

Если в момент "зависания" системы очередь запросов к MySQL (см. mysqladmin ... processlist или mytop) выглядит "по-человечески" (т.е. в очереди мало запросов, время выполнения запросов на среднем уровне), то "узкое место" скорее всего не в MySQL. Если при этом имеет место несколько процессов httpd огромного размера (смотрим через top с командой "m"), то надо исследовать DruPal. Если в момент "зависания" имеет место длинная очередь запросов MySQL и множество ждущих процессов httpd, то "узкое место" все-таки в MySQL.

Просмотрел логи - в моменты зависания (la>10) mysqladmin ... processlist показывает 1-2 запроса.
Время запросов (по slow-queries-log) может быть несуразным. Скажем, SELECT 2 может отрабатывать пару секунд. Думаю, из-за общей загрузки.
Процессор жрёт в основном httpd, память тоже он.

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2430 mysql 20 0 212m 81m 3912 S 4.0 8.1 48:00.61 mysqld
15622 apache 20 0 45244 22m 3628 R 12.0 2.3 0:00.85 httpd
15518 apache 20 0 43812 21m 4160 S 0.0 2.2 0:01.06 httpd
15607 apache 20 0 43840 21m 3668 S 0.0 2.1 0:00.40 httpd
6514 apache 20 0 43664 21m 3660 S 0.0 2.1 0:00.44 httpd
15710 apache 20 0 43492 21m 3620 S 8.0 2.1 0:00.95 httpd
15620 apache 20 0 43516 21m 3644 S 1.0 2.1 0:00.50 httpd
7085 apache 20 0 43548 21m 3628 S 0.0 2.1 0:00.55 httpd
14095 apache 20 0 43552 21m 3620 S 0.0 2.1 0:00.54 httpd
14092 apache 20 0 43368 20m 3628 S 0.0 2.1 0:00.90 httpd
15621 apache 20 0 43180 20m 3560 S 1.7 2.1 0:00.46 httpd
15548 apache 20 0 42988 20m 3640 S 0.0 2.1 0:00.39 httpd
15590 apache 20 0 42948 20m 3632 R 6.7 2.1 0:00.68 httpd
14625 apache 20 0 42992 20m 3628 S 0.0 2.1 0:00.41 httpd
5335 apache 20 0 42992 20m 3616 S 0.0 2.1 0:00.43 httpd
15703 apache 20 0 42960 20m 3620 S 0.0 2.0 0:00.48 httpd
15511 apache 20 0 42952 20m 3628 S 0.0 2.0 0:00.49 httpd
13365 apache 20 0 42676 20m 3640 S 0.0 2.0 0:00.73 httpd
13599 apache 20 0 42732 20m 3640 S 0.0 2.0 0:00.70 httpd
15508 apache 20 0 42684 20m 3584 S 0.0 2.0 0:00.35 httpd
15420 apache 20 0 42452 20m 3636 S 0.0 2.0 0:00.38 httpd
15623 apache 20 0 42500 19m 3484 R 10.3 2.0 0:00.32 httpd
13271 apache 20 0 42280 19m 3580 S 0.0 2.0 0:00.38 httpd
15712 apache 20 0 36568 13m 3300 R 6.3 1.4 0:00.19 httpd
15713 apache 20 0 36160 13m 3328 R 6.3 1.4 0:00.19 httpd
15769 apache 20 0 33932 11m 3272 R 4.3 1.1 0:00.13 httpd
16713 root 20 0 27904 6152 4408 S 0.3 0.6 0:02.71 httpd
15766 apache 20 0 10736 5748 2552 S 3.3 0.6 0:00.10 2.pl
15772 apache 20 0 10740 5744 2552 S 3.3 0.6 0:00.10 2.pl
15440 apache 20 0 28172 5272 2920 S 0.0 0.5 0:00.01 httpd
15277 apache 20 0 28040 3944 1852 S 0.0 0.4 0:00.01 httpd
15732 apache 20 0 28044 3932 1860 S 0.0 0.4 0:00.00 httpd
5156 apache 20 0 28040 3924 1860 S 0.0 0.4 0:00.05 httpd
15711 apache 20 0 28040 3924 1860 S 0.0 0.4 0:00.00 httpd
15694 apache 20 0 28040 3920 1860 S 0.0 0.4 0:00.00 httpd
6214 apache 20 0 28040 3912 1852 S 0.0 0.4 0:00.06 httpd
6609 apache 20 0 28040 3912 1852 S 0.0 0.4 0:00.06 httpd
6610 apache 20 0 28040 3912 1852 S 0.0 0.4 0:00.04 httpd
15531 apache 20 0 28040 3912 1852 S 0.0 0.4 0:00.01 httpd
15702 apache 20 0 28040 3912 1852 S 0.0 0.4 0:00.00 httpd
15738 apache 20 0 28040 3912 1852 S 0.0 0.4 0:00.00 httpd
5479 apache 20 0 28040 3876 1824 S 0.0 0.4 0:00.06 httpd
13361 apache 20 0 28040 3876 1824 S 0.0 0.4 0:00.04 httpd
15036 apache 20 0 28040 3872 1824 S 0.0 0.4 0:00.00 httpd
15319 apache 20 0 28040 3864 1812 S 0.0 0.4 0:00.00 httpd
13361 apache 20 0 28040 3876 1824 S 0.0 0.4 0:00.04 httpd
15036 apache 20 0 28040 3872 1824 S 0.0 0.4 0:00.00 httpd
15319 apache 20 0 28040 3864 1812 S 0.0 0.4 0:00.00 httpd
15712 apache 20 0 28040 3864 1812 S 0.0 0.4 0:00.00 httpd
15737 apache 20 0 28040 3864 1812 S 0.0 0.4 0:00.00 httpd
15763 apache 20 0 28040 3848 1808 S 0.0 0.4 0:00.00 httpd

Просмотрел логи - в моменты зависания (la>10) mysqladmin ... processlist показывает 1-2 запроса.

Это неплохо. А какое время стоит в колонке "Time": "разумное" или не очень?

Скажем, SELECT 2 может отрабатывать пару секунд. Думаю, из-за общей загрузки.

Да, это возможно, но тогда получается система "балуется" своппингом? В принципе, это можно проверить командой "vmstat 1" - внимательно понаблюдать за цифрами в колонках si и so и сравнить с таковыми для "не висящей" системы.

Процессор жрёт в основном httpd, память тоже он.

Я наблюдал подобный эффект на скриптах PHP но в моем случае это проявлялось через несколько дней непрерывной работы с большим количеством запросов, но у меня PHP работает с e-accelerator'ом (пожелание Заказчика :)), и я думаю, эти эффекты происходят из-за ускорителя. Ситуация нормализуется при перезапуске Апача по "kill -1 `cat /var/run/httpd.pid`". Хотя бывают исключения и kill -1 не помогает, тогда приходится делать полный рестарт Апача вместе с MySQL. Может быть попробовать перезапустить Апач, именно когда система "висит"? Кстати, можно еще поэкспериментирвать с параметром nice, например повысить приориет MySQL, или наоборот, понизить его - конечно, все это лучше делать при "висящей" системе.

Свопингом баловалась, пока апачу не объяснили, что детей надо давить почаще. Помогло.
Акселераторов пока нет.
Я так и поступаю - висит в кроне скрипт, который при la>10 останавливает апача, через минуту запускает.
Сервер теперь не виснет, но криво это как-то.
Сейчас вычисляю самые загруженные страницы и перевожу в статику.
Мне не нравится, что запуск пустого друпала на незагруженной машине жрёт почти пол секунды. Надо бы ужать раза в три.

Свопингом баловалась, пока апачу не объяснили, что детей надо давить почаще. Помогло.

Это надо смотреть именно в момент "подвисания" сервера и именно vmstat или чем-то подобным, поскольку занятый объем области swap, как правило, ни о чем не говорит , например в swap может быть выгружено всего 10 страниц, но они могут загружаться и выгружаться с частотой 5000 страниц в сек и наоборот, процессы впадающие в состояние "безнадежного" ожидания выгружаются в swap и спокойно "спят" там, объем занятого пространства в swap вырастает до десятков Гбайт. система работает как ни в чем не бывало.

Я так и поступаю - висит в кроне скрипт, который при la>10 останавливает апача, через минуту запускает.

Я бы порекомандовал попробовать не останавливать/запускать, а просто выдавать Апачу сигнал -1 (HUP) - это не обязательно должно помочь, но если сработает то можно будет обойтись без 1 минуты ожидания и соответственно без потери запросов. Возможно, тут может понадобится подбор порога load average.

Мне не нравится, что запуск пустого друпала на незагруженной машине жрёт почти пол секунды.

Это может быть связано с тем, что интерпретатор PHP должен скомпилировать исходные тексты в интерпретируемый псевдо-код, тогда это не ужмется.

Что насчет mod_evasive?:
http://www.eth0.us/mod_evasive
http://www.mydigitallife.info/2007/08/15/install-mod_evasive-for-apache-to-prevent-ddos-attacks/
От резко возрастающей загрузки от сканов роботов должно защитить более-менее нормально.

в Федоре его нет.
Если не смогу побороть более простыми средствами - попробую прикрутить.

Спасибо за Ваши замечания, Stager, было очень приятно почитать. Попробую уточнить некоторые моменты:

я проводил измерения ...

С использованием транзакций или без?

Конечно без, один и тот же запрос выполнялся на таблице InnoDB и на такой же таблице InnoDB. Конечно, некоторые вещи, такие как SELECT COUN(*) FROM table; на InnoDB могут занимать минуты, на InnoDB они делаются моментально. К счастью, в тех системах где мне приходилось переделывать InnoDB на InnoDB таких запросов не было.

Честно говоря, особой "недоделанности" я в InnoDB не заметил ...

Это Петя Зайцев на мастер-классе сказал. Но я, признаться, не вдавался.

Мне очень стыдно, но это имя мне ничего не говорит. Конечно, в InnoDB есть много недоработок, как есть они и в InnoDB, но блокировка на уровне таблиц оказалась для "моих" систем доводом для отказа от InnoDB.

... MySQL полчаса занимается перестроением индексов...

Так это отличный результат! ...

Это зависит от требований к системе, если, например мы имеем дело с системой приема платежей, которая "делает" $1000/ч., полчаса простоя могут обойтись очень дорого (и не только в смысле финансовых потерь). Можно привести много других примеров когда время простоя системы должно быть минимизировано любой ценой и цена отказа от каких-то возможностей InnoDB в пользу "корявой и недоделанной" InnoDB выглядит не очень высокой.

Ну я и имел в виду, что не надо так делать. Запросов без использования индексов в нормальном случае быть не должно.

Конечно, в нормальном случае неиндексных запросов и нет, неиндесный запрос использовался только при испытаниях InnoDB, чтобы факт блокировки таблицы запросом SELECT можно было увидеть "глазом" и не писать тестовых программ. Опять таки, при переводе БД реальной системы на InnoDB (никаких других изменений не делалось) пропускная способность системы резко увеличилась, что можно рассматривать как подтверждение правильности выдвинутой гипотезы.

Это расширение для того и создано. И работает нормально.

Да, наверное так оно и есть, только в расширении full text search есть какие-то глупые ограничения, из-за них его практическая ценность несколько снижается, во всяком случае мне так и не удалось его применить в одной из систем где оно неплохо бы выглядело.

В общем, я к чему - мне кажется, что сменить тип движка СУБД - это чрезмерно радикальное решение с неоднозначными последствиями. Думаю, что для начала нужно принять обычные меры - спрофилировать запросы, построить индексы. А если не поможет - вот тогда задуматься о радикальных мерах.

Скорее всего - не поможет, точнее поможет, но не надолго. Скорее всего, запросы уже оптимизированы и все мыслимые индексы уже построены разработчиками DruPal. Если предполагается, что посещаемость данного сайта будет расти, то "пассивные" меры просто отодвинут точку "катастрофы" может быть на несколько месяцев (в моем случае оказалось, что на две недели), потом к этому вопросу придется возвращаться снова. Переход на InnoDB - мера конечно радикальная, но по счастью, можно сделать "локальную" версию системы под InnoDB и протестировать ее с усеченной базой данных - это не займет очень много времени, но позволит выявить большинство (или практически все) несовместимости и ошибки в InnoDB и принять взвевешенное решение.

Кстати, о Пете Зайцеве:
Если тут покопаться :
http://www.mysqlperformanceblog.com/
- то можно найти много интересного. Но уж больно долго копаться...

Ну, относительно тонкой настройки Мускула вряд ли скажу что-нибудь полезное.
Было бы очень неплохо конкретизировать о чём идёт речь, ибо телепаты в отпуске.

По моему опыту: смотреть и оптимизировать код.

Cэр, вам сюда: http://nginx.ru

Поставьте вместо апача, и все у вас станет хорошо. В меллисте вас очень оперативно и доброжелательно проконсультируют.

Не надо ковырять мускул раньше времени. Не лохматьте бабушку. У вас проблема с совсем другой стороны.

Если потребуется - киньте телефон на мой email, я звякну и по-быстрому объясню.

А оно заменяет апача?
Мне советовали в качестве фронтенда, для раздачи статики.
Оно умеет php и perl?

Лучше заменить полностью, php в режим FCGI. Тогда большое количество TCP-соединений не будет плодить большое количество процессов, и они перестанут жрать память и душить мускул. Это если в общих чертах.

С perl посложнее, но тоже можно выкрутиться. Зависит от конкретных условий.

Фронтендом можно, станет заметно легче, но лучше сделать сразу "по уму". Я вам серьезно говорю, если надо - скиньте контакты, или куда мои прислать, по телефону объяснить общие принципы и разницу подходов гораздо проще, а дальше по документации сами разберетесь. Не любитель я длинных переписок, когда все можно за пол часа решить.

Лучше заменить полностью, php в режим FCGI.

А есть ли уверенность, что данная версия DruPal будет работать в режиме FCGI без дополнительных изменений? Сколько, ориентировочно, времени эти изменения могут потребовать, сколько ошибок при этом будет внесено и сколько времени уйдет на дополнительное тестирование и отладку уже после перехода на режим эксплуатации (т.е. каковы будут показатели downtime обновленного сайта)?

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

Это справедливо в том случае, если среднее время обработки одного запроса под php в режиме FCGI будет меньше времени между поступлением двух последовательных запросов, если же время обработки одного запроса превысит время между поступлением двух запросов (плюс/минус некая дельта), то "большое количество TCP-соединений" по прежнему будет "плодить большое количество процессов". Есть-ли какие-либо данные насчет того насколько nginx с PHP-FCGI быстрее чем Апач с PHP? И, поскольку память занимаемая процессом определяет суммарную пропускную способность системы, то как соотносится память занимаемая nginx с PHP-FCGI с тем что надобно Апач с PHP (в расчете на один запрос к сайту)?

С perl посложнее, но тоже можно выкрутиться. Зависит от конкретных условий.

Можно узнать, от каких именно условий это может зависеть... и - из чистой вода любопытства - кто будет выкручиваться?

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

Неужели, ngnix так похож на Апач и настолько лучше Апача, что работающую систему можно перевести с Апача под nginx-PHP-FCGI всего за полчаса и при этом она будет работать как ни в чем не бывало, только лучше?

+593 96 255 139

FCGI идея хорошая. А ещё было бы хорошо её развить, создав пул соединений к СУБД. Потому что подключение к СУБД это относительно ресурсоёмкая операция, и от неё тоже надо избавляться.

Cэр, вам сюда: http://nginx.ru

Поставьте вместо апача, и все у вас станет хорошо.

Извините за глупый вопрос, а можно узнать почему все должно стать хорошо? Можно-ли предположить что "все должно стать хорошо" потому что nginx работает быстрее чем Апач? Если да - то можно-ли где-то ознакомиться с результатами измерений, в частности, меня интересует насколько быстрее работает nginx по сравнению с mod_perl и какие показатели надежности при этом обеспечиваются (у меня есть одна не очень быстрая система Апач-mod_perl, я буду рад рассмотреть возможность замены Апача на что-нибудь другое)?

У вас проблема с совсем другой стороны.

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

http://nginx.ru
http://lighttpd.net

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

Совет даден, а пользоваться им или нет - личное дело каждого.

Я знаю, почему тормозит Либрусек.

Знаю, потому что за несколько дней по косточкам перебрал движок, когда писал зеркало библиотеки для своего проекта.

1. В коде есть несколько неоправданно тяжёлых запросов, например вот запрос из функции AllAvtors

$sth = SELECT ("AvtorId, CONCAT_WS(' ', LastName, FirstName, MiddleName) AS Name, SUM(1) AS N
    FROM libavtorname JOIN libavtor USING (AvtorId) JOIN libbook USING (BookId)
    WHERE NOT (Deleted&1)
    GROUP BY 1,2
    ORDER BY 2");

Здесь каждый раз пересчитывается суммарный рейтинг библиографии каждого автора (извините, если где напутал с формулировкой, я для себя это понял так). Но ведь книги у авторов не выходят каждые 10 минут, эти рейтинги можно пересчитывать раз в сутки, а в таблице авторов создать новое поле, где оно будет хранится. Наличие одного только этого поля и настройка функций на корректную работу с ним ускорит работу модуля в несколько раз!

Есть и ещё несколько "узких мест", и просчётов в проектировании БД, например жанры подгружаются очень тяжёло. Я навскидку всё и не перечислю, но таких мест не очень много, где-то 5-7, которые забирают 80% производительности. Если понадобится - могу помочь непосредственно с кодом, Buriy знает мою ICQ.

2. nginx - действительно вещь очень и очень хорошая для отдачи статичного контента, здорово разгружает память. Правда по части конфигов nginx'a я не эксперт, этим занимается мой товарищ по команде. Думаю, он не откажет в помощи если потребуется, от простой консультации и до установки на рабочий проект, обращайтесь.

БелыйВетер написал:
Я знаю, почему тормозит Либрусек.

Знаю, потому что за несколько дней по косточкам перебрал движок, когда писал зеркало библиотеки для своего проекта.

1. В коде есть несколько неоправданно тяжёлых запросов, например вот запрос из функции AllAvtors

$sth = SELECT ("AvtorId, CONCAT_WS(' ', LastName, FirstName, MiddleName) AS Name, SUM(1) AS N
    FROM libavtorname JOIN libavtor USING (AvtorId) JOIN libbook USING (BookId)
    WHERE NOT (Deleted&1)
    GROUP BY 1,2
    ORDER BY 2");

Здесь каждый раз пересчитывается суммарный рейтинг библиографии каждого автора (извините, если где напутал с формулировкой, я для себя это понял так). Но ведь книги у авторов не выходят каждые 10 минут, эти рейтинги можно пересчитывать раз в сутки, а в таблице авторов создать новое поле, где оно будет хранится. Наличие одного только этого поля и настройка функций на корректную работу с ним ускорит работу модуля в несколько раз!

Функция AllAvtors вызывается раз в сутки. Это написано в её второй строчке. Сколь угодно глубокая её оптимизация общую скорость работы не изменит. Совсем.

БелыйВетер написал:
Есть и ещё несколько "узких мест", и просчётов в проектировании БД, например жанры подгружаются очень тяжёло. Я навскидку всё и не перечислю, но таких мест не очень много, где-то 5-7, которые забирают 80% производительности. Если понадобится - могу помочь непосредственно с кодом, Buriy знает мою ICQ.

Перечислите, пожалуйста, поимённо. Все 5-7. ICQ у меня всё равно нет, так что можно прям тут. Оптимизация - дело хорошее, с удовольствием займусь.

Илья, извините, что с такими интервалами, у меня очень дурацкий график работы :)

Цитата:
Функция AllAvtors вызывается раз в сутки. Это написано в её второй строчке

Да верно, не та функция, но общий принцип я изложил правильно... а функцию перепутал...
Вот, функция, которая показывает авторов, здесь точно та же самая беда... Она вызывается каждый раз, когда пользователь жмёт букву с именем автора, а значит довольно часто.

function libLetter($buk) {
  set_title("Авторы, фамилия которых начинается на букву $buk");
  $sth = SELECT("SUM(N) as N, AvtorId FROM `libbook` JOIN libavtor USING (BookId) JOIN libavtorname USING(AvtorId)
                 WHERE LastName LIKE '$buk%' GROUP BY AvtorId ORDER BY 1 DESC LIMIT 22");

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

Чтобы оптимизировать усилия, предлагаю сделать следующее: у Вас есть функция "SELECT", на неё завязаны не все запросы, но для начала сойдёт. Пусть эта функция засекает время в начале выполнения и в конце, считает разницу, и пишет это время и сам запрос в текстовый файлик. Минут 20-30 работы в штатном режиме и Вы получите довольно точную и объективную картину всех узких мест модуля. Изучив её, можно будет составить эффективный план мероприятий по оптимизации.

//:: Из файла librusec.inc
function SELECT($st) {
//:: Засекаем время
$timer_start = getmicrotime();

$args = func_get_args();
array_shift($args);
if (isset($args[0]) and is_array($args[0])) $args = $args[0];
$sth = db_query("SELECT $st", $args);

//:: Считаем время выполнения
$query_time = round(getmicrotime()-$timer_start, 5);

//:: Пишем в лог
$fp = fopen('select_librusec.log','a');
fwrite($fp,$query_time.' '.$st."\n");
fclose($fp);

return $sth;
}

//:: Из файла AJAX.php
function SELECT($st) { 
$timer_start = getmicrotime();
$rs = mysql_query("SELECT $st");
$query_time = round(getmicrotime()-$timer_start, 5);
//:: Пишем в лог, всё то же самое, только лог другой... на всякий случай
$fp = fopen('select_ajax.log','a');
fwrite($fp,$query_time.' '.$st."\n");
fclose($fp);
return $rs;
}

Скорее всего окажется, что нужно избавится от всех COUNT и SUM в запросах, выполняющихся пользователем с произвольной частотой. Но и наверняка всплывёт что-то ещё...

Илья, у Вас там всё нормально? Три дня уже ничего не слышно.
Но вроде сайт открываться лучше стал :)

Как успехи? Нам же интересно...

БелыйВетер написал:
Илья, у Вас там всё нормально? Три дня уже ничего не слышно.
Но вроде сайт открываться лучше стал :)

Процесс непрерывного улучшения непрерывен.
X