Time series/tijdreeksen deel 4: archivering

Het laatste onderdeel dat ik in deze serie wil behandelen is de archivering van tijdreeksen. Eigenlijk gebruik ik hier een stoffige term: iedereen spreekt tegenwoordig van data lakes.

Zo’n data lake is meestal geen panklare one-size-fits-all oplossing. Er dient op een heel ander niveau nagedacht te worden over het ontwerp dan we gewend zijn. Bij een ‘normale’ relationele database zijn we gewend aan het aanleggen van indexen en normaliseren van data. Hier moeten we ineens gaan nadenken over opslagstructuur en bestandsformaten.

Waarom is dat, vraag je je af? Om schaalbaar en performant om te kunnen gaan met grote hoeveelheden data zijn een aantal grote concessies noodzakelijk. Ten eerste: doorzoekbaarheid. Je zult moeten nadenken hoe je je dataset wilt ontsluiten: wat zijn logische blokken om je data per verzoek op te vragen? Wat is de belangrijkste sleutel waarmee ik deze blokken wil kunnen addresseren? Bij welke blokgrootte kan ik het aantal transacties op mijn storage minimaliseren? Dit gieten we in een hierarchisch schema. Eigenlijk precies zoals we dat al jaren (zo niet decennia) doen in filegebaseerde opslagsystemen.
Voor tijdreeksen is de opdeling in de boom vrij logisch: ergens in de vertakkingen zullen blokken per tijdseenheid ontstaan. Denk hierbij aan jaar/maand/dag/uur/…, waarbij je al naargelang je granulariteit van blokken kunt doorgaan op steeds kleinere periodes.
Er komt nogal wat denkwerk kijken bij het ontwerp van dit soort structuren. Ik ga er dan ook niet veel verder op in – voor je het weet ben ik een long-read aan het schrijven (en dat bewaar ik voor later).

Hiernaast is het belangrijk dat je in de ‘bladeren’ van de hierarchie een juiste keuze maakt voor je bestandsformaat. Het klinkt misschien gek, maar hier is echt een wereld aan verschil te halen. Dit is het beste te illustreren aan de hand van een concreet voorbeeld.

Als we alle uurdata van alle weerstations (met alle metingen) downloaden via http://projects.knmi.nl/klimatologie/uurgegevens/selectie.cgi , vanaf 1950 tot nu, trekken we een tijdreeks binnen van 2,7 GB ruwe ASCII data. Weinig spannend qua omvang, ik weet het, maar groot genoeg om e.e.a. te illustreren hier.

Zonder nu verder een ontwerp te maken voor een hierarchie om deze files in te plaatsen, heb ik de gehele dataset in één keer ingelezen op mijn machine, en weggeschreven in verschillende formaten (serialization). Vervolgens heb ik de gehele dataset ook weer vanuit dat formaat in geheugen gelezen (de-serialization). Hieronder de resultaten, voor volume (transparant blauw, kleiner is beter), serialization (oranje, kleiner is beter) en deserialization (groen, kleiner is beter) voor een aantal populaire formaten.

Het moge duidelijk zijn dat hier gigantische verschillen tussen zitten. De ‘human readable’ varianten JSON en CSV zijn groot en relatief traag om in te lezen/weg te schrijven. Voordeel is wel dat deze formaten zonder nabewerking in heel veel systemen direct bruikbaar zijn. CSV komt daarbij voor deze dataset qua formaat nog wel redelijk mee, moet gezegd worden.

De binaire formaten, MsgPack, Protobuf en Parquet laten echter heel andere cijfers zien. Deze zijn over de hele linie bijzonder snel, de een wat sneller in lezen en de ander wat sneller in schrijven. De volumes zijn ook stukken kleiner door de veel beter passende vertaling naar bytes op disk (tekstgebaseerde formaten zijn, vooral voor grote hoeveelheden floating point/decimale getallen, erg inefficiënt).

Voor wat betreft lezen zijn alle binaire formaten ongeveer gelijk; zo’n 30 seconden (op mijn machine) om de hele dataset in geheugen te laden. Da’s best rap aangezien het toch 17 miljoen records zijn, maar ik heb nog een tweede leeskolom toegevoegd, de gele balken getiteld ‘Single column’. Dit is een wat specifieke use case voor dit soort data, waarbij ik uitga van het inlezen van slechts één waardekolom van de data (plus de twee tijdkolommen, datum en uur op de dag natuurlijk). Het inlezen van slechts deze kolommen is voor alle formaten gelijk aan het inlezen van de hele dataset; immers ik moet alle volledige records eerst in geheugen laden voordat ik er één kolom uit kan selecteren (hetgeen in milliseconden gebeurt in-memory).
Er is echter één uitzondering: Parquet. Dit is een ‘colum-oriented’ formaat. Ik laat het aan de Googlende lezer om daar meer over uit te vinden, maar dit formaat slaat data per kolom op, zodat enkele kolommen heel snel uit de file te lezen zijn zonder de hele file in geheugen te laden. Dus alleen temperaturen lezen uit onze KNMI dataset kan in dit geval in 3 seconden (!) in plaats van 30 seconden.

Conclusie: archivering van grote hoeveelheden tijdreeksen is geen standaardoplossing voor. Ontwerp je data lake op basis van zorgvuldig verzamelde en afgewogen requirements op gebied lezen, schrijven en adressering. Daarnaast kunnen de uiteindelijke bestanden op disk of in de cloud een (zeer) groot verschil maken!

Leave a Reply

Your email address will not be published. Required fields are marked *

Copyright ICRIS BV