Comments 126
так они сразу и сохраняются в монго ( outStorage ).
если сохранение отключить (например), производительность увеличится где-то на 100rpq
оно роли не играет
А сколько MongoDB выдержит?

{ "_id": ObjectId(«50636e92fd4968f539000005»), «da»: 1348693650, «ip»: «46.30.167.101», «ui»: «66431», «f_lng»: «ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3», «f_uag»: «Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1», «ur»: «______/___?utm_source=google_adw&utm_medium=cpc&utm_campaign=kyrsy_v_kiev» }

Как вариант IP, конечно, сохранять в int, но все же интересно, как будет себя вести MongoDB при 100 миллионах записей? Сейчас for fun тестим такой лог.
IMXO MongoBD: все зависит от железа, думаю что 100М на хорошем сервере не проблема (Памяти побольше выделите). У меня в проекте (это уже другой проект) она стояла на виртуалке: 2 проца 1Gb, так на 1M записей (1 запись — 1-2K) она подтормаживала.
расскажи как протестили
можно было заморочиться с написанием собственного стораджа, но это имеет место если кол-во возможных переходов не столь велико (десятки тысяч)
Если их более 1 млн, то лучше использовать существующие решения
Как раз-таки собственный сторейдж имеет смысл писать если существующие решения не справляются с вашей нагрузкой.
Ну это смотря что хранить в сторадже. Одну схему я описал ниже, там с хранением данных вполне справлялся MySQL (при суточном трафике до 40 млн. запросов на том самом P-3).
Вот действительно придется писать что-то свое — когда требуется какая-то обработка «на лету» с минимальным временем отклика. Но это такое широкое поле в зависимости от поставленной задачи, что одной статьи будет мало, как минимум — толстая книжка :)
согл, по этому и не стал заморачиваться
а использовал memcachedb

если ко-во переходов очень много, то написать хороший высокопроизводительный стородж, превосходящий по показателям существующие решения будет весьма затруднительно.
Как вовремя-то! Мы как раз собирались свое решение писать.
Только мы хотели использовать исключительно mongodb. Объясните, чем обусловлен выбор memcachedb для inStorage?
есть два необузданных коня: тарантул и кайот, которые могли бы занять место memcachedb.

1) проверенное решение в других проектах
2) key/value
3) занимает столько памяти, сколько выделим
4) достаточная производительность
5) устойчиво в случае сбоя или перегрузки, в отличие от memcache

написать плагин для нового стоража 3-4 часа
так что, если будут какие предложения, то могу доработать.

В свое время и по нынешнее решили остановиться на PHP+MemcacheQ- отличная производительность и масштабируемость:

— php за nginx
— данные кладутся в MemcacheQ
— по крону очередь из MemcacheQ анализируется и кладется в MySQL

Используя MemcacheQ можно не думать о плавающей нагрузке с пиками — данные по кликам обрабатываются независимо от процесса кликов.
такая схема рассматривалась,
с РНР время отклика не устраивало.

при данной схеме, можно вполне обойтись и без PHP
напрямую класть из nginx в мемкешQ (есть такой модуль)
Это зависит от способа запуска PHP.

В нашем случае на PHP был разбор click-карты и расшифровка get-параметров (PwS8AnMUf9840000ZhPTWiq4KfK2cm5kGoq1YAtpu)

Click-карта была в memcache — предварительно помещены в память.
serialized-массив. Порядка 3х элементов на каждый сгенерированный target.
а в смысле размерности,
интересно сколько элементов пихали в мемкеш?
в текущем состоянии — не больше 10к элементов. Работает очень быстро, причем предзагрузка выполняется на пределе винчестера — то есть максимально быстро на текущем оборудовании. Пара секунд.
я думал об этом,
у меня предполагается 3-5 млн
по этому не хотелось бы все пихать в мемкеш
тут нечего оптимизировать,
каждая цель — это позиция товара,
переход на сайт партнера
позиций от 3 млн
на самом деле для memcache 3млн Х 128байт = это всего 366 МБ в памяти, так что крутите memcache без вопросов. Конечно плюс служебные заголовки, serialized etc, но не более гига на 3млн позиций — это нормально.
Я в одной баннерке использую трехслойную схему.
В самом последнем слое (бэкэнде) — MySQL (можно любую другую sql-базу, на вкус). База содержит агрегированные данные статистики, которые можно по-разному вертеть с помощью запросов.
Средний слой — это логи входяшего потока и набор файлов (имя==ключ; содержимое==переход). На небольшом количестве данных (до нескольких тысяч элементов) работает достаточно шустро.
Верхний слой (фронтэнд) — модуль, встроенный в шустрый вебсервер. При такой организации данных работа модуля никак не тормозит вебсервер. Гигагерцовый пень3 держал до полутора тысяч запросов в секунду.
Это я к тому, что можно решить задачу с минимальным усложнением (одна субд проще, чем две :). Плюс, MySQL, если его не грузить базовым трафиком, а только сводной статистикой — более удобен, чем NoSQL хранилища.
у меня миллионы, по этому выбрали memcachedb
было бы меньше миллиона, вполне можно было замутить что-то своё
У меня на упомянутом гигагерцовом 3м пне спокойно держалось 40 миллионов запросов показов-кликов банеров.
А насчет когда лучше мутить свое — до или после миллиона — www.liveinternet.ru/, левый верхний угол. Там конечно больше одного сервера, но не на много. Такое выдержит только свое.
помню это выступление на РИТе, где Лайвинтернет рассказывал, как на IV пне запустил свой подсчетчик кликов, написанный на си, который выдерживал несколько миллионов запросов.
Выступал я. И выступление было отстойным. Рассказывать про то, что сделано не тобой довольно тяжело.
странно что вы все пытаетесь апдейтить базу на хит,
достаточно делать счетчик в памяти, инкрементить на хит, а сбрасывать в базу периодически накопившуюся дельту или новое значение, ну а список ссылок — хеш по id в файле поднимаемом по mmap в nginx

такая схема легко держит практически любое кол-во запросов
Кто сказал, что я каждый хит в базу апдейчу? Она такого потока просто не выдержит. Данные копятся в логах и по таймеру периодически агрегируются и сбрасываются в базу. На основании данных агрегатов рассчитываются и обновляются очереди баннеропоказов. Первоначально копил статистику в счетчиках, но потом отказался ради экономии времени на процесс обновления данных. Процесс «забрать данные-расчитать новые-обновить» заменился более коротким — «обновить». Более простая система стала устойчивей и из-за сокращения времени на забор-обновление информации сократились потери данных.
была идея держать счетчики в памяти и переодически сбрасывать
но пока решил не усложнять.
схема интересна
была идея сделать все средствами nginx (в ввиде модуля)
хеш слишком большой (миллионы)
запросов на один переход слишком мало
ну мы почти так и делаем, только nginx кидает короткий пакет на сервер агрегатор который аккумулирует сумму в памяти и скидывает в базу накопившуюся дельту, узкое место — сетевая карта, если она не умеет разбирать очередь в несколько ядер то количество запросов в секунду ограничивается где-то 150000 RPS
в добавление к вышесказанному
статистика будет обрабатываться с монго
я вообще хотел весь проект на монго замутить,
но решил начать со статистики.
Сэр! Кто же так пишет?
в http_handler.cpp:

#include «tools.c»
#include «inputMcStorage.cpp»
#include «outputMongoStorage.cpp»

в tools.c:
#include «ini_parse.c»
#include «http_handler.cpp»

Что приводит к необходимости использования экранирующих дефайнов в c/сpp файлах…
брррр…
были бы у вас только с этим проблемы… намешан С и С++, куча копипаста, магических цифр (я например с ходу и не понял что такое if (t==100) в парсинге ini (а вообще нахрена писать свой парсер ини файлов это тоже тот еще вопросец), ну а если вы пишете «IStorage » и указываете «the Interface of the Storage Engine», то чистые вирт функции, вместо виртуальных заглушек сам Бог Страуструп велел…

ИМХО в общем код на 2 из 10…
магические цифры только в парсере
который использует re2c
возможно надо было использовать другой парсер, но как я указывал где-то в комментариях, мой парсер ищет ошибки конфига.
По долгу службы, сам занимаюсь разработкой, высокопроизводительного веб сервера/сервиса на плюсах. Конечно было интересно, что другие люди пишут…
мое решение на коленке за три-четыре дня
естественно не идеал. спасибо за замечания
работаю над кодом
но ини-парсер пока не уберу (гибкий фриендли и мне нравится хоть и на си).
но если подскажешь либу, то буду благодарен (если либа понравится то уберу)
парсер uri буду использовать другой. пока смотрю подходящие либы

Что за решение на php такое, что давало задержку 1.5-2с? Может из базы перегруженной просто долго тянуло? Так можно было просто базу поменять, а не на плюсах огород городить.
Что за решение на php такое, что давало задержку 1.5-2с?

ну не 1,5-2 а 0.5-1 сек, все равно не приятно.
там достаточно кривостей было, это я согласен. Сервер был перегружен,
Было использовано решение — принимать PHP скриптом и ложить в лог.
По крону каждые 15 мин забирать из лога и класть в БД. Но, в новом проекте данное решение не устраивает.
Если читать из kv базы и писать переход в лог, то решение на php-fpm+nginx будет не медленнее вашего (2000 в секунду на средненьком двухядерном сервере я лично выжимал). То, что при работе с базой будут задержки, это уже проблемы базы, а не php.

Я для таких целей использовал встроенный в nginx perl. Все очень просто и быстро.
А что такое rpq? requests per what? в минуту? в секунду? в квартал? :)
У нас работает на сервере сбор статистики piwik и баннерная сеть openx, считающая показы(и то и другое apache + php + mysql), оно периодически упирается в жесткий диск (один, без рейдов, потребительского класса обычный 7200rpm, на сервере 2гб ram) и начинает тупить по нескольку секунд на запрос. В пике сейчас 60-70 запросов в секунду.
во многих проектах все упирается в жесткий диск.
мои тесты показали что 1100 — 1200 HTTP запросов в сек — это норма.
Нашел неплохой (на мой взгляд) вариант применения данного решения (легкий http-фронтэнд + легкий http-бэкэнд). Бэкэнд делается многопоточным. Это дает ему возможность параллельно отрабатывать несколько соединений, например в играх или приложениях онлайн-торговли типа форекса и т.п.
Ставить в бэкэнде сервер типа апача для подобных задач может оказаться слишком тяжелым решением. А легкий фронтэнд тира nginx на многопоточную работу не расчитан, зато спокойно проксирует пучок соединений. В итоге из двух слагаемых получаем довольно неплохую сумму :)
За что я люблю Хабр — опиши проблему и с большой вероятностью предложат варианты решений :)
Есть данные по нагрузочному тестированию Tornado? То, что он на Питоне — несколько смущает.
не работал с нодой,
она проксирование допускает?
если да, то не проблема запустить и в ноде.
До ноды проксируют через nginx.
Вообще-то проблема есть раз не работали с нодой — Си код перелапатить в Javascript и перевести в событийную модель.
Когда то давно написал на коленке FastCGI приложение на C++ для подсчета кликов/показов, данные храняться в обычном STL map хеше. Производительность отличная, на нашем сервере обслуживает до 150 запросов/сек. Жаль конечно, что такого функционала нет в базовом наборе модулей nginx, но в принципе написать свое не составляет сложности.
я комментировал выше.
у меня была идея хранить все в map/SLT но мне показалось что 3-5 млн это достаточно большой хеш. Хотя попробую написать сторадж и для map/STL, а в случае сбоя/перегрузки — весь хеш грузить из БД или файла.
Была идея использовать FastCGI, но с libevent был уже опыт работы по этому сделали данное решение.
Ваша программа конечно более функциональна! И у вас хватило смелости выложить исходники (я свои не публикую не потому что жалко, а потому что быдлокод). Кстати еще коллега писал программу для подсчета уникалов, тоже довольно часто встречающаяся задача, которую плохо решает MySQL =)
1. Мой код не идеален, уже двое заметили, за что им отдельная благодарнгсть
2. Задача часто встречаемая, может кому пригодится
3. Выложил исходники, может кто-то
а) сделает хорошие замечания/исправления
б) напишет плагин для другого стоража

Если плагигнов будет 2-3, то можно сделать фабрику и загружать их через конфиг. А пока что нет смысла городить огород, если это решение одного проекта.
UFO landed and left these words here
спасибо что заглянули в исходники,

что верно, то верно…
рефакторить и рефакторить,
много кусков взято из более ранних проектов.
UFO landed and left these words here
про инклюды .c/.cpp тут уже говорили
согл
про чтение из сокета по 1 байту
другого выхода нет, мы не знаем длинну принимаемого сообщения. если прочесть более большой блок, то программа зависнет в ожидании данных.
обственный парсер инишников
а чем он плох,
меня он устраивает, находит ошибки на уровне чтения ini — файла с указание номера линии. в качестве генератора кода использован re2c.
про int вместо bool.
согл
ро собственный парсер URI
а что тут парсить-то. простой цикл… зачем тянуть посторонние либы.
спасибо за комменты учту
UFO landed and left these words here
когда-то юзал libini
меня не устроило

но принципиально согласен, зачем изобретать велосипеды,
если можно заюзать уже испытанные либы.
времени потратишь меньше.
Можно ещё попробовать libconfig: есть пакеты для linux, указывает на ошибки в конфиге, формат конфига похож на Nginx.
в настоящее время прохожу обкатку
доработку и останется время на рефакторинг!
можешь присоединиться, дам доступ в гит
Мне тоже не понятно, как выдача урла по ключу, и запись инфы в мускуль (можно ведь, без ключей и джойнов или вообще отложенную) может занять на php 0.5-1 сек. Хоть режьте.
попробуйте написать такой скрипт и с помощью http_load или любой другой программы посмотрите, сколько запросов в секунду он сможет обработать.
много лет назад поднимал самописный сборщик статистики на p4(2GHz)/2GbRAM, не удовлетворяли возможности платного spylog, Google Analytics тогда еще не было, да и анализировать приходилось довольно специфичные параметры (GA до сих пор так не может). машинка круглосуточно держала 120-150rps, нагрузочное тестирование показывало что она способна была держать до 400-500rps.
потом затея изжила себя и была отправлена на свалку истории, а для сбора более статистики заюзали GA.
написано было на PHP и MySQL. что там может так тормозить (0.5-1s) я тоже не понимаю. может апач в ДНСы часто лазит, но PHP и MySQL тут точно не при чем.
если под это отдать целый сервер, да еще и БД выкинуть на другой сервак, то оно будет так как ты и говоришь. Но, помимо статистики на этом серваке еще крутятся скрипты основного проекта. Нужно было изобрести велосипед эффективное решение, не занимающее много ресурсов.
может и так. просто название статьи заставило меня думать об отдельной машине. почему так не знаю, но про программный сервер не подумал :")
мне нужно было разработать легкое решение не нагружающее сервак
если БД не нагружена, то возможно оно так и будет. Не забывай про нагруженный WEB сервак другими скриптами и мускуль…
CPP не знаю, так что так спрошу — в каком порядке вы запрос обрабатываете? Сперва возвращаете ответ и закрываете сокет а потом уже все манипуляции с данными проводите или наоборот (Получаем данные, обрабатываем, кладем в базу, пишем ответ и закрываем сокет)?
использую http часть из libevent
первоначально обрабатывается HTTP запрос, потом вызывается кэллбэк (моя обработка ), потом осуществляется пост обработка HTTP, т.е отправка всех заголовков, контента и закрытие соединения.

если делать, как предложили Вы, что вполне логично и немного съэкономило бы ресуров, то пришлось бы изобретать еще один велосипед в виде самописного HTTP сервера, на что ушло бы уйма времени (боюсь оценить сколько).

А так мое решение было разработано за три дня. Правда разработка скрипта на РНР это заняла бы не более дня.
В этом плане мне очень MochiWeb понравился — он в клиентскую часть (мой обработчик запроса) передает «объект» у которого есть методы типа start_response(headers, blabla...), write_chunk(body) т.е. процессом «отвечания» можно управлять. Хошь — чанкед ответ пишешь, хошь — сперва отвечаешь а потом обрабатываешь данные, хошь наоборот. Это не удивительно — разработчики мочивеб сами по себе рекламная сеть какая то. Кстати, можете на него поглядеть на досуге — нагрузку он очень неплохую может держать (только он на Erlang, но я за 3-4 дня разобрался с нулевым знанием языка)

А так, что меня очень удивляет, из всех веб-фреймворков что я помню (Django, любой PHP) — все делают именно так, как вы описали: т.е. функция — мой каллбек возвращает целиком объект ответа (тело, заголовки и т.п.). Видимо потому что в 99% случаев пост-обработка запроса не нужна — все запросы и обработки нужны только для генерации странички. (Хотя чанкед — ответ с генерацией контента налету было бы клево делать).

Ох, разговорился что то
UFO landed and left these words here
Я бы вычерпывал из логов сервера, например в апаче2 это параметр "%{Referer}i" — включен в стандартной конфе.
На моей домашнем компе обрабатывает около 5600 запросов, это 0,18мс. на запрос.
Сервер разгрузили, теперь осталось «спокойно» разобрать логи и положить куда надо.
сгенерировал в mongoDB 2 млн урлов, связал с apache2 через wsgi, (на все ушло 15мин = дешевое решение)
выдает 2100 переходов/сек, 0.49мс на запрос
конечно мемкеш тут будет более уместен, «состряпал» из того что было под рукой
не самая хорошая идея, например ddos может сделать ваши логи «неудобоваримыми»
1) ddos может убить любой сервак

2) для защиты против этого есть некие стандартные меры,
например по ограничению запросов (в сек) с одного IP
на фронт-сервере

3) маловероятно что ddos будет долбить на какую-то конкретную ссылку проксируемую на этот сервер, обычно ddos валят главную страницу или страницу второго уровня, но ни как ни 4-го или 5-го, тем более даже не страницы, а виртуальные ссылки.

4) непосредственно в биллинге есть ограничения (чтоб клиенты не платизи за ДДОС и роботов):
ведется blacklist IP роботов
нельзя делать более 100 запросов с одного IP (больше не учитывается)
нельзя делать более 3х переходов на один и тот же url в течении суток с одного IP (больше не учитывается). Но эти ограничения стоят на уровне логики обработки данных

можно ввести этот блэклист на уровне логирования. Наверно я так и сделаю.
показы можно дергать из логов, хотя это наверно будет доработкой моего решения
Вообще никаких кастов не нужно. Потомок к родителю приводится автоматически — в этом суть динамического полиморфизма.
ошибка компиляции без него, как впрочем и с dynamic_cast.
это лучше чем приведение в стиле cи (IStorage)
мне надо везде по возможности от него отделаться.
Ну и проверка на повторный запуск никуда не годится. Как минимум стоит проверить жив ли указанный pid (kill(pid, 0)), ну а в идеале надо делать open + flock(LOCK_EX | LOCK_NB).
ну а в идеале надо делать open + flock(LOCK_EX | LOCK_NB)

спасибо, так и сделаю
1. Берем nginx
2. Тюним нгикс
3. Генерируем мильен файлов с .html с содержимым 4. ????
5. PROFIT

чорт парсер хулиганит
1. Берем nginx
2. Тюним nginx
3. Генерируем мильен файлов с key.html с содержимым
4. ????
5. PROFIT
выборка из key-value хранилища будет быстрее чем поиск фалов в фс.
согл
да и миллион файлов — это расточительно
нужно строить иерархическую директорию
их ежедневная перестройка и пр трудности с файловым кешем
это ни первый и не последний топик где комменты полезнее самого топика.
не спорю и совершенно не говорю, что сам топик бесполезен, спасибо вам за то, что поделились решением и инициировали достаточно интересную дискуссию
Я в своё время поступил следущим образом: клики направляются на скрипт click.php, который полученные параметры «заворачивает» в однострочный пакет данных и отправляет UDP-запросом на localhost. На нужном порту на локалхосте висит php-демон который несколько минут собирает клики а потом сдаёт всё в БД одним запросом для каждого домена :) Он же следит за накрутками в обозначенном интервале времени: ведь все данные у него под рукой: в массиве (домен => ip => click count)
Примитивно, но на тупейшем VDS небольшая баннерная система работала на ура :)
второе мое решение (это третий или уже четвертый вариант) баннерки было подобным, с тем отличием, что клики ложились в файл, а потом я их собирал по крону и постил в БД.
я как-то сделал замер
решение в ввиде ngx-модуля отрабатывало в 80 раз быстрее чем решение в виде РНР-скрипта
думаю, что решение в виде отдельного сервера отработает раз в 20 быстрее

если нагрузка на сервер не критична, то решение в ввиде РНР-скрипта вполне допустимо.
Вообще я дорабатывал тизерную сеть одну… Там нет таргетинга, считается кол-во показов и кликов. Нормально отрабатывало около 90 млн показов тизеров в сутки (сейчас обороты спали примерно до 50 млн) т.е. чуть более 1000qps, в пики гораздо больше. Все это крутится на одном единственном довольно мощном сервере (и второй сервер — только картинки).
Там все данные о тизерах, забаненных айпишниках, ценой за показ/клик забираются из мемкеша. Если в мемкеше каких то данных почему-то нет (Eviction например или холодный старт), то только тогда забирается из БД и кладется сразу обратно в мемкеш. Статистика за последнюю минуту тоже кладется в мемкеш. Потом раз в минуту данные из него собираются отдельным специальным PHP скриптом, обсчитываются и обобщенная статистика кладется в MySQL. Т.е. фактически для открутки тизеров БД вообще не трогается.
(До мемкеша вообще использовали файловую систему для этих данных — летало почти так же как на мемкеше, видимо кеш ФС работал)

Но вам может не подойти потому что не представляю толком как там можно таргетинг реализовать.
Но PHP там совершенно не к месту был как мне кажется. Около 300 процессов php-fpm за нджинксом, довольно большая нагрузка на сервак. В идеале было бы писать на ЯП в котором состояние между запросами сохраняется чтобы можно было часть данных в памяти держать типа шаблонов, каких то констант, настроек, импортов…
в первую очередь
главное выбрать правильное архитектурное решение.
ЯП — второе дело. Хотя чем скорость отклика выше — тем лучше
С точки зрения логики — С/С++ решения наиболее оптимальный выбор.
С точки зрения скорости разработки — где-то на 20-30% больше чем РНР
Насчет скорости разработки верно, но тут дело в том, что сам скрипт открутки очень простой — около 600 строк кода со всей логикой сортировки тизеров, встроенными HTML шаблонами и занесением данных в БД + отдельно примерно столько же API обертка над мемкешем и БД.

И эти скрипты по объему кода от всего проекта (админка, статистика) составляют около 5-10% но создают более 90% нагрузки. Так что разумно было бы переписать их на чем то пошустрее.

Да и с PHP есть проблема, что нельзя отдать клиенту весь контент и закрыть соединение а потом уже заносить данные о клике/показе в БД.
Да и с PHP есть проблема, что нельзя отдать клиенту весь контент и закрыть соединение а потом уже заносить данные о клике/показе в БД

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

Но, во первых есть шанс что это все же медленнее будет, надо проверять. Просто exec() вернет контроль только когда дочерний процесс завершится, так что нужно или запускать shell_exec() с & в конце команды (получаем оверхед на запуск шелла) или юзать proc_open() и сразу закрыть его stdin и stdout, тут надо смотреть как процесс на это отреагирует. Плюс опять же нужно дополнительно сериализовывать и экранировать данные для передачи процессу в виде аргументов. Ну и огромное количество запускаемых новых процессов ОС вряд ли понравится.

Во вторых, на сервере exec() — команды были закрыты, т.к. его как то ломанули и залили вебшелл который активно exec() использовал
знаешь толк в бэдграундный запусках — это радует
многим пытался втолковать вышеописанные минусы.
как говориться — пока не пощупаешь — не поймешь.

костыль конечно: но я использую это code.google.com/p/php-forker/
разработано специально для запуска долгоиграющих процессов, чтоб не было оверхедов на шел и прочих побочек.

Опять же — одна вставка в БД будет короче, чем инициализация шела, запуск процесса, его форканье и тд.
Only those users with full accounts are able to leave comments. Log in, please.