Metrika

2 июня 2011 г.

Автоматическое партицирование OLAP-кубов в SSAS 2008


В статье Партицирование OLAP-кубов в SSAS 2008 я рассказывал зачем и как партицировать кубы в SSAS. Бывают случаи, когда партицировать надо часто и руками это делать скучно. Поэтому хочется этот процесс автоматизировать.
В моем случае мне нужно партицировать в трех кубах в общей сложности 10 групп мер. Если этого не делать каждую неделю, то время процессинга за две недели возрастает с 3,5 часов до 5 с лишним.
В статье Взаимодействие с OLAP кубами (SSAS 2008) на C# я рассказывал, как работать с кубами из кода. Сначала я начал решать задачу автоматического партицирования через разработку проги, но что то сильно углубился в программирование и мне эта идея перестала нравиться, поэтому я выбрал другой путь - создание пакета SQL Server Integration Services (SSIS). Здесь я и опишу этот способ.
Идея следующая - группы мер партицируются по годам плюс есть текущая партиция, которая процесситься каждый день. Т.е., например, для продаж есть партиции за 2010 год, 2011 и current. Раз в неделю для каждой группы мер необходимо изменять партицию за текущий год (2011) и партицию current, так что бы текущая партиция содержала данные только за несколько дней. Потом процессить партицию за текущий год, что бы не было пробелов в данных. После нового года надо будет руками создавать новую годовую партицию и менять процедуру изменения партиций.
Начальное партицирование куба надо провести руками, автоматически все с нуля не создается.
Для примера возьмем, что партиции группы мер продаж формируются следующими SQL-запросами:
2010: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2010-01-01' AND sale_date < '2011-01-01'
2011: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2011-01-01' AND sale_date < '2011-05-31'
current: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2010-05-31'
Эти запросы и надо будет менять в партициях за 2011 год и в текущей партиции.
Для начала необходимо сгенерить скрипты XMLA для изменения партиций. Я буду делать все на примере только одной группы мер. Пакет SSIS будет сделан так, что количество не будет иметь значения.
Cоздаем папку: C:\XMLAScripts. Далее, для генерации скрипта в Management Studio открываем список партиций нужной группы мер, на текущей партиции тыкаем правой кнопкой, а далее как на картинке:

В последнем меню выбираем File... и сохраняем в созданной папке под названием sales_current.xmla. Далее тоже самое повторяем для партиции текущего года и сохраняем в другой файл sales_2011.xmla.
Далее открываем файлы по-очереди и видим что-то похожее на это:
<Alter ObjectExpansion="ExpandFull" xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
    <Object>
        <DatabaseID>cubeDbSales</DatabaseID>
        <CubeID>cubeSales</CubeID>
        <MeasureGroupID>Vw Sales 1</MeasureGroupID>
        <PartitionID>Vw Sales 1</PartitionID>
    </Object>
    <ObjectDefinition>
        <Partition ...>
            <ID>Vw Sales 1</ID>
            <Name>Vw Sales</Name>
            <Source xsi:type="QueryBinding">
                <DataSourceID>dbOLAP</DataSourceID>
                <QueryDefinition>SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date &gt;= '2010-05-31'
</QueryDefinition>
            </Source>
            <StorageMode>Molap</StorageMode>
            <ProcessingMode>Regular</ProcessingMode>
            <ProactiveCaching>
                <SilenceInterval>-PT1S</SilenceInterval>
                <Latency>-PT1S</Latency>
                <SilenceOverrideInterval>-PT1S</SilenceOverrideInterval>
                <ForceRebuildInterval>-PT1S</ForceRebuildInterval>
                <Source xsi:type="ProactiveCachingInheritedBinding" />
            </ProactiveCaching>
        </Partition>
    </ObjectDefinition>
</Alter>

В каждом файле заменяем дату, которая разделяет партицию текущего года и текущую партицию, в данном примере это 2011-05-31, на какую-нибудь последовательность символов. Я поменял на "###dt###", т.е. запрос в скрипте будет выглядеть вот так:
 <QueryDefinition>SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date &gt;= '###dt###'</QueryDefinition>
Далее эта последовательность символов будет меняться на нужную дату.

В BIDS создаем Integration Services Project. Из Toolbox'а перетаскиваем Foreach Loop Container, а в него Script Task и Analysis Services Execute DDL Task. В итоге должно получиться вот так:
Далее создаем переменные:
Розовые уголки в иконках - это у меня плагин стоит, который я описываю в статье Как поменять область видимости переменной в BIDS (SSIS).
Для переменных dt и dt_str устанавливаем свойство EvaluateAsExpression = True. Для dt свойство Expression: GETDATE(). Для dt_str свойство Expression:
(DT_WSTR, 4)(YEAR(@[User::dt]))+"-"+
(LEN((DT_WSTR, 2)(MONTH(@[User::dt])))==1 ? "0"+(DT_WSTR, 2)(MONTH(@[User::dt])) : (DT_WSTR, 2)(MONTH(@[User::dt])))+"-"+
(LEN((DT_WSTR, 2)(DAY(@[User::dt])))==1 ? "0"+(DT_WSTR, 2)(DAY(@[User::dt])) : (DT_WSTR, 2)(DAY(@[User::dt])))
Такое выражение формирует из даты строку формата yyyy-MM-dd, другого пути я не нашел.
В совойствах Foreach контенера устанавливаем:
  1. В разделе Collection:
    1. Enumerator = Foreach File Enumerator
    2. Folder = C:\XMLAScripts
    3. Files = *.xmla 
  2.  В разделе Variable Mappings: Variable=User:file_name, Index = 0
 В свойствах блока Script в разделе Script:
  1. ReadOnlyVariables = User::dt_str,User::file_name
  2. ReadWriteVariables = User::alter_code
В коде скрипта пишем (у меня SQL Server 2008, поэтому пишу на C#):
        public void Main()
        {
            string file_name = Dts.Variables["file_name"].Value.ToString();
            StreamReader sr = new StreamReader(file_name);

            string content = sr.ReadToEnd();

            sr.Close();
            sr.Dispose();

            Dts.Variables["alter_code"].Value = content.Replace("###dt###",  Dts.Variables["dt_str"].Value.ToString());

            Dts.TaskResult = (int)ScriptResults.Success;
        }
 В свойствах блока Alter Cube в разделе DDL:
  1. Connection -> создаем новое соединение с кубом
  2. SourceType =Variable
  3. Source = User::alter_code
На этом создание SSIS пакета закончено. Foreach контейнер выбирает из папки C:\XMLAScripts все файлы с расширением xmla, в блоке Script текущий файл читается, в нем производиться замена строки "###dt###" на сегодняшнюю дату в формате yyyy-MM-dd, а блок Alter Cube передает полученную таким образом команду OLAP-серверу. Для того, что бы добавить другие группы мер для партицирования, надо в папку C:\XMLAScripts добавить XMLA-файлы с альтерами для этих групп, в которых дата в SQL-запросе будет заменена на "###dt###".
Далее нужно создать джоб, в котором первым шагом сделать бэкап куба на всякий случай, вторым шагом запустить этот SSIS пакет, а далее отпроцессить все партиции за текущий год.


19 комментариев :

  1. а не проще сделать так:
    разбить меры на партиции:
    1. Факты_2010_2011: с 2010-по 31.05.2011 (с учетом того что май уже не трогают для изменения)
    2. Факты_текущий_месяц:
    SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >=
    dateadd(day,-Day(getdate())+1 and sale_date <= getdate()),getdate()-2
    3. Факты_текущая_дата:
    SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date between getdate()-1 and getdate()

    Создать SSIS пакет ("Задача обработка средствами AS") с обработкой 2 и 3 секций, и все.
    Вообще 2 и 3 можно объединить, но в моем случае например этого не сделаешь, так как вчерашнюю и сегодняшнюю дату постоянно обновляет внешняя система и заливка не всегда происходит (есть insert, delete).
    Итого:
    первую секцию я залил и вообще не трогаю
    вторую загружаю раз в сутки в 4:00 (когда нет интеграции)
    третью каждый час

    ОтветитьУдалить
  2. Да, так много проще. Сейчас сижу и думаю, почему я так не догадался сделать :))
    Ну а по поводу того, какими частями бить куб на партиции, думаю тут индивидуально все. Зависит от требований к оперативности данных и от объемов данных. Мне удобнее делать текущие партиции в субботу утром. Да и 2011 год иногда еще перепроцешивается, поэтому я его отделил от 2010-го.

    ОтветитьУдалить
  3. Вот насчет того что текущий год могут менять, что не есть гуд, как тут можно разрулится, не подскажите?
    Например в декабре 2011 могут изменить апрель 2011, июль 2011... процессить весь год не есть хорошо и долго.
    Что можно придумать?

    ЗЫ: при использовании более трех партиций на секцию пишет ошибку:
    "Ошибки доступности и конфигурации компонентов: Превышение максимального количества секций в группе мер. Установленные выпуски сервера допускают использование не более 3-х секций и не более 1-ой секции обратной записи."

    Использую SQL Server 2008 Russian Standard х32

    ОтветитьУдалить
    Ответы
    1. Обойти ограничение можно, создав сколько угодно IS-пакетов, запускающихся последовательно (в джобе например) (или, наверное, можно даже в одном пакете последовательность тасков), каждый из которых запускает наборы не более трёх секций (партиций).

      Удалить
    2. Николай, создать сколько угодно секций по Вашему примеру смог, но как их обработать? В Management Studio при запуске скрипта на обработку первой же секции вылетает

      ...
      ...

      Удалить
    3. xml скушался.

      Ошибки доступности и конфигурации компонентов: Превышение максимального количества секций в группе мер. Установленные выпуски сервера допускают использование не более 3-х секций и не более 1-ой секции обратной записи.

      Удалить
    4. Это ограничение Standard Edition, что бы было больше нужен Enterprise

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

      Удалить
  4. По поводу ограничения - это видимо ограничение Standard Edition, у меня стоит SQL Server 2008 R2 Enterprise Edition, максимум используется 4 партиции и не ругается.
    По поводу перепроцешивания - кубы еще пока не в продакшене, а еще в стадии вялотекущей разработки, так что бывает приходиться перепроцешивать.
    По поводу перепроцешивания нескольких месяцев - ну первое что приходит в голову - для текущего года создавать месячные партиции, но в вашем случае с ограничением это не вариант. Есть еще команда Merge, которая сливает партиции в одну. Т.е. идея такая - создаем годовую партицию, допустим только за январь, процессим, потом за февраль и мерджим ее с январской, потом за март и т.д. Но на больших объемах команда Merge будет очень медленно работать, так что может не подойти. У меня кубы порядка 100 гигов, на самых емких мерах я так и не смог дождаться, когда Merdge выполниться :) Еще один вариант - завести еще 1 сервер или хотя бы инстанс SSAS'a, на нем развернуть кубы, перепроцесить, например в выходные, потом забэкапить и развернуть бэкап на рабочий сервер, разворачивание бэкапа проходит много быстрее перепроцессинга всего. У меня кубы, если полностью процессить, наверное будут процесситься часов 7, из бэкапа разворачиваются за час.

    ОтветитьУдалить
  5. хм... спасибо, надо подумать :)

    ОтветитьУдалить
  6. Пожалуйста. На самом деле надо поэкспериментировать, очень советую, если еще нет, завести тестовый инстанс или лучше сервер и эксперименты ставить на нем. В какой то момент мне показалось, что Merge это прекрасная возможность изменять партиции, поэкспериментировал на маленьком кубе - все отлично, но как только запустил на больших объемах - стало понятно, что не катит совершенно такая идея. Так что теперь все эксперименты только на тестовых кубах, которые разворачиваю их бэкапов рабочих кубов.

    ОтветитьУдалить
  7. Возникает еще один момент непонятный мне:
    есть партиция Факты_2010_2011 (до 31.05.2011)
    потом например автоматически разбиваю меру на партиции, как в моем варианте:
    Факты_текущий_месяц и Факты_текущая_дата, т. е. в июне система автоматом обрабатывает июнь, в июле июль и т.д.... но по факту через пол года у меня будут партиции: Факты_2010_2011 (до 31.05.2011) и Факты_текущий_месяц и Факты_текущая_дата ЗА ДЕКАБРЬ, а вот с июня по ноябрь нету.
    Как вот здесь потом, в случае необходимости, обработать данные с июня по ноябрь?

    или может можно руками в самом SQL Manager создавать секции и они будут работать?

    ОтветитьУдалить
  8. Не очень понял, что имеете в виду, что не будет отдельных партиций с июня по ноябрь? Если партиция Факты_текущий_месяц в SQL-запросе будет сверху ограничена функцией getdate(), то она будет расти и вбирать в себя другие месяца, пока руками не создать новую месячную партицию, а предыдущую жестко ограничить сверху < '2011-06-01'

    ОтветитьУдалить
  9. Да расти то она (Факты_текущий_месяц) будет, но только в текущем месяце неболее, смысл ее такой:
    в where стоит sale_date between
    dateadd(day,-Day(getdate())+1,getdate()) and getdate()
    т. е. сегодня это: с 01.06.2011 по 14.06.2011
    завтра это: с 01.06.2011 по 15.06.2011
    и т.д.

    А в след месяце это автоматом будет:
    первого июля:с 01.07.2011 по 01.07.2011
    второго: с 01.07.2011 по 02.07.2011
    и т.д.
    ....

    в итоге в декабре это будет:
    с 01.12.2011 по 31.12.2011

    Но по факту в декабре секции с 01.06.2011 по 30.11.2011 нет! Или SQL сам их будет создавать как-то? если нет то как их потом процессить, в случае необходимости?

    ОтветитьУдалить
  10. ЗЫ: или в секцию Факты_2010_2011 нужно каждый месяц добавлять ограничение по конечной дате предыдущего месяца...
    типа в июне это как щас стоит 31.05.2011
    в июле ставить руками 30.06.2011
    ...
    в декабре 30.11.2011
    ...
    а в 2012 переименовывать ее в Факты_2010_2012 и т.д.?

    ОтветитьУдалить
  11. что то я протупил
    в секцию Факты_2010_2011 поставить в конец
    ДАТА<dateadd(day,-Day(getdate())+1,getdate())

    и все :).
    минус в том что эта секция будет разростатся и через пару лет обработать ее будет нереально.

    ЗЫ: подскажите где можно за эти merge почитать? куда нажать?

    ОтветитьУдалить
  12. ну да, надо партиции жестко ограничивать снизу, например для мая dt >= '2011-01-01' и по getdate() сверху, например dt < cast(getdate()-1 as date). Тогда партиция будет разрастаться вверх неограничено, потом, когда надо, ограничить ее сверху жестко dt < '2011-06-01' и создать новую партицию текущего месяца dt >= '2011-06-01' and dt < cast(getdate()-1 as date). Перенастроить задания процессинга так, что бы он процесил не майскую партицию, а июньскую. Текущая партиция всегда будет ограничена снизу dt >= cast(getdate()-1 as date), ее менять не надо.

    Про Merge - спросите у Гугла :)) http://www.google.com/#hl=en&source=hp&q=ssas+2008+merge&aq=f&aqi=&aql=f&oq=&pbx=1&fp=1&biw=1646&bih=967&cad=b&bav=on.2,or.r_gc.r_pw.

    ОтветитьУдалить
  13. Что то сейчас посидел подумал, что в моем случае getdate() не очень подходит, т.к. если я партицирую кубы по годам, и текущий год будет ограничен сверху функцией getdate(), а текущая мера снизу функцией getdate(), то тогда мне надо будет процессить каждый день и текущий год тоже, что не очень прикольно. Можно конечно создавать более мелкие партиции, которые будут ограничены getdate() сверху, например месяц или неделя, но тогда надо будет раз в какое то время изменять сами партиции, т.е. ограничивать сверху жесткой датой и изменять процедуры процессинга, что бы они больше не процессили старые партиции и процессили новые. Можно этот процесс автоматизировать, но сейчас я к этому не готов :)

    ОтветитьУдалить
  14. да вариантов много в общем :). спасибо.

    ОтветитьУдалить