Pull to refresh

Comments 81

Ну да, а в чём проблема :)? За несколько лет работы 4-7 человек можно и больше написать :). Активного кода — где-то 20 Мб
Пардон, показалось сначала, что 100 мегабайт кода занял инструмент для автоматического предупреждения подобных проблем :-)
получается 3.75 млн строк примерно, за 4 года работы каждому кодеру из 7 человек нужно писать по 466 строк кода каждый день. учитывая выходные и праздники. дайте мне таких кодеров, которые могут 24/7 писать 500 строк нужного рабочего кода в день :) я им буду платить ооочень много
Symfony — нефиговый кодогенератор, так что почему бы и нет.
дык тут же человек говорит, что все руками написано :)
Symfony действительно помогает писать код во многом, как и Propel, генерируя «каркас», поэтому, конечно, действительно вручную написанного кода будет немного меньше. Но несколько лет — это не 4 года, а больше :). Ну и там не весь код на Symfony, большАя часть кода на обычном PHP
У нас проект примерно на 300МБ кода
Исключая внешние библиотеки?..
Так точно. Без Пейпала, картинок интерфейса (css, js и шаблоны будем считать за код — они же написаны вот этими мозолистыми руками :) ) и т.п.
А у вас пейпал (PayPal) какой-то большой?
хотел бы я поглядеть на проект в 300 МБ чистого пхп кода. если в среднем одна строка занимает 80 символов, то это получается 3.75 млн строк кода. вы что амазон там написали? чтобы обслуживать такой воистину громадный проект нужен как минимум кластер. а там уже совсем другие методики оптимизации используются в дополнение к сказанному вами.
Ну, вот цифры для проекта, о котором шла речь:

# количество строк
$ cat `find . | grep .php | grep -v 'cache/'` 2>/dev/null | wc -l
2066539
# количество байт
$ cat `find . | grep .php | grep -v 'cache/'` 2>/dev/null | wc -c
74121750


Думайте сами :))
ну тут же 75 мегабайт кода ;) а не 300. и сдается мне это со всеми библиотеками.
Я говорил про проект в статье :). И это один проект, а их у нас есть ещё другие, поменьше :)
а какой тематики эти проекты, если не секрет, что в них столько логики?
Туризм :). Это не так важно, в любом более-менее крупном бизнесе будет немало внутренней логики, которой извне может быть вообще не видно
Тащемта это моя была реплика про 300МБ и youROCK не имеет никакого отношения к нему. Вот моя статистика:

$ find. \( -name "*.php" -o -name "*.js" -o -name "*.css" \) -exec cat {} \; | wc -l
7400511
$ find. \( -name "*.php" -o -name "*.js" -o -name "*.css" \) -exec cat {} \; | wc -c
283243229

Поводов для гордости не так уж и много — куча легаси, написанного не за один год. Уууу.
Серьёзные размерчики. Я стараюсь держать проекты в рамках 40K строк (без учёта фреймворка). Если проект становится больше — рефакторю, улучшаю архитектуру и в итоге минус несколько тыс. Строк кода. Так уже года три — функционал растёт, а размер не меняется!
наверняка есть много дублирующего кода
есть. Не сильно дублирущего, не так-то и много тупого копи-паста, но он есть. Работаем над этим :)
Простая арифметика:
Если простой вывод из Бд занимает пять строк (строим массив) и он повторяется 300 раз, то уже экономим 1450 строк (1500 — 50 на формирование класса)
Добро пожаловать в мемкеш/редис. Объекты и так кешируются, опкод кешируется, вывод веб-сервера — и тот кешируется (который нужен). Поэтому проблема 1450 строк хоть и присутствует, но не так актуальна.
что-то уж очень много.
Вот и меня заинтересовало, что за проект с кодом в таком объеме.
Искусственный интеллект с памятью на MySQL?
Вот вам смешно, а я ведь действительно отчасти похожими вещами и занимаюсь :(. До искусственного интеллекта, конечно, не дотягивает, но логика порой бывает оооочень сложная, и да, как ни странно, память именно на MySQL, ибо больше никто так компактно хранить информацию не умеет (кроме других реляционных СУБД, конечно же)
Поправил статью, написав чуть более ясно, что под этим подразумевается
Огромное спасибо!
SHOW SESSION STATUS LIKE 'handler_%' -- магия ;)!
Ставим Percona Server, включаем handler socket и радуемся жизни :)
p.s. Percona Server — это форк MySQL, со всякими оптимизациями.
От запросов, которые требуют FULL SCAN на 100 000 строк (потому что индексов соответствующих нет) даже Percona не спасет, я думаю ;)
Хм… а как потестить производительность, если нет такой объёмной БД?
Если такой объемной БД нет, то и проблем у вас, скорее всего, тоже не будет с этим :). Ну попробуйте сделать JOIN для всех таблиц, что у вас есть по условию 1=1 — я думаю, на 100 000 строк сможете набрать даже с 3 таблицами
Нагуглил пакет sysbench и с его помощью потестировал :)

pastebin.com/eHXRbaTb

Правда сравнить не с чем. Тестировалось на виртуалке с ubuntu server 11.10. 70% от 2 ядер E8400@3.33Ghz, 512mb ram.
Сделать тестовое наполнение БД. Не зря при разработке высоконагруженных систем выделяют даже команды подготовителей данных.
Да, profiling тоже бывает очень полезен, но как раз проблему с количеством прочитанных строк он решает далеко не всегда :). И когда используются подзапросы, SHOW PROFILE генерит ооочень много строк
Про оптимизацию запросок как-то мало, я бы еще добавил денормализацию, избавление от групбаев, джойнов. Да и о кешировании как-то глухо.
Да, про собственно оптимизацию запросов я специально ничего не писал, потому что статей на эту тему не просто много, а очень много, и здесь, как раз, серебряной пули нет, к сожалению
А джойны чем плохи? Иногда без них никак.
обойтись можно всегда, а плохи тем, что масштабируются плохо, и кешировать без них проще
> EXPLAIN в MySQL зачастую нагло врёт.

С чего это он врёт? Конечно, никто 100% точности от него не ожидает, но тут он, скорее всего, говорит правду.
Ну, статистика по количеству просмотренных строк (SHOW STATUS LIKE 'handler_%') говорит об обратном — в том-то и дело :)
Ой, простите, я перепутала запросы в блоках: у вас же разные в 5.1 и 5.0
С помощью этой методики мне удалось выявить узкие места и снизить времена исполнения большинства страниц на сайте на Symfony с ~1000 ms до где-то 200-300 ms и добавить в дев-версию инструмент для автоматического предупреждения подобных проблем в будущем.
Это еще раз доказывает, что если хочешь написать Высокопроизводительный порект, или с проект с притензией на Hiloa, то забей на всякие там Симфони и Друпалы и тому подобное…
Решил как-то наш IT начальник использовать ОперКарт. В результате пришлось переписать весь фронтэнд (в том числе и из-за нового интерфейса). По кучи запросов на страницу — это жесь. По одному селекту на вывод одной ветки дерева — это вообще песня…
В результатеот ОпенКарта осталась одна админка, которая использовалась и то на 10%, так как пришлось добавлять кучу своего функционала.
200-300 ms — тоже жесть, у меня в проекте по 20-40 ms на полное формирование стр.
Полностью согласен, что для хайлоада симфони или drupal — не лучший выбор. Но, к сожалению, этот выбор был сделан до меня, а для себя лишь поставил задачу заставить это чудо хотя бы как-то шевелиться. С 200-300 ms на страницу уже можно жить, в принципе
10 000 000 хитов в сутки это уже хайлоад или нет?
Я про sportbox.ru на Drupal, если что
А сколько серверов, и каких, если не секрет, обеспечивают работу сайта?
6 фронтэндов для веба, не забываем про трансляции, когда до 45000 онлайн
1 сервер для спортивной статистики, пропущенные/забитые голы и всё такое.
1 сервер БД
3 мемкеша
+ девелоперское.
Но могу ошибаться, некоторые машины являются виртуальными машинами.
Спасибо, неплохо, 3 мемкеша панацея:) У нас аналогичная нагрузка на чуть меньшем кол-ве серверов работала, там самописный мвц был, к сожалению, не могу раскрывать подробности, коммерческая тайна и всё такое.
Долго искал в статье хоть какой-то намек на использование log-slow-queries и log-queries-not-using-indexes. Не нашел.

Как же это вы забыли про штатный способ поиска узких мест?
Не то, чтобы я про него забыл, я его просто пропустил, как и много чего другого. slow log — это хорошо, но его нужно смотреть отдельно, и польза от него далеко не однозначная, ибо в slow log часто попадают вполне «легальные» запросы. По сути, предложенный мной метод почти полностью повторяет информацию, доступную в slow log, только без, собственно, использования slow log
Под «легальными» запросами я подразумевал различные статистические выборки или выборки, которые исполняются редко, или же, скажем, по крону. В теории, можно всё это настроить и отфильтровать, но мой способ, как мне кажется, намного проще и удобнее в использовании
То есть MySQL предоставляет вам надежный способ учесть:
а) чистое время исполнения запроса, без учета кэширования и времени построения плана и
бэ) количество реально просканированных записей
и даже построить по ним отчет (mysqldumpslow), но вы его пропускаете, потому что в него надо «смотреть отдельно»?
Ладно, давайте поставим вопрос по-другому. Была задача — снизить времена исполнения страниц на сайте. Что дает нам slow log, в самом лучшем случае? Он дает нам непосредственно SQL-запросы, которые были исполнены. По этим SQL-запросам ещё нужно найти кусок кода, в котором происходит вызов этого запроса (что далеко не всегда очевидно, если используется ORM, а у нас она используется очень конкретно) и только потом его исследовать.

Итого, действия при работе со slow log'ом:
1) нужно туда не забывать смотреть (причём для того, чтобы был доступ к slow log'у, по сути, нужен доступ к контейнеру с СУБД)
2) нужно (вручную?) отсеять запросы, которые не исполняются на страницах, а исполняются где-либо ещё (например, в кроне)
3) отсеять «тормозные» запросы, вызванные случайными всплесками активности / длительными блокировками при дампе таблиц и тд
4) по найденному запросу догадаться, на какой части сайта он используется и найти соответствующий кусок кода
5) начать действовать

Конечно, slow log хорош, но он реально полезен только тогда, когда есть откровенно «тормозящие» запросы. Даже ситуацию с тем, что запросов очень много (и они относительно простые), и поэтому не попадают в slow log этот подход уже не решает (при этом, скажем, этот запрос прекрасно виден в SHOW FULL PROCESSLIST через раз).

В моей статье я пытался описать действия, которые можно ещё попробовать, когда стандартные и известные средства плохо помогают (счетчик числа/времен запросов на странице, slow log, авто-explain, авто-show profile, анализ с помощью SHOW FULL PROCESSLIST и наблюдения, «за что глаз зацепится», ...). Понятно же, что есть стандартные средства для диагностики запросов, просто их часто бывает недостаточно, и приходится выдумывать такие фокусы, которые предоставляют, по сути «живой» slow log прямо на странице.

Для более-сложных запросов, из моего опыта, временами блокировок/кеширования/построения плана запроса/оптимизации запроса можно пренебречь, хотя их в любом случае нужно тоже считать вместе с временем исполнения запроса, чтобы получить полную картину. В общем, slow log — это хорошо, но не всегда это позволяет решить конкретную задачу — ускорить исполнение страницы на сервере, а не просто найти все «медленные» запросы.
Аргументация понятна. Но, согласитесь, место ей в начале статьи, а не в подвале коментов.
Соглашаюсь, из моей статьи это могло быть не совсем ясно, что все «стандартные» способы тоже существуют, и что нужно пробовать именно их в первую очередь, а потом уже, если не получилось (а вероятность этого весьма ненулевая), читать мою статью :). Я просто сделал предположение, что люди, которые будут на хабре читать эту статью, уже всё, что знали, перепробовали, и надеятся здесь увидеть что-нибудь ещё, чего они не знали до этого. И, судя по количеству добавлений в избранное, я действительно смог рассказать что-то новое для большого количества людей, которые используют MySQL :)
Для поиска кода, из которого пришел запрос, есть очень простой прием: где–нибудь на нижнем уровне вашего ОРМ добавляете код, который будет автоматически комментировать запросы. В комментарии можно добавить все что угодно: название метода, глобальный счетчик запросов, кусок стектрейса.
Конечно, это не очень хорошо повлияет на QC (одинаковые данные могут кешироваться для разных запросов), но и никто не говорит, что это надо делать постоянно. Можно периодически включать в дебаг режиме для одного из слейвов.
EXPLAIN вроде как ни разу мне не врал. Единственное это то, что по какой-то причине индексы странно себя ведут порой. То есть, например, делаешь EXPLAIN и он показывает что-то похожее на ваши результаты, потом делаешь OPTIMIZE TABLE и он начинает показывать ожидаемо вменяемый результат. Ну и до того как делаешь OPTIMIZE, Cardinality у индексов выдаёт неверное значение. Отчего не знаю, как избавиться тем более, поэтому просто периодически делаю OPTIMIZE.
Это связано с тем, что в InnoDB статистика перестраивается при OPTIMIZE TABLE, а до этого момента она может быть сильно испорченной частыми апдейтами.
Есть несколько простых советов, которые подходят для большинства проектов: как достичь высокой производительности. Скорее всего, чтоб их раскрыть — это тема отдельной статьи.
Первое, надо по возможности исключить все JOIN, используем денормализацию.
Во вторых, кешируем все справочники и неизменные таблицы, не надеемся на Кеш Запросов. Эти данные вытеснятся другими пользовательскими данными. Склейку данных делаем на клиенте.
Третье — предварительная подготовка данных. Используем очереди и бэкграундовские процессы.
Четвертое — большие порции данных бьем на маленькие части (иногда это называют шардинг).
Пятое — для доступа к данным на чтение используем HandlerSocket, так же можно использовать на обновление данных, на вставку не советую. Не забываем, что Autoincrement в HS работает не так как бы нам хотелось.

Используя эти пять простых принципов, у меня ни когда БД не была узким местом.
Не сочтите за рекламу:
много полезного можно найти в этой книге
цена в books.ru 90 руб
Все это, конечно, хорошо, но магия работает только в том случае если на сервере вообще больше ничего не исполняется. Т.е. на продакшне да с более-менее интенсивной загрузкой этот метод зачастую будет показывать сколько лет бабушке Пушкина вместо реальных данных. Хотя метод, конечно же, интересный.
Метод был хорош — но уже морально устарел (для иннодб таблиц удобнее смотреть значение innodb переменных). Если граммотно настроить performance_schema то можно снимать статистику по запросу в режиме рельного времени и, если понимать какие строчки кода что обозначают, можно докапаться не только до истины, что у вас слишком активно читается табличка 1...0 раз, но и понять почему её нет предположим в кэше, а так же что более важно обнаружить другие более узкие места, которые не показываются в сессионных переменных.
вот про это бы и отдельную статью!
Кстати, забыл ответить — performance_schema ведь появилась только начиная с MySQL 5.5, а она используется далеко не везде и далеко не всеми (причём по вполне понятным причинам, ибо в ней довольно много багов)
Странно что вы обращаете внимание сразу на количество строк. По-моему, первостепенным является нахождение запросов у которых происходит using filesort, using temporary table и using fullscan (в порядке уменьшения приоритетов) по мнению неточного Explain-а и преобразование их в такой вид, чтобы было using index. Это, ИМХО, решает примерно 70% потери производительности. Дальше уже можно делать профайлинг, изучать количество строк, убирать из выборки лишние столбцы, кешировать, денормализовать структуру и т.д.

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

P.S. Я обычно выдаю предупреждения на dev-сервере как раз если в Explain-е встречается filesort и temporary table. Когда одновременно, предупреждение жирное :)
Я рад, что смог помочь самим (хоть и бывшим) разработчикам Хабра :). Очень приятно, правда
Запросы

SELECT * FROM some_table WHERE some_field = 100500 ORDER BY id LIMIT 100

и

SELECT * FROM some_table WHERE some_field <> 0 ORDER BY id LIMIT 100

принципиально разные. И если у нас есть индекс по полю some_field, то как раз в первом запросе не надо прочитывать 100500 строк, во втором — надо, так как условия там заданы разные. Или я чего-то крупно не понимаю?
Вы правы, в первом случае при наличии индекса запрос не займет значительного времени, даже при сотнях миллионов записей(ох, был у меня проект со статистикой...). Индексы — наше все!:)
Товарищи, которые плюсуют этот комментарий, прочитайте описание структуры таблицы. Где вы там увидели индекс на some_field?
Хм, действительно, в описании указан только индекс первичного ключа. В таком случае непонятно, почему «буду индекс праймаре использовать», так как он для поиска по some_field совершенно не нужен.
Потому что стоит ORDER BY id
В таком случае, лучше бы Вы реальный вывод EXPLAIN привели, а не свою интерпретацию. Потому что есть поле possible_keys, и есть key, и они имеют разный смысл.
Вывод будет такой (за исключением названия таблицы):


Предполагается, что читателю очевидно, какой будет EXPLAIN, либо ему будет интересно проверить это самому (ибо утверждения действительно не совсем тривиальны). В обоих случаях реальный EXPLAIN не является необходимым
Only those users with full accounts are able to leave comments. Log in, please.