Найти в Дзене
Одиночная палата

Begin /* Историчность

Оглавление

Записная книжка История Болезни. Трудоголизм (с) ОРЗ-ДИЗАЙН, Россия
Записная книжка История Болезни. Трудоголизм (с) ОРЗ-ДИЗАЙН, Россия

Если коротко

В данной заметке речь пойдет больше о проектировании баз данных. К программированию работу с базами данных то относят то нет. Составление запросов чаще всего не относят непосредственно к программированию, но по основным признакам это все те же алгоритмы. Кроме этого конструкции запросов напрямую связаны с тем как спроектирована база данных. В идеальном случае программисту не нужно особенно задумываться о том какие индексы и ключи он задействует при поиске и связывании отношений. Однако, как я много раз это повторяю, реальное положение дел, к сожалению, часто весьма далеко от совершенства. И мало того, что обычно программист все-таки должен понимать как и в каких количествах в его программу попадают данные, чаще чем этого бы хотелось, возникает необходимость как исправлять структуру БД непосредственно, так и искать способы обхода недочетов при её проектировании.

Плохая структура БД или несогласованность с ней прикладной программы чаще всего выливается в "тормоза". Происходит это по двум основным причинам. Первая - программа была создана без учета роста объема данных, еще тогда, когда в базе было десять тестовых записей. Вторая - у программиста нет возможности вносить изменения в структуру под требования и нужды новой задачи.

Для решения второй проблемы, как наиболее часто встречающейся, некоторые общие способы и подходы наработаны и успешно применяются. Они связаны в основном с отделением оперативных данных от основных массивов. Это и хитрые материализованные представления, и кэширование результатов запросов - непосредственное создание дополнительных сущностей для более эффективной работы программы. Они подразумевают под собой надстройку над уже существующей структурой. Эти надстройки реализуются как средствами СУБД, так и определенными методиками работы с данными в программах.

Первая же причина изучена чуть меньше и по сути как-раз является косвенным фактором возникновения проблемных баз данных. Что если изначально сделать так, что бы база данных никогда не превратилась в неподъемную кучу мусора из устаревших по сути данных? Есть стандартные ответы и на этот вопрос. Ну, прежде всего, нужно отнестись с ответственностью к структуре базы данных - грамотно её спроектировать. Так же необходимо провести анализ того как будут развиваться события в будущем. По крайней мере попытаться это сделать. И если по проектированию БД можно при желании найти практически любые инструкции на все случаи жизни, то с определением горизонта прогнозирования и конкретными методами экстраполяции есть определенные проблемы.

Я не буду здесь повторяться о стандартных подходах к проектированию баз данных. Так же, наверное, стоит отдельно поговорить о встроенных средствах оптимизации СУБД. А вот о том, что следовало бы учитывать и как избежать явных проблем в будущем при разработке программ, думаю, следует поговорить немного подробнее.

Архивация

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

Для работы с быстро устаревающими данными существуют определенные средства работы на уровне СУБД. Растущие во времени таблицы можно делить на части - секционировать (partitioning). Невостребованные данные можно физически удалять, оставляя заботу о хранении архивов на какие-то внешние средства аудирования и журналирования.

Архивирование данных, не нужных для оперативного доступа, это достаточно общее решение, и о нем я не буду особенно распространяться. При таком подходе возникает другая менее очевидная проблема. Что если нужно откатить какие-то отдельные участки базы данных? Не всю и сразу и даже не отдельную таблицу, а сделать что-то по более хитрому алгоритму, чем это позволяют встроенные средства СУБД. Что если редко, но старые данные все-таки нужны для какой-то мудреной аналитики, для восстановления каких-то значений, просто посмотреть что было с той или иной сущностью во времени с момента её возникновения? Ну, например, придет следователь и запросит данные по адресу десятилетней давности.

Есть два общих решения. Создать дополнительную процедуру, отдельный модуль или даже совершенно другую программу, работающую с архивом. Этот случай очевидный, но требующий где-то вдвое больше затрат. Либо сделать так, что бы архивные данные были доступны по стандартному запросу, но при этом не участвовали в основной деятельности. Здесь же придется заранее позаботиться о дополнительных сущностях и более изощренных способах работы с ними. Такие сущности описываются таким общим термином как историчность.

Что хранить?

Существует радикальный подход - хранить вообще все изменения в базе данных таким образом, что любой запрос может помимо искомых данных иметь параметр времени. Выбрать такие-то поля из такой-то таблицы по такому-то условию по состоянию на... Такие структуры называются хронологическими базами данных. Однако понятно, что такое решение специфично и не подходит к большинству конкретных задач. Нам обычно не нужно знать сколько домов было на улице в каждый момент времени постоянно. Чуть более сложный запрос по существующей информации о вводе новых и сносе старых зданий решает такую задачу без необходимости хранить всё подряд. Он будет работать медленнее, но снимает необходимость хранить избыточные данные.

Определить какие сущности требуют того, что бы каждое изменение было всегда доступно сложно. Тем не менее предусмотреть лишнюю таблицу истории изменений все же лучше, чем внедрить её в следствии возникшей необходимости годы спустя. Чаще всего такому дублированию подвержены данные, по которым нужны ретроспективные отчеты. Мало хранить текущую стоимость товара, обычно нужно знать его стоимость в этом же квартале прошлого года. А может еще и с учетом проходившей тогда акции? Поэтому общий подход такой: если не уверены понадобится ли историчность, то лучше перестраховаться. В конце концов проще потом удалить не нужное, чем вносить какие-то коренные изменения.

Сама же сущность хранящая историю изменений должна быть отделена от текущих данных. Часто такие структуры поддерживаются средствами самих СУБД посредством триггеров. Иногда требуется и более тонкий подход, когда промежуточные изменения хранить смысла нет. Тогда либо создается более сложный алгоритм фиксации вешек в истории сущности, либо такая функция делегируется пользователю. Бортовому компьютеру автомобиля обычно не нужно помнить последовательность регулировки водительского кресла, а достаточно хранить только конечное состояние профиля водителя.

Как и всегда, не следует уходить в крайности. Сохранение истории не должно приводить к каскаду сложных вычислений и мудреных вставок приводящих к "тормозам" в обратную сторону. Хранение истории всегда должно обходиться дешевле, чем извлечение данных с её использованием.

Как хранить?

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

Снова все зависит от того, к каким именно данным применяется тот или иной подход и каков баланс эффективности их совместного использования. Тут важным фактором выбора является не столько динамика изменений, сколько их количество. Если даже сущность имеет свойство часто и значительно меняться, допустим это данные о погоде, экономить на том, что бы не хранить избыточные данные бессмысленно. Проще не выдумывать какие-то дополнительные дельты показателей, а просто сохранять всё. Другое дело, если это, скажем, бухгалтерская проводка, которая после создания обычно меняет только свое состояние из полученного на проведенное. Однако этих проводок может быть очень много, а меняться эти состояния должны оперативно. Тогда действительно нет смысла сохранять каждое состояние со всеми сопутствующими атрибутами, а хранить лишь изменение статуса, причину и ответственное лицо.

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

Эти вопросы возвращают нас обратно к понятию кэширующих функций и хранению вычисленных ранее значений. Хранение кэша данных это по сути тоже некий вариант историчности, но использует он несколько другие механизмы. С помощью кэширования в определенных условиях можно добиться точно такого же эффекта как и в случае хранения истории. Однако это скорее дополняющий механизм, а не замена. Кэширование, например, не способно решить задачу с данными, которые изменяются может быть не так динамично и много, но постоянно и в течении всего времени существования. В прочем, об этом тоже отдельно.

Отсебятина

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

Разумеется, лучшим путем избежания проблем в будущем является подробное и вдумчивое разжевывание всех нюансов на предварительном этапе. Если же контракт уже подписан, и при составлении ТЗ "забыли вас спросить", то вынужденно приходится либо делать заведомо нежизнеспособный продукт, либо пороть в очередной раз отсебятину. И если с одной стороны никому не охота что-то делать бесплатно, да и частенько это прямо не одобряется, то с другой, хорошему специалисту просто бывает стыдно сдавать то, что через месяц работы просто встанет колом.

Как сделать так что бы и овцы целы и волки сыты? Хочется опять сказать что все зависит от конкретной ситуации, и это так, но все же есть один рецептик. Внутреннюю структуру продукта техническое задание, как правило, не описывает никак. Поэтому можно сделать точно так, как по вашему мнению следует сделать, пускай и с элементами отсебятины. Но раскрывать потенциальный функционал совершенно не обязательно при сдаче. Можно внедрить историчность там где, по вашему мнению, без этого не обойтись точно, а может и там где существует такая вероятность в принципе. Ну а, скажем, в пользовательской форме не добавлять лишнее поле с датой. Пусть оно будет во всех API и полях отношений в базе, но не в пользовательском интерфейсе. Велика вероятность, что после осознания недочета заказчик придет обратно к вам и все вложенные заранее труды будут щедро оценены.

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

*/

End;