Работа с большими объёмами данных и хабраэффект

Одной из целей создания bullshitbingo.ru было посмотреть как ведёт себя google application engine (GAE) в более-менее реальных условиях. Особенно меня интересовала возможность получения собственной статистики, потому что то, что дают GAE и google analitics меня не устраивает по причинам, которые я приведу ниже. На сам пост особой реакции не было, но на главную он вышел и за день сайт получил примерно 15 тысяч загрузок, чего было вполне достаточно. Пик нагрузки составил 3-4 запроса в секунду, в итоге отведённый GAE лимит бесплатных ресурсов превышен не был.

Дальше описание особенностей работы со статистикой в GAE и во второй части графики про полученную нагрузку: собственные и те, которые формирует google. Постарался написать так, чтобы было понятно и тем, кто с GAE вообще не сталкивался.


Часть первая: статистика
GAE, конечно, показывает собственные графики, но есть ряд вопросов:
  • посмотреть их через какое-то время становится невозможно, графики доступны только за последние сутки;
  • представление данных фиксировано и не настраивается;
  • нет возможности построить график по каким-то своим условиям, в случае bullshitbingo мне интересно было бы посмотреть отдельно разные игры.
Google Analitics гораздо более интересная в этом плане штука, но с ним свои проблемы: javascript, ограниченный часом минимальный интервал времени и т.д. Короче меня это всё не устраивало. Поэтому при каждой загрузке страницы я записывал информацию о запросе в базу. В итоге получилось примерно 15 000 записей о запросах, по которым, допустим, хочется построить график.

Проблема: GAE принципиально не умеет возвращать больше 1000 записей. Связано это с нереляционной моделью данных, и, возможно, кому-то это совсем не мешает, но при работе со статистикой мешает и сильно. Ещё одно соображение: строить какие-то сложные запросы и что-то вычислять прямо на стороне GAE может оказаться очень накладно, за процессорное время и хранение больших объёмов данных придётся платить. Так как статистика это, вообще говоря, «мёртвые» данные, не необходимые для функционирования, то её вполне можно забирать с серверов GAE куда-то себе, и уже там обрабатывать. Даже удобнее. Поэтому было решено выгружать статистику в виде csv-файлов и работать с ней уже как-то локально.

Выгрузка данных
Выгрузить данных это отдельная задача, потому что выбирать записи со смещением GAE тоже не умеет. Вернее умеет, но реализуется это фактически на стороне клиента (приложения, а не http-клиента, конечно). То есть, когда я хочу получить 10 записей начиная с 100-й, то сделать это можно, и для этого даже есть соответствующий параметр у вызова fetch(), но по факту из базы будут извлечены все 110 записей, просто первые сто API оставит себе. То есть просто так получить 100 записей начиная с 1000-й вообще нельзя никак. Об этом даже написано в документации, но как-то несколько туманно.

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

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

Скрипт выгрузки статистики в качестве параметра принимает максимальную дату (именно дату, без времени), до которой требуется выдать данные. На это есть две причины:
    1) статистика поступает постоянно, но данные «за вчера и ранее» уже не изменятся;
    2) выгружать сразу всю статистику может просто не получиться из-за большого объёма данных и ограничений на время выполнения, а так будет возможность выгружать хоть по одному дню.

Таким образом, алгоритм крупными мазками получился следующий:
  1. выбрать 1000 самых старых записей при помощи примерно такого запроса: SELECT * FROM request where date < $date order by date limit 1000;
  2. по каждой строке сформировать csv-запись;
  3. запомнить $last_date — максимальная полученная дата;
  4. выполнить запрос, дополнив его условием с $last_date: SELECT * FROM request where date < $date and date >= $last_date order by date limit 1000;
  5. пока результат запроса непустой goto 2.
После получения csv-файла необходимо 10 раз убедиться, что статистика выгрузилась полностью, корректно и за требуемый период, после чего её можно удалять из базы данных GAE.

При сравнении с $last_date используется «нестрого больше» потому что теоретически возможность точного совпадения времени у двух разных запросов всё-таки существует. Чтобы строки на стыке страниц не дублировались нужно сверить их уникальные ключи (а GAE такой ключ генерирует для любых хранимых в БД объектов) и опустить строку, если она уже была выгружена.

В случае с bullshitbingo данные за день, в который я опубликовал пост на хабре, выгрузились за 20 с небольшим секунд, то есть на грани фола. Если данных будет немного больше, то всё-таки придётся разбивать выгрузки не на дни, а уже на часы.

Удаление статистики. С этим, конечно, опять проблемы. Документация уверяет, что эффективнее удалять записи из базы данных массово, а не по одной. При попытке просто удалить всё что меньше заданной даты неизменно получался timeout. Причём когда я проверял эту процедуру на сравнимых объёмах локально, она медленно, но работала. Пришлось переписать процедуру удаления по одной записи. За отведённые на время выполнения скрипта 30 секунд удалялось по 400-600 записей. Я переписал процедуру ещё раз и стал удалять записи по 100 штук, это вроде бы процесс ускорило, разбираться что именно там случилось уже не было сил. Удаляет и ладно, приёмов в 10 всё получилось.

Часть вторая: «хабраэффект?»
На эту тему уже было несколько статей, например вот статья именно про GAE, но на java и там просто отдаётся картинка, а у меня то полнофункциональный проект.

В общем, особо эффектом я бы это дело тоже не называл: в пике нагрузка составила 4 запроса в секунду. На протяжении не более часа нагрузка была около двух запросов в секунду, после чего стабильно падала, это всё видно из графиков.


Полная статистика. Пиковой нагрузки в 4 запроса здесь не видно из-за усреднения.

Время — московское, пост опубликован в 14:40, очевидно примерно через час он вышел на главную. GAE время хранит в GMT, я преобразование выполнял уже на этапе рисования графиков, хотя можно было бы и при загрузке csv-файлов, пожалуй.

Отдельно статистика по главной странице.


Статистика по хабра-игре.


Статистика на следующий день: к 11 утра основная масса людей дочитывает до второй страницы хабраленты.


Административный интерфейс GAE. Здесь же видно и израсходованные ресурсы, скриншоты сделаны часов через 10 после публикации поста. Мне мои рисунки нравятся больше.

Ещё есть статистика за два дня, можно оценить масштаб.

Следует отметить, что никакой оптимизацией я вообще не занимался. То есть при каждом запросе всё что можно (всякие подписи, заголовки, названия и описания, все слова для игр) выбиралось из базы данных, разве что искусственных пустых циклов не расставил. Сделано так во-первых из-за лени, во-вторых чтобы посмотреть что получится. В итоге израсходовано примерно 70% ресурсов, которые GAE предоставляет бесплатно. Узким местом оказался CPU, от всех остальных ресурсов было израсходовано по 1-2%. Кроме того, было получено несколько ошибок вида timeout при обращении к базе данных, поэтому большая часть работы с базой данных позже была перенесена на memcach.

Ещё немного статистики
После поста порядка 150-ти человек попробовали посмотреть на административный интерфейс, было создано 90 игр, почти 30 из них непустые и штук 15 вполне себе с осмысленным содержимым, их авторам – привет.

Всего зашло человек (по ip адресам): 6814
Загрузили более одной страницы: 3573 или 52%
Загрузили более двух страниц: 2334, 34%
Более десяти: 215, 3%

Выводы
Работать с большими объёмами данных в GAE не очень удобно, но вполне можно. Для реальной работы придётся написать скрипты, которые по расписанию будут выгружать статистику, автоматизировано её проверять на корректность и после этого инициализировать очистку выгруженного на стороне GAE. То есть это всё приводит к довольно заметным накладным расходам и создаёт вполне определённые, но преодалимые трудности.
+45
16 декабря 2009, 14:26
30
dug 200,0

комментарии (49)

+1
Violinist #
Спасибо Вам за подробную статистику.
0
dug #
да статистика — моё второе имя ;) забыл про браузеры привести, но это и без меня наверняка сколько угодно раз посчитано
0
AlexVS85 #
Спасибо!
не знал что у GAE есть бесплатный пакет, будет время погуглю на эту тему,
но если кто уже пробовал какие там ограничения/недостатки, простенький сайтик (WordPress, PHP, GD, ..) типа thai-massage.kiev.ua/ на нём будет работать?
+2
dzmitryc #
PHP там нет — есть питон и ява.
Но можете погуглить «any-cms-is-google-app-engine-compatible» — первый результат — 9 цмс-ок.
0
zw0rk #
Формально там есть PHP, через quercus. Однако, целесообразность и отношение профит/ресурсоемкость на один запрос для такого «решения» под большим вопросом.
+2
dbarashev #
Самое существенное ограничение для переноса простенького сайтика — это то что на GAE нет реляционной СУБД. Для тех приложений на которые GAE рассчитан это не showstopper и с этим можно жить, но нужно привыкать к несколько иному стилю обращения с данными.

В смысле ограничений бесплатной квоты, несколько десятков тысяч pageviews в день в квоту уложатся.
0
oleg_podsadny #
Я делал свой микро-цмс. Была идея сделать сайт чисто на сервисах гугл, картинки к фотогалерее хостятся на пикасе, а ссылки на них подтягиваются через gdata api. В общем-то на гае можно что-то приличное даже сделать сочетая карту, календарь, документы, картинки, аналитику (да почти все гуглосервисы). Я рассматриваю ГАЕ как хороший клей для веб сервисов для мобилок. Все равно более 1000 записей экран и перфоманс андроида/айфона отображать не позволит.
+4
dzmitryc #
Гм. По-вашему выходит «хабраэфект» — 3-4 запроса в секунду?
Порядка 40 запросов в секунду тянет слабый VPS (и там работает связка nginx — java — mysql, ява, естественно, не просто статику отдаёт. про спринг/хибер я в то время ещё не знал).

А вообще — здорово.
В то время, как я просто «хочу попробовать что-нибудь сделать на гугл-апп-энджине», Вы показываете технологию, подогреваете интерес — спасибо :)
+2
dug #
Да, «хабраэффект» это 3-4 запроса в секунду. Тут со мной согласен автор упомянутой статьи, поэтому я более-менее в цифре уверен. Так что тем, чьи стайты не выдерживают упоминания на хабре стоило бы задуматься.

У GAE очень клёвая документация. Разве что вопрос с этим ограничением в 1000 записей нифига не раскрыт, только упомянут.
0
opeg #
статику, можно и больше вытянуть, адинамически собираемые страницы при помоци скриптов после 5 страниц в секунду начинаются серьёзные оптимизации.

для полной загрузки одной насыщенной информацией страницы в требуется несколько десятков запросов
например
яндекс — 25 запросов
гугл — 7
рбк — 180
0
dzmitryc #
Вы должны были обратить внимание, что речь шла о запросах к скрипту (то есть к динамике). А статика (картинки, css, скрипты) может хоть в стократном соотношении отдаваться с лёгких зеркал тем же nginx-ом.
0
BarsMonster #
Да, хаброэффект — это 3-4 запроса в секунду. НО! Если если пользователи остаются на сайте и кликают раз 10-100, получим 40-400 запросов в секунду… А если на сайте что-то дежелое есть (типа видео) — вот все и лежит )
0
dug #
Я немного запутался, вы, кажется, имеете отношение к статье, на которую я ссылался, да? Тут ситуация немного другая: само появление на главной к нагрузке не приводило, нужен был именно переход пользователей. Но эти пользователи переходили и во всё подряд там тыкали ;) Видимо поэтому цифры получились похожие.

Я даже удивился, при довольно скромной проявленной реакции (60 что ли голосов всего) 6.5 тысяч уникальных посетителей.

Сайт конечно совсем простенький, но на тот момент (до перехода на memcach) запросов 10 каждый зашедший пользователь к базе генерировал.
0
BarsMonster #
Да, я писал «дедушку» вашей статьи :-)
А голоса — что на них смотреть, многие не имеют акка на хабре, многим лень кликать :-)
НЛО прилетело и опубликовало эту надпись здесь
+4
dug #
Сэр, в статье больше тысячи слов, и вы кроме этой цифры ничего любопытного не отметили?
НЛО прилетело и опубликовало эту надпись здесь
+1
dug #
Сэр, говорят же, 15 тысяч записей при фактическом ограничении на выборку в 1000 записей. Статья про это, а не про нагрузку. Графики я привёл для того чтобы показать как количество запросов распределяется в динамике.
НЛО прилетело и опубликовало эту надпись здесь
+1
dug #
Тогда понятно. Статья не про нагрузку, статья про статистику. «Хабраэффект» довольно смешное слово, никакого эффекта, о чём неоднократно уже выссказано. На GAE пишете?
НЛО прилетело и опубликовало эту надпись здесь
+3
dbarashev #
Согласившись с неудобством работы с данными в GAE вообще, и не особо богатым мониторингом ресурсопотребления, позволю заметить, что:

Во-первых в реляционной СУБД нет номера строки. Бывают создаваемые администратором ключи (возможно, суррогатные), которые могут быть или не быть отсортированными. То же самое можно делать и с GAE, если знать про некоторые тонкости генерирования очередного значения.

Во-вторых, если для анализа достаточно логов запросов, то их можно скачать при помощи appcfg
0
dug #
Ну номер строки — абстрактно. Я же могу сделать в mysql «limit 8000, 100», а в GAE — не могу.

Про во-вторых, это всё сферические кони, в другом проекте статистика будет очень другая и лог запросов не поможет, хотя то, что можно скачать лог запросов я действительно не знал, спасибо.
+1
dbarashev #
Что-то мне подсказывает, что если запрос нетривиальный, то внутри MySQL будет происходить то же самое, то есть получит всё и вырежет нужное окошко. Так что врядли тут есть какое-то существенное отличие (если игнорировать другие существенные отличия :)
0
dug #
А по-моему есть. MySQL этим всем заниматься будет сам, а в GAE сервер базы данных вернёт всё клиенту, то есть этот отступ — не его дело вобоще: спрошено, он выборку построит, дальше сами. А нетривиальные вопросы, кстати, тоже не по части GAE ;)
+1
dbarashev #
Ээээ… The fetch() method skips the first offset results, then returns the rest (limit results). Тот же самый LIMIT, или я чего-то не понимаю?
0
dug #
Вот я и говорю: мутно и невнятно (в документации). От туда же:

«Приложение извлекает из хранилища данных offset + limit результатов. Первые offset результатов не пропускаются самим хранилищем данных.»

что в переводе на русский звучит так: The first offset results are not skipped by the datastore itself.

Я где-то в интернете находил фразу о том, что этим занимается уже клиент СУБД, после чего всё встало на свои места и стало понятно как быть.
+1
dbarashev #
Мне кажется что это претензии к переводчикам документации, умудрившимся datastore перевести как «приложение». Читайте англоязычную версию — там тоже иногда не фонтан, но всё ж таки текст адекватнее.
0
Violinist #
А кто может сказать на телефоны каких операторов в Украине отправляется код подтверждения при регистрации и при этом успешно доходит?)
0
Jeteir #
На МТС доходит точно, и точно не доходит на Билайн. Остальные — с переменным успехом (с неясной корреляцией от времени года).
0
Violinist #
Билайн и Киевстар попробовал — не доходило, думал может вообще нельзя.
0
stoune #
Попробуйте повторно. Я на Киевстаре зарегистрировался без проблем ещё когда по приглашениям вход был. Да и не должно там проблем быть, вон календарь регулярно мне СМС-ки кидает.
+1
dug #
надо график построить :)
+1
aknizubr #
Страница поддерживаемых операторов
www.google.com/support/calendar/bin/answer.py?answer=37226&hl=en#U
0
Ercitix #
Регистрировался с Life, все получилось без проблем. Смс моментальна пришла.
0
Ercitix #
*моментально )
0
intenter #
Небольшой хинт для организации пейджинга. Если не стоит задачи выбирать по 1000 записей, то можно получать PAGE_SIZE+1 записей и на основании результата определять есть ли данные на следующей странице.

Вообще пейджинг на GAE — та еще задача. Впрочем, в Google давно уже обещают механизм для этого сделать какой-нибудь.
+1
mrskam #
Курсоры уже есть, читайте доку. Пока еще неофициальные, но уже работают.
0
dug #
Так сходу нашлось в java, в питоне тоже работает?
0
mrskam #
Да, with_cursors(), как-то так называется
0
dug #
Ну если у нас страница — 100 записей, то получить 11-ю страницу уже не получится.
0
mrskam #
Получится, надо использовать __key__
0
Dalairen #
Да, я придира, но транслитерация «булшыт» полностью верна, однако, «булщит» читается лучше.))
А вообще сервис как минимум забавен.)
Вижу первые.
0
kutu #
для более быстрого извлечения 1000+ записей используйте сортировку по __key__

ещё немного статистики:
у меня есть регулярный пик продолжительностью два часа по 10-12 запросов в секунду, проверяет хеш и выдает из мемкеша немного данных, после чего 40% процессорного времени исчерпывается, ошибок почти не бывает (максимум до 10 и то это не критично для меня)
0
dug #
Мне кажетеся документация не рекомендует раcсчитывать на монотонное возрастание __key__, или я ошибаюсь?
0
kutu #
да, это так, я же имел ввиду если нужно просто перебрать все записи
+1
texamus #
написал о своем опыте получения статистики
habrahabr.ru/blogs/gae/78485/
0
dmgorsky #
Статистика и результаты исследования: Google выдерживает хабраэффект.
;)
0
dug #
да google то прямое попадание ядерной бомбы выдержит :) а вот строить про это графики некому может оказаться, да, проблема.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.