ПРАВИТЕЛЬСТВО РОССИЙСКОЙ ФЕДЕРАЦИИ ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ

advertisement
ПРАВИТЕЛЬСТВО РОССИЙСКОЙ ФЕДЕРАЦИИ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ
ОБРАЗОВАТЕЛЬНОЕ
УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ
"НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ УНИВЕРСИТЕТ "ВЫСШАЯ
ШКОЛА ЭКОНОМИКИ"
Отделение программной инженерии
Кафедра Управления разработкой программного обеспечения
УТВЕРЖДАЮ
Зав. кафедрой УРПО
________________ С.М. Авдошин
«__» _______________ 2014г.
ВЫПУСКНАЯ КВАЛИФИКАЦИОННАЯ РАБОТА
по направлению 231000.62 Программная инженерия подготовки бакалавра
На тему «Программа расчета агрегатов по накапливающимся данным для
построения отчетов».
Студента группы 472ПИ _________________ Гужова Дмитрия Владимировича
(подпись)
__________________
(дата)
__
Научный руководитель
Доцент
_________________
Брейман Александр Давидович
(подпись)
__________________
(дата)
Москва 2014
__
2
Аннотация
В этой работе предлагается новый способ подсчета агрегатов в сложных
реляционных базах данных, а также рассматриваются существующие решения.
Подсчёт агрегатов — ресурсоёмкая задача и существующие решения
имеют слабые стороны, ограничивающие их область применения. Предлагаемое
решение даёт заметное преимущество при масштабировании.
3
Содержание
1.
Определения и сокращения ................................................................... 8
2.
Введение ....................................................................................................... 10
3.
Проблема ...................................................................................................... 12
1.1.
Существующие решения............................................................................13
3.1.1.
4.
Денормализация ............................................................................................... 13
1.2.
MapReduce.........................................................................................................15
1.3.
Кеш .......................................................................................................................15
Предлагаемое решение ......................................................................... 17
1.4.
Таблица агрегатов ........................................................................................17
1.5.
Таблица лога изменений ...........................................................................19
5.
Выбор технологий.................................................................................... 20
6.
Методология исследования ................................................................ 21
7.
1.6.
Процесс разработки .....................................................................................21
1.7.
Тестирование ..................................................................................................21
Ожидаемые результаты ........................................................................ 22
1.8.
8.
9.
Возможные проблемы ................................................................................22
7.1.1.
Размен ..................................................................................................................... 22
7.1.2.
Целостность ......................................................................................................... 23
7.1.3.
Миграция............................................................................................................... 23
Реализация .................................................................................................. 24
1.9.
Composer ............................................................................................................24
1.10.
Модель данных ............................................................................................24
1.11.
Классы StatMetric ........................................................................................26
1.12.
Интеграция StatMetric и Doctrine ........................................................28
Тестирование и оценка производительности ............................ 29
1.13.
Методология .................................................................................................29
1.14.
Тестовые данные ........................................................................................29
1.15.
Тестовые запросы.......................................................................................29
9.1.1.
testRegistrationCount ....................................................................................... 30
9.1.2.
testIncome .............................................................................................................. 30
4
9.1.3.
testRegCountByPartner .................................................................................... 30
1.16.
Результаты замеров ..................................................................................30
1.17.
Место на диске .............................................................................................32
1.18.
Выводы из тестирования ........................................................................33
10. Заключение ................................................................................................. 34
11. Источники .................................................................................................... 35
12. Приложение А: Текст программы ... Error! Bookmark not defined.
1.19.
classes/Core.php .........................................Error! Bookmark not defined.
1.20.
classes/Mongo/StatMetric.php ..............Error! Bookmark not defined.
1.21.
Classes/Mongo/StatMetricAdditive.php ............Error! Bookmark not
defined.
1.22.
classes/Mongo/StatMetricAverage.php .............Error! Bookmark not
defined.
1.23.
classes/Mongo/Model.php ......................Error! Bookmark not defined.
1.24.
Classes/Mongo/Connection.php ...........Error! Bookmark not defined.
1.25.
dist/schema/schema.yaml ......................Error! Bookmark not defined.
1.26.
composer.json ..............................................Error! Bookmark not defined.
1.27.
model/Campaign.php ................................ Error! Bookmark not defined.
1.28.
model/Event.php ........................................Error! Bookmark not defined.
1.29.
model/Organization.php..........................Error! Bookmark not defined.
1.30.
model/Partner.php ....................................Error! Bookmark not defined.
1.31.
model/Registration.php ...........................Error! Bookmark not defined.
1.32.
model/TicketType.php ............................. Error! Bookmark not defined.
1.33.
test/dataclasses/EventTest.php ............Error! Bookmark not defined.
1.34.
tests/inc/EventBenchmarkTest.php ...Error! Bookmark not defined.
1.35.
tests/inc/Diploma_DatabaseTestCase.php .......Error! Bookmark not
defined.
1.36.
tests/inc/BenchmarkListener.php .......Error! Bookmark not defined.
13. Приложение Б. Техническое задание ........... Error! Bookmark not
defined.
1.37.
Введение .......................................................Error! Bookmark not defined.
1.38.
Основания для разработки ..................Error! Bookmark not defined.
1.39.
Назначение разработки.........................Error! Bookmark not defined.
13.1.1.
Функциональное назначение...... Error! Bookmark not defined.
5
13.1.2.
1.40.
Эксплуатационное назначение .. Error! Bookmark not defined.
Требования к программе ......................Error! Bookmark not defined.
13.1.3.
Требования к функциональным характеристикам ............ Error!
Bookmark not defined.
13.1.4.
Требования к интерфейсу ............. Error! Bookmark not defined.
13.1.5.
Требования к формату входных и выходных данных ....... Error!
Bookmark not defined.
13.1.6.
Требования к надежности............. Error! Bookmark not defined.
13.1.7.
Условия эксплуатации .................... Error! Bookmark not defined.
13.1.8.
Требования к составу и параметрам технических средств
Error! Bookmark not defined.
13.1.9.
Требования к информационной и программной
совместимости Error! Bookmark not defined.
1.41.
Стадии и этапы разработки .................Error! Bookmark not defined.
1.42.
Порядок контроля и приемки ............Error! Bookmark not defined.
14. Приложение В. Программа и методика испытаний ........... Error!
Bookmark not defined.
1.43.
Объект испытаний ...................................Error! Bookmark not defined.
1.44.
Цель испытаний ........................................Error! Bookmark not defined.
1.45.
Требования к программе ......................Error! Bookmark not defined.
14.1.1.
Требования к функциональным характеристикам ............ Error!
Bookmark not defined.
14.1.2.
Требования к интерфейсу ............. Error! Bookmark not defined.
14.1.3.
Требования к формату входных и выходных данных ....... Error!
Bookmark not defined.
14.1.4.
Требования к надежности............. Error! Bookmark not defined.
14.1.5.
Условия эксплуатации .................... Error! Bookmark not defined.
1.46.
Требования к программной документации .Error! Bookmark not
defined.
1.47.
Средства и порядок испытаний .........Error! Bookmark not defined.
14.1.6.
Технические средства...................... Error! Bookmark not defined.
14.1.7.
Программные средства................... Error! Bookmark not defined.
1.48.
Методы испытаний ..................................Error! Bookmark not defined.
14.1.8.
Тестовые данные ............................... Error! Bookmark not defined.
14.1.9.
Тестовые запросы .............................. Error! Bookmark not defined.
6
14.1.10.
Запуск автотестов ........................... Error! Bookmark not defined.
15. Приложение Г. Руководство разработчика Error! Bookmark not
defined.
1.49.
Назначение программы .........................Error! Bookmark not defined.
1.50.
Условия выполнения программы .....Error! Bookmark not defined.
15.1.1.
Условия эксплуатации .................... Error! Bookmark not defined.
15.1.2.
Требования к составу и параметрам технических средств
Error! Bookmark not defined.
15.1.3.
Требования к информационной и программной
совместимости Error! Bookmark not defined.
1.51.
Использование библиотеки ................Error! Bookmark not defined.
15.1.4.
Установка библиотеки .................... Error! Bookmark not defined.
15.1.5.
Конфигурирование библиотеки Error! Bookmark not defined.
15.1.6.
Работа с агрегатом ............................ Error! Bookmark not defined.
15.1.7.
Базовые методы.................................. Error! Bookmark not defined.
15.1.8.
Методы StatMetricAdditive............. Error! Bookmark not defined.
7
1.
Определения и сокращения

DBMS, СУБД — Database Management System, система управления
базами данных [1];

Схема — структура базы данных, включающая структуру таблиц и связи
между ними [1];

Агрегат — значение, вычисляемое путем анализа данных из множества
строк таблицы или таблиц, или функция СУБД, вычисляющая такое
значение [2];

SUM — агрегат, сумма выбранных значений [3];

AVG, AVERAGE — агрегат, среднее арифметическое выбранных
значений [3];

Документо-ориентированная СУБД — СУБД, хранящая “документы”
вместо строк в таблице. В отличие от строки таблицы, стуктура
документа не предопределена.;

Алгоритмическоая сложность операции — отношение объема данных
к сложности проведения этой операции над ними [4];

Библиотека — сборник подпрограмм или объектов, который можно
использовать в своём программном продукте;

ORM — библиотеки для представления базы данных в виде иерархии
объектов в парадигме ООП;

Связь — связь между двумя таблицами в БД на основе внешнего ключа.
ORM представляет связи как ассоциации между объектами;

SQL — structured query language, язык описания запросов к релционным
БД. Он и его диалекты используются в большинстве популярных СУБД;

JOIN — операция в языке SQL, комбинирующая строки из нескольких
таблиц базы данных, использующая связи на основе внешнего ключа.
Многие ORM позволяют не прописывать JOIN явно;
8

DQL — диалект SQL, используемый в Doctrine ORM.
9
2.
Введение
Бесчисленное количество веб-приложений полагается на базы данных.
Нередко архитектура этих баз данных недальновидна с самого начала, а
постоянно меняющиеся требования (что не редкость в веб-разработке, особенно
на волне стартап-бума) вынуждают разработчиков вносить еще больше
недальновидных изменений. По мере развития проекта сложность схемы
реляционной базы данных стремительно возрастает. И хотя сама по себе
высокая сложность схемы не представляет большой угрозы, некоторые задачи
становятся нетривиальными. Одна из таких задач — подсчёт агрегатов.
Агрегирующие запросы навроде SELECT SUM имеют алгоритмическую
сложность O(N) при работе с одной таблицей, то есть в самом простом случае.
[5] [6]
В сложной нормализованной базе данных подсчёт агрегата по
нескольким таблицам становится алгоритмически нетривиальным. Задача лишь
усложняется несовершенством популярных DBMS вроде MySQL. Как и многие
проблемы
масштабирования,
эта
проблема
неизбежна,
и
проявляется
неожиданно. При этом, универсального решения нет. К несчастью, все
существующие
решения
плохо
подходят
средним
проектам,
когда
вычислительные мощности еще достаточно скромные и вполне справляются с
нагрузкой, но уже имеют большой объем накопленных данных, чтобы
проблемы масштабирования начали проявляться.
Документо-ориентированные DBMS набирают популярность в вебе, и
предлагают другие подходы к агрегации, но и они имеют преимущества в
распределенных вычислениях.
Серьёзные специализированные решения для анализа данных мало
популярны среди веб-разработчиков и часто игнорируются. Вполне возможно,
что незаслуженно, но этот вопрос выходит за рамки данного исследования.
Задача этого проекта — предоставить еще одно решение этой проблемы,
которое позволит получать значения агрегатов из сложных баз данных с
10
меньшей алгоритмической сложностью, нежели их прямой запрос, при этом
лишенное некоторых недостатков, присущих существующим решениям.
11
3.
Проблема
Рисунок 1. Пример сложной схемы БД
Пример проблемной ситуации, которую этот проект должен разрешить
представлен на рис. 1.
Организатор проводит события со сложной ценовой политикой
(несколько разных типов билетов — EventRegistration). События группируются
в категории. Партнёры проводят кампании по продвижению событий в обмен на
комиссии. Пользователи размещают заказы, в которые может входить
несколько билетов разных типов. Пользователи могут приходить сами, а могут
перейти с одной из кампаний по продвижению.
Запрос, подсчитывающий доход партнёра от определенной организации
вынужден будет объединить JOINом почти все таблицы между Organization и
Partner. Его сложность составит примерно O( N1  N 2  N 3  N 4  N 5  N 6 ) , где N
— количества рядов в таблицах. А ведь актуальное значение этой величины
партнёр желает видеть в карточке организации.
12
1.1.
Существующие решения
3.1.1. Денормализация
Денормализация
проводится
с
целью
оптимизировать
производительность операций чтения из базы данных путём хранения
избыточных, дублирующих данных или группировки данных [7]. Например,
если в таблице Order хранить ключи (id) Organization и Event, то уже не
придётся загружать из базы данных Ticket и Event, чтобы найти заказы для
определенной организации. Таким образом, добавляя избыточные связи
разработчик может значительно сократить цепочку JOINов, однако у этого
подхода есть несколько ограничений и слабых сторон.
Рисунок 2. Пример использования денормализации
Ограничения:

Отношения “один ко многим” можно “пропустить” таким образом
только в одном направлении (от многих к одному), потому что хранение
список всех ссылающихся на данный элементов не даст никакого
выигрыша, лишь потратив дисковое пространство

Отношения “многие ко многим” вообще нельзя исключить из цепочки
JOINов таким образом, потому что они по сути являются двумя «один ко
многим» связями
13

Появление
новой
прямой
связи
накладывает
дополнительное
ограничение. Так, например, в изначальной схеме возможность включить
в заказ билеты на несколько событий не исключена. Если же в заказе
хранить указатель на событие, то возникнет явное ограничение — в
заказе могут быть билеты лишь на одно событие.
Слабые стороны:

Денормализация даёт прирост в производительности на чтение в
обмен на серьёзное усложнение схемы. Усложнение схемы усложнит
разработку, особенно при недостаточной документации. Не всегда
очевидно, какое значение первично, а какое — дубликат. [1]

Разработчик ответственен за целостность данных. Дубликаты
необходимо должны обновляться вместе с оригиналами.
На рисунке 2 изображена сильно денормализованная схема с рисунка 1.
Эта схема иллюстрирует все вышеописанное.
Прошлый пример с получением прибыли партнёра с организации теперь
имеет сложность O(N). Однако все упомянутые недостатки также хорошо
видны. К тому же, если потребуется добавить какие-либо условия на таблицы,
исключенные из JOINа, то весь выигрыш будет утерян. Например, если
партнёра будет интересовать прибыль только с событий определенного месяца.
Самый худший, но вполне реальный случай — если партнера заинтересуют
события из определенной категории. Даже оптимистичное O(N) — это full table
scan, то есть всё равно медленно.
Таким образом, повышая производительность, денормализация угрожает
архитектуре, не решая полностью проблему масштабирования.
Дополнительная проблема — денормализация действующей базы
данных. Для этого потребуется не только миграция схемы, но еще и
дополнительный код, который заполнит новые столбцы данными. Такой код
нелегко писать и тестировать, что создает высокий риск длительного
даунтайма. К тому же сам процесс изменения структуры таблицы может
занимать огромное количество времени на многих СУБД. Добавление или
удаление стоблца для таблицы с 6 миллионами строк может занять несколько
14
часов, в течение которых таблица будет находиться в режиме «только чтение»,
потому что СУБД попросту создает новую таблицу и копирует в неё все данные
из старой.
Если схема не была разумно денормализована до внедрения проекта (а
так часто случается в стартапах), каждая операция по денормализации будет
крайне нетривиальной, а доведение схемы до оптимального состояния может
потребовать неопределенное количество шагов.
1.2.
MapReduce
MapReduce — модель распределённых вычислений, используемая для
параллельных вычислений над большими объёмами данных в кластерах.
MapReduce позволяет описать агрегацию как распределённую по кластеру
задачу. [8]
Это сильное и очень хорошо масштабируемое решение, используемое во
многих проектах под высокой нагрузкой. Основная проблема MapReduce — это
необходимость в кластере. Это не проблема для крупных проектов под
нагрузкой, где кластеры используются для многих задач, но для средних
проектов поддержка кластера далеко не всегда обоснована.
1.3.
Кеш
Кеш — простой способ значительно повысить производительность
приложения. Во многих фреймворках кеширование включается переключением
одной настройки. К сожалению, кеширование направлено против симптомов
проблемы, а не её сути — сама обработка данных не станет быстрее. [9]
Кеширование решает большинство проблем на небольших объемах
данных, которые редко обновляются. В остальных случаях оно создаёт больше
проблем, чем решает:

“Кешировать всё” — не лучший подход, поэтому разработчикам
приходится выбирать, что кешировать;

Всегда будет вероятность того, что запрошенные данные еще не в кеше

Данные в кеше могут быстро устаревать, а отображение даже
незначительно устаревших данных иногда неприемлемо
15

Когда запись в кеше устаревает, приложению снова приходится
выполнять тяжелый запрос для её обновления
16
4.
Предлагаемое решение
Предлагаемое решение — скомбинировать некоторые идеи кеширования
и денормализации в специальной библиотеке StatMetric.
Сложные агрегаты, которые понадобится быстро вычислять необходимо
будет заранее описать в рамках библиотеки вместе с условиями, которые они
должны поддерживать. В дальнейшем можно будет добавлять новые агрегаты
на уже развернутое приложения, с некотоыми ограничениями, описанными в
следующем разделе.
Библиотека предоставит набор методов для приложения, чтобы оно
могло уведомлять об изменениях данных, связанных с агрегатами. Например,
после каждого успешного платежа приложение будет уведомлять библиотеку о
создании нового Order и всех связанных с ним сущностей, и необходимости
зачесть суммы заказа в агрегат «продажи». Библиотека запишет это событие в
своём хранилище.
Приложение сможет делать запросы к библиотеке по любому из
описанных агрегатов.
Библиотека хранит данные в двух дополнительных таблицах: таблице
вычисленных агрегатов и логе изменений.
1.4.
Таблица агрегатов
Как
было
показано выше,
вычисление
агрегатов
может
иметь
недопустимо высокую алгоритмическую сложность. Поэтому, вместо того
чтобы пересчитывать и кешировать значение агрегата при чтении, будет
храниться и обновляться вычисленное значение агрегата. Храня немного
дополнительных данных, можно вычислить новое значение агрегата за не
зависящее от количества данных, им покрываемых, время, то есть не
пересчитывая его заново.
В этой таблице id и значение агрегата хранится для каждого возможного
среза. Для базы данных из рисунка 1 можно определить простой агрегат
«продажи» со срезами по событию, партнёру и организации. «Срезать по Event»
17
— значит сохранить продажи для каждого события отдельной записью. В
данном случае Event — «срезающая таблица». С каждым новым оплаченным
заказом, библиотека будет обновлять все затронутые срезы прибавляя сумму
заказа к сохранённому значению.
В таблице 1 изображен пример содержимого таблицы агрегатов для
описанного агрегата продаж.
stat
slice
slice_data value
sales
100
sales Event
1
20
sales Event
2
30
sales Event
3
50
sales Partner 1
20
sales Partner 2
10
sales Org
1
20
sales Org
2
80
Таблица 1. Пример содержимого таблицы агрегатов
В первой записи хранится полное, не срезанное значение агрегата —
сумма всех продаж. Следующие три записи хранят продажи с событий 1, 2 и 3.
В столбце slice хранится тип среза, а в slice_data — id первичного ключа
элемента из срезающей таблицы, по которому идёт срез. Предполагается, что в
базе данных используются числовые первичные ключи с авто-инкрементом
(стандарт де-факто в веб-разработке).
Идею можно расширять. Например, если хранить количество элементво
и сумму в записи, то можно хранить агрегат AVG (среднее) и обновлять
значение без пересчета. При обновлении данных фреймворк обновит
количество и сумму, после чего, разделив сумму на количество получит новое
среднее.
Кроме того, можно определять срезы по нескольким таблицам, например
“Event,Partner”, чтобы выполнять запросы типа «доход с события X партнёру
Y». Однако, потребление дискового пространства такими срезами хуже
масштабируются. Эта проблема рассмотрена более подробно в разделе
«Ожидаемые результаты».
18
Статистика и отчеты — главные потребители агрегатов — часто требуют
данных за определенный период времени, и хранение срезов по периодам не
представляет сложности — достаточно хранить идентификатор периода в
записи, и по окончании периода создавать новую.
Разумеется, вычисленные агрегаты в таблице могут легко реагировать на
изменения данных, если приложение будет уведомлять об этих событиях.
1.5.
Таблица лога изменений
Хранение вычисленных статистических данных имеет недостаток —
трудно доказать их корректность. Если в сохранённом значении ошибка, то
вычислить её причину невозможно. К тому же, пересчитать значение без
помощи приложения не получится. Таблица логов изменений направлена
против этих проблем.
При каждом изменении данных, связанных с агрегатами в таблицу логов
записывается вся релевантная информация. Это позволит отслеживать
возможные проблемы, перерасчитывать агрегаты с нуля и даже строить новые
срезы. В отличие от таблицы агрегатов, хранящей данные только по заданным
срезам, таблица лога хранит связи со всеми связанными с событием объектами,
позволяя вычислить дополнительные срезы при необходимости.
19
5.
Выбор технологий
Предлагаемая библиотека хранит все данные в отдельных таблицах,
таким образом он не обязан использовать ту же СУБД, что и основное
приложение. В качестве таковой выбрана MongoDB, так как различные типы
агрегатов требуют различные данные для обновления, а данные в лог таблице и
вовсе имеют произвольный формат. Документо-ориентированная СУБД, коей
является MongoDB лучше приспособлена для хранения таких данных.
В качестве языка программирования выбран PHP 5.4, популярный и
широко известный язык для веб-разработки. Есть также и личные причины —
большой опыт автора в работе с данным языком и возможность внедрить
разработку в проекте работодателя.
В качестве системы контроля версий будет использоваться Git, как
наиболее развитая и популярная на данный момент.
20
6.
Методология исследования
Проект предлагает новое решение, которое затем сравнивается с
существующими при помощи набора тестов на разных наборах данных,
моделирующих встречающиеся на практике сложные ситуации. В результате
будут выделены преимущества и недостатки нового решения.
1.6.
Процесс разработки
Процесс разработки разбит на несколько этапов согласно спиральной
модели:

В первой итерации разрабатывается и тестируется работающий
прототип. Этот прототип будет способен хранить и инкрементировать
несколько SUM и AVG агрегатов срезанных по одной колонке;

Вторая итерация будет посвящена управлению лог-таблицей, добавляя
возможности манипулирования историей — откат, перерасчет ;

В третьей итерации библиотека будет усовершенствована: добавление
мультитабличных агрегатов, новых типов агрегатов, оптимизация;

В финальной итерации будет построено демонстрационное приложение с
ипользованием данной библиотеки.
1.7.
Тестирование
Тестирование производительности начнется уже по завершении первой
итерации и будет производиться до конца разработки проекта. Цели
тестирования таковы:

Определить, действительно ли предлагаемое решение превосходит
существующие при каких-либо обстоятелсьтвах;

Определить условия, при которых решение превосходит остальные;

Найти слабые места в решении, требующие оптимизации.
21
7.
Ожидаемые результаты
Ожидается, что предлагаемая библиотека даст большой прирост в
производительности операций чтения, заполнив собственную нишу среди
решений проблем масштабирования СУБД.
Получение значения агрегата сводится к поиску единственной записи в
таблице агрегатов. С BTREE индексами, найти определнный ряд в таблице
агрегатов можно за O(logN) время.
При этом, производительность решения никак не зависит от уровня
денормализации и структуры связей в БД. Срезы по сущностям, связанным как
«один к одному», «один ко многим» или «многие ко многим» никак не
отличаются в рамках данной библиотеки.
Предлагаемое решение упростит построение отчетов, так как по сути все
отчетные данные уже хранятся в готовом виде в таблице агрегатов. Нахождение
«топ 10 событий по продажам» сводится к сортировке таблице агрегатов по
значению, в то время как обычному запросу придётся вычислить суммы продаж
по всем событиям в системе — сложность такой операции огромна.
1.8.
Возможные проблемы
7.1.1. Размен
Цена повышения скорости чтения — место на диске. Темп роста
занимаемого места, к сожалению, куда менее оптимистичен. В простых случаях
он составит O N  , где N — количество записей в срезающей таблице. Срезы
по нескольким таблицам добавят
слагаемые в формулу. Получившееся
число может быть умножено на возрастающее со временем количество срезов
по дате.
Однако ожидается, что затраты места на числовые значения будут
умеренными. Место на диске считается дешевым ресурсом, гораздо дешевле
чем вычислительная мощность.
22
7.1.2. Целостность
Еще одна возможная проблема — целостность данных. Если приложение
не уведомит библиотеку об изменении данных, то ошибка останется в агрегате
навсегда. Однако, многие проблемы с целостностью можно избежать
грамотным проектированием: описание операций с базой данных в отдельном
слое «модели», использование транзакций.
К тому же, финансовые операции, которые в основном и являются
предметом агрегации, обычно не изменяются из-за специфики бухгалтерии.
Принятый платеж уже нельзя удалить или изменить. Единственный способ его
«отменить» — провести еще один платеж в обратном направлении. Такая
операция без проблем отразиться во библиотеке.
7.1.3. Миграция
Как и в случае с денормализацией, при внедрении системы данные
необходимо рассчитать и заполнить. Однако есть несколько способов сделать
это.
Самый простой — рассчитать все значения и записать их как первые
инкременты агрегатов. Такой подход даже не вызовет даунтайма: в то время как
скрипт будет рассчитывать инкременты для агрегатов, возможные новые
инкременты для агрегатов будут успешно приниматься библиотекой не нарушая
целостность.
Другой подход — на основе имеющейся истории создать заполнить логтаблицу и затем «воспроизвести» её при помощи библиотеки.
23
8.
Реализация
Для эффективного тестирования и демонстрации библиотеки было
решено разработать PHP-приложение, состоящее из:
1. Менеджера зависимостей composer;
2. MySQL базы данных со схемой, похожей на использованную
ранее в примерах;
3. ORM Doctrine 1 для манипуляций с базой данных;
4. Фреймворка для тестирования PHPUnit;
5. Примитивной обёртки над PHP-API MondoDB;
6. Собственно библиотеки, названной StatMetric.
Doctrine будет обеспечивать возможность легко описывать схему и
наполнять её данными. При помощи хуков Doctrine события добавления и
удаления данных легко привязываются к обновлению данных в StatMetric. При
помощи PHPUnit можно не только проверять работоспособность приложения но
и тестировать скорость выполнения запросов.
Composer позволяет не
поставлять код внешних библиотек вместе с приложением и заодно решает
проблему автозагрузки классов в PHP.
1.9.
Composer
Composer — популярный менеджер зависимостей PHP. Достаточно
указать в файле composer.json, какие версии библиотек требуются проекту
— composer сам скачает их и подключит в проект. Кроме того, composer
позволяет наладить автозагрузку классов в PHP. Механизм автозагрузки
классов позволяет практически полностью отказаться от использования
конструкции include.
1.10. Модель данных
В файле dist/schema/schema.yaml (см приложение А) была
описана схема будущей базы данных, по которой при помощи Doctrine были
сгенерированы пустые базовые классы.
24
Рисунок 3. Схема демонстрационной БД
Затем на основе сгенерированных классов Doctrine создаёт схему в
MySQL. Получишаяся схема изображена на Рисунок 3. Колонки, которые не
будут использоваться в запросах, были опущены, за исключением колонки
name. Все колонки, по которым будет производиться отбор, покрыты
индексами.
Таким образом мы
получаем
следующие ORM-классы
(классы,
унаследованные от Doctrine_Record и управляемые Doctrine):

Organization — орагнизатор событий;

Event — событие;

TicketType — тип билета на событие (например билеты разных
ценовых категорий);

Registration — зарегистрировавшийся на событие посетитель.
При этом он выбрал определенный тип билета и заплатил за это
его цену на момент регистрации;

Partner — партнёр реферальной программы;
25

Campaign — рекламная кампания, созданная партнёром для
продвижения события или группы событий. Отражается в
реферальной метке в ссылке, например.
1.11. Классы StatMetric
Классы StatMetric содержат всю логику библиотеки и предоставляют
интерфейс для обновления и получения значений агрегатов.
Рисунок 4. Диаграмма классов StatMetric
26
Как видно из диаграммы классов (Рисунок 4), основная логика описана в
абстрактном классе StatMetric. В унаследованных от StatMetric классах
опишем конкретную реализацию работы различных агрегатов. Например,
самый простой агрегат SUM представлен классом StatMetricAdditive,
предоставляющим методы для инкремента и декремента своего значение.
Непосредственно работа с агрегатом выглядит следующим образом:
1. В конфигурации класса опишем агрегат при помощи массива
настроек:
2. Вызвав метод StatMetric::getMetric('regs') получим
экземпляр
класса
StatMetricAdditive
для
работы
со
значениями.
3. При помощи метода increment() сообщаем, что, например,
была одна регистрация для таких-то организации, партнёра,
события, и т. д. При этом данные об объектах для слайсов можно
передать двумя способами:
a. Как пары ключ-значение имя:айди сущности;
b. Как объект Doctrine_Record. Имя слайса и айди будут
выведены на основе типа и данных класса;
4. StatMetric готовит операцию инкремента:
a. Разбирает данные о переданных слайсах, приводя их к
единому
внутреннему
виду
и
дополняя
данные
о
27
мультислайсах (например ‘Organization,Partner’)
на основе данных об обычных слайсах;
b. Определяет таймфреймы, которые понадобится обновить;
c. Поочередно создаёт или обновляет в монго значения всех
затронутых слайсов и таймфреймов;
d. Записывает изменения в лог-таблицу.
При работе со слайсами крайне уместными оказываются некоторые
возможности MongoDB:

Upsert update — операция «обновить или вставить» позволяет
работать со строкой слайса не задумываясь о том, создана ли она
уже;

Increment update — для того чтобы увеличить или уменьшить
числовое значение в таблице не нужно предварительно считывать
текущее значение: монго поддерживает быстрый инкремент
полей;

Отложенная запись — от перестановки слагаемых сумма не
меняется, что позволяет не ожидать от базы подтверждения
успешного инкремента.
1.12. Интеграция StatMetric и Doctrine
Чтобы не возникало проблемы, что в коде приложения был забыт вызов
обновления статистики в StatMetric, необходимо интегрировать их на
уровне ORM. К счастью, Doctrine, как им ногие другие ORM, предоставляет
механизм хуков — методов, вызываемых при наступлении определённых
событий. Чтобы при добавлении новой регистрации произошел пересчёт
статистики, мы можем воспользоваться хуком postInsert, переопределив
метод postInsert() базового класса Doctrine_Record в его наследнике
— Registration.
В демонстрационном приложении для Regisrtration определены хуки
postInsert() и postDelete().
28
9.
Тестирование и оценка производительности
1.13. Методология
Все тесты будут необходимо провести на разных размерах базы данных,
чтобы увидеть, как классические JOINы скалируются в сравнении с StatMetric.
Жизненный цикл каждого теста описывается выглядит следующим
образом:
1. MySQL и Mongo базы данных очищаются и создаются заново;
2. Средствами самого приложения генерируются тестовые данные
определенного
размера,
которые
сохраняются
в
MySQL
средствами Doctrine;
3. Во время наполнения базы StatMetric рассчитывает агрегаты;
4. Выполняются тестовые рассчёты агрегатов различной сложности,
сначала при помощи обычного SQL-запроса, затем при помощи
StatMetric;
5. Для каждого рассчёта проверяется корректность результата и
замеряется время выполнения.
6. Замеряется занимаемое на диске место для MySQL и MongoDB;
1.14. Тестовые данные
Всего предусмотрено четыре объёма тестовых данных. Их структура и
объём описаны в таблице.
Dataset/Entity
Организаций
Событий у организации
Типов билетов у событий
Регистраций на каждый тип билета
Партнеров в ротации
Всего событий
Всего регистраций
Tiny
5
5
1
5
5
25
125
Small Medium Large
5
20
40
10
50
80
2
3
3
50
100
150
5
20
150
50
1000
3200
5000 300000 1440000
1.15. Тестовые запросы
Для
замеров
производительности
использовалось
три
запроса,
представляющих три наиболее типичные проблемные рассчёты. Ниже
29
приведены эти запросы на языке DQL, используемом в Doctrine. DQL позволяет
неявно описывать критерии операции JOIN внутри WHERE. Все запросы
Doctrine обрабатывает как prepared statement, то есть синтаксический разбор
аналогичных DQL и SQL не происходит повторно.
9.1.1. testRegistrationCount
Подсчёт количества регистраций у организатора — самый простой
запрос. Здесь только один джойн, покрытый индексами.
SELECT count(r.id) from Registration r, r.Event e
WHERE e.organization_id = ?;
9.1.2. testIncome
Подсчёт дохода организатора — сумма всех выплаченных посетителями
сумм. Почти то же самое, но уже нужно не считать ряды, а суммировать.
SELECT
sum(r.sum)
from
Registration
r,
r.Event
e
WHERE e.organization_id = ?;
9.1.3. testRegCountByPartner
Подсчёт
регистраций
организатора,
привлечённых
определенным
партнером. Более сложный запрос с двумя джойнами. Также представляет
повышенную сложность и для StatMetric, так как использует «двойной срез».
SELECT count(r.id) from Registration r, r.Event e,
r.Campaign
c
WHERE
e.organization_id
=
?
and
c.partner_id = ?;
1.16. Результаты замеров
Удивительно, но StatMetric даёт выигрыш в производительности даже на
малых масштабах, в любом случае возвращая результат быстрее, нежели
обычный запрос.
На представленных графиках показано время выполнения тестовых
запросов в SQL и получение аналогичного значения из StatMetric.
30
Рисунок 5. Сравнение производительности на запросе с COUNT()
Рисунок 6. Сравнение призводительности на запросе с SUM()
31
Рисунок 7. Сравнение производительности на запросе с несколькими JOIN
1.17. Место на диске
Для тестовых данных размера Large размер коллекции stats в
MongoDB составил 1364 Кб, в то время как размер, занимаемый самой большой
таблицей registration в MySQL — 138,7 Mb.
Рисунок 8. Сравнение занимаемого на диске места
Как можно видеть из рисунка 8, таблица в MySQL растёт знаительно
быстрее коллекции в MongoDB.
32
1.18. Выводы из тестирования
Предлагаемое
решение
действительно
показало
прекрасную
масштабируемость на объём данных. На графиках хорошо видно, что в то время
как
производительность
SQL-запросов
падает,
производительность
предлагаемого решения практически не меняется.
Опасения по поводу занимаемого места на диске по результатам
тестирования не оправдались — темпы роста MySQL таблицы и Mongo
несопоставимы.
33
10. Заключение
Расчет агреатов в сложных базах данных — ресурсоёмкая задача.
Известные способы сокращения времени ответа запросов не всегда приемлемы,
что поднимает серьёзную проблему.
Её возникновение может оказаться
зловещим сюрпризом для растущего стартапа.
Как было продемонстрировано выше, известные решения плохо
подходят стартапам — они либо уже недостаточны, либо еще слишком дороги.
Комбинируя денормализацию и кеширование, этот проект предлагает
хорошо масштабируемое решение, которое сможет занять нишу между
денормализацией и распределенными вычислениями. Такое решение сможет
сгладить проблемы при росте стартапа и сократить расходы.
34
11. Источники
1. Date C.J. An Introduction to Database Systems. 8th ed. Addison-Wesley, 2003.
2. 10gen Inc. update — MongoDB Manual // MongoDB Manual. 2014. URL: http://
docs.mongodb.org/manual/reference/command/update/#dbcmd.update (дата
обращения: 3.05.2014).
3. Ramez Elmasri S.B.N. Fundamentals of Database Systems. Addison-Wesley, 2010.
4. Kolaitis P.G., University of California, Santa Cruz, IBM Research-Almaden.
Relational Databases, Logic, and Complexity // JACK BASKIN SCHOOL OF
ENGINEERING. 2009. URL: http://users.soe.ucsc.edu/~kolaitis/talks/gii09final.pdf (дата обращения: 14.01.2013).
5. Carlos Ordonez J.G.G., "Evaluating Join Performance on Relational Database
Systems," Journal of Computing Science and Engineering, Vol. 4, No. 4,
December 2010. pp. 276-290.
6. Pavlić M., Kaluža M., and Vrček N. DATABASE COMPLEXITY MEASURING
METHOD // Proceedings of the 19th Central European Conference on Information
and Intelligent Systems. 2008. pp. 577-583.
7. Schwartz B., Zaitsev P., Tkachenko V., Zawodny J., Lentz A., and Balling D. High
Performance MySQL. 2nd ed. O'Reilly Media, 2008.
8. 10gen, Inc. Aggregation Framework // MongoDB Manual. 2013. URL: http://
docs.mongodb.org/manual/applications/aggregation/ (дата обращения:
28.01.2013).
9. Doctrine Team. Caching — Doctrine 1.2.4 Documentation // Doctrine 1.2.4
Documentation. 2013. URL: http://docs.doctrine-project.org/projects/doctrine1/en/
latest/en/manual/caching.html (дата обращения: 27.05.2014).
35
Download