Vous souhaitez professionnaliser vos compétences ? Jetez un oeil sur les formations big datas proposées par OpenClassrooms : architecture big data
Résumons : les données massives produites par nos applications sont collectées dans le master dataset. Des analyses par lot sont faites sur ces données dans la batch layer et le résultat des ces analyses est stocké dans la serving layer. Alors qu'est-ce qui nous manque alors pour compléter notre architecture ?
Ce qui nous manque, c'est un composant qui va analyser les données les plus récentes qui n'ont pas encore été ajoutée à la batch layer.
Dans le schéma ci-dessous, les jobs d'analyse de données par lots sont représentés en vert. Le premier job va analyser les données du lot #1, le deuxième job va analyser les données des lots #1 et #2, etc. Après l'instant t1, les données du lot #1 sont visibles dans la serving layer. Mais les données du lot #2 ne seront pas visibles avant t2' : cela signifie que la serving layer ne disposera pas des données les plus fraîches.
Elles sont pas fraîches mes données ?!?
Le rôle de la speed layer va être d'agréger les données temps réel et d'exposer une vue pour que les utilisateurs puissent faire des requêtes sur les données les plus fraîches. Les données de cette vue vont pouvoir être effacées dès qu'elles ne vont plus être nécessaires. Par exemple, à t = t1', on va pouvoir effacer dans la speed layer les données datant d'avant t1.
Clairement, les données présentes dans la speed layer n'ont pas vocation à être immuables et source perpétuelle de vérité, contrairement à nos données dans le master dataset. Si jamais une erreur s'immisce dans nos données de la speed layer, elle sera corrigée dès la fin de la prochaine analyse par lot dans la batch layer. On peut donc se permettre quelques facilités dans la speed layer. Notamment, on peut se permettre de stocker nos données sous une forme dénormalisée. Après tout, ce n'est pas un problème d'avoir dans la speed layer des informations dupliquées.
Par ailleurs, on peut aussi se permettre de stocker des données agrégées dans la speed layer. Dans notre exemple où nous comptons le nombre de visites d'un site web par page et par heure, on peut tout à fait stocker dans la speed layer un décompte total par page et par heure, plutôt que de stocker chacune des visites. Vous me direz : mais est-ce qu'on n'a pas dit plus tôt qu'on devait stocker les données brutes, non agrégées ? Oui, mais ça c'était dans le master dataset ! Et même, c'est souhaitable de stocker des données agrégées, plutôt que des données brutes, pour des raisons de performances. Prenons un exemple pour mieux comprendre : imaginons que notre système d'analytics génère 24 Go de données par jour et qu'il faille au plus six heures pour qu'une donnée entre dans la serving layer (t2' - t1). Alors il faudra que la speed layer contienne les données accumulées durant ces six heures, ce qui va représenter 6 Go de données brutes. Si la speed layer stockait ces données brutes, à chaque requête d'un utilisateur il faudrait réaliser une agrégation sur ces 6 Go de données -- exactement de la même manière que dans la batch layer. La speed layer mériterait alors très mal son nom, puisqu'elle serait très lente.
En fait, la speed layer joue un peu le même rôle que la base SQL dans notre tout premier brouillon de compteur de visites, au début de ce cours : on stocke des données dénormalisées, agrégées, mais ça ne nous pose pas de problème parce que la speed layer n'est pas une source de vérité absolue.
Bref, pour schématiser (vraiment) très grossièrement : dans le master dataset tout est propre et bien rangé, et dans la speed layer on met les choses en vrac. C'est plus rapide et de toute façon les données expirent très rapidement.
Traitement des flux de données temps réel
Alors comment va-t-on procéder pour agréger les données et les exposer dans une vue avec une latence la plus faible possible ? On va mettre en place une solution de traitement des flux de données temps réel. Le rôle de ce ce composant va être de recevoir les données et de mettre à jour la vue, tout ça avec une latence la plus faible possible.
Dans notre exemple, le rôle de ce composant va être ramené à une seule fonction, écrite ici en pseudo code :
function process_visit(time, url) {
time_bucket = get_bucket(time)
db.run("UPDATE visits SET count = count + 1 WHERE time = " + time_bucket + " AND url = '" + url + "')
}
Comme toujours, il faut que cette solution de traitement des données temps réel passe bien à l'échelle et qu'il soit tolérant aux pannes. Mettre en place une solution qui réponde à ces critères n'est pas aussi simple qu'il n'y paraît, mais heureusement nous en parlons en détail dans le cours Gérez des flux de données temps réel 0:-) On y parle notamment de Storm, un outil qui correspond parfaitement à nos besoins.
Notez que dans notre exemple, on traite les données une par une ; on pourrait imaginer traiter les données par petits lots, et notre pseudo implémentation prendrait alors cette forme :
function process_visits(visits) {
for(time, url in visits) {
time_bucket = get_bucket(time)
db.run("UPDATE visits SET count = count + 1 WHERE time = " + time_bucket + " AND url = '" + url + "')
}
}
Il s'agit ici de micro-batch : sans rentrer dans les détails, sachez simplement que Spark propose de faire du micro-batch avec Spark Streaming
Implémentation d'une vue temps réel
Notre sytème de traitement des données temps réel va mettre à jour la vue de la speed layer. Voici les contraintes auxquelles cette vue va être sujette :
Écritures aléatoires : contrairement à la vue de la serving layer, on va devoir insérer des données de manière aléatoire dans la vue de la speed layer.
Lectures aléatoires : comme d'habitude, il faut s'attendre à ce que les utilisateurs fassent des requêtes qui concernent n'importe quelle donnée.
Les contraintes que va subir la vue de la speed layer vont donc être plus fortes que dans la serving layer. Mais la bonne nouvelle, c'est que si vous avez déjà choisi une solution surdimensionnée pour la serving layer, alors vous pouvez la réutiliser dans la speed layer ! Par exemple, si vous avez choisi Cassandra, MongoDb ou ElasticSearch dans la serving layer, ces trois outils fonctionneront parfaitement dans la speed layer. (je sais pas si je l'ai déjà dit, mais ces deux derniers sont décrits dans le cours sur les bases de données NoSQL :-D)
Expiration des données
Je vais vous faire un aveu : il y a un épineux problème que j'ai quasiment passé sous silence jusqu'à présent dans ce chapitre. Ce problème, c'est celui de l'expiration des données. À la fin de chaque job de la batch layer, on va pouvoir effacer certaines données. Par exemple, à t1' on va pouvoir effacer les données de la speed layer datant d'avant t1. Mais comment faire ça si les données sont agrégées dans le temps ? Dans notre exemple, on agrège les données par heure. La vue de la speed layer va commencer à agréger les données à partir de t1 = 00:00. On va supprimer une partie des données à t1' = 00:15 et t2' = 00:30. Mais comment différencier ces données à supprimer, alors qu'elles sont toutes agrégées dans les mêmes lignes de la vue ? (celles pour lesquelles time = 00:00)
Il n'y a pas de solution élégante à ce problème, et c'est un reproche que l'on peut faire à l'architecture lambda. La seule solution qui couvre tous les cas de figure consiste à créer deux vues au lieu d'une : à tout instant, les données traitées dans la speed layer sont insérées dans chacune des deux vues A et B. Cela signifie que les données sont dupliquées, et qu'elles nécessitent deux fois plus d'espaces. C'est imparfait, mais ça fonctionne.
L'une de ces deux vues est la vue courante sur laquelle les utilisateurs font des requêtes. L'autre vue est la vue future qui va devenir la vue courante dès que les données de la vue courante seront supprimées. Voici une ligne temporelle, forcément un peu compliquée, qui illustre ce qui se produit :
La vue courante est représentée en traits pleins, et la vue future est en pointillés. Lorsqu'un job se termine, la vue qui était courante devient la vue future, et inversement. Dans MongoDb, les deux vues seraient deux collections différentes dont les noms seraient échangés pour indiquer laquelle est la vue courante.
La mécanique à mettre en place pour gérer une expiration correcte des données est assez complexe, mais elle est nécessaire pour conserver des données perpétuellement à jour.
Conclusion
Avec la speed layer, on a vu le dernier composant de notre architecture lambda permettant de gérer tous les cas de figure liés aux données massives. À ce stade, si vous n'avez pas déjà suivi les autres cours décrivant les différents outils utiles à chacun des composants, je vous encourage fortement à le faire : ça vous permettra de comprendre en détail comment fonctionne chaque brique. Et si vous avez déjà suivi tous ces cours, alors... il ne vous reste plus qu'à vous mettre au boulot pour résoudre les vrais problèmes des mégadonnées !