Tant de données, si peu d'argent

calendar_month
3 décembre 2020

Flux de données avec un budget limité

Il est devenu un cliché de dire qu’il ne faut jamais se soucier des performances ou de la scalabilité dans une startup. Après tout, vous n’êtes pas encore Google, n’est-ce pas ? C’est vrai, mais vous ne disposez pas non plus de leurs ressources. Et toutes les startups ne créent pas une simple application de workflow, avec peu d’interactions et aucun traitement de données réel.
Lorsque vous avez un budget limité et un calendrier serré, mais que vous devez quand même concevoir un pipeline de données fiable, quelles sont vos options ?
Bien sûr, il existe plusieurs façons de procéder, et je ne prétends pas que nos choix soient meilleurs que d’autres. Mais ils ont donné de bons résultats jusqu’à présent, donc cette exploration peut servir de base de réflexion si vous êtes confronté à des défis similaires.

Notre contexte

AdAlong rend le contenu des réseaux sociaux facilement consultable et regroupable, grâce à une combinaison d’extraction d’étiquettes via label par ordinateur et à l’utilisation d’un moteur de recherche.
Chaque jour, chez AdAlong, nous traitons plusieurs dizaines de Go de données médias. Nous :

  • collectons ces données depuis différents réseaux sociaux, tels qu’Instagram et Twitter
  • appliquons des algorithmes de vision par ordinateur et de deep learning pour extraire des informations visuelles
  • réalisons quelques opérations de transformation sur les métadonnées, comme la détection de la langue
  • ingérons ces données dans Elasticsearch

De plus, nous devons disposer de sauvegardes peu coûteuses et d’un moyen simple de les recharger.
Enfin, notre traitement des données est en grande partie linéaire.

Surtout, nous ne sommes que cinq développeurs dans l’équipe technique, et notre plateforme dessert des clients depuis 2 ans.
Ça vous parle ? Explorons ensemble nos options !

Le workflow simplifié basé sur une base de données

Lorsque vous commencez un projet en tant que monolithe reposant sur une seule base de données, et que vous souhaitez le rester aussi longtemps que possible (disons, jusqu’à obtenir un certain traction), il est très tentant d’utiliser la base de données pour orchestrer votre traitement de données.
Une façon de procéder est d’enregistrer les enregistrements à traiter, avec les informations relatives au traitement des données (soit intégrées dans le même document, soit liées via une clé étrangère). Les enregistrements passent alors d’une étape de traitement à une autre, soit par un cron job quelconque, soit (Dieu nous en préserve) via des triggers dans la base de données ou l’ORM.

Les problèmes liés à cette approche sont assez évidents :

  • Au moment de l’ingestion, vous sollicitez fortement votre base de données, surtout si vous traitez vos enregistrements un par un via des triggers, tout en maintenant le processus d’indexation actif. J’ai déjà dû gérer une pipeline de données basée sur les callbacks Active Record dans Ruby on Rails, et comme vous pouvez l’imaginer, le débit n’était pas très impressionnant.
  • Vous modifiez généralement vos enregistrements pendant le traitement. Cela rend la reprise de traitement, ou plus généralement le retour en arrière, impossible. Si vous souhaitez conserver différentes versions des mêmes enregistrements, le stockage peut devenir coûteux dans une base de données « chaude ».

Cela dit, parce que c’était l’approche la plus simple, nous l’avons adoptée lors des débuts d’AdAlong, en ingérant des enregistrements non traités, avec leurs métadonnées de traitement, directement dans Mongo. Un cron job promouvait ensuite les enregistrements d’un état à un autre. Comme prévu, au-delà d’une certaine échelle, la base de données était trop sollicitée et nous avons dû trouver une autre solution.‍

Les outils Enterprise™ Big Data®

Spark ! Hadoop ! Dataproc ! Beam ! Flink ! Airflow !
La liste des frameworks et outils de traitement de données, souvent accompagnés de promesses de performance audacieuses et de logos douteux, ne cesse de s’allonger (ou en tout cas, c’était le cas). Les startups sans réelle expérience en ingénierie des données pourraient être tentées de prendre la pilule rouge et d’apprendre à utiliser un ou plusieurs de ces outils. Après tout, un outil dédié à cette tâche spécifique doit forcément être un choix sûr, non ?

Erreur !

Voici quelques raisons pour lesquelles ce choix peut être catastrophique pour les startups qui veulent rester agiles :

- Ces frameworks sont généralement complexes et nécessitent une formation adéquate de l’équipe si vous ne voulez pas que vos développeurs passent leur temps à faire du cargo-cult sur Stackoverflow dès qu’un problème survient. Sans parler de l’apprentissage de nouveaux langages dans certains cas (par exemple Scala pour Spark — même si théoriquement on peut s’en passer, c’est quand même l’approche privilégiée).

- Ils sont gourmands en ressources, surtout ceux basés sur HDFS, qui demandent réplication, coordination, sérialisation, etc. Si vous n’avez pas besoin de répartir votre charge sur des dizaines de nœuds, vous ne pouvez pas vraiment amortir ces coûts.

Le bon compromis

Nous avons trouvé un bon équilibre en combinant les éléments suivants :

  • Une infrastructure de traitement de données fiable, gérée par des fournisseurs cloud, avec des logiques bien définies. Nous avons cherché à déléguer un maximum de la gestion des erreurs et des mécanismes de réessai à cette infrastructure.
  • Des programmes personnalisés simples, faciles à exécuter et à déboguer, qui effectuent des opérations idempotentes et peuvent, de manière générale, être arrêtés brutalement sans trop de conséquences.

Le bus d’événements


Puisque notre stack fonctionne sur Google Cloud Platform, nous avons basé notre pipeline sur l’infrastructure de Google Pub/Sub. Il s’agit d’un bus d’événements géré par Google, auto-scalable, qui prend en charge :

  • plusieurs abonnements,
  • les réessais automatiques en cas d’échec avec back-off exponentiel,
  • la rétention des messages déjà consommés.

Les processeurs de données

Notre traitement est réparti entre plusieurs exécutables qui consomment et produisent des données via Pub/Sub. Ces exécutables tournent dans un cluster Kubernetes géré par Google, ce qui permet de les faire évoluer, redémarrer, remplacer, et gérer de manière simple, homogène et bien documentée.

Ils sont principalement écrits en Go, un langage qui permet de générer des exécutables légers, à démarrage rapide, particulièrement adaptés à une exécution dans Kubernetes. De plus, la courbe d’apprentissage de Go est relativement douce comparée à d’autres alternatives, ce qui permet à des non-experts de contribuer rapidement.‍

Le combo

Cette combinaison a très bien fonctionné pour nous, en particulier dans les cas suivants :

  • Pics d’activité : Pub/Sub offre un tampon de 7 jours, ce qui nous laisse largement le temps d’adapter notre capacité d’ingestion en cas de pic. Dans notre cas, c’est particulièrement utile pour les opérations de machine learning coûteuses, pour lesquelles nous utilisons un pool dédié de nœuds préemptibles auto-scalables. Tandis que de nouveaux enregistrements s’accumulent rapidement dans notre bus d’événements, notre pool peut monter en charge quand des nœuds sont disponibles, et redescendre quand il n’y a plus de backlog. Cela constitue une stratégie de traitement très économique, puisque les nœuds préemptibles coûtent environ un tiers du prix des nœuds à la demande, et ne fonctionnent que lorsqu’ils sont nécessaires.
  • Ingestion fluide : il est important de contrôler le débit d’ingestion vers notre base de données de destination (dans notre cas : Elasticsearch) pour éviter toute dégradation du service pour nos utilisateurs. Nous avons rencontré ce problème avec la stratégie du « workflow piloté par base de données » vue précédemment. Grâce au tampon Pub/Sub placé entre la collecte en amont et notre base de données, nous pouvons nous assurer que les données sont injectées à un rythme maîtrisé.
  • Gestion des erreurs : si un composant de traitement plante, les enregistrements en attente s’accumulent simplement devant lui, le temps que nous le réparions. Dans des cas moins critiques, comme quelques enregistrements échoués, ceux-ci restent dans la file d’attente de la souscription et sont rejoués avec une temporisation croissante (exponential backoff), ce qui nous laisse le temps de corriger le composant fautif ou de supprimer les enregistrements problématiques.
  • Déploiement : comme nous avons de nombreux petits exécutables réalisant des opérations idempotentes, il est très facile de déployer de nouvelles versions sans se compliquer la vie avec des stratégies d’arrêt progressif ou de vidage des files. Nous les arrêtons simplement sans ménagement, et leurs remplaçants reprennent le travail à peu près là où ils s’étaient arrêtés.
  • Coût : en utilisant de simples programmes Go sans surcharge inutile (pas de bases auxiliaires, de Zookeepers, de nœuds maîtres/esclaves, etc.), notre besoin en puissance de traitement est très faible, ce qui est essentiel pour conserver une marge opérationnelle élevée.

Limitation

Bien sûr, les Enterprise™ Big Data® Toolsets existent pour une bonne raison ! Mais nous ne sommes pas confrontés à des problématiques telles que :

  • Le windowing ou la sessionization
  • Les joins distribués nécessitant des opérations de shuffling
  • Le traitement en temps réel (même si notre architecture s’y prêterait bien)
  • Les conversions entre une multitude de formats de données (ORC, Parquet, Arrow, Avro, etc.)

Mais même dans ces cas-là, une analyse rigoureuse des besoins et l’exploration d'options alternatives restent indispensables avant de plonger tête baissée dans l’abîme du Big Data.

Conclusion

Opter pour une solution complète de traitement de données peut s’avérer nécessaire pour vous, mais il se peut que cela prenne du temps avant que vous ayez vraiment besoin de cette complexité et les ressources pour la justifier.

Vous risquez de rester longtemps dans une situation où la stratégie simpliste de workflow de traitement de données ne suffira plus, voire pourrait mettre votre entreprise en danger à cause de ses limites.

Dans ce cas, une combinaison de programmes simples et compréhensibles, fonctionnant sur une infrastructure fiable et standardisée, pourrait être votre meilleure option.