dotScale 2015

Pour la deuxième année consécutive, je suis allé faire un tour à dotScale. Toujours au même endroit, au Théâtre de Paris, dans une belle salle de théâtre (même si il y faisait un peu chaud dans l'aprem). Ça fait plaisir de voir autant de gens réunis pour parler scalabilité (si seulement autant de personnes venaient parler CSS...).

Il y avait en tout environ 750 personnes, et comme à son habitude l'organisation était au top, les buffets bien remplis et très bons. Comme à son habitude, dotScale n'émets pas de wifi dans la salle de conférences pour que les gens se concentrent sur les talks (on nous incite même à ne pas utiliser nos laptops, mais j'ai bravé l'interdit pour prendre les notes qui m'ont permis d'écrire cet article).

Comme d'habitude aussi, j'y croise du beau monde que je connais déjà, mais c'est toujours agréable de discuter.

Je suis resté un peu sur ma faim en milieu de journée quand la conférence s'est mise à devenir un dotNoSQL plutôt qu'un dotScale, mais les derniers talks sont revenus sur la bonne piste donc j'en sors finalement content.

Automatic Unattended Reboots

Le premier talk de la journée, de Matt Bostock de gov.uk commence directement à nous parler de reboots automatiques. Matt travaille donc pour le gouvernement du Royaume Uni et l'availability des différents sites de ministères et ambassades. Ils ont environ 12 millions de visiteurs uniques par semaine, à toute heure du jour et la nuit (voyageurs, expatriés à l'autre bout du globe qui utilisent les sites des ambassades).

Bien souvent quand on fait une mise à jour, il est nécessaire de faire un reboot. Parfois, un simple restart des services qui vont bien suffit, mais c'est pas toujours évident à faire. D'une part c'est un peu plus manuel qu'un bon vieux reboot, et parfois ça ne suffit même pas (comme quand on mets à jour des lib ssl -heartbleed, anyone ?-).

Reboots

Du coup, rebooter manuellement tout ses serveurs de sa batterie, ça va bien deux minutes, mais il y a de bons moyens de rendre ça moins pénible. Ubuntu, qu'ils utilisent, est configuré pour mettre automatiquement à jour les paquets qui contiennent des updates de sécurité (cron qui check toutes les demi-heures de minuit à 9h du matin).

Comme je le disais plus haut, c'est déjà pas mal, mais parfois il faut faire un reboot après ça. Dans ce cas, il est possible de spécifier un Unattended-Upgrade::Automatice-reboot "true" pour que le serveur reboot dans la foulée. Il faut par contre faire attention à ce que tous les serveurs ne rebootent pas en même temps, en décalant légèrement le moment où les cron tournent entre plusieurs machines.

Ils ont donc mis au point un système automatique pour s'assurer que tous les reboots se fassent correctement.

Leur script bash tourne toutes les minutes, de minuit à 9h. Il check si /var/run/boot-required existe (ce fichier est ajouté par Ubuntu lorsqu'une update nécessite un reboot, c'est ce fichier qui est la cause du message qu'on voit parfois quand on se loggue sur un serveur qui a besoin d'être rebooté).

Si c'est le cas, il check un flag dans Puppet qui indique si cette machine a le droit de se rebooter seule. Certaines machines nécessitent un reboot manuel, et dans ce cas la config Puppet s'assure du feature flipping. Ils sont en train de modifier tout leurs serveurs pour qu'ils soient tous capables de se rebooter, mais comme ils n'en sont pas encore là, la feature peut être désactivée au cas par cas.

Ils requêtent ensuite Icinga (un fork de Nagios qui expose un endpoint en json avec l'état actuel de santé des machines) pour voir si le cluster sur lequel tourne le serveur est en bonne santé. S'il y a un trop grand nombre d'erreurs critiques en ce moment sur le serveur, on ne reboot pas, bien trop dangereux.

Ils requêtent ensuite etcd qui, d'après ce que j'ai compris, leur permet de d'obtenir un mutex distribué pour savoir si ce serveur peut reboot. Si le lock est dispo, le serveur le prends, sinon le script s'arrête et recommencera dans 1mn, au prochain passage du cron.

Au reboot, le serveur release son lock et les autres serveurs du pool peuvent alors le récupérer pour faire la même danse et se rebooter aussi.

Bien sur, avant de tester tout ça en production, ils ont testé dans leur environnement de staging, qui est une copie conforme de l'env de prod et qui reçoit une copie en miroir de toutes les requêtes que la prod reçoit aussi. Ils ont laissé tourné comme ça pendant plusieurs semaines, pour s'assurer que tout fonctionnait bien avant de déployer en prod une fois confiants.

Finalement, leur système marche bien et ils en sont très content. Depuis ils l'ont un peu amélioré, en passant le cron à 5mn plutôt que 1. 1mn était trop court et les serveurs n'avaient pas le temps de redémarrer correctement. 5mn leur laisse plus d'espace et est encore assez court pour que tous les serveurs puissent reboot avant 9h. Ils ont aussi réécrit une partie du parsing du json (anciennement en regexp) par du Ruby.

Scaling Humans

Le deuxième talk était de David Mytton, de Server Density. David nous a expliqué comment ils gèrent les astreintes chez Server Density.

Comme tous les bons ops, il commence par rappeler que quelque soit les précautions qui sont prises, il y aura toujours des moment de downtime, qui passent au travers des protections automatiques mises en place et doivent être gérées par des humains.

Scaling Humans

Et ces humains doivent être d'astreinte, joignables et prêts à intervenir au plus vite. Pour cela, il utilisent un système de rotation primary/secondary. Les primary sont prévenus en premier et doivent être dispo pour répondre en quelques minutes, se logguer sur les serveurs, commencer à comprendre d'où vient l'erreur et pouvoir passer ces informations à la seconde équipe qui n'arrive qu'au bout de 30mn si jamais le problème est toujours en production.

Tous les ops d'astreinte ont le droit à une journée off après un call d'astreinte, pour se reposer et pouvoir reprendre le travail en forme sans avoir eu une nuit blanche entre les deux.

Plus un système devient complexe et moins il a de chance de tenir dans la tête d'une seule personne. Et même si c'est le cas, cette personne ne sera peut-être pas toujours là, ou ne sera pas dispo pour cette urgence. Il est donc indispensable que l'infra soit documentée, ainsi que les erreurs communes. Cette documentation doit être searchable facilement et rapidement et surtout, elle ne doit pas être hébergée sur les mêmes machines que celles qui risquent de planter (mettre la doc sur Google Docs est donc une bonne idée).

La doc doit comprendre la liste des personnes à contacter pour les différents problèmes, que ce soit des développeur en interne, des responsable de la relation client, ou des contacts chez les différents prestataires. Les différents codes d'accès aux différents outils doivent aussi être facilement accessible pour ne pas se retrouver refoulé à l'entrée parce qu'on n'a pas le bon mot de passe du support ou pas la bonne clé ssh quand les serveurs sont en train de bruler.

Dans le même ordre d'idée, il faut s'assurer que le canal de communication principal (HipChat par exemple) n'utilise pas la même architecture que celle qui est susceptible de planter (AWS par exemple). Les moyens de communications doivent aussi être redondés.

Avant de se lancer tête baissée dans la résolution du problème il y a déjà 3 points qui doivent être respectés. Tout d'abord, et avant tout, de manière primordiale, ouvrir la checklist et vérifier de bien tout faire, dans l'ordre. Si une erreur survient le lundi matin alors qu'on est en pleine forme, c'est pas un soucis, mais si jamais on doit sauver le monde à trois heures du matin après une soirée arrosée au réveillon, on n'aura pas les idées très claires et suivre la checklist nous assurera de ne rien oublier et de le faire dans l'ordre.

Ensuite, on se connecte sur une war room dédiée. Celle-ci est destinée uniquement aux ops en train d'investiguer le problème, on n'y discute pas, on n'y partage que des infos qui font avancer l'investigation.

On loggue ensuite l'erreur dans un JIRA ou autre tracker et on balance l'url dans la room. Comme ça, on a une seule référence de l'erreur, avec un numéro unique et tout le monde peut bien parler de la même chose. On y poste les différentes commandes qui ont été exécutées, les pistes, etc de manière à ce qu'une nouvelle personne qui arrive sur le problème puisse voir en une seule fois les symptômes et ce qui a été tenté. Elle permettra aussi de se pencher sur un post-mortem ensuite.

Et seulement après la checklist, la war room et l'issue JIRA se mets on à investiguer réellement les causes. Comme je le disais, si on tente des commandes, si on fait des reboots des serveurs, on le note dans l'issue.

On prévient aussi nos utilisateurs aussi souvent qu'on le peut de l'avancée de nos investigations, ce qu'on suppose, ce dont on est sur, ce qu'on va faire et un ETA si on en a un. Il n'y a rien de pire qu'avoir son site down et de n'avoir pour seule information qu'une page de status mise à jour il y a plus de 4h qui dit "on s'en occupe".

Il est aussi utile d'avoir toute l'équipe dispo à portée de main. Tout le monde ne peut pas vraiment aider en même temps, mais de savoir que si besoin on peut aller voir la personne en charge de telle ou telle partie d'où le bug a pu provenir et qu'elle sera dispo là tout de suite pour aider à corriger, c'est un gros plus.

Et une fois que la catastrophe est endiguée, on laisse couler quelques jours pour se reposer, pour répondre aux clients, et on se retrouve pour faire un post-mortem. On raconte ce qu'il s'est passé, les causes, les conséquences, les solutions, et ce qu'on peut faire pour que cela ne se reproduise plus.

On peut ensuite poster ça publiquement sur le blog de la société. Selon l'affinité technique du public on peut rentrer plus ou moins dans les détails, mais au moins on le fait en interne au reste de l'équipe. Etsy, Heroku, Amazon et plein d'autres font ça régulièrement: ce qu'il s'est passé, pourquoi, ce qu'ils ont essayé, ce qui a marché, ce qui n'a pas marché et les solutions pour la suite.

Un bon guide de savoir-vivre de l'astreinte, concentré sur le coté humain, sur la communication, savoir expliquer ce qu'il se passe, pas mal de bonnes idées.

Worst and best in time series data

Paul Dix, d'InfluxDB nous a ensuite parlé de time series et des challenges que leur stockage comportait pour des bases de données classiques.

Déjà, un petit rappel sur ce qu'on appelle une time series. Un exemple concret et parlant sont des lignes de log par exemple. C'est un ensemble de données (url, host, temps de réponse, etc) lié à un timestamp. Ces données arrivent les unes après les autres à un serveur. Elles peuvent arriver de manière régulière (toutes les X minutes) ou de manière irrégulière (en réponse à des événements).

Réussir à modéliser ce genre de données dans une base de donnée (NoSQL ou relationnelle) classique est très compliqué. Mais si on s'intéresse aux spécificités de ce genre de data, on se rends compte qu'elles ont des propriétés très spéciales dont on peut tirer parti.

Time Series

Tout d'abord, elles ont un nombre d'écritures très importants. Les logs arrivent tout le temps, sans jamais s'arrêter. Plus on veut tracker de metrics, plus on va recevoir de logs. Elles ont aussi besoin de pas mal de lectures, vu que les utilisateurs vont avoir besoin de voir les metrics enregistrées, générer des graphs, voir les évolutions sur plusieurs minutes, heures, jours, semaines, mois.

Si on stocke l'ensemble des logs qu'on reçoit, on se retrouve en plus avec des quantités de données astronomiques et plus de place pour les stocker et encore moins pour réussir à faire des queries performantes sur un tel dataset.

Mais si on y regarde de plus près, on se rends compte que notre donnée perds de son intérêt au fil du temps. Les logs de l'instant présent sont plus importants que ceux d'il y a deux ans. Et on peut se permettre d'effectuer un map/reduce sur nos anciennes datas pour ne garder que les metrics agrégées extraites de nos milliers de logs sans en garder le détail précis.

On remarque aussi que les time series ne font jamais d'update de donnée existante. On ne va jamais modifier un log existant, on va juste en ajouter des nouveaux, et en plus on va toujours les ajouter récemment. Cette particularité est très importante parce que cela veut dire qu'on peut se permettre de supprimer de gros chunks de données dans le passé sans gêner la consistency de ce qu'on a dans le présent.

Le simple fait de ne pas faire d'update de donnée signifie qu'on ne peut pas avoir deux ordres qui demandent de modifier la même donnée de deux manières différentes et qui nous demanderaient de savoir lequel des deux est arrivé en premier. On n'a pas ce problème, on se contente d'ajouter les logs comme ils viennent. On peut donc facilement faire de gros calculs sur des blocs entiers de donnée ancienne en ayant la certitude qu'un ordre ne va venir ajouter ou modifier un de nos items pendant qu'on fait notre calcul.

Et même dans le cas où notre base est distribuée sur plusieurs serveurs et que pour une raison ou une autre un des serveurs n'a pas reçu l'ensemble des ordres d'écriture, il nous est facile de reconstituer le set manquant en faisant la différence avec ce que les autres ont reçu.

Au final, le talk était un très bon moyen de mettre en évidence les spécificités des time series et de ce qu'elles impliquent en terme de type de base de données. Même si son nom n'a jamais été cité de tout le talk, j'imagine qu'InfluxDB doit résoudre l'ensemble des problèmes évoqués.

Borg

John Wilkes, de Google, nous a parlé de Borg, l'outil utilisé chez Google qui permet de faire de la parallélisation de tâches sur de multiples machines/cœurs/threads/whatever.

Borg

Honnêtement, je ne sais pas si c'était la chaleur ou le fait que le speaker parlait vite, et de manière assez monocorde mais j'ai piqué du nez à plusieurs reprises durant le talk et n'ai pas vraiment grand chose à vous raconter sur celui-ci !

Lightning Talks

Après la pause déjeuner, on est repartis pour une petite série de lightning talks. C'est une super idée qui permet de digérer tranquillement sans s'assoupir devant une longue conférence qui demande de faire travailler des méninges qui sont en général à ce moment en train de sortir de leur torpeur.

Doclipser

Le premier talk était sur un projet assez bizarre, de faire tourner Docker sous Eclipse. De ce que j'ai compris, ça permet d'ajouter de la coloration syntaxique et un vérificateur de syntaxe aux Dockerfile, ainsi que de lancer des images en quelques clics depuis son IDE.

J'avoue que ça m'a pas paru plus facile à utiliser que de taper docker run whatever, mais ça doit être parce qu'Eclipse ne m'a jamais paru facile.

Your system is distributed

Sam Bessalah (que je croise souvent en meetup et avec qui j'échange juste quelques mots), m'a particulièrement surpris avec un sujet super intéressant sur la place des recherches académiques au sein des différents projets open-source.

Il trouve que bien des projets mentent bien souvent par omission, en considérant que le réseau est fiable, que la latence n'existe pas, que nous avons une bande passante illimitée, etc.

Network fallacies

Ces complexités existent, il ne faut pas fermer les yeux en se convainquant qu'il n'y a pas de problèmes, il faut penser un système qui les englobe et qui sait les gérer. De la même manière, réussir à mettre au point un algo de consensus dans un système distribué n'est pas quelque chose de simple.

Et c'est tellement peu simple qu'il existe des recherches sur le sujet, des papiers de plusieurs centaines de pages qui expliquent mathématiquement les problèmes et leurs différentes solutions. Mais bien souvent, tout cela passe au dessus de la tête des développeurs qui trouvent ça trop compliqué et recodent leur propre version.

Il ne faut pas avoir peur des recherches déjà effectuées, au contraire, il faut continuer à bâtir sur les épaules des géants qui nous ont précédés, il ne faut pas cantonner ces recherches au monde de la théorie et notre code à celui de la pratique, les deux ne font partie que d'un seul et même monde.

Good point, j'ai beaucoup apprécié. Même si je suis bien incapable de comprendre le moindre papier mathématique, je ne me risquerai pas à recoder un algo de consensus distribué.

Leader Election with Cassandra

Matthieu Nantern de Xebia nous a ensuite lu un texte explicatif sur Cassandra et la manière dont le Leader est choisi dans un cluster. Le talk n'apportait rien de plus que l'article, et était même plus difficile à suivre.

Convergent Replicated Data Types

Dan Brown nous a quand à lui parlé de la Joconde, de Jesus-Christ et de Léonard de Vinci. Ah non, pardon, il nous a surtout rappelé qu'il était impossible de différencier un échec d'une lenteur, si on ne définit pas un timeout maximum.

Consistency and Candy Crush

Neha Narula, dont la bio sur le site se limitait à "she has a PhD and she's from Google" a quand à elle entamé le premier talk de l'après-midi, sur la cohérence dans une base de donnée et ce que ça implique.

Ce talk était un de plus à nous parler du CAP Theorem, et un de plus à plus parler de database que ce que je m'attendais à trouver sous le terme de scaling.

Consistency

On commence par un petit récapitulatif de ce qu'ACID veut dire.

Le A est pour atomic, soit un ordre s'est exécuté, soit il ne s'est pas exécuté. "Do. Or do not. There is no try" comme dirait l'autre.

Le C est justement pour cette Cohérence (Consistency), c'est à dire que le résultat est correct, selon des règles que nous avons nous même définies.

I pour Isolation, les différentes transactions sont indépendantes et n'interfèrent pas les unes avec les autres.

Et finalement, le D pour Durable, c'est à dire qu'on peut reconstruire la DB même après un crash. C'est la lettre qu'on oublie généralement.

Elle nous explique aussi ce qu'on entends sous le terme de serializability, c'est à dire la capacité d'exécuter un ensemble d'ordres comme si on les exécutaient les uns après les autres. Attention, ça ne veut pas dire que c'est comme ça que ça se passe à l'intérieur, ça veut juste dire que le résultat est le même si on exécute une transaction serialisable que si on exécute les ordres qui la compose les uns après les autres.

Dans l'esprit du codeur, c'est plus simple de se représenter une transaction serialisable, car on n'a alors pas besoin de penser à tous les problèmes d'enchevêtrement des ordres.

Il y a donc, dans le monde des DB, plusieurs modèles de cohérence. On a d'abord l'eventual consistency qui dit simplement que si on n'a arrête d'avoir de nouvelles updates, alors, au bout d'un moment, tout nos nœuds auront la même data. La difficulté avec ce genre de modèle c'est déjà que la notion de temps est assez floue et que ce n'est pas évident de savoir exactement quel est le dernier ordre, et aussi qu'en général les updates ne s'arrêtent jamais réellement.

De l'autre coté, on a la strict consistency. Les ordres sont exécutés dans l'ordre où ils sont envoyés, de manière linéaire. Eventual et Strict sont deux extrêmes d'un spectre avec des tas de variations entre les deux.

Finalement, le take away de sa conférence est que l'eventual consistency c'est compliqué à gérer et que bien souvent ça amène plus de problèmes que ça n'en résout. En tout cas, elle conseille de commencer ses développement avec une cohérence stricte. Et seulement quand cela crée des goulots d'étranglement tenter de les contourner (et l'eventual consistency peut être une solution mais pas forcément la seule).

Au final il faut être bien conscient des avantages et des inconvénients des DB qu'on utilise. Il faut savoir ce qu'on gagne, savoir ce qu'on perds, et ne pas laisser le système de base de donnée faire ce choix pour nous.

Internet as a giant computer

Un talk un peu meta pour la suite, cette fois-ci avec Ben Firshman. Il nous rappelle qu'aujourd'hui on a arrêté de vouloir avoir des serveurs de plus en plus gros, au contraire on cherche à en avoir de plus en plus petits, mais de plus en plus nombreux et de plus en plus éparpillés sur le globe.

Et même ces serveurs qu'on possède, on les splitte en machines virtuelles qu'on va elle-même splitter en containers. On a encore besoin physiquement d'avoir une machine sur laquelle faire tourner tout ça, mais on a tellement de couche de virtualisation entre temps qu'au final on s'en fiche un peu.

Giant Computer

Tout parvient à tourner sur tout et chaque container a embarqué avec lui la totalité des langages et librairies dont il a besoin, de manière complètement isolée. On peut faire tourner des écosystèmes complètement différents sur la même machine sans que cela ne pose de soucis, et sans même forcément le savoir.

Tout ce qu'on demande au serveur, c'est de savoir gérer l'orchestration de tout ça, et d'être en mesure de tuer ou de spawner des containers très rapidement. Et quand il faut aujourd'hui moins d'une seconde pour lancer un container avec une stack complète et le tuer aussi rapidement, on peut faire des trucs très impressionnants.

On peut paralléliser des dockers, pour lancer un maximum de tâches en parallèle, de manière à ce que le temps final d'exécution de la tâche soit égale à la durée de la tache la plus longue. Pour lancer ses tests c'est parfait, une test suite complète qui tourne en quelques secondes, c'est un pas de géant.

Bien sur, on peut déjà faire ça offline, sur son laptop facilement, en forkant des process pour les avoir qui tournent en parallèle. Mais on peut aller plus loin et imaginer que plutôt que de forker un process on envoie une requête POST à un swarm de serveurs en ligne et que eux bootent des centaines d'instances docker en parallèle et nous retournent notre résultat computé très rapidement.

En faisant ainsi, on pourrait utiliser notre réseau de serveur comme un super computer à la puissance quasi-illimitée. Des outils comme Compose, Kubernetes et Mesos sont déjà sur cette voie. Ce n'est plus qu'une question de temps.

postgreSQL

Simon Riggs, développeur sur PostgreSQL depuis plus de 12 ans, nous a fait un récapitulatif de cette base de données qui a une réputation d'ancien, mais qui est bien plus moderne que ce qu'on peut croire.

Il commence par nous rappeler la bénédiction qu'a été l'arrivée du langage SQL dans le milieu des bases de données. Avant SQL, chaque DB avait sa propre syntaxe et les compétences d'une technologie à une autre étaient difficilement transférables. De plus, chaque query devait être manuellement optimisée par les développeurs.

PostgreSQL

Avec l'arrivée de SQL, on avait un langage standardisé, qui permettait de définir les ordres qu'on souhaitait faire jouer, et alors chaque implémentation pouvait alors les jouer à sa manière, et optimiser les requêtes en fonction de la structure et du volume des données. À l'époque, SQL était le langage "haut niveau" pour discuter avec une DB, et on faisait confiance à l'implémentation pour faire the right thing©.

Au fil des années, on a ajouté de nouvelles manières de représenter nos données dans nos bases, pour s'adapter aux nouvelles demandes. Relationnel, schemaless, multidimensionnel, relationnel imbriqué, etc. Différentes représentations pour différents types de données. Rester enfermé dans un seul type de représentation parce que notre DB ne peut faire que ce type est une erreur. C'est le type de donnée qu'on possède qui définit la manière dont on va la représenter et qui entraine le choix de la DB, pas l'inverse.

Et une DB doit idéalement être en mesure de représenter différentes types de données, de différentes manières, selon les use-cases. Elle doit être flexible, et on ne devrait pas avoir à tordre notre donnée pour la faire rentrer dans les cases attendues par notre DB. Si tel est le cas, il faut changer de DB (au moins partiellement).

Il a été pas mal reproché à postgreSQL de ne pas avoir réussi à suivre l'évolution du marché et les innovations shinys de tous les concurrents NoSQL. Postgre s'est surtout concentré sur ses utilisateurs principaux, ceux qui représentent 99% de leurs utilisateurs et qui sont de plus petites structures qui n'ont pas besoin d'avoir de la redondance forte.

Néanmoins, dans les dernières versions, postgre a quand même sorti quelques features très intéressantes, comme la capacité de stocker du JSON en JSONB. C'est à dire un format compressé tout en gardant la capacité d'indexer son contenu et de faire des queries dessus directement.

Ils ont aussi ajouté des limites de timeout sur les queries qui peuvent prendre beaucoup de temps. Par exemple, calculer la moyenne sur des millions de records va prendre beaucoup de temps, mais avec postgre on peut spécifier qu'on veut que la requête ne prenne pas plus que X secondes. Dans ce cas, postgre va prendre un sample de la donnée qu'il sait avoir le temps de processer en moins de X secondes pour retourner les bonnes infos. Bien sur, plus on laisse un long timeout, plus on aura des données précises, mais c'est là le développeur qui fait le choix de sacrifier de la pertinence contre de la performance.

Il a aussi fait le tour de pas mal de fonctionnalités de postgre dans le même genre qui permettent au développeur de choisir lui-même, requête après requête, les tradeoff qu'il souhaite faire en terme de consistency ou performance. Le choix se fait sur chaque requête, pas au global sur le choix de la techno.

Au final, moi qui n'ai jamais utilisé postgre, j'ai découvert une base de donnée bien solide qui semble capable de faire bien plus que ce qu'elle peut laisser présager.

Jepsen

Kyle Kingsbury, le destructeur de base de données, dont les posts de blog peuvent changer la perception de technos était sur scène à dotScale. Il travaille aujourd'hui à Stripe.

J'ai pris très peu de notes à cette conférence, car près de 120 slides en 20 minutes, je me suis surtout concentré sur le propos.

API on fire

Il nous a fait un récapitulatif rapide de ce qu'il avait dit dans ses derniers articles de blog. Comme quoi MongoDB perdait des quantités incroyables de données dans certains, et que ElasticSearch pouvait aussi perdre pas mal d'infos dans certains cas de partition réseau.

Depuis la parution de ses articles, les deux technos ont émis des bug fix et mis à jour leur doc, mais il semblerait que MongoDB continue de mentir ouvertement et de nier ses pertes de données sur son site, alors qu'ElasticSearch a mis en place une page d'explications très détaillée sur ce qui fonctionne et ce qui ne fonctionne pas.

Il a rapidement aussi présenté Jepsen, l'outil qu'il utilise pour pousser toutes ces bases de données dans leurs derniers retranchement.

Cloudflare

On a ensuite enchainé sur un talk passionnant de John Graham-Cumming (de Cloudflare) et de leur problématique de gestion des logs. Cloudflare est un système de CDN en frontal devant de nombreux sites à fort trafic, qui s'occupe de détecter les potentielles attaques (DDoS) et de les bloquer.

Ils logguent l'intégralité des logs d'accès et les envoient dans un datacenter "secret" pour les stocker et les analyser. Ça fait quand même 400To par jour (compressé) soit environ 4 million par seconde.

Cloudflare

Avec autant d'information, leur plus grande tâche est de réussir à faire la distinction entre le signal et le bruit. C'est d'autant plus important qu'ils doivent être en mesure de détecter une attaque en temps réel, et pas 4h plus tard. Ils présentent aussi sur le dashboard de leurs utilisateurs le % de hit miss, la bande passante utilisée, les origines de requêtes.

Au final, l'astuce n'est bien sur pas de logguer absolument tout, mais d'extraire de chaque log les metrics qui seront répertoriées sur le dashboard. D'attendre aussi d'en avoir assez et de tout envoyer en batch, régulièrement. Selon leur criticité, certaines informations sont envoyées plus régulièrement que d'autres.

La majorité de leur outils internes sont écrits en go. Les serveurs frontaux sont des nginx avec un module LuaJIT pour filtrer les requêtes à logguer. Les Nginx ont un ensemble de règles qui étudient des patterns dans les requêtes pour détecter telle ou telle intrusion et en extraire les infos importantes. Un même serveur nginx va servir de reverse proxy devant plusieurs sites web, et donc il doit aussi être en mesure de loader la configuration de différents utilisateurs (car tous ne souhaitent pas logguer les mêmes informations).

Cet nginx va donc dropper les requêtes qui n'intéressent pas l'utilisateur et passer les autres à un de leurs outils en Go qui la ensuite les compresser (lz4) et les envoyer en batch à leur datacenter sur un flux de streaming ouvert en permanence vers un Kafka.

Sur ce flux, la donnée est compressée avec Cap'n proto, qui permet justement de compresser la donnée pour l'envoyer facilement on the wire. D'après leurs benchmarks, c'est 20% plus rapide que CJSON, qui est déjà bien optimisé. Avec ce format, ils peuvent quand même facilement enlever ou supprimer des attributes sans pénalité.

Une fois arrivé coté Kafka, celui-ci va dépiler les infos au fur et à mesure qu'elles arrivent, et cela de manière parallélisable. Si jamais leur Kafka a du mal à ingérer un afflux massif de données, ils peuvent facilement scaler en en ajoutant de nouveaux au pool, et en les enlevant ensuite. Et même dans le pire des cas, si les Kafkas tombent, la queue continue d'être sauvegardée et pourra être rejouée et rattraper son retard ensuite (max 24h).

Les données sont alors agrégées et alimentent une base postgreSQL qui sera directement requêtée pour générer le dashboard des utilisateurs. Ici, ils utilisent aussi CitusDB qui permet de gérer ses instances postgreSQL comme des shards et d'envoyer une même requête qui sera exécutées sur plusieurs datacenters en même temps et qui retournera l'union des retours.

Ils utilisent aussi du Redis, mais je ne me souviens plus exactement à quel endroit, je pense que c'est pour la gestion du cache.

Pour réussir à gérer toute cette stack, ils se sont fait fabriquer des serveurs sur mesure par Quanta. Ils ont besoin de pouvoir ingérer 15Go/s et de stocker 12To pour leur analytics.

Une belle plongée dans l'infra d'un géant du web avec une infra originale pour des problématiques aussi originales.

Disque

Salvatore Sanfilippo, alias @antirez, le créateur de Redis nous a parlé de Disque, un système de message queuing sur la même architecture que Redis.

Il s'est mis à écrire un système de message queue car beaucoup de personnes utilisaient Redis pour ça, bien qu'il n'ait pas été pensé pour ce use-case. Il a donc décidé de prendre Redis, d'enlever tout ce qui en fait Redis, et de faire message queue system avec.

Disque

En gros on garde le squelette, la syntaxe, les protocoles, les helpers de Redis, mais on n'en fait plus un key/value store. Ici encore, tout reste en mémoire et la persistance est en option.

Un message queue system doit gérer un type de données très particulières, ce qui le rends plus facile à développer qu'une DB qui peut accueillir n'importe quel type de données. Le but premier dans Disque est de pouvoir faire un système d'exécution de job complètement asynchrone.

On a donc un producer, qui peut envoyer des messages (ou jobs) à une queue. La queue réponds dès qu'elle a reçu le message, mais n'exécute pas le job tout de suite. Elle l'exécutera quand elle pourra.

N'importe quel type de message peut être envoyé: une image, un json, un format propriétaire, ça n'a pas d'importance car c'est transféré comme du binaire.

Disque peut processer plusieurs queues en même temps, il suffit d'indiquer sur quelle queue on veut envoyer son message quand on exécute ADDJOB. On peut ensuite aussi récupérer la liste des jobs en attente sur une ou plusieurs queues avec GETJOB.

On a donc d'un coté un producer qui envoie des ADDJOB et de l'autre un listener qui va poller Disque régulièrement avec des GETJOB. Le listener va ensuite effectuer le job en question, et quand il a fini, il renvoie un ACKJOB à Disque avec l'id du job pour dire que celui-ci est exécuté, ce qui va le supprimer de la queue.

Du coup, il est tout à fait possible qu'un même job soit lu par plusieurs listeners si jamais plusieurs listeners le pollent le temps qu'il soit exécuté. C'est voulu, et cela permet de faire plusieurs services qui vont chacun avoir une tache à faire sur un même job.

Néanmoins, il est aussi possible de définir un TTL quand on envoie un ADDJOB pour indiquer la durée de vie du job. Si au bout du timeout il n'a toujours pas reçu de ACK, on le supprime quand même de la queue.

Disque est donc très simple, il fait ce sur quoi il s'engage (une simple pile de job qu'on peut push et pull). Par contre, il a des limitations clairement définies: l'ordre de traitement des jobs n'est pas assuré. Disque fait ça en mode best effort seulement.

Dans les prochaines features, il prévoit d'ajouter en plus du ACK un NACK pour dire que le job a été traité, mais a foiré. Dans le même ordre d'idée, il va ajouter de meilleurs options de debug et d'introspection. Pour le moment, Disque est encore techniquement dans une version instable mais déjà utilisée en production par quelques boites.

Your infrastructure is not a unique snowflake, and that's ok

Dernier talk de la journée, et celui qui m'a paru le plus intéressant, par Jeremy Edberg. C'est pour moi le talk a montrer à toute personne qui fait de l'IT aujourd'hui. C'est un excellent récapitulatif de ce qu'est l'état de l'art en 2015 quand on doit gérer un site/app online de taille respectable.

Il commence par introduire son sujet en nous parlant de l'overhead qu'une boite produit automatiquement, c'est à dire tout ce que font ses employés qui n'apporte pas réellement de valeur directe à l'entreprise. C'est bien souvent du temps perdu dans des process manuels.

J'ai eu un peu de mal à suivre la logique de son plan au début, ça semblait assez décousu, mais c'est devenu plus clair dans la seconde partie.

On se pose pas mal de questions dans la mise en place d'une infra, et dans son administration. La difficulté n'est pas de trouver les réponses à ces questions, mais de savoir poser les bonnes.

Par exemple, après un incident, on ne cherche pas à savoir qui est à blâmer pour un problème, mais à comprendre ce qui a pu mal tourner. Ainsi, on évite que ça se reproduise, plutôt que de faire peur aux gens et les empêcher de prendre des initiatives.

Une autre question qui revient fréquemment, c'est le "Build or Buy?". Il y a sans doute un outil déjà existant qui fait 90% de ce qu'on veut faire, mais qui va être difficile à utiliser pour les 10% restants. Ou alors on peut subir le Not Invented Here syndrome et faire ce qui nous convient parfaitement (parce qu'on aime bâtir des choses), qui fera exactement les 10% qui nous manquent, mais nous demandera de maintenir des tas de edge cases dans les 90% restant.

Idem, doit-on bâtir un monolithe ou des microservices. Les microservices permettent d'éviter les SPOF et de scaler plus facilement, mais un monolithe est quand même beaucoup plus facile et rapide à bâtir. La majorité des gros services aujourd'hui ont commencé par du monolithique avant de bouger petit à petit vers des microservices: Netflix, AirBnB, Pinterest, etc.

Car quand on part sur des microservices, le plus difficile ce n'est pas de bâtir les services, c'est de bâtir la plateforme sur laquelle ils se trouvent, qui leur permet de communiquer entre eux. Il faut savoir arrêter de développer la plateforme quand elle est "good enough" pour soi. Il n'y a que les providers de services en SaaS qui doivent continuer à améliorer constamment leur plateforme pour en faire un élément différenciant. Quand c'est pour un usage uniquement interne, inutile d'en faire trop.

Et en plus, vous et vos concurrents bâtissez à peu près le même type d'infra, ce qui est un gaspillage monstrueux car cela prends du temps et n'ajoute pas forcément grand chose comme valeur à votre core business. Chacun le refait parce que personne n'a trouvé quelque chose sur le marché qui corresponde à son besoin. Il y a bien Netflix qui mets à dispo en open-source pas mal de leurs projets, mais ça reste quand même très centré sur Netflix et difficilement utilisable dans un autre contexte.

Pour lui, une société qui se monte aujourd'hui doit commencer par sélectionner un des cloud providers existants (Google, Azure, Amazon et bâtir la première version de son infra dessus. Partir sur du hardware en 2015 quand on se lance est une énorme erreur, un gaspillage de temps et d'argent. Attention toutefois, le cloud est nimbé d'une aura médiatique qui semblent le rendre complètement magique. La vérité est entre les deux, tout n'est pas aussi rose, mais ces avantages sont indéniables.

Une fois qu'on a cette base d'infra, on va y poser dessus différents microservices qui vont s'occuper de différentes parties de notre système. Ils ne sont pas tous forcément visibles par nos utilisateurs, mais ils sont tous indispensables à la bonne marche du système. On parle ici de l'intégration continue, du logging, des metrics, de la gestion du trafic, de la découverte des ressources, des tests, de la sécurité et du stockage de nos données.

CI

Pour l'intégration continue on a le choix. Jenkins, TravisCI, TeamCity, CircleCI. Ils font tous à peu près la même chose, le choix de l'un ou de l'autre ne doit être drivé que par quelques questions: Est-ce qu'il peuvent être entièrement automatisés ? Est-ce qu'ils me produisent des objets directement utilisables ? Est-ce que je peux faire un déploiement en un clic ? Si un de ces système réponds Oui aux trois questions, ne vous posez pas plus de questions.

Logs

Les logs sont une grosse question. Il prennent rapidement beaucoup de place si on garde tout, et deviennent rapidement très difficile à exploiter. Pour détecter les problèmes sur le système, il est bien généralement inutile de les stocker éternellement. Il est plus intéressant de les étudier en temps réel que d'aller fouiller dans un gros historique.

À la rigueur on commence à les garder quand on détecte une erreur, mais jamais avant. Inutile de logguer tous les logs de succès, ce qui nous intéresse ce sont les logs d'erreur et surtout leur augmentation. On en extrait des metrics, mais on ne garde pas les logs raws.

Metrics

Justement, continuons sur les metrics. Au delà des metrics techniques récupérées par nos logs, on a aussi besoin de metrics de satisfaction humaine.

Le speaker nous donne un exemple sur le formulaire de recherche de Reddit. Ils avaient remarqué que leur recherche n'était pas géniale. Ils ont donc rajouté un formulaire de satisfaction ("Est-ce que vous avez trouvé ce que vous cherchiez. Oui / Non") avant de faire des modifs. Ça leur a permis de voir qu'elle était satisfaisante pour 70% des utilisateurs.

Ils ont ensuite commencé à changer le système, et ont laissé le formulaire trois mois après avoir tout remplacé, pour voir l'évolution. Ils s'en sortent avec une satisfaction de 90% à la fin. Et ce n'est qu'au bout de ces trois mois qu'ils annoncent officiellement sur le blog avoir tout remplacé.

Ils ont bien fait de ne pas prévenir en cours d'expérience qu'ils apportaient des modifications car les utilisateurs étaient alors persuadés que la modification avait été faite récemment, pas il y a trois mois. Les prévenir que la recherche allait changer aurait influencé leur choix et la satisfaction aurait été faussée.

Bref, mesurez avant, mesurez après, et seulement comme ça vous pourrez connaitre les impacts d'une modification.

Trafic

Le trafic entre chacun de vos services est très important. Si chaque service peut tout à fait fonctionner indépendamment, ce qui fait que votre infra fonctionne réellement ce sont les échanges entre chaque partie. Il vous faut des outils simples (comme des cartes mises à jour en temps réel) qui vous permettent de voir par où passe le trafic et où il peut être bloqué.

Il peut y avoir des centaines de raisons pour lesquelles le trafic entre deux parties de votre réseau peut être bloqué. Cela ne doit pas devenir un SPOF, mais vous devez être en mesure de rediriger ce trafic par d'autres endroits si nécessaire. Certes, ce sera moins rapide que de passer par la ligne directe initialement prévue, mais ça sera une infinité de fois mieux que de ne pas avoir de trafic du tout.

En plus de le voir, il vous faut aussi des outils qui peuvent rerouter le trafic bloqué par des routes accessibles, et si possible automatiquement, sinon au moins facilement.

Oh, et dernier conseil. Ne développez pas votre propre système de load-balancer, il y a tellement plus de subtilités que ce que vous pouvez imaginer. Utilisez HAProxy.

Service discovery

Bon, c'est bien cool tout ça. On a une carte de nos ressources, des lignes entre chaque service, la possibilité de rerouter le service en cas de problème, mais on fait comment pour mettre cette carte à jour quand de nouveaux serveurs arrivent ou disparaissent ?

Malheureusement, là dessus, il y a encore du boulot à faire. Les DNS restent la solution de facto pour tester si un service réponds, mais c'est encore assez pourri, du fait du cache (TTL) engendré par le protocole, qui n'est jamais réellement suivi à la lettre.

Zookeeper est une solution à ce problème, mais reste encore un SPOF. Netflix a mis au point une version open-source, nommée Eureka, qui est censé aider à la résolution de ce problème. Mais même de l'aveu du gars de Netflix, ça reste encore moyen, et surtout encore une fois très lié au use-case de Netflix.

Néanmoins, il nous laisse quelques petits tips sur la distribution des items au sein d'un shard. Si on a 3 serveurs pour stocker l'ensemble de nos objets, on peut hasher chaque objet en un ID unique et faire ensuite un modulo 3 sur ce hash pour savoir sur quel serveur le stocker. En faisant ainsi, on a réparti équitablement notre charge sur les 3 serveurs.

Malheureusement, cette approche est un peu naïve, car quand on va ajouter un nouveau serveur au pool et qu'on va devoir faire cette fois un modulo 4, on va devoir déplacer tous les items. Et il n'y a rien de plus couteux que de déplacer des items dans un shard. Non, à la place il vaut mieux utiliser un algo de consistent hashing qui distribue les éléments sur une horloge à 360°. Pour 3 serveurs, cela nous découpe notre cadran en tiers et si on ajoute un 4e serveur, on va avoir un quadrant découpé en 4 quarts. Seuls les éléments qui se trouvent aux bordures auront besoin d'être déplacés.

Test automatisés

Il ne faut pas se leurrer, tout va forcément péter à un moment ou à un autre. La devise chez Netflix est you don't know if you're ready to production unless you break it yourself, intentionnaly and repeatidly.

On est tous d'accord pour dire que faire des backups c'est bien, mais seulement si on vérifie qu'on est capable de les lire ensuite, sinon c'est complètement inutile. Pour la production c'est pareil, faire des fallbacks et éviter les SPOFS c'est bien, mais seulement si on pète la prod régulièrement pour vérifier.

On en revient donc comme souvent à la superbe idée des Chaos Monkeys. On casse des bouts de l'infra, régulièrement, de manière aléatoire, pour voir comment elle résiste, et on consolide là où on détecte des faiblesses.

Monkeys

Leur famille simiesque aime casser des choses à différents niveaux. Le Chaos Monkey va simplement tuer aléatoirement des instances en production. Cela permet de détecter les machins qui ont trop de responsabilités et sans qui le système ne peut même pas tourner en mode dégradé.

On passe ensuite au Chaos Gorilla qui va détruire des zones entières. Cela permet de tester les fallbacks des requêtes, vérifier que les nodes de rechange peuvent assurer la nouvelle charge qui leur arrive dessus et que les backups sont fonctionnels.

Au stade suprême, on a le Chaos Kong qui va détruire des régions entières, ce qui a pour effet de rediriger tout le trafic d'une zone vers une autre. Cela permet de vérifier que les load balancer fonctionnent bien, que le monitoring est capable de détecter les instances mortes et que tout passe bien au rouge sur les dashboard de monitoring. Ils le lancent automatiquement toutes les semaines, et cela passe globalement inaperçu.

Et enfin, un cousin éloigné de cette famille de singes, on a le Latency Monkey qui est encore plus vicieux. Il ne tue pas des instances ou des zones, il se contente juste de leur ajouter de la latence. Car c'est finalement assez facile de détecter un serveur mort, c'est bien plus dur d'établir un diagnostic quand il est lent de temps en temps. Définir qu'un serveur est mort dépends juste du destinataire (s'il ne réponds pas, il est mort), mais définir qu'un serveur est lent dépends à la fois du destinataire mais aussi de l'expediteur (on doit s'attendre à des temps de réponses plus longs quand deux serveurs sont géographiquement éloignés).

Security

La sécurité doit se trouver à deux niveaux primordiaux. Déjà, une sécurité au niveau applicatif. Les serveurs frontaux ne doivent pas exposer de failles de sécurité, le personnel doit être formé aux techniques de social engineering, etc. Mais même si (quand) quelqu'un parvient à s'introduire sur le réseau, il ne doit pas avoir un accès open-bar à tout ce qui traine. Chaque serveur ne doit avoir que des accès limités aux autres serveurs, limités par ce dont il a réellement besoin pour la bonne marche de son système.

Ici encore, le grand conseil est de ne pas développer ses propres systèmes de sécurité. La sécurité c'est un travail d'expert, c'est extrêmement complexe et un système de sécu mal pensé est plus dangereux que pas de système de sécu du tout.

Data

Il finit finalement assez rapidement sur la question du stockage des données. Il faut évidemment plusieurs versions de sa donnée, à la fois pour des backups, mais aussi des instances chaudes qui peuvent reprendre la main quand l'instance principale vient à lâcher. Pour se prémunir d'autres soucis typiquement humains et naturels, il faut aussi stocker sa donnée physiquement dans plusieurs datacenters, à des endroits différents du globe.

Conclusion

Au final, cette dernière présentation conclu à merveille ce dotScale 2015. Même si on y a trop parlé de base de données à mon gout, les quelques derniers REX donnent à réfléchir et permettent de voir les problèmes auxquels d'autres grosses boites sont confrontés et les solutions créatives qu'on est alors obligé de trouver.

Les infra des Géants du Web ont cette particularité d'être toutes complètement différentes car adaptées à des use-cases diamétralement opposés, tout en gardant un socle commun éprouvé durement de multiples fois qui devient maintenant juste du bon sens.

Final

Bref, dotScale c'est pas cher et c'est bon. Si vous êtes sur Paris, mangez en et venez voir comment ça se passe dans les coulisses des grands acteurs du Web.


Tags : #dotscale

Want to add something ? Feel free to get in touch on Twitter : @pixelastic