Inner workings QlikView QlikSense

In dit blog nemen we letterlijk een kijkje onder de motor(kap) van QlikView en Qlik Sense. Een technisch blog over de inner workings, want het kan (nog) sneller en efficiënter. Leesvoer voor developers die van plan zijn hun data na de zomer (anders) aan te pakken. Reacties op dit artikel worden op prijs gesteld.

Engines

Binnen QlikView zijn er zeven aparte engines te onderscheiden:

  1. De script engine, die het verwerken van het script mogelijk maakt
  2. De autorisatie engine, die bepaalt welke user waar bij mag
  3. De logical inference engine. Deze bepaalt de uitkomsten van de selecties en welke velden er dan nog mogelijk zijn. Dit is dus eigenlijk de selectie-engine.
  4. De calculation engine, die alle aggregatie doet
  5. De rendering engine, die tekent de resultaten van de calculaties in de vorm van een grafiek of een tabel
  6. De export engine, die het omzetten naar pdf van bijvoorbeeld een tabel mogelijk maakt.
  7. En als allerbelangrijkste: De QIX engine, of de interne database

De laatste is (bijna) hetzelfde tussen QlikView en Qlik Sense. Van alle andere engines zijn alleen op dit moment nummer 5 niet hetzelfde, want in Sense wordt alles aan de cliëntkant gerenderd, en nummer 6, nu de export mogelijkheden in Sense nog beperkt zijn, in verband met de mogelijke toevoeging van NPrinting-opties in latere versies.

In het navolgende geldt dat wat er geschreven wordt voor zowel QlikView als Qlik Sense geldt.

Inlaadprocedure

Stel dat de basisdata de volgende is:

PRODUCTNAAM PRODUCTPRIJS CATEGORIE
Fietsventieldopje 1 Fiets
Fietshelm 20 Kleding
Fietsbroek 30 Kleding
Voorband 20 Fiets
Achterband 30 Fiets

Bij het inladen van een tabel gebeurt het volgende:

  1. De unieke waardes van elk veld worden omgezet in een bit-stuffed pointer met de clear-text betekenis er bij, dit zijn de symbol-tabellen. Voor elke waarde wordt er dus een unieke bit-string opgebouwd. Als er 2 distinct waardes zijn, is de lengte van deze bit-stuffed pointer dus maar 1 bit lang: de keuzes zijn 1 of 0. Als 7 waardes zijn, is deze 3 bits lang (000,001,010,011,100,101,110 en 111 wordt dan niet gebruikt).
  2. Voor rij in de basisdata wordt de data omgezet in deze bit-stuffed pointers. Dit zijn data-tabellen

Dit betekent dat er per kolom dus een symbol-tabel ontstaat:

PRODUCTNAAM VALUE   

 

 

PRODUCTPRIJS VALUE   

 

 

CATEGORIE VALUE
000 Fietsventieldopje 00 1 0 Fiets
001 Fietshelm 01 20 1 Kleding
010 Fietsbroek 10 30
011 Voorband
100 Achterband

En de volgende data-tabel

PRODUCTNAAM PRODUCTPRIJS CATEGORIE
000 00 0
001 01 1
010 10 1
011 01 0
100 10 0

 

De data wordt hiermee dus automatisch ook gecomprimeerd, de unieke waardes worden alleen nog clear-text opgeslagen en alle verwijzingen daarna vinden plaats met de pointers. Dit heeft als implicatie dat hoe minder unieke waarden er zijn in je datamodel, hoe sneller en efficiënter deze is! Er zijn dan immers langere pointers en meer waarden in de symbol-tabel waar over gerekend moet worden.

Er is in de symbool-tabellen nog een extra smaakje met de autonumber-functie. In dat geval wordt er zelfs maar één kolom gebruikt in de symbol-tabel, wat je applicatie nóg sneller doet functioneren.

Selecties

Dan hoe de selecties in QlikView en Qlik Sense werken. In de logical inference engine worden twee dingen bepaald:

  1. Is een waarde geselecteerd
  2. Is een waarde mogelijk

Deze waarden worden onafhankelijk van elkaar bepaald. Immers: ik kan maart, april en mei geselecteerd hebben, maar daarna een selectie doen op Q1. Ik heb dan nog steeds 3 maanden geselecteerd, maar enkel maart is dan nog mogelijk.

In de symbol-table wordt bepaald of iets geselecteerd en/of mogelijk is. Dit gebeurt elke keer wanneer er iets geselecteerd wordt. De uitkomsten worden in een zgn. state vector opgeslagen. Dit komt er op neer dat voor elke combinatie van velden die mogelijk zijn, bepaald wordt of de waarde nog ‘actief’ is met de huidige selectie.

Stel dat we in het voorbeeld selecteren dat categorie KLEDING moet zijn en prijs 1 of 20:

PRODUCTPRIJS VALUE State Sel
00 1 0 1
01 20 1 1
10 30 0 0

 

Immers: 1 en 20 zijn wel geselecteerd, maar omdat Kleding al geselecteerd is, kan productprijs 1 niet meer. Wanneer in meerdere aparte tabellen of in meerdere velden in je sterschema een selectie gedaan wordt, bepaalt de engine zelf de meest optimale route om de state vector te bepalen.

In de productnaam-tabel is geen selectie gedaan, maar door de selectie in de andere velden is enkel de fietsbroek nog mogelijk.

PRODUCTNAAM VALUE State
000 Fietsventieldopje 0
001 Fietshelm 0
010 Fietsbroek 1
011 Voorband 0
100 Achterband 0

Schermafbeelding 2015-07-09 om 15.54.42

De data-tabel ziet er derhalve als volgt uit:

PRODUCTNAAM PRODUCTPRIJS CATEGORIE State
000 00 0 0
001 01 1 0
010 10 1 1
011 01 0 0
100 10 0 0

 

Wanneer in een calculatie gebruik gemaakt wordt van set analyse, wordt dit ook eerst door de logical interference engine gehaald.

Calculation engine

De calculation engine haalt de resultaten van de state vector binnen. Als er in het eerdere voorbeeld bijvoorbeeld een sum(productprijs) wordt uitgevoerd, gebeurt dit niet over de hele set, maar enkel over de rijen waar State = 1.

Dit is tevens de reden waarom set-analyse zoveel sneller is in Qlik dan if-statements. Bij de if-statements dient immers een waarde geëvalueerd te worden, dit kost rekenkracht en RAM, terwijl voor set analyse enkel een nieuwe state vector wordt aangemaakt. De berekening in één tabel vindt plaats door het maken van een hypercube.

Wanneer er data uit meerdere tabellen in de calculatie betrokken zijn, wordt er door de QIX engine een tijdelijke tabel gemaakt waarin dit berekend kan worden. Dit gebeurt vol-automatisch. Een dergelijke join kan echter zeer intensief qua geheugengebruik en CPU gebruik zijn. Dit is de reden waarom in je stermodel zoveel mogelijk genormaliseerd dient te worden, dus zo min mogelijk verschillende tabellen in het stermodel, wanneer performance een issue wordt. Deze oplossing zorgt er voor dat minder hypercubes met elkaar gecombineerd hoeven en de resultaten dus veel sneller verschijnen.

In het bovenstaande voorbeeld is daar natuurlijk geen sprake van. Stel dat er een extra tabel is met orderaantal. Als er een sum(orderaantal) gedaan wordt, is deze zeer snel, dit kan immers door de state vectors in één tabel gedaan worden. Voor sum(orderaantal*productprijs) is dit al anders. Er wordt dan in een tijdelijke tabel een combinatie gemaakt van de tabellen waar dit in voorkomt. Hieruit volgt automatisch dus ook de conclusie dat de performance sterk achteruit gaat indien er waarden gecombineerd worden die door meerdere tabellen gescheiden zijn in een stermodel. QIX zelf zorgt voor de optimale combinatie van deze tabellen en houdt daarbij rekening met 1-op-n relaties.

Set analyse

Een van de grote krachten van QlikView en QlikSense is de mogelijkheid van set analyse.

Sum( {$<PRODUCTPRIJS={“=$(= Max(PRODUCTPRIJS))”}>} ORDERAANTAL*PRODUCTPRIJS)

Deze expressie bepaalt de omzet van het duurste product, of van de duurste producten indien deze dezelfde prijs hebben.

Als we deze uit elkaar halen zien we het volgende:

Sum(ORDERAANTAL*PRODUCTPRIJS) . Deze uitdrukking doet met de standaard selectie-vector een berekening

Sum({…} ). De eerste accolades geven de set aan waarover gerekend moet worden. $ is de standaardset, en maakt dus gebruik van de standaard selectie-vector. Er kunnen ook andere sets zijn, zoals 1 (geen enkele selectie) of bijvoorbeeld een alternate state.

Sum( {$<PRODUCTPRIJS=>}…. Het gedeelte tussen dus hoekhaken geeft een modificatie van de set aan en heet derhalve een set modifier. Wanneer er van een alternatieve set gebruik wordt gemaakt, wordt er dus een nieuwe selectie-vector gemaakt waarin deze selectie toegepast is.

Sum( {$<PRODUCTPRIJS={…}>}…. Tussen de nieuwe accolades staat deze selectie. Dit is de set van waarden in de nieuwe selectie vector. Er verschijnt dus in elke data-tabel een nieuwe kolom, met een nieuwe state en een nieuwe selectie-status.

Sum( {$<PRODUCTPRIJS={“….”}>}…. De zoekwaardes worden aangegeven door dubbele aanhalingstekens. Let op! Enkele aanhalingstekens werken ook, maar dit is een gevolg van een bug van heel lang geleden. Dit zou namelijk niet moeten werken, maar omdat veel applicaties met enkele aanhalingstekens werken is er voor gekozen om dit niet te veranderen.

Sum( {$<PRODUCTPRIJS={“>=30”}>}…. Door middel van een operator wordt aangegeven dat er op nummer gezocht moet worden en niet op string

Sum( {$<PRODUCTPRIJS={“$(…)”}>}…. De dollar expansie geeft aan dat die opdracht als eerste ingevuld moet worden, voordat de evaluatie geparsed wordt naar de calculation engine.

Sum( {$<PRODUCTPRIJS={“$(=…)”}>}…. Met een = teken in de expansion wordt aangegeven dat het gedeelte tussen de haken geen waarde is, maar iets is wat eerst geëvalueerd moet worden.

Sum( {$<PRODUCTPRIJS={“=$(= Max(PRODUCTPRIJS))”}>} ORDERAANTAL*PRODUCTPRIJS)

Er volgt dus automatisch dat de expressie van binnen naar buiten werkt binnen de set

  1. De set wordt bepaald, dit is de standaard set
  2. Dan wordt in de selectie engine gekeken naar de standaard selectie-vector, welke mogelijkheden zijn er nog (state = 1)
  3. Vervolgens wordt op die set de maximale productprijs bepaald.
  4. Deze waarde wordt daarna weer opgezocht in de standaard selectie-vector
  5. De waarden hiervan worden in een aparte selectie-vector gezet
  6. Op deze vector wordt vervolgens een tijdelijk join tussen de twee tabellen gemaakt
  7. De waarden worden vermenigvuldigd met elkaar
  8. En vervolgens dmv de sum opgeteld

Thijs de Bruijn HippoLine

Thijs de Bruijn, QlikView-consultant HippoLine B.V.

0 antwoorden

Plaats een Reactie

Meepraten?
Draag gerust bij!

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *