В качестве отправной точки мы поговорим о PHP, JavaScript и MySQL. Также я приведу некоторые цифры по тестам производительности и потерям времени, которые могут убить проект, на примере одного из продуктов, который мне недавно пришлось открыть для поиска проблемных мест, и покажу, как убить проект в три шага.
.
Предисловие.
Недавно мне дали задачу помочь найти проблемы в одном из веб-проектов, созданных, как обычно, на PHP + MySQL, и все это тоже было обернуто в Symfony Framework. База данных начала сильно разрастаться, так как люди собирали события поведения (например, автопарк), которые заливались буквально каждые 5 минут. Естественно, таблица событий разрасталась, и чтобы MySQL как-то с ней справлялся, ее разделили на разделы.
В конечном итоге всё сводилось к различного рода выборкам и отчётам, т. е.
аналитике.
В результате даже простая выборка за период плюс небольшой подсчет заняли 11 секунд и более.
Видимо поэтому было принято решение ограничить выбранный период днями.
И мы пошли:
Задача 1 – База
В качестве базы данных была выбрана MySQL. Видимо тот факт, что это бесплатно, всех подкупает. Но я считаю первой ошибкой и самой главной привязывать к этой базе третьеуровневую, тяжелую аналитику.На тестовом примере в 700 тыс.
записей в таблице событий выборка за период увеличилась до 30 секунд .
Вся сложность была в данных.
В таблице событий была запись «события разных типов» со свойством ON или OFF, т.е.
начало и конец определенного события.
Все довольно стандартно, и каждый легко сможет сделать выбор:
Для примера и теста использовался один тип событий: 1 начало и 2 окончания, хотя пар в базе около 40. В результате, просто выбирая записи за период, добавляя сразу конец этого события, убирает из MySQL порядок 20-30 сек.SELECT ev1.event_point,ev1.event_date, (select event_date from test_events where ev1.event_point = event_point and event_type = (CASE WHEN ev1.event_type = 1 THEN 2 ELSE 1 END) and event_date > ev1.event_date limit 1) as end_date FROM test_events as ev1 WHERE ev1.event_date BETWEEN '2012-1-1' AND '2012-1-31' AND ev1.event_type IN (1 , 2)
.
Для сравнения также была взята бесплатная база данных SQL Server 2008 в редакции Web, все это запускалось на MS Server 2008 R2 в виртуальной среде VirtualBox. Скажу сразу, Веб-редакция хоть и бесплатна, но не имеет ряда важных опций, особенно важных для аналитики, например, нет оптимизации и кеширования Вида и Процедуры.
Все тесты показали, что MS SQL легко выполняет запрос менее чем за 1 сек в 1КК данные.
Далее нужно было сделать простой расчет и выбрать, сколько времени потребовалось для определенных типов событий за определенный период времени.
Это несложно вычислить на уровне SQL-сервера; накладываем сверху условие и получаем готовый отчет: SELECT event_point,SUM(TIMESTAMPDIFF(HOUR,event_date,end_date)) FROM (
SELECT
ev1.event_point,ev1.event_date,
(select event_date from test_events
where ev1.event_point = event_point
and event_type = (CASE WHEN ev1.event_type = 1 THEN 2 ELSE 1 END)
and event_id > ev1.event_id limit 1) as end_date
FROM test_events as ev1
WHERE
ev1.event_date BETWEEN '2012-1-1' AND '2012-1-31' AND ev1.event_type IN (1 , 2)
) report
GROUP BY event_point
Вот пример той же базы данных MS SQL, который запускается мгновенно, менее чем за 1 секунду.
А вот с MySQL можно не дождаться результата, увеличив срок, база данных ляжет намертво.
Видимо те, кто собирал проект, это поняли и решили сосредоточиться на обработке выделения непосредственно на PHP. И чтобы не терять 20-30 секунд на JOIN, было решено сделать простой SELECT за период, закинуть все в Array и потом быстро найти начало и конец и потом легко распечатать суммы.
Что и было сделано, в итоге я снова зашёл в тупик, рост периода стал занимать до 1 минуты.
для расчета.
И я начал изучать вторую проблему, пытаясь понять, почему просто пробежка по массиву занимает так много времени.
Я надеялся, что это просто опечатка или ошибка, но оказалось, что просто пробежка по массиву занимает много времени.
Вот вторая проблема.
Проблема 2 — Массив в PHP
Изучив все данные, я понял, что PHP не может быстро обрабатывать большие массивы.Почитав на различных форумах, увидел обсуждения, подтверждающие эту версию.
Это проблема PHP. Было решено написать простой тест для проверки фактов.
<Эphp
$summary[] = array();
$count = 5000;
for($i1=0;$i1<$count;++$i1){
$summary[$i1] = $i1;
}
$t = microtime(true);
for($i1=0;$i1<$count;++$i1){
for($i2=0;$i2<$count;++$i2){
if("5468735354987"!="654654655465"){
$summary[$i1] = $i1*$i2;
}
}
}
echo "<li>time: ".
(microtime(true)-$t).
' ms</li>';
$sum = 0;
for($i1=0;$i1<$count;++$i1){
$sum = $sum + $summary[$i1];
}
echo "<li>test["+$sum+"]";
?>
Код был создан на основе задачи в проекте.
В этом тесте перебиралось 5000*5000 = 25КК петель, а для увеличения скорости даже использовался ++$i вместо $i++.
В результате этот тест дал 11 секунд .
Попытка запустить этот код прямо в консоли без веб-сервера дала мне 10 секунд .
И все это запускалось у меня на компьютере, а не на хосте или виртуальной машине, и все с конфигурацией: Intel Core i5, 8 ГБ.
PHP 5.3.9
Понимая, что для PHP это предел того, что можно выжать, я решил протестировать этот код на других платформах, имея под рукой виртуальную машину с MS Server 2008 R2. Тестовый код легко перенесся на ASP, ASPX, WSC, VBS и NodeJS. В результате я получил следующие данные:
На скриншоте мы видим три варианта теста.
1) виртуальная машина 2) мой компьютер 3) доступные мне серверы в Интернете.
1. PHP на удивление выдал очень похожие результаты, учитывая, что все запускалось на совершенно разных ресурсах и мощностях.
2. ASP, близкий аналог PHP, показал худшие результаты, и это тоже зависит от мощности окружения.
3. WCS очень разочаровал, учитывая, что Microsoft в свое время описала его как скомпилированную версию ASP, которая должна работать гораздо быстрее, что оказалось совершенно неверно.
4. VBS — чисто консольный вариант скрипта, хоть и показал лучшие результаты, чем PHP, но для веб-проекта неприемлем.
5. ASPX показал просто отличные результаты.
Я не удивлен, ведь это C#.
6. NodeJS также показал, как и ожидалось, просто отличные результаты.
Вот все варианты скрипта: test.php <Эphp
$summary[] = array();
$count = 5000;
for($i1=0;$i1<$count;++$i1){
Теги: #php #MySQL #JavaScript #benchmark #разработка веб-сайтов #php #JavaScript