В одной из заметок нашего блога мы освещали подход к хранению и обработке данных, о котором получили несколько вполне ожидаемых вопросов такого плана: «В общем, ждём реализации, вот уж оно залетает...». По результатам нескольких реализаций я расскажу о плюсах и минусах этого подхода на примере одной из наших разработок.
Своим заказчикам мы обычно предлагаем достаточно мощный и гибкий инструмент BI, способный решить все их задачи, однако это — зарубежный коммерческий продукт, а клиентов всё чаще интересует тема импортозамещения. В рамках изучения наших перспектив в этом плане мы начали тестирование собственного инструментария BI, используя open-source решения и платформу разработки, построенную на квинтетах.
![](https://habrastorage.org/webt/u9/pk/5e/u9pk5ewq1x1ri6jrggrn9e38qu0.png)
В качестве эталона мы взяли наш существующий коммерческий продукт, так что сравнение, по возможности, будем проводить с ним.
Задача заключалась в создании следующих компонентов:
- база данных для хранения витрин данных и служебной информации (пользователи, настройки и т.п.);
- веб-интерфейс системы и её ORM;
- функционал BI — загрузка данных, произвольные отчеты, графики, сводные таблицы.
За основу мы взяли платформу разработки, которая включает конструктор модели данных, запросов, шаблонизатор и поддерживает ролевую модель управления доступами пользователей.
Тематика заказа — сверка банковской отчетности и анализ её данных, накопленных за последние 7 лет. Исходные данные хранятся в HDFS, сами отчеты рассчитываются там же, далее их результаты попадают в витрину реляционной базы данных. Витрины содержат около 300ГБ данных для нескольких десятков различных отчетных форм и нескольких сотен связанных отчетов. Система должна обслуживать 20 пользователей в пике нагрузки, время отклика интерфейса должно быть в пределах 1 секунды.
Создание структуры данных не заняло у нас много времени: редактор типов платформы позволяет создавать и описывать объекты бизнеса как есть, без необходимости адаптировать требования пользователя к специфике окружения (типы и размерность данных, ключи, ограничения, имена полей данных, идентификаторы и проч.). Это первый плюс квинтетов, который мы можем зафиксировать.
Вот так создаётся структура данных — мы определяем нужные термины, а из них формируем объекты нашей предметной области:
![](https://habrastorage.org/webt/ca/xm/ow/caxmowlcuib8qxvoeferzoit26y.gif)
После нескольких минут активности в формате copy/paste мы получили требуемую структуру данных для одной формы и нескольких вспомогательных справочников для неё. Структура данных формы (после декомпозиции аналитиком) выглядит так:
![](https://habrastorage.org/webt/ok/gd/9d/okgd9dlwt9nkiaet4nhee488nl4.png)
Аналитик, знакомый с бизнес-смыслом представленных здесь данных, читает здесь следующее: Каждый экземпляр Ф110 (Форма 110 в терминологии ЦБ) имеет заданную точность (она бывает «точная» или «округленная») и включает в себя набор кодов, для каждого из которых указаны суммы в рублях и валюте.
В навигаторе данных, где данные представлены согласно заданной структуре, мы видим такую таблицу о существующих в системе экземплярах формы:
![](https://habrastorage.org/webt/ro/n6/ft/ron6ftlagq_rqj5tz8c_ghxdkzk.png)
Идентифицирующее значение формы — это её отчетная дата, отчетные коды хранятся в виде подчиненного массива, размер которого мы видим в скобках.
Как было упомянуто, мы немного декомпозировали данные, если их сравнивать с исходной структурой, чтобы не хранить в базе длинные ряды повторяющихся значений, вроде того, что видит программист или администратор базы данных:
Таблица с данными в эталонной системе:
![](https://habrastorage.org/webt/nr/4m/k3/nr4mk3p6jfxs-ejr8upnxi1vbns.png)
Кстати, именно поэтому мы позиционируем платформу (далее будем называть её Интеграл) как средство разработки для аналитика, а не программиста.
Структура вспомогательных данных несоизмеримо больше, потому что в ней хранятся все исходные данные, настройки отчета, правила для проверки целостности данных внутри отчета и в связанных с ним отчетах, история расчетов и сверок, а также некоторые правила бизнес-трансформации данных при построении отчета:
![](https://habrastorage.org/webt/5-/ty/xy/5-tyxyaidwomjswea4rpn8sibhs.png)
(структура приведена не целиком)
Когда структура данных готова, можно загружать в неё данные. Самый простой способ здесь — загрузить файл, подготовленный в формате Интеграла (аналог .csv, но с разметкой типов). Этот формат содержит описание данных и сами данные.
В примере ниже первые 3 строки файла описывают структуру формы (если её нет в системе, то она будет создана), а далее идут сами данные — параметры формы и параметры подчиненных ей объектов отчета.
268:Ф110:DATE;277;270;
277:Точность:SHORT;
270:Код обозначения расшифровки:SHORT;Сумма в рублях:SIGNED;Сумма в иностранной валюте:SIGNED;
268::20121231;281;;
270::A/5.2;1233682389.47;;
277:281:Точная;
270::A/5.3;622836720.22;;
270::A/6.4;19800;;
270::A/9.2;27125165.14;;
270::S16203/1.2;608607846.309999;;
270::S16305/4;2727510994.84;;
270::S16305/4.1;32049069.51;;
270::S16305/14;2737711.65;;
270::S25302/4;2725748122.98999;;
270::S25302/4.1;40952511.36;;
270::IL/2;87429694.5699999;62717458.21;
270::IL/4;33517212.95;;
270::IL/9;1423281.69;8278.24;
270::IL/11;86433534.5699999;519956.63;
270::IA/1;147792224.509999;4517060.94;
270::IA/2;737704.92;;
270::IA/3;27099836.07;2637.79;
270::IA/6;5607868.86;408410.4;
270::IA/8;103837028.49;48841202.69;
270::IA/10;112302573.56;;
268::20121231;280;;
270::A/5.2;1233682;;
277:280:Округленная;
270::A/5.3;622837;;
270::A/6.4;20;;
270::A/9.2;27125;;
270::S16203/1.2;608608;;
Для этой формы в базе данных 4470 отчетных дат, которые, будучи выгружены в плоский файл, занимают чуть больше 1 МБ. В исходной базе данных (Oracle) они занимают 3.1 МБ (без индексов) в нормализованном виде и 4.2 МБ в денормализованной витрине, которую мы и пытаемся повторить в виде квинтетов. Квинтеты проиндексированы и нормализованы, и в их формате эти данные занимают уже 10МБ.
Объемы данных для сравнения сведены в таблицу (в мегабайтах):
Текст | РСУБД | Квинтеты | |
---|---|---|---|
Данные | 1.1 | 3.1 | 5.1 |
Денормализованные | 4.2 | ||
Индексы | 6.2 | 5.1 | |
Данные + Индексы | 9.3 | 10.2 |
В этом месте следует обратить внимание на размер, занимаемый базами при сравниваемых подходах. За счет дополнительной нормализации квинтетов и расходов на составные индексы в эталонной базе итоговый занимаемый базами размер практически одинаков.
Эталонная база:
![](https://habrastorage.org/webt/07/ue/vu/07uevub4nkj0qnbgctmpqfho9pe.png)
Квинтеты:
![](https://habrastorage.org/webt/cq/_e/d2/cq_ed2rliwl4gkwnxgp_pqsjlvy.png)
При этом в Интеграле у нас проиндексированы сразу все поля таблицы, а в эталонной системе — только дата, код и сумма в рублях, что потребует дополнительных затрат при возникновении новых потребностей.
Для справки: в базе данных полный размер одной этой формы, включая вспомогательные отчеты и настройки, составляет около 400 МБ (она сравнительно невелика).
Итак, данные загружены и предстоит самая сложная часть проекта — создание интерфейса. Интерфейс эталонной системы позволяет просматривать данные форм, вспомогательных отчетов, настроек и жизненного цикла форм. Для управления доступами и общения с базой данных мы использовали базовые возможности нашей платформы — ролевую модель и конструктор отчетов.
Список пользователей с их ролями выглядит так:
![](https://habrastorage.org/webt/ie/v1/g5/iev1g5bfej66iarj8prm4rxeen4.png)
Если кликнуть название роли (отмечено красным овалом на рисунке выше), то можно посмотреть на её содержимое:
![](https://habrastorage.org/webt/g1/8x/4w/g18x4wxhcqn-ybvo_fxrocan-s4.png)
Объектам роли может быть задано 3 уровня доступа, возможно применение маски:
![](https://habrastorage.org/webt/dv/e4/tr/dve4trqzpgdl84uijgfbn_1lcds.png)
Редактирование данных также сделано средствами базового интерфейса. Вот, например, форма редактирования пользователя:
![](https://habrastorage.org/webt/jd/vt/xz/jdvtxzhmaylnkg-zhv0swxwefqa.png)
Специфическое меню нашего приложения и его рабочие места мы сверстали в одном компактном файле, поскольку все они однотипны: форма запроса из 2-3 элементов и таблица с результатами запросов.
![](https://habrastorage.org/webt/up/el/pr/upelprt0of7_mecdtpk_mqgxvd4.png)
Архитектура получилась весьма простой: мы создали множество запросов к данным (представлений) и написали плагин, который реализует произвольные выборки данных в рамках входящих в эти представления таблиц и полей.
Например, у нас есть так называемый расшифровочный отчет к форме 110, он содержит неагрегированные данные, по которым она построена. Вот так выглядит этот отчет:
![](https://habrastorage.org/webt/jd/zm/fc/jdzmfcrujmetvb_w2vqrkfa6g-y.png)
В целях проверки правильности формы пользователь должен иметь возможность делать любые выборки, сортировку, фильтрацию, группировку, транспонирование, а также создавать собственные вычисляемые поля. Наш плагин вызывается кнопкой «Действия» вверху таблицы.
![](https://habrastorage.org/webt/n4/kx/ni/n4kxnicwdqq4y6_ykf06x-pnhi0.png)
Плагин повторяет функционал построителя запросов Интеграла, но помимо выборки данных может рисовать графики и сводные таблицы. Например, нам нужно наложить фильтр по Разделу, добавить пару вычисляемых колонок и сделать выборку сумм с группировкой по ним. Задаём всё это:
![](https://habrastorage.org/webt/zd/hi/rx/zdhirxamjgonu9xzepqwike0cqe.png)
Новые колонки добавляются в список одноименной ссылкой. По кнопке «Вычисления» задаем формулы для них с помощью простого конструктора:
![](https://habrastorage.org/webt/d6/tu/fc/d6tufcwrz1qaxfhca1fwiokqdjo.png)
Задаем новый порядок колонок и нажимаем «Применить» и наш отчет видоизменяется, как требовалось — вместо 7 базовых колонок мы видим три, две из которых мы только что создали:
![](https://habrastorage.org/webt/4t/sp/1a/4tsp1arr8uu8nbz7zeut6qd0foi.png)
Плагин общается с веб-сервисом приложения по api, он выполнил такой запрос:
api/neo/report/1392573?FR_date=20181231&FR_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB=&FR_%D0%9A%D0%BE%D0%B4_%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F=&SELECT=LEFT(\:1392578\:\,5),SUBSTRING(\:1392578\:\,6\,3),1392617:SUM&ORDER=1392617&FR_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB=1&TOTALS=1392617:SUM&LIMIT=10
И получил такой ответ:
{
«columns»: [
«LEFT(:1392578:,5)»,
«SUBSTRING(:1392578:,6,3)»,
«1392617»
],
«formats»: [
«SHORT»,
«SHORT»,
«SIGNED»
],
«data»: {
«LEFT(:1392578:,5)»: [
«60324»,
«40817»,
«47425»,
«47404»,
«60302»,
«47404»
],
«SUBSTRING(:1392578:,6,3)»: [
«810»,
«810»,
«810»,
«840»,
«810»,
«978»
],
«Сумма в рублях»: [
«153 825.71»,
«527 901.11»,
«2 415 189.23»,
«3 000 000.02»,
«5 588 330.88»,
«58 000 000.00»
]
},
«totals»: [
«„,
“»,
«69 685 246.95»
]
}
Если кому интересно, какой реальный SQL-запрос был выполнен в базе квинтетов, то вот он:
Аналитик не видит этот SQL, он пользуется построителем запросов, описанным ниже.
SELECT LEFT(a182088.val, 5) v13,
SUBSTRING(a182088.val, 6, 3) v14,
SUM(round(a182090.val, 2)) ’Сумма в рублях’
FROM neo a182081
LEFT JOIN neo a182083 ON a182083.up=a182081.id AND a182083.t=182083
LEFT JOIN neo a182088 ON a182088.up=a182083.id AND a182088.t=182088
LEFT JOIN neo a182090 ON a182090.up=a182083.id AND a182090.t=182090
LEFT JOIN neo a182091 ON a182091.up=a182083.id AND a182091.t=182091
LEFT JOIN neo a182092 ON a182092.up=a182083.id AND a182092.t=182092
LEFT JOIN neo a299 ON a299.t=299 AND a182083.val=a299.val
LEFT JOIN neo a328 ON a328.up=a299.id AND a328.t=328
LEFT JOIN neo a303 ON a303.up=a299.id AND a303.t=303
LEFT JOIN neo a304 ON a304.up=a299.id AND a304.t=304
LEFT JOIN neo a182089 ON a182089.up=a182083.id AND a182089.t=182089
WHERE a182081.up!=0 AND length(a182081.val)!=0
AND a182081.t=182081 AND a182081.val=’20181231?
AND a328.val =’1?
AND a303.val>=’19000101? AND a303.val<=’20181231?
AND a304.val>=’20181231? AND a304.val<=’20991231?
GROUP BY v13, v14
ORDER BY CAST(SUM(round(a182090.val, 2)) AS SIGNED)
LIMIT 10
Запрос может показаться несколько сложнее, чем ожидалось, потому что он выбирает актуальные данные отчетных кодов с учетом их срока действия. В построителе отчетов Интеграла этот запрос состоит из колонок отчета, параметров и условий объединения таблиц, если это необходимо.
![](https://habrastorage.org/webt/ro/mx/tw/romxtw85qjp94yfrzqmf47vnvsm.png)
Интеграл сам способен сгенерировать условия объединения таблиц, так как все они определяются связями квинтетов, однако в случае проверки версионности нам пришлось объединить таблицы вручную и явно указать условие для JOIN. Коды в условии ON — это идентификаторы объектов колонок и запросов.
Запрос объединяет три таблицы, выбирая из них следующие поля данных (реквизит «Колонки запроса»):
![](https://habrastorage.org/webt/_a/f3/cs/_af3cspclqx4lkhvfasksxbchxg.png)
(полноразмерная картинка)
Здесь перечислены колонки отчета, вычисляемые поля, фильтры и прочее, из чего состоит SQL-запрос. Построитель отчета позволяет реализовать почти любую конструкцию языка SQL, в том числе объединение запросов и вложенные запросы.
Помимо выборок с группировками пользователю полезен механизм сводных таблиц. Мы добавили в наш плагин популярный инструмент для работы с таблицами pivottable.js.org.
Выберем интересующие нас колонки и переключимся в режим сводных таблиц:
![](https://habrastorage.org/webt/5n/wb/du/5nwbduykd19ryanqanvvtaymp4g.png)
Здесь, используя drag’n’drop, мы можем анализировать данные, полученные сконфигурированной нами выборкой, включая наши произвольные поля. Кроме того, здесь можно дополнительно фильтровать данные по любому полю.
![](https://habrastorage.org/webt/y6/dg/tg/y6dgtgwvwf4yprzf6qvp2dijbss.gif)
Для рисования графиков мы использовали бесплатный продукт www.amcharts.com. С ним, как и с pivottable, все достаточно просто: мы выбираем тип графика и инициализируем компонент нашим массивом данных, полученным из Интеграла:
![](https://habrastorage.org/webt/j3/zt/eu/j3zteutdk8xhxddxikns8nr12fo.gif)
На этом мы, фактически, выполнили поставленную задачу в той мере, которая удовлетворяет пользователей существующего продукта. Теперь у нас есть система, проходящая по требованиям импортозамещения: все продукты свободные и заменяемые. Да, мы реализовали далеко не все возможности, что предоставляет существующая система, а только те, что необходимы этому заказчику. Но ведь мы только начали!
Так залетало или нет?
Отдельно стоит обсудить быстродействие получившейся системы. В этой задаче, при достаточно большом объеме данных (сотни гигабайт), выборки затрагивают небольшие фрагменты, квинтеты которых всегда собираются с использованием индексов. Это приводит к тому, что при любом размере базы данных запросы отрабатывают с приемлемой скоростью, не приводя к лавинообразной деградации производительности.
Мы записали 20 действий пользователей в тест-скрипт и прогнали его на сервисе loadimpact.com. Получилось 27 различных запросов, потому что некоторые действия выполняются за 2 запроса к серверу (для построения постраничного отображения, например).
import { group, sleep } from ’k6?;
import http from ’k6/http’;
// Version: 1.3
// Creator: Load Impact URL test analyzer
export let options = {
stages: [
{
«duration»: «3m0s»,
«target»: 25
}
],
maxRedirects: 0,
discardResponseBodies: true,
};
export default function() {
group("page_1 — https://*****.ru/neo/dict«, function() {
let req, res;
req = [{
«method»: «get»,
«url»: «https://*****.ru/neo/info»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«upgrade-insecure-requests»: «1»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8»
}
}
}];
res = http.batch(req);
sleep(0.62);
req = [{
«method»: «get»,
«url»: «https://*****.ru/neo/info»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«upgrade-insecure-requests»: «1»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392573?FR_date=20181231&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392573?FR_date=20181130&ORDER=1392617&LIMIT=10&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392573?FR_date=20181031»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392573?FR_date=20180930&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «*/*»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1387723?&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «*/*»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1387723?&LIMIT=10&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «*/*»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392741?»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392757?&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392768?&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/img/nav_dropdown_arrow.svg»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/img/nav_detailed_report.svg»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/img/nav_classifiers.svg»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/img/nav_launch_report.svg»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/img/nav_manage_form_status.svg»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/img/nav_quality_management.svg»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/img/nav_download.svg»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «image/webp,image/apng,image/*,*/*;q=0.8»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/download/neo/css/variables.css»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392779?&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392538?&LIMIT=10&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/edit_obj/1392129»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/edit_obj/1390552»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/object/18»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1387723?&LIMIT=140,10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1387723?&LIMIT=140,10&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392573?FR_date=20180731&FR_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB=&FR_%D0%9A%D0%BE%D0%B4_%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F=&SELECT=1392576,1392617:SUM,1392589&LIMIT=100»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392678?FR_date=20180831&FR_section=1&SELECT=1392698,1392685,1392690&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392678?FR_date=20180630&FR_section=1&SELECT=1392698,1392685,1392690&LIMIT=10&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392678?FR_date=20180531&FR_section=1&SELECT=1392698,1392685,1392690&LIMIT=500»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392678?FR_date=20181231&FR_section=2&SELECT=1392698,1392685,1392690&LIMIT=500»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392678?FR_date=20181231&FR_section=4&SELECT=1392698,1392685,1392690,1392694&LIMIT=20»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/report/1392678?FR_date=20181231&FR_section=4&SELECT=1392698,1392685,1392690,1392694&LIMIT=20&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/neo/info»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/api/neo/report/1392678?FR_date=20190131&FR_section=1&FR_precision=280&SELECT=1392698,1392685,1392715&LIMIT=50»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/api/neo/report/1392678?FR_date=20190131&FR_section=1&FR_precision=280&SELECT=1392698,1392685,1392715&LIMIT=50&RECORD_COUNT»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/api/neo/report/1392573?FR_date=20190131&FR_section=1&FR_precision=280&FR_%D0%9A%D0%BE%D0%B4_%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F=A60302/9&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://*****.ru/api/neo/report/1392573?FR_date=20190131&FR_section=1&FR_precision=280&ORDER=1392617&FR_%D0%9A%D0%BE%D0%B4_%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F=A60302/9&LIMIT=10»,
«params»: {
«headers»: {
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «text/css,*/*;q=0.1»,
«referer»: «https://*****.ru/neo/dict»
}
}
},{
«method»: «get»,
«url»: «https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu4mxP.ttf»,
«params»: {
«headers»: {
«origin»: «https://*****.ru»,
«accept-encoding»: «gzip, deflate»,
«accept-language»: «en-US»,
«user-agent»: «Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36»,
«accept»: «*/*»,
«referer»: «https://fonts.googleapis.com/css?family=Roboto»
}
}
}];
res = http.batch(req);
// Random sleep between 5s and 10s
sleep(Math.floor(Math.random()*5+5));
});
}
Вот что мы увидели, запустив скрипт — генератор нагрузки отправлял нашему приложению всё возрастающее число запросов в секунду, получилась классическая лесенка для поиска максимальной/пиковой производительности:
![](https://habrastorage.org/webt/ig/a_/xt/iga_xtu0manyadhs8cz_ytmj3k4.png)
Этот генератор отправляет запросы пачками, а не равномерно, как это было бы при одновременной работе пользователей. Поэтому график показывает скачки нагрузки, и общие показатели также выглядят хуже в таком режиме.
С точки зрения нашего сервера всё это происходило вот так:
![](https://habrastorage.org/webt/zn/an/je/znanjexiyp3mkguce5d0ycw0ulc.png)
Поскольку база данных организована с использованием метаданных, каждый запрос тест-скрипта к приложению преобразуется в набор SQL-запросов. В данном тесте, судя по логам, требовалось от 8 до 33 SQL-запросов для выполнения каждого такого запроса: необходимо проверить сессию пользователя и его права, выбрать мета-данные, сконструировать SQL-запрос (включая вложенные и присоединенные, если есть) и только после этого выполнить его.
Тем не менее, при 20-25 запросах в секунду (пиковая нагрузка при работе 20 пользователей), сервер укладывается в требования по производительности — 1 секунда на отклик. В целом ресурсы его недогружены, хотя это самый бюджетный вариант: 1 ядро 2.4 ГГц при 1 ГБ оперативной памяти.
На ненагруженной системе (до 10 запросов в секунду) запросы к серверу выполняются за 0.1-0.3 секунды, в зависимости от сложности выборки.
vaboretti
Очень сложная реализация BI тула.
Посмотрите, как сделан github.com/statsbotco/cube.js (а уж тем более, уже есть несколько других опенсурсных BI). Ваша штука не взлетит
NikZanyat Автор
Спасибо за полезную ссылку, есть на что посмотреть и подумать.
.Немного голословно и непонятно, куда не взлетит.
Тут важно понимать цель — наша штука рассчитана на конкретные потребности, и, при всем разнообразии доступных компонентов, в нашем случае исследование buy vs build не имеет готового buy решения.