X
    Categories: blog

La nuova pipeline editoriale del New York Times

Il New York Times (NYT) nel corso del 2017 ha rivisto il proprio sistema software di raccolta ed organizzazione dei contenuti editoriali (la pipeline) sfruttando Apache Kafka, uno degli strumenti più utilizzati di streaming distribuito.
Il caso d’uso è stato presentato al “Kafka Summit 2017” [2] svoltosi a New York dove ha ricevuto apprezzamenti e suscitato qualche perplessità. I motivi saranno indicati più avanti dopo aver visto le caratteristiche salienti della nuova architettura.

Le premesse

La ristrutturazione è partita da alcuni dati di fatto.

  • A. Il NYT dispone di un enorme archivio di contenuti che sono il frutto della produzione di 160 anni di giornalismo. Una parte di tali contenuti sono online su un insieme di CMS (Content Management System). Altri provengono da fonti dati di terze parti ed altri ancora sono disponibili in archivi digitalizzati non ancora online.
  • B. Tutti questi documenti rappresentano una fonte di informazioni che si vuole rendere disponibile per una serie di nuovi servizi ed applicazioni. Ad esempio motori di ricerca avanzati, servizi di personalizzazioni per gli utenti, generatori di feed, applicazioni mobile etc.. Quindi ogni volta che un contenuto viene pubblicato dovrebbe essere reso disponibile con una latenza molto bassa a tutti i vari servizi ed applicazioni.
  • C. L’architettura del sistema esistente era basata su API. I producers fornivano API per l’accesso ai contenuti e feed a cui iscriversi per la notifica della pubblicazione di nuovi contenuti. I consumers, rilevata la disponibilità di nuovi contenuti, invocavano le API del consumer per accedere al contenuto.

    Architettura di pubblicazione basata su API

Questa architettura presentava un limite dovuto al forte “accoppiamento” tra producers e consumers. Ogni consumer doveva conoscere esattamente le API dei producer per poter accedere ai relativi contenuti. A volte, inoltre, era necessario anche “normalizzare” le informazioni lette poiché, ad esempio, CMS diversi possono rappresentare la stessa informazione in modo diverso. Un’ulteriore problematica era dovuta al fatto che ogni singola lettura di un contenuto richiedeva una chiamata alle API. Dunque per leggere una lunga serie di contenuti si generavano facilmente picchi di carico.

La nuova publishing pipeline

La nuova publishing pipeline è basata su un’architettura a Log (nel caso specifico Apache Kafka). L’uso di un Log come struttura dati generica sostitutiva di un database tradizionale è un’idea che è stata descritta nel testo “Designing Data-Intensive Applications” di Martin Kleppmann nel 2017 [1]. Immaginiamo, per ora in maniera molto astratta, che tutti i contenuti prodotti vengano accodati ad un topic “immutabile” di Kafka in ordine cronologico. Altri servizi accedono consumando il topic. Ai consumatori non serve conoscere chi e come produce i contenuti. Ai produttori non serve sapere quanti e quali consumatori sono destinati a riceverli. Il topic nella nuova pipeline non rappresenta solo il canale di comunicazione ma anche l’archivio persistente e immutabile di tutti i contenuti prodotti (source of truth).

Architettura di pubblicazione basata su log

Perché un log e non un database?

Con il Log come “source of truth”, non è più necessario che tutti i sistemi attingano da un singolo database le informazioni di cui necessitano. Ogni sistema consumatore può creare il proprio database (una sorta di propria vista materializzata) che include solo i dati di cui ha bisogno e nella forma che gli è più utile.
Un’architettura basata su log, inoltre, semplifica l’accesso ai “flussi” di contenuti. In un’architettura basata su database tradizionali l’accesso allo “storico” (ad es. uno snapshot) e l’accesso ai dati “live” (ad esempio un feed dei contenuti più recenti) sono modalità di funzionamento distinte. Questa distinzione scompare in un sistema basato su Log: lo si inizia a consumare a partire da un certo offset in avanti. Ad un certo punto si raggiungerà il traffico “live” ma questo sarà trasparente per il consumatore.
Infine, il database di un sistema consumer può essere ricreato da zero a partire dal Log. Ciò può essere comodo se si ritiene preferibile ricreare e popolare il database piuttosto che riportare delle modifiche non banali allo schema.

Il Monolog

Il Monolog (così è stato chiamato il topic immutabile), dunque, è la “source of truth” e contiene tutti i contenuti pubblicati dal 1851 ordinati in base alla data di pubblicazione. Un consumatore può scegliere da quale data vuole iniziare a consumarli per crearsi una propria vista personalizzata del Monolog. Ad esempio un sistema che fornisce elenchi di tutti i contenuti sportivi pubblicati inizia a consumare il Monolog dall’inizio (cioè dal 1851) e genera la sua versione persistente di questi dati. Un altro servizio, invece, potrebbe fornire in tempo reale solo un elenco delle ultime risorse pubblicate. Questo servizio non ha bisogno di un proprio archivio persistente. Al suo avvio comincia a consumare il Monolog indietro di qualche ora mantenendo, poi, in memoria l’elenco degli ultimi n contenuti pubblicati.
Gli elementi che concorrono a costruire un contenuto completo (titolo, sottotitolo, immagine, testo etc.) sono pubblicate sul Monolog in forma normalizzata. Ciò significa che ogni “pezzo” indipendente viene scritto su Kafka come singolo messaggio. Ad esempio, un’immagine è indipendente da un articolo, poiché essa potrebbe essere inserita in diversi articoli.

Componenti dei contenuti normalizzati

In figura sono rappresentati due articoli che hanno riferimenti a diversi elementi. Ad esempio l’elemento “sottotitolo” viene pubblicato singolarmente e poi referenziato da entrambi gli articoli. Tutti i singoli elementi sono identificati da una URI.
Il Monolog è implementato come topic a singola partizione poiché è necessario che quando si sta leggendo il Log si veda sempre un elemento referenziato prima dell’elemento che lo referenzia. Ad esempio la pubblicazione nel Log degli articoli visti nell’immagine precedente è mostrato schematicamente nella figura successiva.

Ordine di pubblicazione dei contenuti

Il log denormalizzato

Per alcuni sistemi consumatori la visualizzazione normalizzata dei dati non è adatta. Ad esempio per indicizzare i dati in ElasticSearch è necessaria una vista denormalizzata. Ciò implica che per poter creare articoli abbinando diversi elementi (titoli, immagini, didascalie etc.) questi dovranno essere rappresentati tutti insieme all’interno dell’oggetto dell’articolo. In pratica quando viene pubblicato l’Articolo1 dell’esempio precedente, si pubblica su un ulteriore Log denormalizzato l’articolo e tutte le sue dipendenze in un singolo messaggio.

Log denormalizzato dopo la pubblicazione dell’Articolo1

Il consumatore di Kafka che alimenta Elasticsearch può semplicemente leggere questo messaggio dal Log, riorganizzare gli elementi nella forma desiderata e inserirlo nell’indice. Quando viene pubblicato l’Articolo2, tutte le dipendenze sono impacchettate, comprese quelle già pubblicate per l’Articolo1.

Log denormalizzato dopo la pubblicazione dell’Articolo1

Se un elemento viene aggiornato, l’intero pacchetto viene ripubblicato. Ad esempio, se l’Immagine2 viene aggiornata, tutto l’Articolo1 viene riscritto sul Log.

Log denormalizzato dopo l’aggiornamento dell’Immagine2

Il Log denormalizzato viene alimentato da un componente chiamato Denormalizer. Si tratta di un’applicazione Java che utilizza le Streams API di Kafka. Consuma il Monolog e mantiene un archivio locale dell’ultima versione di ogni elemento insieme ai riferimenti a tale elemento. Quando un elemento di alto livello viene pubblicato, il Denormalizer ne raccoglie tutte le dipendenze dall’archivio locale e lo scrive come un bundle nel registro denormalizzato. Se viene pubblicato un elemento referenziato da uno di livello più alto, il Denormalizer ripubblica tutte gli elementi di alto livello che lo referenziano.
Poiché questo log è denormalizzato, non richiede più un ordinamento come quello richiesto dal Monolog. Bisogna solo assicurarsi che le diverse versioni dello stesso elemento di più alto livello siano nell’ordine corretto. Ciò consente di utilizzare un log partizionato di Kafka e quindi di avere più sistemi consumer che leggono il Log denormalizzato in parallelo. Utilizzando gli streams di Kafka e sfruttando la possibilità di scalare il numero di istanze dell’applicazione che consuma il Log denormalizzato, è possibile eseguire una rilettura molto veloce dell’intera cronologia delle pubblicazioni. Ad esempio, nella figura seguente è mostrato il funzionamento del servizio di back-end utilizzato per implementare le ricerche sul sito NYTimes.com utilizzando Elasticsearch.

Pipeline per il servizio di ricerca

Un elemento pubblicato o aggiornato dal CMS viene scritto sul Monolog. Il Denormalizer legge dal Monolog. Se si tratta di una risorsa di alto livello, raccoglie tutte le sue dipendenze dal relativo archivio locale e le scrive in bundle nel Log denormalizzato. Se questo elemento è una dipendenza, tutte le risorse di livello più alte vengono scritte nel registro denormalizzato. Il partizionatore di Kafka assegna gli elementi alle partizioni basate sull’URI dell’elemento di basso livello. I nodi di ingestion eseguono tutti un’applicazione che utilizza gli Streams di Kafka per accedere al registro denormalizzato. Ogni nodo legge una partizione, crea gli oggetti JSON che bisogna indicizzare in Elasticsearch e li scrive su specifici nodi Elasticsearch.

Apprezzamenti e perplessità

La critica principale si basa sulla considerazione che Kafka è particolarmente adatto a gestire grossi flussi di dati real time (si pensi ad un sistema di log di un social network come Twitter o al flusso di dati in un grosso sistema IoT (Internet Of Things) con migliaia di sensori che rilevano dati con frequenza elevatissima). L’uso di Kafka, dunque, è stato ritenuto da qualcuno eccessivo e non adeguato in un sistema editoriale dove i flussi di dati da raccogliere non sono così numerosi e concorrenti. Qualche addetto ai lavori, inoltre, ha fatto notare che l’uso di database (eventualmente NoSql) avrebbe assolto al compito di collettore di tutti i contenuti editoriali meglio di un sistema di Log.
Queste osservazioni sono condivisibili ma, di fatto, la nuova pipeline editoriale di una organizzazione importante come il NYT è basata su Log e l’architettura progettata offre spunti interessanti per diversi motivi. L’uso di un sistema di streaming di dati robusto garantisce la consegna di ogni singolo contenuto inserito sul topic anche in presenza di picchi di traffico o di momentanee situazioni di offline di uno o più consumatori. Con l’uso del Log i dati storici e quelli real-time (ad esempio un feed degli ultimi n contenuti pubblicati) vengono trattati in maniera trasparente ai consumatori che leggono sempre e solo dati da una coda. Questo alto disaccoppiamento offerto dal modello Publish & Subscribe dovrebbe favorire, già nel futuro immediato, la semplificazione del modello di sviluppo di produttori e consumatori di contenuti. Ogni sistema consumer legge contenuti storici o live in maniera trasparente e si costruisce la propria rappresentazione dai dati nel modo a lui più congeniale. Supponiamo, inoltre, che in futuro qualche nuovo tipo di produttore di contenuti richieda di rappresentare gli articoli in maniera totalmente diversa e che il Log, così come è ora, diventi obsoleto e inutilizzabile. Si potrà consumare il log dall’inizio e riscriverne uno nuovo inserendo gli articoli in una modalità diversa. Si adatterà il Denormalizer in modo da leggere dal nuovo Monolog e lasciare inalterata la struttura denormalizzata. Il vecchio Monolog potrà essere eliminato e l’ecosistema dei consumer continuerà a funzionare.

Riferimenti


[1] Designing Data-Intensive Applications, Martin Kleppmann, O’Reilly Media, March 2017


[2] The Source of Truth: Why The New York Times Stores Every Piece of Content Ever Published in Kafka®


[3] Apache Kafka® Delivers a Single Source of Truth for The New York Times


[4] Publishing with Apache Kafka at The New York Times

Mauro Coletta: Dopo la laurea in Ingegneria Informatica ho avuto esperienze nella ricerca accademica in ambito IoT e poi in aziende private come progettista e sviluppatore di applicazioni web. Dal 2013 lavoro in Maggioli e mi occupo principalmente di progettazione e di gestione di progetti ICT caratterizzati da una forte spinta innovativa. Attualmente svolgo il mio lavoro nel gruppo di R&D impegnato su progetti di datawarehousing, analytics, architetture a microservizi e analisi semantica dei testi. Sono appassionato di musica e di viaggi e mi diverto a suonare la chitarra.
Related Post