[ Поиск ] - [ Пользователи ] - [ Календарь ]
Полная Версия: Проблемы с организацией ТОП'а
Arc_SilveR
Разрабатывая свой сайт с картинками для сотовых телефонов, я наткнулся на проблему создания скрипта, выводящего популярные картинки. Я хотел сделать топ, похожий на http://browse.deviantart.com/ . Спросил у знающих людей, мне посоветовали следующий способ. Имеется таблица с картинками (pictures) и с логами (loads_log).

1. При каждом нажатии на "скачать картинку" делается запись в БД (id картинки, штамп времени, IP адрес для антинакрутки).
2. Скрипт top.php делает следующий запрос (привожу уже с подставленными параметрами переменных):

Код
SELECT loads_log.id_pic , loads_log.stamp, pictures.category_ind, pictures.name_pic,
pictures.file_name, COUNT(*) as loads FROM `loads_log`, `pictures`
WHERE stamp > (NOW() - INTERVAL 24 HOUR) AND loads_log.id_pic=pictures.id_pic
GROUP BY id_pic ORDER BY loads DESC LIMIT 0, 9;


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

Какие здесь плюсы?
1. Динамичность, то есть топ меняется каждую минуту и картинки не висят подолгу в нем.
2. Гибкость. Можно просматривать логи за большой период, анализировать и использовать эти данные по своему усмотрению, а также делать выводы популярных за любой период.

Минус очевиден. Ругается хостер на огромное количество просматриваемых строк.

Отсюда вопрос. Как правильней будет переорганизовать ТОП? Сейчас он отключен.

Способ инкремировать "загрузки" прямо в записях картинок и обнулять их каждые сутки/неделю/месяц соответствующие не катит, так как не будет никакой динамики. PHP знаю достаточно, а с MYSQL все время проблемы.

Буду благодарен за любые подсказки, потому что у меня нет идей. Надеюсь на поддержку на этом форуме, так как оптимизаторы меня уже послали smile.gif



Спустя 45 минут, 41 секунда (24.03.2008 - 14:43) sergeiss написал(а):
Цитата(Arc_SilveR @ 24.3.2008, 13:58) [snapback]35502[/snapback]
Буду благодарен за любые подсказки, потому что у меня нет идей. Надеюсь на поддержку на этом форуме, так как оптимизаторы меня уже послали smile.gif

Для начала - не начинай вопрос с того, что тебя уже где-то послали smile.gif Потому что появляется подсознательная мысль "а по делу послали, наверное..." rolleyes.gif

По сути вопроса. Я лично не понял сути записи "LIMIT 0,9" в конце SELECT'а. Там, вроде как, должно быть целое число больше нуля, которое как раз и ограничивает число записей. Например, укажешь 10 - и только 10 первых записей будут выбраны.

Спустя 1 час, 2 минуты, 59 секунд (24.03.2008 - 15:46) Arc_SilveR написал(а):
Никак нет. В этом то я чуть чуть разбираюсь. "LIMIT 0,9" означает, что будут выбраны первые 9 записей, подходящие под условия, начиная с 0-ой. Если указать "10,9", то будут выбраны 9 записей, начиная с 10-ой.
Нужен принципиально другой подход... а какой - не знаю...

Спустя 15 минут, 57 секунд (24.03.2008 - 16:02) sergeiss написал(а):
Насчет синтаксиса сорри, погорячился я немного smile.gif Я подразумевал LIMIT в PostgreSQL, а не в MySQL. А у них есть различия.
Ты написал "Скрипт top.php делает следующий запрос (привожу уже с подставленными параметрами переменных):" - а этот запрос действительно в таком виде формируется? Ты делал его контрольный вывод или только предполагаешь, что он такой должен быть после подстановки?

Спустя 30 минут, 52 секунды (24.03.2008 - 16:33) Arc_SilveR написал(а):
Да, в таком виде. Да, делал контрольный вывод. Более того, я взял конкретный пример (но он не единственный), который предъявляет мне хостер в специальном присланном сообщении мне.

Спустя 5 минут, 35 секунд (24.03.2008 - 16:39) LoneCat написал(а):
Тут по-моему без упрощения структуры БД не обойтись, вариант который мне представляецца: отказацца от записи IP'а в этой таблице, вынести защиту от накрутки в отдельную, также в таблицу load_logs добавить числовой параметр count, и добавлять записи в таблицу только через какой-то определенный интервал, иже раз в минут, 5 минут, час... в остальных случаях увеличивать count текущего временного интервала, просто с твоими нагрузками - у тебя выходит:

24 часа * 60 минут * 60 секунд = 86400 секунд / 15000 загрузок = 5.76 секунд на одну загрузку

Тоесть новая запись появляется в среднем раз в 5 секунд. Надо-ли оно?
Если сделать интервал по-крайней мере минуту - это уже 1,440 записей в БД, вместо 15,000, и 43,200 записей за месяц, вместо 450,000... согласись, разница в 406,800 записей - это уже неплохо, а это всего-лишь обновление картинок раз в минуту а не раз в 5 секунд, а если поставить 5 минут - это уже 288 записей в день, и 8,640 записей в месяц, это уж любой хостинг потянет. Не думаю что для отображения популярных картинок нужна такая уж запредельная динамичность, какая стоит сейчас.

Спустя 2 часа, 56 минут, 8 секунд (24.03.2008 - 19:35) Sylex написал(а):
Цитата
1. При каждом нажатии на "скачать картинку" делается запись в БД (id картинки, штамп времени, IP адрес для антинакрутки).

Зачем? Каждая картинка имеет параметр count_download. При скачивании это поле увел. на 1. Запрос получиться на ТОП очень простойsmile.gif.

Проверку на IP ... надо подумать другое решение....

Спустя 15 часов, 18 минут, 50 секунд (25.03.2008 - 10:54) Arc_SilveR написал(а):
2LoneCat
Большое спасибо, мне нравится твоя идея smile.gif Именно так и сделаю! Отпишусь, как получилось. По идее все верно и динамичность останется довольно большой, раз в 5-10 минут вполне достаточно. А при последующем увеличении посещаемости можно будет просто увеличить интервал.

То есть делать так:
1. Ищем запись в логах с "`stamp` > (NOW() - INTERVAL 5 MINUTE) and `id_pic`='$id'".
2. Если есть, то увеличиваем параметр count.
3. Если нет, то добавляем новую запись со значением count=1.
Ок. С этим все понятно. А что делать с выборкой? В моем запросе просто считается количество записей. А тут каждая запись получается имеет свой "вес" - картинку могут скачать и 2 раза за 5 минут, и 5 раз, и 1 раз. Каким образом это учесть? Если можно, помогите с перестройкой запроса. Вот это мне не понятно.

И еще. Нужно ли здесь расставлять индексы? Если нужно, то на какие поля?

2Sylex
Если вы внимательно прочитали мою тему, то наверняка должны были заметить, что этот способ мне не подходит:
Цитата
Способ инкремировать "загрузки" прямо в записях картинок и обнулять их каждые сутки/неделю/месяц соответствующие не катит, так как не будет никакой динамики.

Спустя 2 часа, 6 минут, 51 секунда (25.03.2008 - 13:01) Arc_SilveR написал(а):
Сижу, думаю. И вот, неувязочка, однако! Смотри: самая популярная картинка обычно не имеет более 100-150 загрузок. Поэтому твои расчеты не будут работать. К примеру, чел скачал одну картинку, затем вторую, затем третью (разные) за 1 минуту. И эти записи будут все добавлены, несмотря на то, что будет стоять интервал. А расчеты работают только если идут запросы к одной и той же картинке. Так у меня и так стоит антинакрутчик на минуту где то.

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

Вопрос открыт, жду предложений.

Спустя 1 час, 15 минут, 58 секунд (25.03.2008 - 14:17) sergeiss написал(а):
Мне так кажется... Надо переделывать сам алгоритм. Чтобы БД меньше росла. И чтобы увеличить производительность.

Например, делаешь в БД запись для каждой картинки. Одну уникальную на один день (для каждой картинки). В этой таблице нужны будут такие поля: дата, идентификатор картинки, количество загрузок за текущий день, время последней загрузки. И по мере обращения к картинке апдейтишь количество и время (или создаешь новую запись, если это первое за день обращение). Прибавка записей в день не будет превышать общего количества картинок.
Насчет "антинакрутки" не понял... Кто-то специально может вызывать определенную картинку неадекватно большое число раз? Если это так, то делаем еще одну таблицу. С полями дата, IP компьютера, идентификатор картинки. При обращении проверяем, есть ли уже запись этого пользователя в БД, для данной картинки. Если есть (было обращение данного юзера к данной картинке) - то ничего не делаем. Если такого обращения не было, то делаем дополнения как к данной таблице, так и к первой таблице (предыдущий абзац). Максимально возможное количество новых записей за день равно произведению количества пользователей на количество картинок... Но реально будет меньше smile.gif И уж точно не превысит твои 15000 ежедневных обращений.

И еще... Я так понял, что ты не используешь встроенные в БД функции? Если это так, то в данном случае их как раз стоит использовать, т.к. это уменьшит количество внешних запросов к БД и уменьшит время обработки. Т.е. то, что я описал в предыдущем абзаце (про 2 разные таблицы) лучше бы делать через встроенную функцию. Передал ей идентификатор картинки и IP юзера - а дальше уже сама БД (ее функция) будет решать, что и куда добавлять. А твой скрипт будет только брать данные у БД.

Собственно говоря... Формирование списка "ТОП 10" ты можешь делать также во встроенной функции и потом забирать его оттуда. При правильной организации работы на этом тоже можно время съэкономить.

Ну... А уж как с этим всем работать, мне так сдается, ты и сам разберешься smile.gif Я только про идею сейчас говорил. "Моцик не мой, я только разместил объяву" (С) smile.gif

Спустя 52 минуты, 48 секунд (25.03.2008 - 15:09) LoneCat написал(а):
Да, ты прав, мои расчеты верны только если на сайте одна картинка smile.gif а когда она одна - вопрос популярности не встает smile.gif однако всетки некоторая оптимизация будет, при условии совпадения времени загрузки у сразу нескольких пользователей. Однако попробую развить свою мысль дальше:

Устанавливает всем записям старше дня - одинаковую временную метку, обрезая время - иже дата остаецца прежней, а время ставицца в 00:00:00
Код
UPDATE loads_log SET stamp = DATE(stamp) WHERE stamp > (NOW() - INTERVAL 24 HOUR);


Прописываем всем рядам с одинаковой меткой времени в count сумму всех значений count группы
Код
UPDATE loads_log SET count = SUM(count) WHERE stamp > (NOW() - INTERVAL 24 HOUR) GROUP BY stamp;


Итого у нас получаецца что

id / stamp / count
1 / 25.03.2008 13:00 / 5
2 / 25.03.2008 14:00 / 7
3 / 25.03.2008 14:30 / 10

Мы преобразуем в:

id / stamp / count
1 / 25.03.2008 00:00 / 22
2 / 25.03.2008 00:00 / 22
3 / 25.03.2008 00:00 / 22

И вот с удалением дубликатов я повис, толи с помощью PHP их сначала читать, а-ля

Код
SELECT stamp, COUNT(*) as count FROM loads_log WHERE stamp > (NOW() - INTERVAL 24 HOUR) GROUP BY stamp;


А потом каждый полученный ряд удалять через

Код
while($row = mysql_fetch_assoc($result)) {
  $stamp = $row['stamp'];
  $count = $row['count'] - 1; // вычитаем с кол-ва дубликатов еденицу (чтобы совсем уж все не удалять :)
  mysql_query("DELETE FROM loads_log WHERE stamp = '{$stamp}' LIMIT '{$count}'");
}


Или что-то с MySQL'ем поумнее намудрить, но сама суть в чем - ты-же не собираешь статистику какая картинка была самой популярной 45 дней назад, с 13:00 до 15:00, соот-но данными о времени можно пренебречь, собрав несколько полей в одно, из минусов - обновление будет грубо говоря раз в сутки, потому как если был скачок посещений одной картинки 17-го, то как только наступит 26-ое 00:00 - недельная статистика картинки резко упадет на значение этого скачка. Хотя если уж действительно нашаманить с базой данных - можно сделать таких операций несколько, сжимая статистику предыдущего дня - до раз в 15 минут, предпредыдущего - до раз в час и т.п., иже поступенчато.

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

Спустя 1 день, 2 часа, 21 минута, 12 секунд (26.03.2008 - 17:31) Arc_SilveR написал(а):
2sergeiss
Цитата
Насчет "антинакрутки" не понял... Кто-то специально может вызывать определенную картинку неадекватно большое число раз?

Да, может. Кто нибудь загрузит картинку и начинает тыкать на нее, она и висит в топе целые сутки, пока эта срань не опустится smile.gif Либо еще и скрипт как напишет, что как вылезет в топ! Поэтому организовал защиту. И она уже есть, для этого и записывается у меня IP в логах загрузок.

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

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

Цитата
И еще... Я так понял, что ты не используешь встроенные в БД функции?

Что за встроенные функции? Нет, не использую. Где про это прочитать можно и какая от них польза? Это что то вроде функций в PHP? Я создаю функцию, а она потом работает, так что ли?

Цитата
Формирование списка "ТОП 10"
Может это и не имеет значения, но на всякий случай скажу, что у меня не топ 10, а полноценный топ многостраничный по всем картинкам.



2LoneCat
Цитата
однако всетки некоторая оптимизация будет, при условии совпадения времени загрузки у сразу нескольких пользователей.

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

Вообще недельной и месячной статистикой я могу пренебречь, так как она пользуется ничтожной популярностью. Особенной популярностью пользуется почему-то именно дневная и "за все время". "За все время" щас и так работает тупо по общему кол-ву загрузок. А дневная отключена щас, а ведь был самый популярный раздел на сайте.


В любом случае ваши рассуждения натолкнули меня на одну очень, на мой взгляд, неплохую мысль, что то вроде симбиоза ваших мыслей. В общем, оцените, вроде просто и действенно.
Что если тупо создать отдельную таблицу с id картинки и 24 полями, означающих соответствующую часть суток. Ну или хотя бы 12. И в каждый временной интервал инкременировать соответствующее поле. При наступлении каждого часа CRON'ом обнулять новый час.

Что это дает? Да это дает все! Автоматически данные, записанные сутки назад, стираются и записываются новые. Думаю, 12 или 24 поля это не очень много. Как идея? По-моему, должно отлично работать. И кол-во просмотренных строк вообще не будет превышать 9 ти (если запрашивать 9 самых популярных и так далее на каждой странице далее тупо использую LIMIT), верно же? И обновление раз в час вполне норм. Ну как раз в час .. даже чаще, просто уменьшаться будет только раз в час.

Но для этого по ходу надо сделать 26-е, последнее поле, которое будет являться суммой все полей. Это случайно нельзя сделать как нибудь автоматически средствами MYSQL? Еще и индекс сделать по id картинки, так вообще конфетка будет. Только попробуйте сказать, что такая схема работать не будет! С нетерпением жду ваших дальнейших рекомендаций smile.gif

Спустя 2 часа, 5 минут, 19 секунд (26.03.2008 - 19:36) sergeiss написал(а):
Цитата(Arc_SilveR @ 26.3.2008, 17:31) [snapback]35683[/snapback]
Цитата
И еще... Я так понял, что ты не используешь встроенные в БД функции?

Что за встроенные функции? Нет, не использую. Где про это прочитать можно и какая от них польза? Это что то вроде функций в PHP? Я создаю функцию, а она потом работает, так что ли?

Ищи в хэлпе (или в и-нете) по словам Stored Routines, Stored Procedures, Stored Functions, Triggers. Это программы, функции, триггеры, которые работаютс с БД вне зависимости от того, через какую программу ты обращаешься к БД. Нормальная БД поддерживается серверной программой, которая как раз и работает с этими объектами.
Например, твой скрипт может поддерживать целостность данных. Но другой скрипт или программа, обратившись к той же БД, могут эту целостность нарушить. Но: при использовании хранимых (встроенных) процедур эта проблема исчезает. Потому что вне зависимости от того, откуда приходит запрос сделать то-то и то-то, будут выполнены определенные дополнительные действия.
Также, вместо организации сложного запроса (на много связанных таблиц) можно эту задачу переложить на встроенные процедуры. Ты один раз ее отлаживаешь, а потом запрашиваешь данные как через простой запрос. И тогда из разных частей одного скрипта или из разных скриптов ты можешь вызывать эти данные и быть уверенным, что полученные данные достоверны. А второй раз ты потратишь на организацию запроса очень мало времени.
Цитата(Arc_SilveR @ 26.3.2008, 17:31) [snapback]35683[/snapback]
Цитата
Формирование списка "ТОП 10"
Может это и не имеет значения, но на всякий случай скажу, что у меня не топ 10, а полноценный топ многостраничный по всем картинкам.

Говоря "ТОП 10" я условно имел ввиду smile.gif

Цитата(Arc_SilveR @ 26.3.2008, 17:31) [snapback]35683[/snapback]
Чем отличается это от того, чтобы создать поле в записях картинок `loads_today` и обнулять его просто каждый день? Этот вариант не подходит опять же из за столь редкого обновления.

Да, можно и такое поле создать.
И я не понял - где тут редкое обновление? Я же говорил о том, что эти записи обновляются при каждом обращении к определенной картинке.
Отличие от твоего первоначального варианта в том, что при обращении не делается уникальная запись, а обновляется существующая (если она уже была создана). НО!!! Я говорил про проверку того, что какой-то юзер уже "втыкал" в эту картинку. В этом случае также вовсе не обязательно делать запись в БД, а можно только обновить запись для данного юзера и для данной картинки (во второй таблице).

А насчет алгоритма... Да, описанный тобой алгоритм будет работать. Только я не до конца понял логику. Ну хорошо, ты видишь сумму обновлений каждый час. Но в начале каждого часа все равно происходит обнуление данных!!!
Да и вообще, тогда уж лучше создать 24 записи, с полями "дата", "час", "количество". И тогда вопросы суммирования за день решаются легко и просто. Но чем это принципиально отличается от того, что говорил я smile.gif? Только уменьшением интервала до одного часа? Но только все равно в начале каждого часа будет неопределенность, т.к. на первое место будут выходить картинки, которые первыми посмотрели. Можно, конечно, в течение некоторого времени (например, минут 15) с начала часа текущего показывать статистику часа предыдущего, пока копятся данные.

И еще... Я бы, мне кажется, ограничил этот ТОП одной страничкой или каким-то разумным количеством. Какая разница, какая картинка находится например, на сотом или 150-м месте? Более интересно, какие картинки возглавляют список популярных.


_____________
Быстрый ответ:

 Графические смайлики |  Показывать подпись
Здесь расположена полная версия этой страницы.
Invision Power Board © 2001-2025 Invision Power Services, Inc.