Волею судеб пришлось вспоминать как устроена база данных на моей предыдущей работе. С удовольствием повидался с друзьями-сослуживцами, окунулся в знакомую атмосферу. Но я не об этом. После долгого перерыва старые наработки смотрятся по-другому, иногда поражая, как решения, придуманные дюжину лет назад, соответствуют сегодняшним общепризнанным подходам. На эту мысль меня навел маленький и простенький пример.
В прошлой своей "жизни" я был создателем систем социологических баз данных. Специфика работы заключалась в том, что мы принимали, загружали, обрабатывали и использовали данные социологических исследований, которые мы получали по подписке от международной фирмы-исследователя.
Данные поступали ежедневно и в огромных объемах, к примеру, в одну из таблиц каждый день должны были попадать записи в количестве близком к миллиону. Хуже всего то, что должен был сохраняться массив целиком, с тем, чтобы можно было получить информацию в точности так, как она была получена в любой из дней, загруженных в архив.
Естественно, необходимо было предпринимать специальные усилия для сокращения как объемов сохраняемых данных, так и их времени загрузки и обработки.
Эта проблема - как работать с "историческими", медленно изменяющимися данными - широко распространена в хранилищах данных и т.н. "многомерных" БД. Эта интересная тема заслуживает отдельного рассмотрения в блоге, и я предполагаю написать несколько заметок на эту тему в ближайшем будущем.
Разбираясь в структуре этой БД, я наткнулся на интересное решение, которое мы самостоятельно придумали и использовали еще тогда, а теперь распространяется как стандартное в составе средств MS SQL Server 2008. Суть вопроса в том, что при получении новой порции данных о респондентах опроса, мы должны внести только изменения атрибутов респондентов, которые изменились по сравнению с текущим состоянием данных о нем. В системе был принят подход, который гуру хранилищ данных Ральф Кимбэлл назвал "slow changing dimension type II". Это означает, что записи в таблице респондентов никогда не изменяются и не удаляются, а только добавляются новые. Эти добавленные записи становятся "текущими", отмечаются, как актуальные, а старые записи переводятся в архив. Поэтому, чтобы иметь возможность получить правильный набор атрибутов на любой день, в записи были добавлены поля даты начала действия записи и окончания действия этой записи в качестве актуальной/текущей. При добавлении новой более свежей записи в нее заносится текущая дата в качестве начала, дата окончания проставляется открытой. Старая или прежняя запись, напротив, остается с первоначальной датой начала действия и получает текущую дату в качестве даты окончания. Если делать это все буквально, то это будет занимать каждый раз безумное количество времени.
Ральф Кимбэлл советует выполнять проверку на наличие изменений в новых данных по отношению к текущей БД не на сервере, а с помощью специальной программы, которую запускают на "клиенте". Сначала мы применили именно такой подход. Закачка каждой порции данных занимала от минуты до часа в зависимости от загруженности системы и объемов поступивших данных. Анализируя "узкие места" загрузки (это также тема отдельных публикаций) мы обнаружили, что главной задержкой является скорость помещения записей на сервер. Этот подход также не позволяет применять специализированные утилиты загрузки, имеющиеся в составе каждой крупной СУБД. Как бы выполнять эту проверку не на клиенте, а на сервере?.
Решение "в лоб" путем сравнения "новых" и "старых" значений атрибутов на языке SQL в предложении INSERT дало безумные планы выполнения SQL и увеличение времени загрузки на порядок. Уж больно много полей-атрибутов надо проверить в одном условии.
А если использовать «сигнатурный подход»?: подумали мы. Сравниваются не все подряд атрибуты, а некие вычисленные значения, зависяшие от значений ВСЕХ атрибутов в записи. Причем, вычислять такие значения-сигнатуры можно заранее, в момент загрузки записей на сервер, тратя на это минимальное время. В качестве такой сигнатуры может подойти checksum по надежному алгоритму.
Например, для таблицы:
CREATE TABLE TNSTV.DEMOTRANSACTS
(
DTRID NUMBER NOT NULL, -- заполняется триггером
DTRRSPNO NUMBER NOT NULL,
DTRBDAY NUMBER NOT NULL,
DTREDAY NUMBER NOT NULL,
DTRCRC NUMBER NOT NULL,
DTRMD5 VARCHAR2(100) NULL,
DTRHOUSEHOLD NUMBER NULL,
DTRGENDER NUMBER(1,0) NULL,
DTRAGE NUMBER(2,0) NULL,
DTRAGEGRP NUMBER(2,0) NULL,
DTREDUCATION NUMBER(2,0) NULL
)
TABLESPACE USERS
/
Скрипт для загрузки мог бы выглядеть так:
INSERT INTO TNSTV.DEMOTRANSACTS(DTRRSPNO, DTRBDAY,DTREDAY, DTRMD5,DTRHOUSEHOLD,DTRGENDER,DTRAGE,DTRAGEGRP,DTREDUCATION )
select
inboxsupport.nmvalues('MEMBER_R',x.TROWSTR),
tnstv.get_dayid(x.IEFDATE), FOREVER,
tnstv.md5(x.TROWSTR),
inboxsupport.nmvalues('HOUSEHOLD_NR',x.TROWSTR),
inboxsupport.nmvalues('SEX',x.TROWSTR),
inboxsupport.nmvalues('AGE',x.TROWSTR),
inboxsupport.nmvalues('AGEGRP',x.TROWSTR),
inboxsupport.nmvalues('EDUCATION',x.TROWSTR)
from
TNSTV.SQLINBOX x;
/*Здесь использован собственный пакет inboxsupport для доступа к входным атрибутам и макрос FOREVER, помещающий в поле максимально возможную дату.*/
Пример очень схематичен, только для иллюстрации. Если получится, я постараюсь уделить этой теме в подробностях больше внимания позже.
Что выбрать в качестве checksum-сигнатуры? Зависит от требований к системе. Для нас вполне подходили стандартные алгоритмы, среди которых имелся выбор: мы могли применить числовую Checksum – занимает меньше места, быстрее сравнивается, а могли воспользоваться встроенными в Oracle функциями.
Вот пример тестового запроса:
select
OWA_OPT_LOCK.checksum(d.demogfx),
tnstv.md5(d.DEMOGFX),
d.*
from tnstv.spareddemo d
order by id;
Использует для вычисления либо функцию checksum из пакета OWA_OPT_LOCK, либо собственную функцию, которая вычисляет уже символьную строку с MD5
CREATE OR REPLACE FUNCTION TNSTV.MD5 (input_string varchar2) return varchar2 as
begin
return dbms_obfuscation_toolkit.md5(input_string => input_string);
end;
Это решение, может быть и не самое оптимальное, хорошо работает на практике уже более десятка лет.
Share This | Email this page to a friend
Tagged checksum, datawarehouse, DTS, DW, Embarcadero, ETL, MD5, RapidSQL, slow changing dimension