Pull to refresh

Comments 22

Всегда при изучении подобных кейсов возникает вопрос: если все эти данные нужны исключительно для статистики, нельзя просто логировать только определенный процент сессий/клиентов/запусков приложения, достаточный для построения необходимых доверительной вероятности и доверительного интервала. Если не изменяет память, то для построения статистики по 100 миллионам пользователей с доверительной вероятностью 99% и погрешностью 1% достаточно собрать информацию о примерно 16000 пользователей.
Все почти так, если наша задача — подсчитывать и показывать количество уникальных пользователей.
Но у нас задача еще проще — показывать абсолютное количество событий, и это всегда выражается одной цифрой, независимо от количества пользователей.
Проблема у нас не в количестве пользователей, а в количестве комбинаций разных параметров фильтрования. Их у нас сейчас около полмиллиарда, и количество их от увеличения числа пользователей не изменится.
Если записывать из каждых 5000 обращений лишь одно, количество увеличенний счетчиков уменьшится в 5000 раз, нагрузка на проц при их поиске сократится…

С другой стороны, полная статистика пропадет, и самое неприятное в этом — всякие единичные случаи потеряются. Не уверен, что они не важны.
Прежде, чем применять эти рассуждения на практике, всё же стоит ознакомиться с ограничениями задачи, для которой сделаны такие выводы. В частности, если я помню правильно, задача не предусматривает, что после семплирования область анализа может быть ограничена. Например, если надо посмотреть статистику по людям определённого возраста (1/60) из определённой страны (1/200) предполагая, что распределение по возрастам и странам равномерное, то у меня в результирующем логе из 8333 пользователей останется только 1.3 пользователя.

Как видите, простейший подход не работает. Тут можно попробовать подобрать семплирование для каждой страны/категории. Однако, при изменении набора фильтров опять всё ломается.

Предложенный вами подход, всё же можно применять для оптимизаций, но например при скане полного датасета. Когда, прочитав только часть датанных, зная распределение встречаемости данных и предположив такую же встречаемость в оставшемся датасете, можно с какой-то вероятностью отдавать предсказание результата.

Данные целиком помещаются в памяти, в наличии 48 процов. В чем такая прям сложность задачи? Почему не подошел Tarantool?

Использование Java — это совсем без комментариев.
Использовать 48 процессов Tarantool одновременно- интересная идея, если я правильно понимаю, о чем вы.
Если вас не затруднит, не могли бы вы чуть более развернуто описать свое видение решения, базирующего на Tarantool и выполняющего все 10 требований, описанных в статье?
Вы указали в промежуточных выводах, что key-value хранилища, в частности Tarantool, вам не подходят и отмели вариант его использования. Вопрос, почему?
В первую очередь, требование #2 (Фильтровать по любой комбинации полей и по интервалу дат) и требование #3 (Представлять результаты в виде фасетов) довольно таки сложно эффективно реализовать с помощью даже таких навороченных KV хранилищ, как Tarantool. В нашем случае невозможно придумать ключ, по которому бы извлекались только нужные записи. Невозможно также заранее определить поля, по значениях которых будут фильтроваться записи перед суммированием, поэтому вторичная индексация не поможет. Поэтому, единственное решение в контексте KV хранилищ — написать процедуру на Lua, которая будет прочесывать все записи и суммировать результат. Но на одном процессоре производительность будет не та, даже если предположить, что Lua настолько же быстра, как и Java.

В теории, если расшардить данные на 48 процессов и так заставить работать все ядра, мы бы могли намного увеличить производительность. Должен сказать, что об этом мы не подумали и это довольно интересная идея. Но в любом случае, здесь слишком много предположений, которые еще нужно проверить и мне не кажутся реалистическими, как, например, скорость Lua. Для воплощения требований #4, #5 и #9 нужно будет писать дополнительную обертку, что усложняет систему.

В дополнение к этому всему, ни один из KV хранилищ не сжимает данные. 500 миллионов счетчиков в формате msgpack просто бы не поместились в память.
Поэтому, единственное решение в контексте KV хранилищ — написать процедуру на Lua, которая будет прочесывать все записи и суммировать результат.

Да, это было бы разумно сделать, написать небольшой скрипт на Lua, который делал бы вам нужную выборку данных.

Но на одном процессоре производительность будет не та, даже если предположить, что Lua настолько же быстра, как и Java.

Вы использовали Lua сами? Делали замеры производительности? Рекомендую осторожно относиться к таким синтетическим тестам и замерам производительности.

В теории, если расшардить данные на 48 процессов и так заставить работать все ядра, мы бы могли намного увеличить производительность.

Именно! Вы представляете, какую скорость вы бы получили.

ни один из KV хранилищ не сжимает данные

Опять же, кроме Redis вижу, что вы не имеете опыт использования KV-хранилищ. Посмотрите хотя бы RocksDB.

Я все это к тому, что не нужно писать свои велосипеды, ограничиваясь при этом языком, который вы знаете. Существует много готовых систем.
Теперь вы еще будете обязаны поддерживать свою систему, а это очень много времени.
Простите, но зачем вы приплели сюда Lua? Это, конечно, замечательный язык, а luajit так вообще гениально сделан; но почему-то никто на нем числодробилки все равно не делает, как и на каком-нибудь Javascript.

Здесь использовалась Java, потому что автор, его коллеги, включая меня,
и еще половина разработчиков на планете вообще прекрасно знакомы с языком и всеми его основными преимуществами и недостатками. Надо признать, сам язык несколько угловат, но у него все же один из лучших современных just-in-time копиляторов, и простая и понятная система типов.

Данная система показала себя исключительно надежно, и, учитывая объемы данных, оказалась еще и замечательно удобной — все наши задачи по отображению различных срезов данных она решает.

Может быть, имело бы смысл что-то передать на нативный код (C или C++ или что-то в этом роде), благо логики здесь немного, и счетчики все равно хранятся вне сборщика мусора.

Но Lua… Это мило, но бессмысленно.
Lua здесь упомянут исключительно потому, что он используется в Tarantool.

То, что у вас получилась хорошая система, я не спорю.
Посмотрите хотя бы RocksDB.

Отбросим Tarantool, посмотрим на RocksDB. Вы, наверное, ссылаетесь вот на эту хитроумную особенность системы, которая была только предложена тогда, когда мы уже 6 месяцев, как использовали CubeDB. Раз мы уже там, предлагаю вам посмотреть на замеры, из которых следует, что RocksDB будет как минимум в два раза медленне для нашего сценария. Зато в RocksDB много захватывющей функциональности, которая в нашем случае совсем не нужна.

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

Теперь вы еще будете обязаны поддерживать свою систему, а это очень много времени.

Создание собственной системы — всегда рискованное предприятие. Но как раз тот факт, что за 14 месяцев пользования системой был обнаружен только один серьезный баг (да и тот — в коде third party library), подтверждает правильность выбора архтитекутры.

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

Ну а так, я согласен с вами: используя 48 пилочек и собственноручные насадки к ним можно искуссно забить гвоздь, не прибегая к молотку.

Какой сборщик мусора использовали в Java?

Сборщик мусора — который по умолчанию у Java 8. Тот, который параллельный.
Хочу все же приметить, что сложности в основном возникали, когда все 48 процессоров хотели зарезервироваиь себе память под короткоживущий обьект (HashMap), который отбрасывался сразу же после отдачи результата.
Я немного дополню ответ sztanko

Дело в том, что в CubeDB вообще сборщик мусора не так критичен, т.к. те данные, где могло бы быть много ссылок, находятся вне кучи, и сборщик мусора их не трогает.

Сборка мусора здесь приходится, по большей части, на обработку входящих запросов, что не так уж и критично в смысле количества «мусора».
А что с отказоустойчивостью? Оно как-то кластеризуется?
Нет. Для нас это не было требованием.

Отчеты, для которых был создан CubeDB, в основном конфиденциальны и работают только в корпоративном интранете. Ими пользуется от силы сто человек в день. Это такой себе
high load навыворот — пользователей немного, но данных очень много. Теоретически, если база падает, то SystemD ее сразу запускает обратно. Загрузка данных с диска длится где-то 3 минуты, что для нас было бы позволительным.
За год пользования системой проблемы возникали только раз, из-за вышеупомянутого бага.

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

Если бы вдруг возникло бы требование сделать систему отазоустойчивой, мы бы скорее всего просто бы запустили еще одну копию на другой машине и балансировали бы нагрузку стандартными инструментами.

Тема для меня очень интересная. Статья написана хорошо и понятно — спасибо!


На данный момент наш единственный экземпляр содержит в себе около 600 кубов, которые состоят из порядка 500 млн записей.

Хочу уточнить маленький нюанс. Скажите пожалуйста, 500 млн записей — это а) количество уникальных активных точек в Н-мерном пространстве или б) количество неуникальных событий, которые приводят, к инкременту уникальных счетчиков?


Если ответ б), то еще один вопрос — каково тогда количество уникальных активных точек в Н-мерном пространстве, другими словами, — сколько счетчиков у вас сейчас инкрементится?

Спасибо за ваш комментарий. Конечно, что считаем мы количество уникальных активных точек, а не входных данных. Неуникальных событий, которые приводят к инкременту, у нас около 12 миллиардов в день.
У нас есть две разбивки — по часам и по дням. У каждой из разбивок 300 кубов, и счётчики хранятся 90 часов и дней соответственно. Все вместе это набирает уже больше, чем 500 000 000 точек.

Если для задания каждой точки (пересечение всех пространств) используется такая форма записи:
"1:1:1:1:1 1500000. И всё это теперь занимает 5x2 + 8 = 18 байтов."
т.е. координата точки на каждую плоскость записывается 2 байтами (как в примере выше), а кубов (плоскостей) у Вас 300, то для задания одной такой точки потребуется:
2 x 300 + 8 = 608 байт.
И если считать точек 500 000 000, то ОЗУ мы должны занять:
500 000 000 * 608 байт = 300 Гб (и это без накладных расходов), но:
"Всё это занимает около 80 ГБ резидентной памяти"
т.е. как теоретические 300 Гб помещаются в физические 80 Гб?


Скорее всего, активных точек существенно меньше (чем 500 000 000). Мне бы хотелось посчитать плотность вашего Н-мерного пространства. На основании неё можно бы было посчитать КПД всего решения с учетом накладных расходов. При высокой плотности (заполненности пространства активными точками) теоретически можно применить жесткое Н-мерное пространство. И это дало бы КПД решения близкое к 100%.

Извините за задержку с ответом. Но постараюсь ещё в этом году.


Ваши расчёты правильны, но боюсь, мы по разному понимаем терминологию. В нашем случае под кубом мы как раз понимаем N-мерное пространство, в котором есть точки. Таких пространств у нас как раз 300, и каждое из них есть некоторая фильтрация и проекция изначальных данных. Так, события нажатия на кнопку попадают в основной куб нажатий на кнопку, главный куб всех событий, и ещё несколько специфических кубов. Во всех случаях в кубы попадают только некоторые проекции этого события, каждый раз разные. Размерность каждого куба варьируеться от 12 до 30, и суммарное количество всех точек во всех кубах есть 500 000 000. К сожалению, кубы совсем незаполнены, как пространство. Согласен, что если бы дело обстояло иначе, КПД можно было бы увеличить, но с другой стороны, появились бы новые проблемы, как например, потребность переадрессации при изменении количества елементов в любом из измерений.


Я надеюсь, этот комментарий вносит больше ясности. И с Новым Годом вас!

Теперь понял :)


К сожалению, кубы совсем незаполнены, как пространство.

В таком случае Ваш подход (хранение индексов координат) является оптимальным.


Благодарю Вас за подробное описание-уточнение и рассеивание моего коммуникационно-технического противоречия.


С Новым 2018 Годом! Пусть в нем царит саморазвитие, доброта и взаимопонимание :-)

Sign up to leave a comment.