Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Nous vivons à une époque incroyable où vous pouvez connecter rapidement et facilement plusieurs outils open source prêts à l'emploi, les configurer avec votre « conscience éteinte » selon les conseils de stackoverflow, sans vous plonger dans les « lettres multiples », et lancer les mettre en exploitation commerciale. Et lorsque vous avez besoin de mettre à jour/d'étendre ou que quelqu'un redémarre accidentellement quelques machines, vous réalisez qu'une sorte de mauvais rêve obsessionnel a commencé dans la réalité, tout est soudainement devenu plus compliqué au-delà de toute reconnaissance, il n'y a pas de retour en arrière, l'avenir est vague et plus sûr, au lieu de programmer, élever des abeilles et faire du fromage.

Ce n'est pas pour rien que des collègues plus expérimentés, la tête parsemée de bugs et donc déjà gris, envisagent le déploiement incroyablement rapide de packs de « conteneurs » en « cubes » sur des dizaines de serveurs dans des « langages à la mode » avec support intégré de E/S asynchrones non bloquantes, souriez modestement. Et ils continuent silencieusement à relire « man ps », à se plonger dans le code source de « nginx » jusqu'à ce que leurs yeux saignent, et à écrire, écrire, écrire des tests unitaires. Les collègues savent que la chose la plus intéressante viendra quand « tout cela » sera un jour mis en jeu la nuit du réveillon du Nouvel An. Et ils ne seront aidés que par une compréhension approfondie de la nature d'Unix, de la table d'état TCP/IP mémorisée et des algorithmes de tri-recherche de base. Pour redonner vie au système au fur et à mesure que le carillon retentit.

Oh oui, j'ai été un peu distrait, mais j'espère avoir réussi à transmettre l'état d'anticipation.
Aujourd'hui, je souhaite partager notre expérience dans le déploiement d'une pile pratique et peu coûteuse pour DataLake, qui résout la majorité des tâches analytiques de l'entreprise pour des divisions structurelles complètement différentes.

Il y a quelque temps, nous avons compris que les entreprises ont de plus en plus besoin des fruits de l'analyse produit et technique (sans parler de la cerise sur le gâteau sous la forme de l'apprentissage automatique) et de comprendre les tendances et les risques - nous devons collecter et analyser de plus en plus de métriques.

Analyse technique de base dans Bitrix24

Il y a plusieurs années, parallèlement au lancement du service Bitrix24, nous avons activement investi du temps et des ressources dans la création d'une plate-forme analytique simple et fiable qui permettrait de détecter rapidement les problèmes d'infrastructure et de planifier l'étape suivante. Bien entendu, il était conseillé de prendre des outils prêts à l'emploi, aussi simples et compréhensibles que possible. En conséquence, Nagios a été choisi pour la surveillance et Munin pour l'analyse et la visualisation. Nous avons désormais des milliers de chèques en nagios, des centaines de cartes en munin, et nos collègues les utilisent avec succès chaque jour. Les métriques sont claires, les graphiques sont clairs, le système fonctionne de manière fiable depuis plusieurs années et de nouveaux tests et graphiques y sont régulièrement ajoutés : lorsque nous mettons en service un nouveau service, nous ajoutons plusieurs tests et graphiques. Bonne chance.

Prendre le pouls - Analyses techniques avancées

Le désir de recevoir des informations sur les problèmes « le plus rapidement possible » nous a conduit à des expériences actives avec des outils simples et compréhensibles - pinba et xhprof.

Pinba nous a envoyé des statistiques dans des paquets UDP sur la vitesse de fonctionnement de certaines parties de pages Web en PHP, et nous avons pu voir en ligne dans le stockage MySQL (Pinba est livré avec son propre moteur MySQL pour une analyse rapide des événements) une courte liste de problèmes et répondre à eux. Et xhprof nous a automatiquement permis de collecter des graphiques d'exécution des pages PHP les plus lentes auprès des clients et d'analyser ce qui pourrait conduire à cela - calmement, en versant du thé ou quelque chose de plus fort.

Il y a quelque temps, la boîte à outils a été complétée par un autre moteur assez simple et compréhensible basé sur l'algorithme d'indexation inverse, parfaitement implémenté dans la légendaire bibliothèque Lucene - Elastic/Kibana. L'idée simple d'un enregistrement multithread de documents dans un index Lucene inverse basé sur les événements dans les journaux et d'une recherche rapide dans ceux-ci à l'aide de la division par facettes s'est avérée très utile.

Malgré l'apparence plutôt technique des visualisations dans Kibana avec des concepts de bas niveau comme « seau » « coulant vers le haut » et le langage réinventé de l'algèbre relationnelle pas encore complètement oubliée, l'outil a commencé à bien nous aider dans les tâches suivantes :

  • Combien d'erreurs PHP le client Bitrix24 a-t-il eu sur le portail p1 au cours de la dernière heure et lesquelles ? Comprenez, pardonnez et corrigez rapidement.
  • Combien d'appels vidéo ont été passés sur des portails en Allemagne au cours des dernières 24 heures, avec quelle qualité et y a-t-il eu des difficultés avec la chaîne/le réseau ?
  • Dans quelle mesure les fonctionnalités du système (notre extension C pour PHP), compilées à partir des sources dans la dernière mise à jour du service et déployées auprès des clients, fonctionnent-elles ? Y a-t-il des erreurs de segmentation ?
  • Les données clients tiennent-elles dans la mémoire PHP ? Y a-t-il des erreurs concernant le dépassement de la mémoire allouée aux processus : « mémoire insuffisante » ? Trouver et neutraliser.

Voici un exemple concret. Malgré des tests approfondis et à plusieurs niveaux, le client, avec un cas très non standard et des données d'entrée endommagées, a reçu une erreur ennuyeuse et inattendue, une sirène a retenti et le processus de réparation rapide a commencé :

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

De plus, Kibana vous permet d'organiser des notifications pour des événements spécifiés et, en peu de temps, l'outil de l'entreprise a commencé à être utilisé par des dizaines d'employés de différents départements - du support technique et du développement à l'assurance qualité.

L'activité de n'importe quel service de l'entreprise est devenue pratique à suivre et à mesurer - au lieu d'analyser manuellement les journaux sur les serveurs, il vous suffit de configurer une fois l'analyse des journaux et de les envoyer au cluster élastique pour en profiter, par exemple, en contemplant dans le kibana tableau de bord le nombre de chatons à deux têtes vendus imprimés sur une imprimante 3D au cours du dernier mois lunaire.

Analyse commerciale de base

Tout le monde sait que l’analyse commerciale dans les entreprises commence souvent par une utilisation extrêmement active, oui, d’Excel. Mais l’essentiel est que cela ne s’arrête pas là. Google Analytics basé sur le cloud ajoute également de l'huile sur le feu : vous commencez rapidement à vous habituer aux bonnes choses.

Dans notre entreprise en développement harmonieux, des « prophètes » d'un travail plus intensif avec des données plus volumineuses ont commencé à apparaître ici et là. Le besoin de rapports plus approfondis et multiformes a commencé à apparaître régulièrement, et grâce aux efforts de gars de différents départements, une solution simple et pratique a été organisée il y a quelque temps - une combinaison de ClickHouse et PowerBI.

Pendant assez longtemps, cette solution flexible a beaucoup aidé, mais peu à peu, on a commencé à comprendre que ClickHouse n'est pas en caoutchouc et qu'on ne peut pas se moquer de cette façon.

Ici il est important de bien comprendre que ClickHouse, comme Druid, comme Vertica, comme Amazon RedShift (qui est basé sur postgres), sont des moteurs d'analyse optimisés pour des analyses assez pratiques (sommes, agrégations, minimum-maximum par colonne et quelques jointures possibles ), parce que organisé pour un stockage efficace des colonnes de tables relationnelles, contrairement à MySQL et aux autres bases de données (orientées lignes) que nous connaissons.

Essentiellement, ClickHouse n'est qu'une "base de données" plus volumineuse, avec une insertion point par point pas très pratique (c'est comme ça que c'est prévu, tout va bien), mais des analyses agréables et un ensemble de fonctions puissantes et intéressantes pour travailler avec des données. Oui, vous pouvez même créer un cluster - mais vous comprenez que marteler des clous avec un microscope n'est pas tout à fait correct et nous avons commencé à chercher d'autres solutions.

Demande de python et d'analystes

Notre société compte de nombreux développeurs qui écrivent du code presque tous les jours depuis 10 à 20 ans en PHP, JavaScript, C#, C/C++, Java, Go, Rust, Python, Bash. Il existe également de nombreux administrateurs système expérimentés qui ont connu plus d'un désastre absolument incroyable qui ne rentre pas dans les lois des statistiques (par exemple, lorsque la majorité des disques d'un raid-10 sont détruits par un violent coup de foudre). Dans de telles circonstances, pendant longtemps, ce qu’était un « analyste python » n’était pas clair. Python est comme PHP, seul le nom est un peu plus long et il y a un peu moins de traces de substances psychotropes dans le code source de l’interpréteur. Cependant, à mesure que de plus en plus de rapports analytiques étaient créés, les développeurs expérimentés ont commencé à comprendre de plus en plus l'importance d'une spécialisation étroite dans des outils tels que numpy, pandas, matplotlib, seaborn.
Le rôle décisif a très probablement été joué par l'évanouissement soudain des employés suite à la combinaison des mots « régression logistique » et à la démonstration d'un reporting efficace sur des données volumineuses en utilisant, oui, oui, pyspark.

Apache Spark, son paradigme fonctionnel sur lequel s'intègre parfaitement l'algèbre relationnelle, et ses capacités ont tellement impressionné les développeurs habitués à MySQL que la nécessité de renforcer les rangs auprès d'analystes expérimentés s'est imposée comme le jour.

Nouvelles tentatives de décollage d'Apache Spark/Hadoop et ce qui ne s'est pas déroulé tout à fait conformément au script

Cependant, il est vite devenu évident que quelque chose n'allait pas systématiquement chez Spark, ou qu'il était simplement nécessaire de mieux se laver les mains. Si la pile Hadoop/MapReduce/Lucene a été créée par des programmeurs assez expérimentés, ce qui est évident si vous regardez attentivement le code source en Java ou les idées de Doug Cutting dans Lucene, alors Spark, du coup, est écrit dans le langage exotique Scala, qui est très controversé du point de vue pratique et ne se développe actuellement pas. Et la baisse régulière des calculs sur le cluster Spark en raison d'un travail illogique et peu transparent avec l'allocation de mémoire pour les opérations de réduction (plusieurs clés arrivent en même temps) a créé autour de lui un halo de quelque chose qui a de la place pour se développer. De plus, la situation était aggravée par un grand nombre de ports ouverts étranges, des fichiers temporaires se développant dans les endroits les plus incompréhensibles et un enfer de dépendances jar - ce qui faisait ressentir aux administrateurs système un sentiment bien connu depuis l'enfance : une haine féroce (ou peut-être ils devaient se laver les mains avec du savon).

En conséquence, nous avons « survécu » à plusieurs projets analytiques internes qui utilisent activement Apache Spark (notamment Spark Streaming, Spark SQL) et l'écosystème Hadoop (et ainsi de suite). Malgré le fait qu'au fil du temps, nous avons appris à bien le préparer et à le surveiller, et que « cela » a pratiquement cessé de planter soudainement en raison de changements dans la nature des données et du déséquilibre du hachage RDD uniforme, le désir de prendre quelque chose de déjà prêt , mis à jour et administré quelque part dans le cloud, est devenu de plus en plus fort. C'est à cette époque que nous avons essayé d'utiliser l'assemblage cloud prêt à l'emploi d'Amazon Web Services - EMR et, par la suite, j'ai essayé de résoudre des problèmes en l'utilisant. EMR est Apache Spark préparé par Amazon avec des logiciels supplémentaires de l'écosystème, un peu comme les versions Cloudera/Hortonworks.

Le stockage de fichiers en caoutchouc pour l'analyse est un besoin urgent

L’expérience de « cuisiner » Hadoop/Spark avec des brûlures sur diverses parties du corps n’a pas été vaine. La nécessité de créer un stockage de fichiers unique, peu coûteux et fiable, résistant aux pannes matérielles et dans lequel il serait possible de stocker des fichiers dans différents formats à partir de différents systèmes et de créer des échantillons efficaces et rapides pour les rapports à partir de ces données, est devenue de plus en plus nécessaire. clair.

Je voulais également que la mise à jour du logiciel de cette plate-forme ne se transforme pas en un cauchemar du Nouvel An avec la lecture de traces Java de 20 pages et l'analyse de journaux détaillés d'un kilomètre de long du cluster à l'aide de Spark History Server et d'une loupe rétroéclairée. Je voulais avoir un outil simple et transparent qui ne nécessiterait pas de plongées régulières sous le capot si la requête MapReduce standard du développeur cessait de s'exécuter lorsque le travailleur de réduction des données tombait à court de mémoire en raison d'un algorithme de partitionnement des données sources pas très bien choisi.

Amazon S3 est-il un candidat pour DataLake ?

L'expérience avec Hadoop/MapReduce nous a appris que nous avons besoin d'un système de fichiers évolutif et fiable et de travailleurs évolutifs par-dessus, « se rapprochant » des données afin de ne pas les déplacer sur le réseau. Les travailleurs doivent être capables de lire des données dans différents formats, mais de préférence ne pas lire d'informations inutiles et être capables de stocker les données à l'avance dans des formats pratiques pour les travailleurs.

Encore une fois, l'idée de base. Il n'y a aucune volonté de « déverser » le Big Data dans un seul moteur analytique de cluster, qui tôt ou tard s'étouffera et vous devrez le partager de manière moche. Je souhaite stocker des fichiers, uniquement des fichiers, dans un format compréhensible et effectuer des requêtes analytiques efficaces sur eux à l'aide d'outils différents mais compréhensibles. Et il y aura de plus en plus de fichiers dans différents formats. Et il est préférable de fragmenter non pas le moteur, mais les données source. Nous avons besoin d'un DataLake extensible et universel, nous avons décidé...

Et si vous stockiez des fichiers dans le stockage cloud évolutif familier et bien connu Amazon S3, sans avoir à préparer vos propres côtelettes à partir de Hadoop ?

Il est clair que les données personnelles sont « faibles », mais qu’en est-il des autres données si nous les extrayons et les « exploitons efficacement » ?

Écosystème d'analyse de cluster-bigdata d'Amazon Web Services - en termes très simples

A en juger par notre expérience avec AWS, Apache Hadoop/MapReduce y est activement utilisé depuis longtemps sous diverses sauces, par exemple dans le service DataPipeline (j'envie mes collègues, ils ont appris à le préparer correctement). Ici, nous configurons les sauvegardes de différents services à partir des tables DynamoDB :
Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Et ils fonctionnent régulièrement sur des clusters Hadoop/MapReduce intégrés comme sur des roulettes depuis plusieurs années maintenant. "Réglez-le et oubliez-le":

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Vous pouvez également vous lancer efficacement dans le satanisme des données en configurant des ordinateurs portables Jupiter dans le cloud pour les analystes et en utilisant le service AWS SageMaker pour entraîner et déployer des modèles d'IA au combat. Voici à quoi cela ressemble pour nous :

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Et oui, vous pouvez vous procurer un ordinateur portable pour vous-même ou pour un analyste dans le cloud et le connecter à un cluster Hadoop/Spark, faire les calculs puis tout définir :

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Vraiment pratique pour les projets analytiques individuels et pour certains, nous avons utilisé avec succès le service EMR pour des calculs et des analyses à grande échelle. Qu’en est-il d’une solution système pour DataLake, est-ce que cela fonctionnera ? À ce moment-là, nous étions au bord de l’espoir et du désespoir et nous avons continué nos recherches.

AWS Glue - Apache Spark soigneusement emballé sous stéroïdes

Il s'est avéré qu'AWS possède sa propre version de la pile « Hive/Pig/Spark ». Le rôle de Hive, c'est-à-dire Le catalogue des fichiers et de leurs types dans DataLake est réalisé par le service « Data catalog », qui ne cache pas sa compatibilité avec le format Apache Hive. Vous devez ajouter des informations à ce service sur l'emplacement de vos fichiers et leur format. Les données peuvent être non seulement dans s3, mais aussi dans la base de données, mais ce n'est pas le sujet de cet article. Voici comment notre répertoire de données DataLake est organisé :

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Les fichiers sont enregistrés, super. Si les fichiers ont été mis à jour, nous lançons des robots d'exploration manuellement ou selon un calendrier, qui mettront à jour les informations les concernant à partir du lac et les enregistreront. Ensuite, les données du lac peuvent être traitées et les résultats téléchargés quelque part. Dans le cas le plus simple, nous téléchargeons également sur s3. Le traitement des données peut être effectué n'importe où, mais il est suggéré de configurer le traitement sur un cluster Apache Spark à l'aide de fonctionnalités avancées via l'API AWS Glue. En fait, vous pouvez prendre le bon vieux et familier code python en utilisant la bibliothèque pyspark et configurer son exécution sur N nœuds d'un cluster d'une certaine capacité avec surveillance, sans creuser dans les entrailles de Hadoop et sans faire glisser les conteneurs docker-moker et éliminer les conflits de dépendances. .

Encore une fois, une idée simple. Il n'est pas nécessaire de configurer Apache Spark, il vous suffit d'écrire du code python pour pyspark, de le tester localement sur votre bureau, puis de l'exécuter sur un grand cluster dans le cloud, en spécifiant où se trouvent les données sources et où placer le résultat. Parfois, cela est nécessaire et utile, et voici comment nous le configurons :

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Ainsi, si vous avez besoin de calculer quelque chose sur un cluster Spark en utilisant les données de s3, nous écrivons du code en python/pyspark, le testons et bonne chance pour le cloud.

Et l'orchestration ? Et si la tâche tombait et disparaissait ? Oui, il est proposé de faire un beau pipeline dans le style Apache Pig et nous les avons même essayés, mais pour l'instant nous avons décidé d'utiliser notre orchestration profondément personnalisée en PHP et JavaScript (je comprends, il y a une dissonance cognitive, mais ça marche, pour ans et sans erreurs).

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

Le format des fichiers stockés dans le lac est la clé de la performance

Il est très, très important de comprendre deux autres points clés. Pour que les requêtes sur les données des fichiers dans le lac soient exécutées le plus rapidement possible et que les performances ne se dégradent pas lorsque de nouvelles informations sont ajoutées, vous devez :

  • Stockez les colonnes de fichiers séparément (afin que vous n'ayez pas besoin de lire toutes les lignes pour comprendre le contenu des colonnes). Pour cela nous avons pris le format parquet avec compression
  • Il est très important de diviser les fichiers dans des dossiers tels que : langue, année, mois, jour, semaine. Les moteurs qui comprennent ce type de partitionnement examineront uniquement les dossiers nécessaires, sans passer au crible toutes les données d'affilée.

Essentiellement, de cette manière, vous présentez les données source sous la forme la plus efficace pour les moteurs d'analyse suspendus au-dessus, qui, même dans les dossiers fragmentés, peuvent saisir et lire de manière sélective uniquement les colonnes nécessaires des fichiers. Vous n'avez pas besoin de « remplir » les données n'importe où (le stockage va tout simplement éclater) - il suffit de les mettre immédiatement et judicieusement dans le système de fichiers au format correct. Bien entendu, il doit être clair ici que stocker dans DataLake un énorme fichier csv, qui doit d'abord être lu ligne par ligne par le cluster afin d'en extraire les colonnes, n'est pas très conseillé. Pensez à nouveau aux deux points ci-dessus si vous ne comprenez pas encore pourquoi tout cela se produit.

AWS Athena - le jack-in-the-box

Et puis, en créant un lac, nous sommes tombés par hasard sur Amazon Athena. Soudain, il s'est avéré qu'en organisant soigneusement nos énormes fichiers journaux dans des fragments de dossiers dans le format de colonne correct (parquet), vous pouvez très rapidement en faire des sélections extrêmement informatives et créer des rapports SANS, sans cluster Apache Spark/Glue.

Le moteur Athena alimenté par les données dans s3 est basé sur le légendaire Presto - un représentant de la famille d'approches MPP (traitement parallèle massif) du traitement des données, prenant les données là où elles se trouvent, de s3 et Hadoop à Cassandra et aux fichiers texte ordinaires. Il vous suffit de demander à Athena d'exécuter une requête SQL, et tout «fonctionne rapidement et automatiquement». Il est important de noter qu'Athena est « intelligent », il accède uniquement aux dossiers partitionnés nécessaires et lit uniquement les colonnes nécessaires à la requête.

La tarification des requêtes vers Athena est également intéressante. Nous payons pour volume de données numérisées. Ceux. pas pour le nombre de machines dans le cluster par minute, mais... pour les données réellement analysées sur 100 à 500 machines, uniquement les données nécessaires pour compléter la demande.

Et en demandant uniquement les colonnes nécessaires à partir de dossiers correctement partitionnés, il s'est avéré que le service Athena nous coûte des dizaines de dollars par mois. Eh bien, super, presque gratuit, par rapport aux analyses sur clusters !

Au fait, voici comment nous partageons nos données dans s3 :

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

En conséquence, en peu de temps, des départements complètement différents de l'entreprise, de la sécurité de l'information à l'analyse, ont commencé à adresser activement des demandes à Athena et rapidement, en quelques secondes, à recevoir des réponses utiles du « big » data sur des périodes assez longues : des mois, six mois, etc. P.

Mais nous sommes allés plus loin et avons commencé à chercher des réponses dans le cloud. via le pilote ODBC: un analyste écrit une requête SQL dans une console familière, qui sur 100 à 500 machines « pour quelques centimes » envoie des données à s3 et renvoie une réponse généralement en quelques secondes. Confortable. Et vite. Je n'arrive toujours pas à y croire.

En conséquence, après avoir décidé de stocker les données dans s3, dans un format de colonnes efficace et avec un partage raisonnable des données dans des dossiers... nous avons reçu DataLake et un moteur analytique rapide et bon marché - gratuitement. Et il est devenu très populaire dans l'entreprise, parce que... comprend SQL et fonctionne des ordres de grandeur plus rapidement qu'en démarrant/arrêtant/configurant des clusters. « Et si le résultat est le même, pourquoi payer plus ?

Une demande adressée à Athéna ressemble à ceci. Si vous le souhaitez, vous pouvez bien sûr en former suffisamment requête SQL complexe et multipage, mais nous nous limiterons à de simples regroupements. Voyons quels codes de réponse le client avait il y a quelques semaines dans les journaux du serveur Web et assurons-nous qu'il n'y a pas d'erreurs :

Comment nous avons organisé un DataLake très efficace et peu coûteux et pourquoi

résultats

Après avoir parcouru, pour ne pas dire, un chemin long mais douloureux, en évaluant constamment et adéquatement les risques, le niveau de complexité et le coût du support, nous avons trouvé une solution pour DataLake et l'analyse qui ne cesse de nous plaire tant par sa rapidité que par son coût de possession.

Il s'est avéré que la construction d'un DataLake efficace, rapide et peu coûteux à exploiter pour les besoins de départements complètement différents de l'entreprise est tout à fait à la portée de développeurs même expérimentés qui n'ont jamais travaillé en tant qu'architectes et ne savent pas comment dessiner des carrés sur des carrés avec flèches et connaissez 50 termes de l’écosystème Hadoop.

Au début du voyage, ma tête s'éloignait des nombreux zoos sauvages de logiciels ouverts et fermés et de la compréhension du fardeau de la responsabilité envers les descendants. Commencez simplement à créer votre DataLake à partir d'outils simples : nagios/munin -> elastic/kibana -> Hadoop/Spark/s3..., en collectant des commentaires et en comprenant profondément la physique des processus en cours. Tout est complexe et trouble - donnez-le aux ennemis et aux concurrents.

Si vous ne souhaitez pas accéder au cloud et souhaitez prendre en charge, mettre à jour et corriger des projets open source, vous pouvez créer un système similaire au nôtre localement, sur des machines de bureau peu coûteuses avec Hadoop et Presto en plus. L'essentiel est de ne pas s'arrêter et d'avancer, de compter, de chercher des solutions simples et claires, et tout s'arrangera à coup sûr ! Bonne chance à tous et à bientôt !

Source: habr.com

Ajouter un commentaire