Товарищ phpчувак открыл блогом «Оптимизация PHP+MySQL» интересную тему, захватывающую большую аудиторию.
Расскажу о том, как я Web 2.0 убил два своих сервера (в том числе по моей глупости) и планирую убить еще.
Все началось осенью 2006 года, когда я решил открыть небольшой сайт на 70 миллионов страниц.
И нет, они не будут сделаны в соответствии с Цепи Маркова , а именно было бы полезно.
Почему 70 миллионов? Потому что в тот момент я мог найти очень много доменов .
com/.
net/.
org. Ссылку на проект давать не буду - устал за вас мат чистить, товарищи хабромены.
Если очень хотите, посмотрите мою предыдущую тему про «заработок на стартапах».
Расскажу в историческом порядке, поэтому прежде чем использовать memcached для спасения от Heise DDoS, я, наверное, доберусь до этого во второй части.
Итак, у меня возникла идея – найти в Интернете наименее употребляемое слово.
Для этого нужно просканировать 70 миллионов сайтов (список честно содран с кто , я пытался получить доступ Программа доступа к зоне TLD , но меня не пускают).
Лингвисты, кстати, уже, наверное, посмеялись над моей идеей найти редчайшее слово, но тогда я мало что знал о словах.
Собственно, вопрос.
Как организовать очередь? То есть начните с aaaa.com и закончите zzzz.com - это нужно как-то куда-то сохранить? Ну с MySQL понятно! id, url, status={'были';'еще не были'} Мне не сразу стало понятно, почему мой компьютер такой тупой.
MySQL не стала с особым энтузиазмом принимать 72 миллиона записей.
Транзакции — будь то InnoDB или MyISAM. Также возникает проблема, что с каждым последующим INSERT они становятся медленнее.
Хорошо, мы что-то сделали.
Мы используем MyISAM, потому что InnoDB как-то плохо переварил такой объём.
(Может я что-то не так сделал, но сути это не меняет).
Идти, SELECT id,url WHERE status='еще не был'; ОБНОВЛЕНИЕ СТАТУСА = 'пытаюсь' .
Да, это не так просто.
Транзакций нет, 2-3-10 ботов начинают флудить на один и тот же адрес (при этом происходит их SELECT, все получают один и тот же URL и при этом устанавливают себе новый статус, отбрасывая все последующие, и они вместе идут по этому адресу).
Именно здесь я придумал (точнее, догадался для себя — изобретением это не назовешь) первый трюк по оптимизации MySQL под большой нагрузкой — для MyISAM без транзакций.
Добавьте поле rand, затем каждый поток генерирует случайное число и выполняет ПЕРВЫЕ операции.
ОБНОВЛЕНИЕ… rand='92803423' ГДЕ (статус = 'еще не был') И (rand IS NULL) LIMIT 1 , а потом ВЫБЕРИТЕ ГДЕ rand='92803423' .
Получаем полный ATOMIC — эту запись гарантированно получит только один поток нет транзакций .
Ладно, но скорость была более чем дрянной — смешивать 72 миллиона записей с сотнями потоков — это не шутка для компьютера.
В общем, MySQL ползал.
(не про mysql) Тогда я придумал, как мне показалось, "гениальный" план - поместить все в файл, где есть только одна строка - один url и мы сделаем это так - стекаться, прыгать (fseek) на a случайное место в файле - куда-то попадаем - потом в середину строки и читаем до символа \n, теперь читаем переводы новой строки до первого буквенно-цифрового символа.Тогда я подумал, что мои треды в порядке и зачем им каждый раз обращаться к базе, если они могут взять сразу 100 URL, удалить их, сделать и только потом мы будем подавать заявки на новые.Отлично, читаем строку, сохраняем ее, fseek до начала этой строки и заполняем ее символами \n, отпускаем flock. Затем мы запускаем grep раз в час, чтобы удалить пустые строки из файла.
Казалось, это всё решило — все операции имеют практически постоянное время, даже сложение ничего не растёт, хоть 100 миллионов, хоть миллиард, но MySQL линейно увеличивает время работы с каждым новым элементом.
Я не думал, что здесь будет оооочень много ввода/вывода.
В общем 100 тредов за месяц такими методами убили жёсткий диск на сервере.
Установили новый, но надо было что-то придумать.
Сразу пришла на ум еще одна оптимизация - а именно вместо id,url,status +SELECT/ ОБНОВЛЯТЬ я могу использовать временную таблицу с идентификатором, URL-адресами и SELECT/ УДАЛИТЬ .
Сама таблица выглядела так:
идентификатор; URL-адреса «1»; "aaaa.com;aaaab.com;aaaac.com;aaaad.com." «2»; «ааа.Я впервые использовал денормализацию в MySQL, но выбора не было.«720000»; "zzzzzxxx.com;zzzzy.com;zzzzzz.com"
то есть вторым полем был ТЕКСТ, который я затем разделил(';') в PHP. Почему 100, а не 1000? Ээмпирически.
Нужно было рассчитывать на то, что скрипт, взяв 100 URL, мог зависнуть на каком-то одном, мог произойти segfault и что угодно еще, сервер перезагрузится, поэтому нужно было ограничить потери.
Плюс если я замечал ошибку, мне приходилось останавливать систему — это делалось методом kill -9. В среде за 30 секунд было обработано 100 урлов, поэтому потеря в данном случае составила бы максимум 10 000 урлов (100 х 100 потоков), а на деле получилось не более 1000. Для хобби-проекта это нормальный.
Вот вы меня спросите - почему я придал такое значение тому, чтобы два потока, не дай Бог, не захватили один и тот же URL, и не посчитали потерю 10к URL проблемой? Потому что в первом случае я проведу легкую DoS-атаку на удаленный сайт, чего мне не хотелось делать, даже если бы их было всего 2, а что, если бы их было все 100 одновременно?
В целом такая организация очередей себя более чем оправдала — MySQL обрабатывал всё очень быстро, этому способствовала периодическая OPTIMIZE TABLE.
Частоты слов
Тогда возник вопрос - мне нужно было хранить кэш найденного, а точнее список слов, которые были найдены на главной странице и их количество.
.…итак, сохраните количество слов.чтобы затем проанализировать и найти Святой Грааль - редчайшее слово, которое будет настолько редким, что будет существовать только один раз на всех сайтах, а затем исчезнет после появления моего.
Оказалось, что слово «йойдж» — это мой никнейм.
Как и твой ник, читатель, и ники всех, кто где-то регистрируется.
Проще говоря, это оказались не слова, а прозвища, опечатки и т. д.
Я пошел очевидным путем.
URL (varchar); слово (varchar); счетчик (int) Жаль, что рядом не оказалось моей школьной учительницы математики - она бы мне быстро объяснила с линейкой на голове, что 72 миллиона сайтов по 1000 слов на каждом - это 72 миллиарда записей, а я только ныл по поводу того, что MySQL еле справляется с 72 миллионами и постоянно тормозит. В целом идея та же — я начал хранить урл в базе данных; сериализовать($слова).
Потом позже его заменили на json_encode($words), потому что боты уже были переписаны на Python и хоть и была поддержка PHP Serialize, но медленная, поэтому JSON имел приоритет. Собственно, так это все и хранится до сих пор — выдержало атаки TechCrunch, Heise.de и многих других.
Конечно, это еще не все, что нужно было сделать — они еще придумали свой SphinxSearch и memcached в качестве базы данных — кстати, неплохо заработало.
:) Но об этом в другой раз.
ОБНОВЛЯТЬ Я забыл про финальную версию (которая есть сейчас).
Все сайты в базе - 72 миллиона записей, без индекса, кроме PRIMARY KEY=url(varchar), и запрос SELECT. WHERE url> 'kite.com' LIMIT 100, где kite.com – это сайт, который был просканирован последним, время этого запроса постоянно во всей базе данных O(1), никаких обновлений не происходит вообще, мы обрабатываем эти сотни в потоках, когда количество активных потоков приближается к 20. Допустим, этот SELECT выдает «klara.com» как последний — сохраним поток «klara.com» где-нибудь — теперь мы делаем SELECT. ГДЕ URL> 'klara.com' ПРЕДЕЛ 100 В PHP потоки — это процессы, подсчет процессов осуществляется через popen('ps aux | grep имя процесса','r').
Просто сейчас все боты переведены на Python.
Йои Хаджи, вид с Хабра
Теги: #php #MySQL #очередь #скорость #оптимизация #денормализация #разработка сайтов
-
К Вопросу Личной Эффективности
19 Oct, 24 -
Сервер Приложений На Pl/Pgsql
19 Oct, 24 -
Конференция Инвесттех 2017
19 Oct, 24