Continuus Integration dla kostek wielowymiarowych (SSAS Multidimensional)

Continuous Integration dla kostek wielowymiarowych (SSAS Multidimensional)

Tematy DevOpsowe w ostatnim czasie dość mocno zadomowiły się w przypadku pracy z danymi. Używanie systemu kontroli wersji dla projektów bazy danych czy też rozwiązań klasy Business Intelligence (SSIS, SSRS, SSAS dla technologii Microsoft) nie powinno już nikogo dziwić, natomiast implementacja Continuous Integration (CI) czy Continuous Delivery (CD) dla tych usług wydaje mi się, że jest znacznie mniej popularna, ale na pewno coraz częściej się pojawia. W dzisiejszym wpisie chciałbym pokazać w jaki sposób można przygotować skrypt/skrypty do automatycznego publikowania projektu kostek wielowymiarowych. Pokażę również metodę na zaimplementowanie takiego scenariusza na maszynie developerskiej bez konieczności posiadania specyficznego oprogramowania. Będzie zatem mowa o Continuous Integration dla kostek wielowymiarowych SSAS.

Continuous Integration dla kostek wielowymiarowych SSAS

Tradycyjny scenariusz publikowania zmian dla kostek wielowymiarowych składa się z trzech etapów. Po pierwsze zbudowania projektu, po drugie samej publikacji i po trzecie procesowania. Zwykle wszystkie te kroki wykonywane są ręcznie niezależnie od środowiska (DEV, TEST, PRE-PROD, PROD). Tematy DevOpsowe zagościły już jednak na dobre w projektach bazodanowych, a co za tym idzie również projekty BI zostają automatyzowane. Dzieje się to jednak znacznie rzadziej.

Continuous Integreation / Continuous Delivery / itp to pojęcia, które opisują, we właściwym dla siebie zakresie, automatyzację wdrażania i cyklu życia aplikacji czy też systemu. W dużym skrócie DevOpsi, czyli osoby odpowiadające za tę automatyzację dążą do tego, aby przenoszenie zmian ze środowiska developerskiego na środowisko produkcyjne było jak najbardziej sprawne. Zgodnie z definicją powinniśmy dążyć do pełnej integracji, czyli automatyzacji dla każdego środowiska oraz powinniśmy dbać nie tylko o szybkie wypuszczenie nowej wersji, ale również dbać o jego jakość. Stąd też mówiąc o CI czy też CD powinniśmy również pamiętać o różnego rodzaju testach, które powinny być częścią cyklu wdrażania nowej wersji. W dużych i dobrze zorganizowanych projektach do pomocy wykorzystywane są dodatkowe narzędzia jak TeamCity, Jenkins czy też Visual Studio Team Services, natomiast nawet bez tych dodatkowych narzędzi możemy zadbać o automatyzację.

W niniejszym poście postaram się pokazać, że nawet w przypadku SSAS jest to wykonalne i w cale nie jest trudne.

Konfiguracja i przygotowanie środowiska

Jak już zostało wspomniane wyżej proces publikowania zmian dla SSAS odbywa się w trzech krokach, których pierwszy jest “Build”, czyli budowanie projektu, a drugim – kluczowym – jest przygotowanie skryptu do publikacji lub od razu publikacja nowej wersji bazy danych SSAS. W tym celu mamy dwie możliwości:

  • użycie Deployment Wizard – które pozwoli nam określić szczegóły publikacji oraz pozwoli albo na publikację nowej wersji albo na wygenerowanie skryptu do publikacji.
  • użycie bezpośrednio Visual Studio – które de facto wykorzysta również “Deployment Wizard”, natomiast skorzysta z domyślnych ustawień projektu dla publikacji

Dobrym pomysłem wydaje się zatem przygotowanie plików ustawień dla każdego profilu publikowania, czyli dla każdego serwera (DEV, TEST, PRE-PROD, PROD, etc). Można to osiągnąć za pomocą “Deployment wizzard” uruchomionego w trybie “answer mode” – parametr “/a” z poziomu linii poleceń CMD. Wywołanie może wyglądać tak:

"C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Microsoft.AnalysisServices.Deployment.exe" "C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE\bin\AWDW2014Multidimensional-EE.asdatabase" /a

Naszym oczom ukażą się standardowe okna “Deployment wizard”, gdzie możemy skonfigurować ostateczny skrypt do publikacji. Przykładowo, jak na screenie ustawienia źródła danych:

Po zakończeniu pracy kreatora w lokalizacji bazy danych wszystkie pliki konfiguracji zostaną nadpisane:

  • [nazwa bazy danych SSAS].assecurityinformation
  • [nazwa bazy danych SSAS].configsettings
  • [nazwa bazy danych SSAS].deploymentoptions
  • [nazwa bazy danych SSAS].deploymenttargets

Na potrzeby kompleksowego rozwiązania warto przygotować te ustawienia ręcznie dla każdego środowiska lub odpowiednio zmodyfikować pliki konfiguracyjne oraz przed wykonaniem skryptu podmienić domyślne pliki.

Konfiguracja skryptu

Konfiguracja skryptu, czyli ustawienie podstawowych zmiennych, które będą przydatne w skrypcie/skryptach.

############# USER CONFIG #############
$CIDirectory = "C:\code\ci\"
$CILogFileName = "CI_log_SSAS.txt"

$DenvPath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe"
$DeploymentWizardPath = "C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Microsoft.AnalysisServices.Deployment.exe"

$SSASSOlutionDirectory = "C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE\bin\"
$SSASSolutionPath = "C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE.sln"
$SSASDatabaseName = "AWDW2014Multidimensional-EE.asdatabase"
$SSASDeploymentSettingsFileName = "AWDW2014Multidimensional-EE.deploymentoptions"

$SSASDeployScriptFileName = "deploy.xmla"

$SSASServerName = "demo\ssasm"
$SSASDatabaseServerName = "AdventureWorksDW2014Multidimensional-EE"

W przykładzie między innymi ścieżki do “Deployment Wizard”, “Visual Studio”, czy też scieżki do plików konfiguracyjnych, kodu bazy danych i folderu logowania.

Logowanie

W zależności od narzędzi i ostatecznego scenariusza możemy chcieć dodatkowo logować postępy. Poniżej jeden z przykładów jak to osiągnąć, czyli polecenie “Start-” i “Stop-Transcript”.

# Start logging
$ErrorActionPreference="SilentlyContinue"
Stop-Transcript | out-null
$ErrorActionPreference = "Continue"
New-Item -ItemType directory -Path ($CIDirectory + $NewFolderDateTimeName)
Start-Transcript -path ($CIDirectory + $NewFolderDateTimeName + "\" + $CILogFileName) -append



#Finish logging
Stop-Transcript

Oczywiście jeżeli będziemy korzystać z VSTS lub TeamCity oraz cały proces zostanie podzielony na mniejsze kroki dodatkowe logowanie raczej nie będzie wymagane.

Build

Budowanie projektu można przeprowadzić za pomocą samego VIsual Studio wywołanego z poziomu linii poleceń CMD. Sama komenda może wyglądać następująco:

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe" "C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE.sln" /build

Ponownie w zależności od narzędzi i scenariusza powyższa komenda może być wystarczająca, jeżeli jednak chcemy skorzystać z jednego skryptu PowerShell warto zwrócić uwagę na to, że sam “build” może zająć kilka sekund i skrypt powinien zaczekać, aż proces ten się zakończy. Poniżej przykład:

# Build project
Write-Host "`r`r`r"
Write-Host "Build project"
& $DenvPath $SSASSolutionPath /build
 
Write-Host "`r`r`r"
Write-Host "Build project - progress / wait"
while (
((Get-Item ($SSASSOlutionDirectory + $SSASDatabaseName)).LastWriteTime -lt $CurrentDateTime))
{
    Start-Sleep -Seconds 1

    $ElapsedTimeSeconds = $ElapsedTimeSeconds + 1

    if ($ElapsedTimeSeconds -gt $MaxElapsedTimeSeconds)
    {
        $isError = "Error. Build timeout"
        break;
    }       
}



# Wait couple more seconds so the database will be created
Write-Host "`r`r`r"
Write-Host "Wait 5 seconds for database script to be created"
Start-Sleep -Seconds 5

W przykładzie powyżej w pętli czekamy określony limit czasu, aż plik bazy danych zostanie utworzony. Dodatkowo czekamy pięć sekund więcej po to, aż zapis do pliku się zakończy. Wynika to z faktu, że nawet gdy sam “build” się zakończy – szczególnie w przypadku bardziej skomplikowanych/większych projektów – to zapis obiektów (bazy danych) trwa dłużej.

Tworzenie skryptu do opublikowania

Sama publikacja może zostać przeprowadzona z wykorzystaniem “Deployment Wizard” uruchomionego z linii poleceń CMD w trybie “silent” – parametr “S”. Komenda może wyglądać następująco:

"C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Microsoft.AnalysisServices.Deployment.exe" "C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE\bin\AWDW2014Multidimensional-EE.asdatabase" /s /o:"C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE\bin\"

Jako parametr “o”, czyli “output” możemy określić, gdzie powinien zostać utworzony finalny skrypt. W przykładzie poniżej, dodatkowo, przenoszone oraz nadpisywane są domyślne pliki konfiguracyjne projektu, które zostały przygotowane we wcześniejszym punkcie.

# Create deployment script
Write-Host "`r`r`r"
Write-Host "Create deployment script"
if ($isError -eq "")
{
    # Overwrite the config file
    Write-Host "Overwrite the config file"
    Copy-Item ($CIDirectory + $SSASDeploymentSettingsFileName) ($SSASSOlutionDirectory + $SSASDeploymentSettingsFileName)

    # Create deployment scripts
    Write-Host "Create deployment scripts"
    & $DeploymentWizardPath ($SSASSOlutionDirectory + $SSASDatabaseName) /s ("/o:" + $SSASSOlutionDirectory + $SSASDeployScriptFileName)
}
else
{
    Write-Host "Creating deployment script failed, because ssas database file was not ready."
}

Pliki przenoszone są za pomocą podstawowej komendy PowerShell “Copy-Item”. Całość jest wykonywana tylko wówczas, gdy budowanie projektu zakończyło się sukcesem.

Publikowanie

Skrypt do publikacji jest już gotowy i sama publikacja będzie ograniczać się do jego wykonania.

# Deploy SSAS Database
Write-Host "`r`r`r"
Write-Host "Deploy SSAS database"
Invoke-ASCmd -Server: ($SSASServerName) –InputFile: ($SSASSOlutionDirectory + $SSASDeployScriptFileName)

W tym przypadku wykorzystano komendę “Invoke-ASCmd”, gdzie jako parametr podajemy adres serwera oraz ścieżkę do pliku, który zawiera skrypt.

Procesowanie

Kolejnym krokiem “Continuous Integration” będzie przeprocesowanie bazy/kostek. Tutaj również można skorzystać z wielu opcji oraz przygotować wielu scenariuszów. Jedną z propozycji w przeciwieństwie do skryptów może być bezpośrednie wykorzystanie biblioteki “Microsoft.AnalysisServices” i API Analysis Services.

Add-Type -Path 'C:\Program Files (x86)\Microsoft SQL Server\130\SDK\Assemblies\Microsoft.AnalysisServices.dll'
#[System.reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")  

$svr = new-Object Microsoft.AnalysisServices.Server
$svr.Connect($SSASServerName)

#Process all dimension
Write-Host "`r`r`r"
Write-Host "Process dimensions"
foreach ($db in $svr.Databases[$SSASDatabaseServerName])
{
    foreach ($dimension in $db.Dimensions)
    {
        if ($dimension.State -ne "Processed")
        {
            "Process dimension: " + $dimension.Name
            $dimension.Process([Microsoft.AnalysisServices.ProcessType]::ProcessFull);
        }
    }
}



#Process measure groups
Write-Host "`r`r`r"
Write-Host "Process measure groups"
foreach ($db in $svr.Databases[$SSASDatabaseServerName])
{
    
    foreach ($cube in $db.Cubes)
    {
        foreach ($mg in $cube.MeasureGroups)
        {
            if ($mg.State -ne "Processed")
            {
                "Process measue group: " + $mg.Name
                $mg.Process([Microsoft.AnalysisServices.ProcessType]::ProcessFull)
            }
             
        }
    }
}

Powyższy skrypt najpierw procesuje wymiary, sprawdzając, który wymiar nie jest już przeprocesowany, a następnie procesuje grupy miar, również procesując te grupy miar, które nie są przeprocesowane.

Inne

Powyższe elementy to tak naprawdę minimum, które powinniśmy spełnić, aby zautomatyzować publikowanie zmian dla kostek Analysis Servivces. Oczywiście proces ten może, a nawet powinien zostać rozbudowany o takie elementy jak testy:

  • funkcjonalne
  • regresji
  • wydajnościowe

W przypadku SSAS ciężko jednak mówić o standardach dla takich testów i nie będą one omawiane w tym poście, natomiast w większości przypadków będą one opierać się o wykonywanie określonych skryptów MDX lub wcześniej przygotowanych pakietów SSIS. Być może zostanie to omówione w osobnym poście.

Dobrym pomysłem jest również dodanie kolejnego kroku, który zapisze tak zwane artefakty do odpowiedniej lokalizacji. Przykładowo:

#Copy deployment artifacts
Write-Host "`r`r`r"
Write-Host "Copy deployment artifacts"
Copy-item -Force -Recurse -Verbose ($SSASSOlutionDirectory) -Destination (($CIDirectory + $NewFolderDateTimeName))

Przenosimy wszystkie pliki z folderu projektu do nowego folderu oznaczonego datą i nazwą środowiska. W ten sposób skopiowane zostaną zarówno ustawienia publikacji, skrypt bazy danych jak i sam skrypt publikacji.

Cały skrypt

Poniżej cały skrypt, który pokazuje przykład CI dla kostek Analasys Services.

############# USER CONFIG #############
$CIDirectory = "C:\code\ci\"
$CILogFileName = "CI_log_SSAS.txt"

$DenvPath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe"
$DeploymentWizardPath = "C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Microsoft.AnalysisServices.Deployment.exe"

$SSASSOlutionDirectory = "C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE\bin\"
$SSASSolutionPath = "C:\code\adventure-works-multidimensional-model-project\Enterprise\AWDW2014Multidimensional-EE.sln"
$SSASDatabaseName = "AWDW2014Multidimensional-EE.asdatabase"
$SSASDeploymentSettingsFileName = "AWDW2014Multidimensional-EE.deploymentoptions"

$SSASDeployScriptFileName = "deploy.xmla"

$SSASServerName = "demo\ssasm"
$SSASDatabaseServerName = "AdventureWorksDW2014Multidimensional-EE"



# Preparation
$CurrentDateTime = Get-Date
$ElapsedTimeSeconds = 0
$MaxElapsedTimeSeconds = 20
$isError = ""
$log = ""
$NewFolderDateTimeName = ((Get-Date).ToString("yyyyMMdd") + "_" + (Get-Date).ToString("hhmmss"))


# Start logging
$ErrorActionPreference="SilentlyContinue"
Stop-Transcript | out-null
$ErrorActionPreference = "Continue"
New-Item -ItemType directory -Path ($CIDirectory + $NewFolderDateTimeName)
Start-Transcript -path ($CIDirectory + $NewFolderDateTimeName + "\" + $CILogFileName) -append




############# SCRIPT #############

# Build project
Write-Host "`r`r`r"
Write-Host "Build project"
& $DenvPath $SSASSolutionPath /build
 
Write-Host "`r`r`r"
Write-Host "Build project - progress / wait"
while (
((Get-Item ($SSASSOlutionDirectory + $SSASDatabaseName)).LastWriteTime -lt $CurrentDateTime))
{
    Start-Sleep -Seconds 1

    $ElapsedTimeSeconds = $ElapsedTimeSeconds + 1

    if ($ElapsedTimeSeconds -gt $MaxElapsedTimeSeconds)
    {
        $isError = "Error. Build timeout"
        break;
    }       
}



# Wait couple more seconds so the database will be created
Write-Host "`r`r`r"
Write-Host "Wait 5 seconds for database script to be created"
Start-Sleep -Seconds 5



# Create deployment script
Write-Host "`r`r`r"
Write-Host "Create deployment script"
if ($isError -eq "")
{
    # Overwrite the config file
    Write-Host "Overwrite the config file"
    Copy-Item ($CIDirectory + $SSASDeploymentSettingsFileName) ($SSASSOlutionDirectory + $SSASDeploymentSettingsFileName)

    # Create deployment scripts
    Write-Host "Create deployment scripts"
    & $DeploymentWizardPath ($SSASSOlutionDirectory + $SSASDatabaseName) /s ("/o:" + $SSASSOlutionDirectory + $SSASDeployScriptFileName)
}
else
{
    Write-Host "Creating deployment script failed, because ssas database file was not ready."
}



#Copy deployment artifacts
Write-Host "`r`r`r"
Write-Host "Copy deployment artifacts"
Copy-item -Force -Recurse -Verbose ($SSASSOlutionDirectory) -Destination (($CIDirectory + $NewFolderDateTimeName))



# Deploy SSAS Database
Write-Host "`r`r`r"
Write-Host "Deploy SSAS database"
Invoke-ASCmd -Server: ($SSASServerName) –InputFile: ($SSASSOlutionDirectory + $SSASDeployScriptFileName)



Add-Type -Path 'C:\Program Files (x86)\Microsoft SQL Server\130\SDK\Assemblies\Microsoft.AnalysisServices.dll'
#[System.reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")  

$svr = new-Object Microsoft.AnalysisServices.Server
$svr.Connect($SSASServerName)

#Process all dimension
Write-Host "`r`r`r"
Write-Host "Process dimensions"
foreach ($db in $svr.Databases[$SSASDatabaseServerName])
{
    foreach ($dimension in $db.Dimensions)
    {
        if ($dimension.State -ne "Processed")
        {
            "Process dimension: " + $dimension.Name
            $dimension.Process([Microsoft.AnalysisServices.ProcessType]::ProcessFull);
        }
    }
}



#Process measure groups
Write-Host "`r`r`r"
Write-Host "Process measure groups"
foreach ($db in $svr.Databases[$SSASDatabaseServerName])
{
    
    foreach ($cube in $db.Cubes)
    {
        foreach ($mg in $cube.MeasureGroups)
        {
            if ($mg.State -ne "Processed")
            {
                "Process measue group: " + $mg.Name
                $mg.Process([Microsoft.AnalysisServices.ProcessType]::ProcessFull)
            }
             
        }
    }
}



#Finish logging
Stop-Transcript

Po zmianie zmiennych wewnątrz skryptu powinien on działać na każdym środowisku.

Konfiguracja na środowisku develoeprskim

Jak już zostało nadmienione w dużym (dobrze zorganizowanym) projekcie do zapewnienie automatycznej publikacji nowych wersji będzie stosowane stosowne oprogramowanie dodatkowe. Jeżeli jednak nie posiadamy takich narzędzi, ewentualnie jeżeli chcielibyśmy zapewnić również automatyczny “deployment” na środowisku developerskim możemy skorzystać bezpośrednio z funkcjonalności GIT-a. Scenariusz jaki chcielibyśmy uzyskać to automatyczny “deploy” w chwili wprowadzenia nowej zmiany w projekcie (commit) na środowisku developera. Powinno to pozwolić wykryć ewentualne problemy jeszcze przed przeniesieniem zmian na środowisko testowe czy jakościowe. Na pewno też powinno zoptymalizować oraz ułatwić pracę programiście.

Metoda jest bardzo prosta i opiera się na wykorzystaniu “funkcjonalności” GIT-a o nazwie “hooks“. Mechanizm ten służy do pisania swego rodzaju “triggerów”, które powinny zostać wykonane po konkretnym zdarzeniu, na przykład, właśnie po wykonaniu polecenia “commit”. Jest to domyślna funkcjonalność gita, a wszystkie “hook’i” można znaleźć w folderze “.git” w repozytorium. W celu wykonania skryptu wystarczy odwołać się do PowerShell’a oraz podać lokalizację pliku. Przykładowo:

#!/bin/sh
c:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -ExecutionPolicy RemoteSigned -Command '.git\hooks\SSAS_CI.ps1'

Po dodaniu takiego wpisu w “project_path\.git\hooks\pre-commit” za każdym razem po wykonaniu polecania “commit” zostanie uruchomiony podany skrypt PowerShell.

Jak widać zaraz po wykonaniu “commit” skrypt do Continuus Integration został uruchomiony i nowa wersja zostanie opublikowana na wskazane środowisko developerskie.

Podsumowanie

Automatyzacja wdrażania nowej wersji w przypadku SSAS jak widać jest możliwa i ogranicza się tak naprawdę do kilku mniejszych lub jednego większego skryptu PowerShella (na przykład). Sam proces jest zatem stosunkowy prosty, natomiast największą trudnością może okazać się odpowiednie przygotowanie scenariusza procesowania oraz testowania. W przypadku “niższych” środowisk na pewno polecam redukcję partycji, natomiast w przypadku środowiska produkcyjnego powinniśmy trochę więcej czasu poświęcić samej strategii procesowania. Dobrym pomysłem wydaje się, po przeprocesowaniu wymiarów, skorzystanie z trybu “Process Structure”, który sprawi, że po przeprocesowaniu tylko jednej partycji cała kostka będzie dostępna. Ustalając odpowiedni harmonogram oraz ustalając priorytet grup miar jesteśmy w stanie ograniczyć przestój działania kostki dla najważniejszych informacji do kilkudziesięciu minut lub kilku godzin w przypadku naprawdę dużych kostek. Warto również pamiętać o testach, które za pomocą zwykłych zapytań MDX i prostych pakietów SSIS pozwolą nam utrzymać jakość i szybkość odpowiedzi kostek analitycznych.

Slawomir Drzymala
Follow me on

Leave a Reply