big-data

Les moteurs de bases de données de 1flow

By on 29 March 2014, in 1flow, Blog, Development, Sysadmin

La question m’a été posée de comprendre pourquoi 1flow utilise 4 moteurs de bases de données. Même si memcached n’est pas considéré comme un moteur de BDD à part entière, ça en fait quand même 3 (PostgreSQL, MongoDB & Redis).

L’argumentaire qui suit tient plutôt lieu d’explication pour indiquer quelles données sont stockées aujourd’hui dans chacun d’eux. Cet article ne rassemble pas l’intégralité des raisons pour lesquelles la situation est ce qu’elle est. Mais il dresse un bon panorama.

PostgreSQL

Quand le projet a commencé, c’était un passage obligé car Django ne démarrait pas sans BDD, et le module social-auth non plus. Pas la peine de me parler de MySQL, vu comme South l’adore. SQLite c’est bien pour un environnement de développement, mais on parlait d’une plateforme qui hébergerait à terme des milliers d’utilisateurs, et puis on est des gens sérieux ; donc PostgreSQL.

Aujourd’hui, on pourrait peut-être supprimer cette dépendance, mais :

  • il y a plus prioritaire en termes de fonctionnalités. À part la dépendance à l’installation, PotsgreSQL n’introduit aucune instabilité ni aucun problème, il ne complexifie pas le projet, car il doit être là de toute manière. Il est parfaitement intégré dans Django et sa suite de tests.
  • le supprimer nécessitera de trouver / développer un module de contenu multi-lingue pour MongoDB, équivalent à django-transmeta, qui n’existe pas à ma connaissance.

Aujourd’hui (version 0.26) nous y stockons non seulement les comptes sociaux, mais aussi tous les textes de l’application landing (le contenu de 1flow.io quand on n’est pas identifié). Vu l’intégration parfaite côté Django, nous l’utiliserons aussi pour la future application index (en projet pour en faire quelque chose de similaire à l’index docker, hors-sujet de cet article).

Le module django-transmeta permet d’avoir des attributs multi-lingues et faire les traductions directement en base. C’est super pratique d’avoir les contenus mis à jour en temps réel sans devoir passer par le process gettext ni un redémarrage des services : les CM n’ont pas besoin des développeurs pour mettre à jour la FAQ, par exemple.

MongoDB

C’est le cœur de l’appli, et je l’ai choisi car il permet une mise à l’échelle rapide via l’ajout de nœuds à l’horizontale dans le cluster, sans configuration compliquée comme c’est le cas pour PostgreSQL. L’équilibrage et beaucoup de fonctions spécifiques au cluster sont automatiquement gérées et calibrées par le moteur, et dans notre environnement de « petite équipe », ce choix nous permet de gagner du temps.

Gardez en tête qu’historiquement nous étions en mode startup — donc toujours des milliers d’utilisateurs à héberger sur une une seule plateforme fermée, un peu comme Github — donc il nous fallait quelque chose qui pouvait croître très vite sans nécessiter trop d’administration système.

La question du volume de données reste toujours d’actualité vu la taille de la base de données et sa vitesse de croissance. Les décisions de départ façonnent le chemin. Même si nous avons pivoté et ne montons plus une plateforme unique, nous ne pivotons pas en repartant de zéro, mais du point où nous sommes rendus. Se passer de MongoDB représenterait un travail trop conséquent, et il serait dommage de ne pas pouvoir « scaler » les nœuds facilement.

Qui peut le plus peut le moins, et en l’état je ne considère pas le projet « compliqué » (bloated diraient certains). Complexe, peut-être, mais ce n’est pas du tout la même chose. La complexité me semble inhérente à tout projet qui acquiert de la maturité au cours du temps. Il a de plus en plus d’historique et de fonctionnalités métiers, ce qui en rend certaines parties difficiles à apréhender sans introduction. Mais je pense que c’est le lot de tout projet qui adresse un ensemble de fonctionnalités non-triviales, et 1flow en fait partie.

Par ailleurs, nous avons deux bases de données MongoDB dans 1flow : la base « cœur » qui contient les données de production, et la base « archive » qui contient l’intégralité des données « brutes » récupérées dans les flux RSS. Au cas où nous améliorerions certaines fonctionnalités (un parseur, que sais-je) dans le futur, les données d’origine pourraient être rejouées pour améliorer la qualité du contenu. Il va sans dire que cette base est complètement optionnelle, et peut ne pas être utilisée du tout. On peut la dropper sans risque à tout moment.

Redis

Redis est utilisé à plusieurs niveaux, certains étant externes à 1flow comme c’est le cas de celery, qui a sa « db » dédiée. Du coup, pour celery par exemple, il fallait un tunnel pour le broker, et si ce n’était pas redis c’était RabbitMQ — donc une dépendance supplémentaire, pour le coup — ou MongoDB — avec des performances plus que médiocres et un support déclinant à cause de ça. Les sessions sont dans redis, constance (l’équivalent des settings Django, mais dynamiques) est dans redis, et quelques autres bricoles.

Redis est un peu mon coup de cœur dans le projet. Il est fiable et très flexible, et nous n’exploitons pas encore toutes ses capacités, ce qui me laisse extrèmement optimiste pour l’avenir en termes de fonctionnalités offertes à l’utilisateur final car il fait partie des quelques projets qui permettent « d’imaginer sans crainte ».

Notre Redis héberge pour 1flow toutes sortes de compteurs et d’attributs à titre de cache, qui varient énormément au cours du temps, mais pour lesquels nous avons néanmoins besoin de persistance car un recalcul est inenvisageable — car trop long — au redémarrage de l’application ou des machines.

À titre d’anecdote, ces attributs sont accédés de manière transparente comme des attributs Python grâce à une couche d’abstraction qui gagnerait à être externalisée et distribuée comme une bibliothèque autonome, que j’appellerai alors pyredesc (pour Python Redis descriptors). C’est comme hot-redis, mais auto-bindé sur des objets issus de l’ORM Django ou de MongoEngine via des descripteurs Python. En fait ça fonctionne sur n’importe quelle classe qui a un attribut .id unique à travers un réseau d’applications.

Dans le code, aucune différence avec un attribut classique, sauf que c’est distribuable en réseau. Ultra pratique pour implémenter des verrous ré-entrants [transparents pour le développeur] quand plusieurs tâches sont susceptibles d’intervenir en parallèle sur un flux, un abonnement, un article ou une lecture.

Dans 1flow, ça représente des dizaines de milliers de valeurs entières ou de dates, mises à jour plusieurs fois par seconde pour certaines. Toujours dans le cadre de milliers de flux ou d’utilisateurs, ce qui est le cas sur 1flow.io et pourrait être le cas dans n’importe quel futur nœud qui se respecte (je rêve que Greenpeace utilise un 1flow en interne, par exemple).

Impossible d’impacter MongoDB pour ça : d’un part le coût en écriture est trop grand, d’autre part les données en tant que telles ne sont que les résultats de calculs issus de données de la base MongoDB. Elles servent dans les éléments d’interface ou les tâches en arrière-plan (jusqu’à plusieurs centaines par seconde, dans celery). Les séparer de la base MongoDB peut être vu comme une « bonne chose » car ce ne sont pas des “données” au sens du cœur de l’application.

Memcache

Lui, c’est parce que certains modules Django ne savent pas utiliser Redis. Les porter sur Redis n’est, encore une fois, pas le plus prioritaire. Nous l’utilisons aussi pour le cache de Django, mais ça pourra changer à n’importe quel moment. Je présens que ça sera le cas lors de la migration vers Django 1.6, ou peut-être 1.7.

Conclusion

C’était l’occasion de parler un peu de l’architecture de 1flow. C’est forcément incomplet, vu la taille du projet. Mais si ça a pu répondre un peu à la question d’origine, alors c’est bien, car c’était le but ;-)