Я ищу паттерн проектирования или просто совет.
Есть набор ресурсов, например файлов, все ресурсы идентифицируются по URL
Необходимо вести учет количества просмотров. Это само по себе не очень сложно.
Необходимо в добавок вести учёт всех этих просмотров, что бы можно было ответить на вопрос, какие файлы и сколько раз просмотрели за последние Х дней. Т.е. если какой-то URL не запрашивали Х дней - то его статистику нужно потерять.
Этакий чёрный ящик - с одной стороны как конвейер в него скармливается очередь событий (просмотров URL) с другой стороны он способен отдать список активных URL за последние Х дней.
Решение с SQL базой не подходит из-за высокой нагрузки, т.е. та очередь которую ящик потребляет очень интенсивна.
Хотелось бы хранить всё в памяти и только писать на диск список активных URL время от времени, например раз в день. Но как этот список формировать, обновлять и хранить?
Состояние ящика можно потерять, через некоторое время он нагенерит всё заново или прочтёт с диска. Список активных URL тоже не обязан быть real-time можно генерить раз в 3 часа или около того.
Я вижу на разных сайтах это проблема решена - вижу счетчики просмотров-загрузок, вижу топы. Как это вообще проектируют?
Update:
просмотров в секунду - 800 и растёт
уникальных URL ~ 1ооо ооо и растёт
X дней - 7 и 30 дней
April 11 2009, 06:33:56 UTC 3 years ago
или делать счётчики (хоть на основе мемкеша, хоть это и не очень эффективно) и раз в какой-то интервал проходится по ним и складывать в базу.
или, вот, написать модуль к апачу, который бы складывал эту информацию в shm, например
а потом её по крону переносил в базу.
April 11 2009, 09:32:58 UTC 3 years ago
April 11 2009, 12:33:19 UTC 3 years ago
лишняя нагрузка как на сервер на котором работает веб сервер
так и на сервер который их получает.
nginx, например, и так хорошо пишет логи, и буферизирует их.
прощще их ротейтить чащще и копировать на сервер где стоит обработчик, который асинхронно их будет парсить. порядок следования нам же непринципиален в рамках некоторого интревала времени.
April 11 2009, 19:35:17 UTC 3 years ago
3 years ago
3 years ago
3 years ago
April 11 2009, 13:01:10 UTC 3 years ago
April 11 2009, 13:29:59 UTC 3 years ago
собирать-обрабатывать логи - прощще.
пишется отдельный лог только с необходимыми данными.
раз в 5-10-30 минут логи ротейтятся и по scp, например копируются с фронтенда на, например, бэкап сервер. где по мере поступления новых - sort |wc -l, и пачка апдейтов уже уходит на счётчики. и хоть в бд их (счётчики) храни. не успевает сервер обработать данные в период пиковой нагрузки - ничего страшного, нагрузка спадёт, обработает позже.
как то так.
мы, впрочем, решили подобную проблему, когда в первый раз с ней столкнулись, тем что написали модуль к апачу (ни nginx ни memcached тогда небыло) который всё складывал в shm, откуда раз в 10 минут всё переносилось в mysql. но у нас и трекилось куда больше информации и потери были недопустимы, и статистика была нужна "онлайн."
April 11 2009, 19:38:20 UTC 3 years ago
April 11 2009, 09:35:48 UTC 3 years ago
April 11 2009, 12:45:55 UTC 3 years ago
что вообще за привычка всё что есть валить на memcached ?
в данном случае прощще логи парсить, ей богу. куда как эффективнее.
April 11 2009, 12:56:01 UTC 3 years ago
вести учет количества просмотров само по себе не очень сложно. мне не понятно как вести список активных URL, как этот список формировать, обновлять и хранить. Т.е. можно складывать в hashmap на локальном хосте, это пока единственное что в голову приходит...
April 11 2009, 21:44:23 UTC 3 years ago
совет на будущее: при постановке задачи, особенно хайлодной, всегда важно иметь представление о порядке величин. в данном случае интересно количество просмотров в секунду, количество дней Х и количество уникальных URL-ов. при отсутствии данных люди будут вынуждены делать предположения, и вы будете удивлены, насколько разные эти предположения будут. и, как следствие, дизайн.
теперь про базу. есть люди, которые считают, что базы данных "медленные". а когда спрашиваешь про порядок величины - впадают в ступор. не знают. медленные, и всё. надо знать, что современные базы тянут примерно 10 тысяч простых маленьких (как у вас) транзакций в секунду на относительно дешёвом железе (скажем, quad core с двумя массивами raid 10). то есть если ваша высокая нагрузка, тем не менее, укладывается в 10 тысяч просмотров в секунду, то можно даже дальше и не думать, а писать прямо в базу.
поэтому для того, чтобы продолжить рассказ, мне нужно сделать предположение, что ваша нагрузка превышает 10 тысяч в секунду. предположим, что у вас миллион в секунду. хорошая цифра - миллион. во-первых, круглая. во-вторых, ни у кого такой нагрузки нет, разве что у гугла.
надо ли теперь отказываться от базы? не факт. если нужна persistence, то лучше баз этого никто не делает, даже близко никто не стоял. если нужна high availability, то всякие разные high-availability решения (например, mirroring) для баз есть готовые.
поэтому следующим шагом нужно подумать, как справиться с нагрузкой, превышающей возможности одномашинной базы. способов, подходящих в данном случае, два: 1. sharding/partitioning и 2. pre-aggregation.
1. что такое sharding/partitioning? это "растаскивание" базы на несколько машин. его ещё горизонтальным партиционированием называют.
обозначим количество машин N. вычисляем хэш урла и берём из него любой байт B (ну или два байта, если на сотни машин надо раскидать). остаток от деления B на N - это zero-based номер машины, которой этот урл принадлежит.
2. pre-aggregation. те же машины, что раздают файлы/урлы, накапливают текущую статистику и раз в несколько секунд сбрасывают накопленное в базу. если таких машин, предположим, 1000, а сброс в базу происходит раз в 10 секунд, то базе придётся обслуживать 100 запросов в секунду, что раз плюнуть.
сравнение: очевидно, способ 2 в данном случае гораздо лучше и дешевле; не нужны сотни машин для хранения данных. вообще, если pre-aggregation возможен, то, видимо, всегда лучше его делать. иногда бывает, что нельзя - либо нужно хранить сырые данные, либо аггрегировать нужно сразу по многим измерениям, так что лучше сначала положить в базу что есть, а потом асинхронно с этим разбираться.
April 11 2009, 23:48:43 UTC 3 years ago
но вернемя к нашёй рафинированной задаче.
Я вижу как можно побороть удаление старых записей в этой конркетно задаче - можно делать дневные таблицы и писать в новую каждый день. По истечении 30 дней снова писать в первую - rotating tables
Давайте прикинем пару цифр. При сегодняшней нагрузке это даст 70 миллионов записей в сутки. Приняв размер row (храним только URL) в ~252 байта получим 16Gb "логов" в сутки. В месяц соотвественно 480 Gb. Сколько займёт агрегирующий запрос на таких объёмах данных мне трудно прикинуть - хочется только сказать "медленно"
можно пре-агрегировать по дням и так хранить вчерашнююю таблицу, тогда 70 миллионов превратится в ~ 700ооо строк и соотвественно в 200 Mb в сутки или в 6Gb в месяц. Вроде быстрее должно. Но теперь вся статистика опаздывает от 0 минут до 24 часов.
хотелось бы какого-то in memory решения, которое не такое затратное, не требует дополнительного сервера базы данных...
April 12 2009, 07:09:30 UTC 3 years ago
никто кстати не мешает понаделать несколько таблиц и в одну инсертить в друго инкрементить) и таких большую кучку
судя по динамике обновлений примерно так работают ливинтернетовские счетчики)
April 12 2009, 08:29:53 UTC 3 years ago
1а. по 15ооо удалять не надо, надо удалять по 5ооо. после 5ооо в одной транзакции наступает lock escalation и лочится вся таблица (события "залочить индекс" не бывает. залочить можно row, page, или всю таблицу целиком, если дизайн кривой). что вы органолептически наблюдаете, но понять, что происходит, очевидно, не можете.
1b. да и по 5000 - тоже экстремизм, никакой разницы в скорости между 5000 и 500 вы не заметите.
1c. а скорее всего, по уму, вам вообще ничего физически (транзакционно) не надо удалять, а надо данные запартиционировать и swap partition делать раз в день или какой у вас там time frame. это вообще моментально происходит, поскольку metadata only operation.
а форса-то, гонора сколько! а как обычно, просто нету нормального датабазника.
вернёмся к нашей рафинированной задаче. нафига каждый чих-то хранить? в постановке задачи было - количество просмотров за последние X дней. значит надо хранить одну запись per url per day. поскольку вы так и не сказали, сколько у вас уникальных url-ов, я сделаю обидное предположение, что их у вас тысяча. тогда за месяц нужно хранить 1000 * 30 записей размера int + smallint + int (url_id, date_id, count) = 10 байт. итого, общий размер гигабазы получается... триста килобайт. не 480 гигабайт, а триста килобайт. oops.
а датабазника нанять нужно обязательно. он объяснит значение волшебного слова "нормализация".
а, про "сколько займёт агрегирующий запрос" забыл прокомментировать. м-мда. не знаю, что и сказать даже. у вас правда так принято, прямо вот фигачить агрегирующие запросы каждый раз прямо по 480 гигабайтам сырых данных? и после этого, наверное, принято жаловаться, что sql server медленно работает?
спешу обрадовать, что в sql сервере с 2005 года есть такая штука, которая называется indexed view (
москалиораклисты их называют ma-te-ri-a-li-zed). ну а сам паттерн известен года с 1998, и называется "report tables". правда, до появления indexed views их приходилось руками поддерживать.you are welcome, обращайтесь ещё.
April 12 2009, 15:23:54 UTC 3 years ago
я там сверху вчера апдейт сделал, как вы просили
Re: задача про "залоченный индекс"
> 1a, b
практика показала, что 4 транзакции по 4ооо каждую минуту выполняются медленнее 1-ой по 16ооо раз в 4 минуты. Т.е. да, lock escalation, но всё равно так выходит быстрее.
даю статистику: таблица 120Gb, 15ooo ooo записей, +6ооо ооо вставляется каждый день, удаляется чуть меньше, потому что не успеваем удалять.
> а надо данные запартиционировать и swap partition делать раз в день или какой у вас там time frame
предположим сделаем 2 партиции, пишем всё время в одну, в полночь переключаем. читаем из обеих по очереди? Из той в которую пишем чтение будет довольно быстрым, из той которая вчерашняя довольно медленным из-за того что там идёт процесс удаления, но ваши аргументы - что эти два запроса всё равно быстрее чем из одной партиции?
Я полагаю indexed view вы тут не предлагаете использовать - слишком часто идут изменения в партициях.
Re: рафинированная задача
смотрите, вы сэкономили на space из-за того, что данные нормализовали, это означает, что когда у вас есть только URL нужно выполнить SQL и получить его id - не очень хорошо - дополнительная нагрузка на базу, это самое sequential ID потом очень мешает делать sharding - его нельзя потерять и изменить трудно. Кроме того, вы об этом конечно не могли знать, URLs они довольно часто redirect на друг друга, мутируют так сказать, поэтому в каждый отдельно взятый момент времени одному URL можно сопоставить от нуля до нескольких id.
Впрочем, я понимаю аргументы - я усомнился в размере, вы предложили нормализацию. trade-off, как всегда.
насчёт indexed view:
я не понимаю как это тут помогает, у нас очевидно задача с высоким количеством insert/delete и редким select, а не наоборот, когда indexed view действительно бы было удобно. Каждый раз когда вставится/удалится строка - clustered index на view будет пересчитан.
Т.е. у нас будет два clustered index-а один в оригинальной "log" таблице один во view. Какая от этого выгода? Очевидно вы не предлагали indexed view сделать агрегирующим, тогда что?
я правильно понимаю, что вы бы эту задачу решали только с SQL базой данных? Других идей у вас нет?
> you are welcome, обращайтесь ещё.
я прощаю вам ваш высокомерный тон, потому что вам есть что сказать по теме которая меня интересует
3 years ago
April 12 2009, 16:44:54 UTC 3 years ago
в котором всё хорошо с ресурсами, хватает машин, памяти и дисков, не стоят проблемы зашкаливающего iowait, рандомного чтения, бд работают быстро и эффективно.
читал ваши камментарии с восхищщением, спасибо !
April 12 2009, 18:02:52 UTC 3 years ago
по теме:
я за предварительное собирание логов и внесение в базу.
url_fnv_stamp int()/bigint()
day int()
count int()
1 млн урлов это макс 30 млн рядов в месяц. сколько там размер базы получится в gb?
April 13 2009, 00:42:41 UTC 3 years ago
url_fnv_stamp int()/bigint()
day int()
count int()
3 years ago
3 years ago
3 years ago
3 years ago
April 13 2009, 06:14:26 UTC 3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
April 13 2009, 05:30:32 UTC 3 years ago
Ничто, кстати, не мешает парзиь логи не раз в сутки, а раз в час/полчаса. 800 r/s ~ 3M запросов в час. Никаких проблем с обработкой лога на такое количество строк не будет.
Даже если предположить что все запросы за час уникальны, мы получим массив даных в 3M записей. При грамотной организации индекса все это займет в памяти максимум 60 мегабайт. Фактически, это даже не такая сложная задача как кажется на первый взгляд. Написать можно на чем угодно, начиная от самописного индекса, и заканчивая berkeley db или mysql с heap таблицами.
April 13 2009, 06:08:30 UTC 3 years ago
April 13 2009, 06:10:30 UTC 3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
April 13 2009, 12:45:16 UTC 3 years ago
#!/usr/bin/perl -w use strict; my %d; # $d{день}{урл} for (my $day=1; $day<32; $day++ ) { print "$day\n"; for ( my $i=1; $i<1000000; $i++ ) { my $t = 1000000 - $i; my $url = "$day $i $t $day $i $t $i $t $day $i $t $i $t $i $t $day"; # эмулятор урла $d{$day}{$url}++; } } my $day=1; print "$_ ".$d{$day}{$_}."\n" foreach sort keys %{$d{$day}};При желании можно на порядок ускорить, уйдя от хеша хешей. Но нафига?Ну там демоном повесить, язык запросов придумать, денег взять - пионеров тучи.
Технических проблем ни с одной стороны не видно.
April 14 2009, 07:50:16 UTC 3 years ago
April 14 2009, 08:25:35 UTC 3 years ago
Не надо держать в памяти все счётчики за месяц. Мне просто прикольно было перлануть немного. В памяти достаточно держать *один* счётчик - сегодняшний. Остальные в базе. Для одного счётчика хватит четверти гига. То есть двести пятьдесят мегабайт озу...
Итого: возьмите писюк, на котором виста не пошла, поставьте там бубунту и прекратите смешить людей на тему highload. Вам хватит тупого перлового скрипта, парсящего лог в себя, который под утро форкается рожая новый хеш, хороня старый в базу.
Ещё раз: потребные ресурсы по памяти - 250Mb ram на хеш + ещё немного под новый пока старый в базу утекает. Всё. Компьютер с меньшим объёмом памяти нонче только на базаре купить можно среди разного старья.
Единственная польза от этого треда - послушать господина Суркова о том, как это кошерно сделать под mssql ;-)