Reading Time: 12 minutes
Nel mondo dello sviluppo, git è un elemento imprescindibile che ci permette di gestire il codice in maniera sicura e collaborativa, permettendo di apportare modifiche allo stesso senza causare problemi ad ambienti di produzione. Il codice principale è presente nel branch main. Possiamo creare poi dei branch diversi, a partire dal main, sui quali apportare delle modifiche, che verranno salvate sugli stessi attraverso dei commit.

Solitamente il branch main è quello collegato ai deployment che avvengono sugli ambienti di produzione. Una volta che le modifiche sono pronte per andare in produzione, a seguito di una fase di review e di una fase di test, sarà possibile effettuare una merge request del nuovo branch verso il main. A questo punto le modifiche saranno presenti nel branch main, dal quale possiamo effettuare un nuovo deployment che conterrà il codice aggiornato sull’ambiente target.

Sarebbe interessante portare gli stessi concetti anche in ambito Data: prelevare i dati con la pipeline dalle sorgenti, inserirli in un branch dedicato e a seguito dell’esecuzione positiva di alcuni test effettuare il merge dei dati aggiornati sul branch principale così da poter essere acceduti in produzione. E se dovessimo accorgerci di alcune incongruenze quando ormai è stato fatto il merge? Ci comportiamo come nello sviluppo: con un rollback al commit precedente, o comunque all’ultimo momento in cui i dati erano corretti. Proviamo dunque a vedere come raggiungere questo scopo sfruttando soluzioni open source come Dremio, MinIO e Apache Iceberg.

 

Dremio, MinIO e Apache Iceberg

Come primo tassello occorrerà il sistema su cui salvare fisicamente i dati. In questo caso utilizziamo MinIO, un object storage che espone le API di S3 e altamente performante. Per ottenere le funzionalità git-like occorrerà effettuare transazioni ACID sull’object storage, che di sua natura non offre, e questo viene fornito grazie ad un table format che attraverso la gestione dei metadati consente di garantire questa proprietà. In questo caso il table format scelto è Apache Iceberg, anch’esso open source. Un table format, tuttavia, mantiene le informazioni sulla singola tabella. Per avere una vera esperienza git-like vorremmo poter gestire più tabelle contemporaneamente. Questo viene garantito grazie ad un table repository, che riesce ad avere contezza di tutte le tabelle da gestire e che mantiene il puntatore all’ultima versione dei metadati su cui debbano avvenire scritture o letture. Ecco che corre in nostro soccorso Project Nessie, un table repository open source che offre un’esperienza git-like sui dati. Infine, occorre uno strumento con il quale poter accedere i dati presenti nel data lakehouse e dal quale poter creare delle viste utili per gli scopi finali, come ad esempio Dremio.

Object Storage

L’object storage è il sistema che permette di salvare grandi quantità di dati non strutturati al suo interno. Corrisponde in questo caso al data lake, ed i dati vi sono salvati sottoforma di oggetti. MinIO è un object storage opensource che espone le API di S3, quindi questo lo rende molto appetibile visto l’ampio utilizzo di S3 nel mondo data e cloud. Il data lake offre la possibilità di immagazzinare grandi quantità di dati non strutturati, a differenza del data warehouse nel quale i dati salvati hanno una struttura ben definita. Tuttavia la caratteristica di salvare dati non strutturati può rivelarsi anche uno svantaggio in quanto le performance di ricerca sono inferiori. Inoltre, l’object storage non supporta le transazioni ACID che invece sono presenti nei database relazionali tradizionali e che permettono di effettuare delle validazioni sullo schema e sulla consistenza in fase di ingestion dei dati anziché in fase di lettura. Oltre a questo, permettono di garantire la consistenza dei dati quando vi sono più scrittori/lettori in contemporanea su una stessa tabella.

Table format

Per ovviare all’assenza delle transazioni ACID, e per far diventare il data lake, un data lakehouse, è fondamentale il table format. Esso crea un livello di astrazione sopra l’object storage che migliora le performance ed abilita le transazioni ACID direttamente sul data lake.

Il table format altro non è che un metodo per strutturare ed unificare i dati presenti nell’object storage come un’unica tabella. Risponde alla domanda “quali dati ci sono nella tabella?” e lo fa grazie a come vengono organizzati e gestiti i metadati. Questo permette agli utenti ed ai tool che interagiscono con la tabella, di farlo in maniera più efficiente.

Apache Iceberg è un open table format e tra le caratteristiche principali sono presenti:

 

  • Transazioni ACID: attraverso la sua architettura, Apache Iceberg abilita le transazioni ACID abilitando la concorrenza ottimistica in situazioni in cui più scrittori o lettori vogliono accedere ad una determinata risorsa.
  • Dinamicità nelle partizioni: nel Data Lake, senza Iceberg, se volevamo cambiare il partizionamento di una tabella, dovevamo riscriverla interamente. Con Iceberg, invece, il partizionamento diventa dinamico e può cambiare in ogni momento.
  • Hidden partitioning: Iceberg produce i valori per le partizioni e le utilizza nelle query in maniera trasparente a chi scrive o legge.
  • Ottimizzazini sulle righe: è possibile ottimizzare gli aggiornamenti a livello di riga di una tabella attraverso o la copy-on-write oppure la merge-on-read.
  • Time travel: grazie a degli snapshot immutabili sui dati, è possibile effettuare delle query su degli snapshot indietro nel tempo evitando la duplicazione dei dati.
  • Version rollback: grazie alla precedente proprietà, è possibile anche effettuare un vero e proprio rollback su versioni precedenti dei dati.
  • Evoluzione dello schema: Iceberg supporta anche i cambiamenti che possono avvenire sullo schema di una tabella.

Table repository

Il catalogo di Iceberg mantiene il puntatore all’ultimo metadata file da poter utilizzare per le scritture e per le letture. Esso è relativo alla sola tabella di indagine. Il table repository è il luogo in cui vengono salvati i cataloghi Iceberg per tutte le tabelle. In questo modo, gli utenti o gli strumenti che interagiscono con le tabelle presenti nel catalogo sanno di leggere o scrivere la versione corretta.

Project Nessie è uno dei table repository supportati da Iceberg. Sicuramente la feature più importante garantita da Nessie è proprio quella per cui scriviamo questo articolo, ossia che abilita l’esperienza git-like. Questo è possibile perchè ogni cambiamento al contenuto del Data Lake, in Nessie viene visto come un commit senza la necessità di copiare i dati veri e propri.

Alcuni dei concetti fondamentali in Project Nessie sono:

  • Commit: cambiamento atomico che avviene al set dei dati ed è il cambiamento minimo possibile in Nessie. In altre parole, rappresenta uno snapshot dei dati ad un certo punto nel tempo. Nel caso in cui dovessimo effettuare dei rollback basterà puntare ad un commit precedente.
  • Branches: un branch referenzia l’ultimo commit presente in una catena di commit. Branch diversi possono puntare ad uno stesso commit o a commit diversi, ed è in questo modo che Nessie gestisce la concorrenza di job verso uno stesso data set.
  • Merge: è possibile inserire i cambiamenti apportati su un branch verso un altro branch target.

 

Data access

Dremio è una piattaforma che abilita le analitiche self-service su un Data Lakehouse. Offre un’ampia gamma di sorgenti dalle quali attingere dati e grazie alle ottimizzazioni utilizzate (Apache Arrow e Reflections tra le altre) consente di accedere velocemente ai dati presenti nel data lake. Tra le varie sorgenti presenti vi è anche proprio Project Nessie ed inoltre supporta la scrittura in Apache Iceberg.

Project Nessie sarà il catalogo per le tabelle Apache Iceberg, che ovviamente dovranno essere scritte in un data lake (MinIO nel nostro caso). Utilizzando Dremio, è possibile promuovere una sorgente Project Nessie con la quale effettuare operazioni git-like direttamente da interfaccia o tramite query SQL-like. [Partecipa al webinar dedicato a Dremio cliccando qui].

Nell’immagine precedente è possibile notare le diverse componenti in gioco e come interagiscono tra loro. Specifichiamo che il table format descritto è semplificato visto che dal metadata pointer passiamo direttamente al data layer, questo perchè l’intento dell’immagine è quella di far vedere che diverse tabelle sono gestite dal table repository e che il table format alla fine gestisce dei file raw che vengono salvati sull’object storage.

 

Installazione delle componenti

L’installazione delle componenti utilizzate per questo tutorial è molto semplice. Ad esempio, facciamo riferimento ad una installazione on-prem utilizzando i pacchetti precompilati e un servizio standalone.

Per l’installazione di MinIO per un ambiente di test, è possibile effettuarne una Single Node Single Drive, seguendo gli step definiti nella guida ufficiale.

Per quanto riguarda l’installazione di Project Nessie è sufficiente scaricare il servizio standalone ed eseguirlo, come specificato nella documentazione ufficiale.

Anche l’installazione di Dremio è molto semplice, se utilizziamo una macchina che supporta l’installazione di pacchetti rpm basta seguire questa documentazione, altrimenti è possibile utilizzare i Tarball.

Promuovere una sorgente Project Nessie

Da interfaccia Dremio, cliccando su Sources è possibile scegliere la sorgente da utilizzare, in questo caso useremo il Nessie:

Tra le varie configurazioni, dovremmo settare un nome e l’endpoint sul quale è in ascolto, oltre un eventuale metodo di autenticazione:
Dovremmo specificare poi lo storage da utilizzare, come anticipato utilizzeremo MinIO per cui dovremmo specificare il bucket, l’access key e la secret key:
Allo stesso modo di come avviene se dovessimo promuovere come sorgente MinIO, dobbiamo specificare anche l’endpoint ed altre proprietà:

Creazione di una tabella Iceberg

Vi è la possibilità di creare una tabella Iceberg specificando il catalogo creato, il cui nome in questo esempio sarà nessie:

create table nessie.user_table (id integer, name varchar, surname varchar, address varchar)

A questo punto verrà creata una tabella vuota all’interno di Project Nessie ed è possibile visualizzare lo schema della stessa:

A questo punto, dal query engine di Dremio, potremmo inserire dei record all’interno della tabella:

INSERT INTO nessie.user_table VALUES (1, 'Ned', 'Stark', 'The North');
INSERT INTO nessie.user_table VALUES (2, 'Robert', 'Baratheon', 'The Stormlands');
INSERT INTO nessie.user_table VALUES (3, 'Jamie', 'Lannister', 'The Westerlands');
INSERT INTO nessie.user_table VALUES (4, 'Daenerys', 'Targaryen', 'The Crownlands');

Possiamo quindi visualizzarli su Dremio:

 

A questo punto, proviamo a creare un nuovo branch con una query SQL-like sempre da Dremio:

CREATE BRANCH "scrittura_user_table" IN nessie;

E da UI possiamo vedere che effettivamente il branch è stato creato correttamente:

Adesso proviamo a scrivere dei nuovi record sul nuovo branch. Il branch può essere indicato in fase di inserimento delle nuove righe:

INSERT INTO nessie.user_table AT BRANCH "scrittura_user_table" VALUES (5, 'Tony', 
'Soprano', 'New Jersey');
INSERT INTO nessie.user_table AT BRANCH "scrittura_user_table" VALUES(6, 'Don', 
'Draper', 'New York');

A questo punto possiamo notare la differenza tra i due branch:

Possiamo dunque effettuare una merge del branch creato verso il main:

MERGE BRANCH "scrittura_user_table" INTO "main" IN nessie;

E a questo punto effettuando una query sul main branch vedremo che i risultati sono presenti:

Il branch sorgente non è stato eliminato, ed è possibile avere una lista di tutti i commit effettuati:
A questo punto, ci siamo resi conto che le nuove righe aggiunte non sono consistenti. Possiamo fare dunque rollback alla versione precedente (specificando un commit oppure un istante nel tempo). In questo caso decido di tornare al commit precedente, prendendo l’identificativo direttamente da interfaccia:

ALTER BRANCH "main" ASSIGN COMMIT 
"dea98a368953af01c9bc0b42398851e1851966bc791926debae2ff883eeb671f" IN nessie;

Ed effettuando una query è possibile notare che siamo tornati alla situazione di partenza:

Per evitare ulteriori danni, è possibile anche eliminare il branch nuovo che avevamo creato:

DROP BRANCH "scrittura_user_table" FORCE IN nessie;

Effettivamente anche da UI non è più presente:

Creazione tabella Iceberg a partire da una VDS

Vi sono molti modi per creare una tabella Iceberg, partendo da un CSV o da un JSON, oppure anche a partire da una VDS. Ad esempio, per creare una tabella Iceberg nel catalogo Nessie a partire da una VDS, nella query dovremmo specificare il catalogo, il nome della tabella e il nome della VDS da cui essa ha origine:

create table nessie.”pd_incidents” as select * from “@admin”.”police_incidents”;

A questo punto è disponibile nel catalogo Nessie e può subire le medesime operazioni viste in precedenza:

In questo articolo abbiamo visto come l’adozione della metodologia git in ambito data management possa semplificare la gestione dei dati. Il tutto con un ecosistema composto da sole soluzioni open source che permettono all’utente di accedere facilmente ai dati oltre che di avere completa ownership sugli stessi, senza alcun lock-in. Per saperne di più, partecipa al nostro webinar.