[Case Locomizer] Comment accélérer le calcul d'une carte thermique de 20 000 fois en deux ans et demi

Cet article est une continuation de la série Case Locomizer, voir aussi


Bonjour

FDC: TC, EMR, IDEA

Savez-vous ce qu'est une autopsie? Ceci est une histoire sur la façon dont nous sommes arrivés à une telle vie.

Je ne suis pas sûr de vous, mais j'aime vraiment lire des histoires sur le processus de développement de logiciels hautement spécialisés ou de bas niveau. Les collègues peuvent avoir une idée intéressante avec laquelle travailler, et il est toujours curieux de suivre ce qui s'est passé avec le programme du prototype au produit mature, qui fait de la magie dans un domaine inconnu.

En outre, si je jette simplement un lien vers un référentiel avec un tel logiciel, il est peu probable que quiconque soit en mesure de savoir ce que c'est et pourquoi, et pour quelles tâches il peut être utile. Même si je traduis de l'anglais trois douzaines de pages d'instructions pour commencer. Néanmoins, le framework Spark n'est pas simplement un autre métier sur l'angulaire, il faut comprendre que les auteurs ont fumé pourquoi il était écrit de cette façon et non autrement.

Cet article est une introduction historique à One Ring. Il n'y a pas de code et l'histoire est plus populaire que scientifique. Mais seulement sur le développement, et sur rien d'autre, à l'exception de deux ans et demi de développement.

La dernière fois, j'ai parlé suffisamment en détail (j'espère assez) des difficultés d'extraire des données à partir d'ensembles de données anonymisés dans la voie du milieu, et finalement j'ai rattrapé une intrigue pas faible. Laissons sa résolution pour la dernière fois, et aujourd'hui nous parlerons du long et difficile chemin vers la perfection de notre outil principal:

  • Les mégadonnées sont grandes
  • Notre cas n'est pas standard
  • Prototype en C # et PostGIS
  • Première approche de Hadoop MapReduce
  • L'avènement de CI et Spark
  • Troisième approximation à GeoSpark
  • Analystes japonais et migration d'Azure vers AWS
  • Ash Nazg Durbatuluk, Ash Nazg Gimbatul, Ash Nazg Trakatuluk, Ag Burzum Ishi Krimpatul !!
  • Optimisation et géocatarse avec Uber H3
  • Dehors tout blanc

Les mégadonnées sont grandes


Le Big Data n'est pas une question de taille.

Il peut y avoir des dizaines, voire des centaines de millions d'enregistrements dans un ensemble de données mensuel dans la région du Grand Londres, mais ce n'est pas beaucoup. Une seule itération sur eux du début à la fin repose sur la vitesse de lecture linéaire du disque. Si le disque est un SSD, cela prendra quelques secondes.

(Je vous rappelle que l'ensemble de données en question est un ensemble de fichiers CSV avec un ensemble de champs spécifiques au fournisseur. Le regroupement des enregistrements avec les coordonnées des utilisateurs anonymes dans un fichier se produit le long des frontières de la région administrative du pays, de la préfecture ou de la ville. Les fichiers eux-mêmes sont générés sur la date sélectionnée, quotidienne ou mensuelle.Plus de détails sont tous décrits dans la partie précédente, exécutez-la en diagonale s'il n'y a pas assez de contexte.)

Notre processus est en plusieurs étapes. Les heuristiques initiales d'enrichissement des données brutes qui fonctionnent uniquement dans un seul mode d'itération sont rapides, et vous pouvez les écrire au moins en Python, au moins en C ++, même en PHP. Même sur une machine faible, le traitement sera rapide.

Si l'ensemble de données se trouve quelque part dans le cloud, à condition que le gestionnaire soit placé dans le même cloud, il n'y a pas de problème particulier pour y accéder, passer en revue et enregistrer le résultat à côté de lui. De plus, généralement, le fichier est déjà là, car les fournisseurs de données téléchargeront avec grand plaisir l'archive sur votre stockage cloud, ce qui vous donnera un lien de téléchargement. Il ne reste plus qu'à déployer la machine virtuelle, et toutes les bibliothèques pour accéder au référentiel seront soigneusement placées par le vendeur dessus, toutes les clés d'accès sont enregistrées, il suffit de saisir l'API entre vos mains et de l'utiliser. Ce sera rapide aussi.

Eh bien, avec les premiers pas, tout est clair. Ils ont pris le fichier, l'ont parcouru plusieurs fois, ont remis la version traitée. Mais que se passe-t-il si les étapes suivantes de notre algorithme nécessitent un ensemble de calculs légèrement plus complexes pour chaque enregistrement?

Prenez quelque chose comme déterminer la distance entre une paire de coordonnées. Il existe une méthode Haversine extrêmement rapide («haversinus» selon la version de la salle), qui donne une précision acceptable à courte distance, et permet de ne pas prendre le géoïde WGS84 , dont le calcul fonctionne beaucoup plus lentement.

En soi, un tel calcul, s'avère-t-il, ne coûte pas si cher s'il est unique. Et même s'il y en a des dizaines de millions, c'est, en principe, un non-sens.

Et maintenant, nous prenons le cas de notre algorithme breveté, lorsque nous devons calculer la distance de chaque signal à chaque POI de la catégorie sélectionnée, et rejeter ceux qui sont supérieurs à un demi-kilomètre (une telle distance qui est facile à parcourir).

Pour la région du Grand Londres, environ un million d'établissements font partie des PI ciblés dans la catégorie des magasins et des points de vente. Et comme je l'ai dit, dans les dizaines de jeux de données mensuels, des centaines de millions d'enregistrements viennent pour lui. Et nous obtenons donc ...

1000000 POI × N, 000,000 signaux = N, 000,000,000,000 distances.


Oh, viens. Des milliers de milliards de calculs de distance et de comparaisons de constantes de seuil.

La situation classique avec le produit cartésien . Deux ensembles peu puissants individuellement donnent facilement N × 10 12 résultats intermédiaires, et ce n'est que d'un mois dans une région! Un tel montant se transforme déjà en qualité. Non seulement la taille du résultat intermédiaire est déjà un problème grave, car il ne rentre pas entièrement dans la mémoire, et il est nécessaire de le traiter immédiatement sur le lieu de réception, mais le nombre de calculs nécessaires pour l'obtenir prend trop de temps informatique. Et si, pour un seul enregistrement, compte tenu de tous les retards de transmission sur le réseau et d'autres frais généraux, seules 100 nanosecondes sont dépensées, alors des millions de secondes sont des jours et des semaines de calculs dans un flux.

Ou, si nous devons éliminer un segment de la population générale, par exemple, la condition "ne prend pas en compte les intérêts des utilisateurs qui vivent dans une certaine zone", alors nous devrons comparer le device_id de chaque enregistrement de l'ensemble de données enrichi de la région entière avec un ensemble dans lequel des centaines de milliers d'enregistrements avec exclu les résidents de device_id de cette zone. Et ce sont des comparaisons de chaînes à bien des égards, pas aussi rapides que pour deux pouces. Encore une fois, il y a une sorte de nombre insensé de zéros dans l'évaluation d'une opération simple, et nous les avons pour un ensemble complet d'heuristiques pour un projet moyen avec une douzaine, voire plus.
Les mégadonnées sont des données qui, en raison de leur taille, rendent nécessaire l'utilisation de techniques algorithmiques spéciales en raison de l'inadéquation ou de l'impraticabilité du traitement direct.

... même si le résultat final du calcul s'effondre dans un seul écran du tableau Excel.

Vous pouvez essayer de paralléliser le gestionnaire «naïf» par le nombre de processeurs virtuels disponibles sur la machine sur laquelle nous effectuons le calcul. Vous pouvez diviser l'ensemble de données en morceaux et exécuter le calcul des strass sur une douzaine de machines virtuelles dans le cloud. Mais tout cela ne donnera pas un résultat qualitativement excellent. La mise à l'échelle "en largeur" ​​donne des rendements décroissants à partir d'une certaine largeur. Et le problème de la synchronisation et du partitionnement sortira certainement, et la gestion de toute une flotte de machines virtuelles coûtera du temps et de l'argent. Les garder allumés tout le temps coûte cher, et démarrer et arrêter à la demande demande beaucoup de travail.

Par conséquent, pour les mégadonnées, des systèmes logiciels spéciaux de l'écosystème Hadoop, qui ont déjà des contrôles d'échelle, sont utilisés, ainsi qu'un ensemble spécial d'algorithmes qui permet au mammouth de manger en petites portions sans risque d'étouffer la quantité astronomique de données intermédiaires et simplifie considérablement la vie d'un développeur de mégadonnées. Mais vous ne pouvez pas simplement utiliser Hadoop et commencer à l'utiliser. Vous devez d'abord faire un plan.

Surtout si ...

Notre cas n'est pas standard


Si vous demandez comment les bureaux impliqués dans l'analyse de grands ensembles de données construisent leurs processus, il s'avère que deux approches principales sont utilisées dans la pratique mondiale.

Approche numéro 1. Lac de données


Pour les données qui s'accumulent au fil du temps et restent pertinentes pour toujours, un type spécial de stockage est conçu, le « lac de données ».

L'architecture de ces référentiels est optimisée pour un accès aléatoire rapide. De nombreux ensembles de données collectés sont traduits dans un format spécialisé qui vous permet d'effectuer rapidement des sélections multicritères et des tranches par ensembles de colonnes. Contrairement aux bases de données relationnelles et documentaires traditionnelles, le stockage en colonnes est utilisé dans les lacs de données. Habituellement, ils sont définitifs, c'est-à-dire que le format des conteneurs contenant les données est tel qu'après remplissage et indexation, les données du même ensemble de données ne changent plus jamais. Par exemple, les fichiers parquet qui ne nécessitent pas de modification.

Après cela, une foule de data- satanistes ou de data- analystes se précipitent, et dans des logiciels spécialisés («ordinateurs portables» comme Jupyter) collectent des statistiques, des indicateurs, etc. en ligne. Ces statistiques sont déchargées du lac quelque part vers l'extérieur, ou simplement ajoutées ensemble sous la forme des mêmes fichiers finaux pour une agrégation ultérieure.

Approche numéro 2. Streaming de données


Pour les données qui arrivent en temps réel et doivent être traitées rapidement (c'est-à-dire les données en streaming), des bus de données ou des files d'attente de messages sont conçus.

Dans une infrastructure avec un bus de données, il y a des générateurs à une extrémité et des consommateurs à l'autre, et les flux de données eux-mêmes sont composés d'événements.

Des générateurs sont générés, et les consommateurs, en temps réel ou presque réel, analysent les événements, accumulant des résultats finaux, qui peuvent à nouveau générer des événements que le prochain ensemble d'agrégateurs consommera via le même bus, et ainsi de suite jusqu'à ce que le résultat final soit obtenu, plié dans le référentiel des résultats finaux.

Il est piloté par Apache Kafka et un stockage rapide comme Aerospike.

Notre cas


Mais notre cas ne rentre pas dans ces deux approches.

Tout d'abord, cela n'a aucun sens pour nous de conserver un lac de données, car l'ensemble de données dure rarement plus d'un an (les pistes utilisateur pour 2016 en 2019 ne sont plus nécessaires à personne), et chaque fois que les clients ont besoin d'une partie complètement imprévisible de toutes les données accumulées. De plus, étant donné que pour chaque segment de la population et de la catégorie, son propre modèle est créé, nous sommes toujours obligés de ne prendre que la pièce requise, et les fusionner dans un lac commun n'a pas beaucoup de sens. Il est plus facile de conserver chaque jeu de données mensuel dans sa forme d'origine - les fichiers CSV dans son propre répertoire distinct. Le chemin d'accès au fichier est obtenu ... / provider / country / region / subregion / year / month / dataset files, et un sous-ensemble est sélectionné simplement par le masque de nom de fichier, par exemple, ... / Tamoco / UK / Greater_London / * / 2019 / {6, 7.8} / *. Csv.

Deuxièmement, la nature des ensembles de données est discrète et non en continu. Bien sûr, on pourrait, bien sûr, calculer directement certains indicateurs directement dans le processus de téléchargement sur le stockage du réseau, mais les cartes thermiques finies pour la région de Moscou et la région voisine de la région de Moscou ne sont pas en corrélation avec la carte thermique finale de la région combinée de Moscou et de la région ( en raison du fait que trop de personnes vivent dans la région et travaillent à Moscou), et nous ne savons toujours pas à l'avance de quelle région nous aurons besoin. Peut-être ni Moscou, ni la région de Moscou, mais juste une ville 17. Il est très coûteux de conduire des heuristiques et de calculer des indicateurs pour tous les ensembles de données.

Par conséquent, nous devons sélectionner rapidement un sous-ensemble des ensembles de données accumulés, déployer rapidement une batterie de serveurs informatique qui convient à l'alimentation, effectuer rapidement un processus de calcul unique mais standardisé, cracher le résultat et ... peut-être ne plus jamais revenir à un sous-ensemble ou à une batterie de serveurs de cette taille , pas au modèle. Et nous ne pouvons absolument pas conserver un cluster de performances bien réglé sur notre propre matériel, ce qui couvrirait les besoins de tous nos projets, du plus petit au plus difficile, car ils sont trop différents.

Je ne pense pas que nous soyons si uniques. Dans les conversations avec des collègues, le besoin d'instrumentation de cas similaires éclatés apparaît régulièrement, mais ici, chacun construit le processus à sa manière. Habituellement, des solutions pour les cas non standard sont attachées au convoyeur existant à partir des approches n ° 1 ou n ° 2 sur le côté; notre processus est entièrement composé de projets privés, nous avons toutes les tâches comme "burst".

Et bien maintenant. Pendant deux ans et un sou, nous avons pu proposer une trousse à outils pour automatiser autant que possible mon travail, et c'est précisément cela que je présenterai pour une utilisation générale dans la troisième partie de mon histoire. En attendant, parlons de l'évolution, et de toutes ces erreurs et problèmes, corrigeant et résolvant que nous sommes arrivés à un processus durable par expérience.

Prototype en C # et PostGIS


Tout a commencé il y a quelques années. Deux mecs très intelligents nommés Alexei Polyakov et Alexei Polyakov - ne riez pas, ils sont en fait homonymes, mais de différentes parties du monde - un biologiste et un spécialiste du marketing, ils ont décidé d'appliquer la méthode de la dissertation sur le comportement collectif des populations cellulaires dans les cultures cellulaires, testée expérimentalement sur des souris. , à la publicité et au marketing.

Cela a fonctionné sur les gens.

Et puis le projet Locomizer est né. Je dis «projet» parce que c'est comme une startup avec une LLC pour conclure des contrats, mais pas tout à fait. Les membres de notre équipe sont dispersés dans le monde entier, travaillent dans différents endroits et bureaux en tant que pigistes ou sous-traitants (et pas tous à temps plein), et nous utilisons nos algorithmes pour des clients très différents avec différents modèles d'interaction lorsque nous recevons ou trouvons des commandes. Il existe des abonnements, mais des tâches ponctuelles plus privées.

Mais c'est tout de suite. Et il y a quelques années, tout était encore plus chaotique. Qui a écrit la première implémentation logicielle pour calculer la vitesse, je ne sais généralement pas. (Si vous connaissez soudain ces héros inconnus, dites-leur bonjour.) À la fin de mon dernier article sur la carrière d'un programmeur dans une ville particulière, j'ai écrit littéralement ce qui suit: «Je suis venu parler à l'endroit où je travaille maintenant, et PM a déclaré dès le seuil que le projet est infernal. Rien. Encore une fois, SIG, seuls les calculs sont tous basés sur MapReduce (et je le veux sur Spark), les cartes sur ArcGIS, et tout cela tourne dans des nuages ​​que personne ne peut imaginer. À mon avis, une excellente option! »- à ce moment-là, c'était déjà comme ça, et je ne peux que restaurer la toute première étape du développement du projet en code à partir des souvenirs de mitra_kun , qui lui-même est apparu sur le projet un an plus tôt.

Les heuristiques rudimentaires pour le traitement des ensembles de données brutes ont été écrites en PHP, Python et C ++, et le calcul principal de la vitesse pour la carte de chaleur a été effectué par un programme en C #.

L'ensemble du projet en C #

Elle a travaillé comme ça:

  1. Tout d'abord, nous lisons directement la chaîne dans le tableau à partir du fichier d'ensemble de données.
  2. Exécutez-le foreach'em, créez une table de hachage sur polzakz.
  3. La base POI est une table littérale dans une base de données PostgreSQL avec des champs PostGIS de type GEOMETRY, et pour calculer la distance entre chaque signal utilisateur et chaque POI, la fonction ST_DISTANCE est tirée à travers un petit stockage , le résultat est ajouté à une table de hachage avec une clé pour chaque utilisateur.
  4. Ensuite, nous procédons à chacun sur la table avec l'accumulation du résultat du score d'intérêt pour chaque clé du tableau.
  5. Encore une fois, groupe, pour chaque catégorie.
  6. Après la fin du calcul, qui prend entièrement de quelques heures à une semaine, le résultat est ajouté au fichier CSV ...
  7. ... puis traitées manuellement, superposées à la carte et visualisées dans ArcGIS .

Il est clair que le volume maximum traité est limité par la mémoire disponible sur la machine, et la vitesse des requêtes uniques vers la base de données provoque une certaine alarme.

Première approche de Hadoop MapReduce


Quelque chose a été calculé sur le prototype local, la pertinence des traitements appliqués pour la préparation des jeux de données et la construction des cartes thermiques a été testée, et la question s'est posée de savoir comment mettre les travaux en service. Eh bien, il est important de ne pas traiter le coucher de soleil manuellement, mais d'utiliser les capacités de certaines plates-formes, de préférence écrites par les baleines de l'industrie, et d'évoluer au moins au minimum.

Comme je l'ai dit, la plate-forme standard de traitement des mégadonnées est l'écosystème Hadoop. Un large éventail de bibliothèques hétérogènes, y compris un système de fichiers distribué, des shedulers pour les tâches de parallélisation, des abstractions relativement pratiques sur la réduction de carte, des moteurs pour exécuter des requêtes et même un tas de choses pour l'analyse des données. Et toute cette infrastructure logicielle est disponible dans les nuages ​​auprès de différents fournisseurs sous la forme de packages intégrés, et elle sera automatisée, mais plus à ce sujet plus tard.

Ok Google, recherche Hadoop.Mes prédécesseurs ont pris le prototype et réécrit le calcul principal de C # en Java, remplaçant littéralement tous foreach par le mappeur et réducteur Hadup correspondant, et ont pris toutes les mesures pour préparer et enrichir des ensembles de données dans des utilitaires séparés dans des langages de script pour se développer plus rapidement, car avec l'avènement de différents les algorithmes clients ont commencé à évoluer activement. Nous avons commencé à écrire séparément le backend pour l'interface utilisateur Web au printemps (ce n'est pas la meilleure solution, s'il n'y a pas d'expérience de développement Java précédente, il serait préférable d'écrire en PHP), avec un front sur Node.js avec l'intégration de cartes d'ArcGIS.

Une petite partie d'un projet Java

Ils ont soulevé le «grand cluster» de Hadoop sur cinq machines virtuelles dans Microsoft Azure pour ce cas. Pourquoi Azure Premièrement, pour les startups, il y a une grande remise pour les premières années. Deuxièmement, ArcGIS Desktop pour Windows pour la visualisation des cartes était déjà déployé dans ce cloud.

Le cluster Hadoop a été déployé manuellement, et non à partir du service Azure HDInsight correspondant, qui était difficile à configurer. Sur chacune des machines du cluster, ils ont soulevé Postgre + PostGIS (une décision plutôt douteuse, car MR et la base commencent à se battre pour le processeur) afin de ne pas parcourir de distances vers un serveur séparé. Nous avons fait un petit script qui a dispersé des répliques de la base de données POI à travers les nœuds du cluster.

Le projet était encore un prototype, juste un peu plus avancé. PostGIS était toujours utilisé parce que le geofencing est apparu, et les gars ne savaient pas encore comment le mettre en œuvre avec un minimum de travail. C'était comme si tout était terriblement lent, et le nombre d'étapes à effectuer manuellement dépassait une douzaine et demie.

C'est à ce moment que je me suis intéressé à une proposition d'un peu connu de notre petite mais très ville informatique (il y a plus de sept douzaines de bureaux à Izhevsk avec des équipes de développement, où travaillent environ trois mille programmeurs), un bureau avec un nom absolument générique "Russian Information Technologies", Soudain, sans raison, il a fallu un développeur Java senior avec une vaste expérience dans le déploiement et l'automatisation, et au moins j'ai entendu parler du Big Data et des nuages ​​du fond de mon oreille. Eh bien, au moment où j'ai entendu un peu parler des nuages ​​et des mégadonnées.

Comme pour tout le reste, j'ai plus qu'assez d'expérience :( Par conséquent, la première chose que j'ai dite quand j'ai vu le code et l'état des processus était dans les meilleures traditions d'Artemy Lebedev, fort et beaucoup. Je ne le répéterai pas.

Eh bien, si le code et les processus sont de qualité compréhensible, ils ont certainement une place pour l'optimisation. Pour commencer, vous pouvez au moins envoyer des demandes à PostGIS une à la fois, mais par lots, environ 5000 points à la fois. Les bases de données sont, en règle générale, bien optimisées pour la résolution des produits cartésiens. Il est dit - fait, le stockage avec l'appel ST_DISTANCE a été réécrit de manière à renvoyer immédiatement un grand tableau pour un ensemble de points, et à partir de zéro, le calcul a été accéléré immédiatement de 40 fois, car maintenant il n'était plus nécessaire d'établir une connexion à la base de données si souvent, et autant d'index sur la géométrie dans la table avec POI a commencé à travailler avec beaucoup de sens.

Certes, une méchante erreur ésotérique s'est glissée dans le calcul, du fait que le prototype n'était pas complètement correctement porté de C # vers Java. Les gars ont raté le point d'une variable importante, et les savoirs traditionnels formels sur le prototype ne les ont pas du tout atteints, perdus quelque part en cours de route. Ensuite, nous avons restauré tous les algorithmes à partir de descriptions fragmentaires, mais c'était déjà bien plus tard. Cependant, cette erreur dans son ensemble n'a pas gâché le résultat du calcul, elle a simplement réduit le contraste de la carte thermique.

Mais vous n'obtiendrez pas beaucoup de performances de MapReduce, car le mappeur lit les données de HDFS et les réécrit, et le réducteur suivant de la chaîne fait de même, et ainsi de suite jusqu'à ce que toutes les étapes soient terminées. Il est également très gênant de gérer un processus en plusieurs étapes, surtout si l'algorithme a des branches en raison de paramètres. L'algorithme entier est un code dur, et si vous voulez réorganiser les étapes, vous devez les déplacer dans des modules séparés avec votre propre lanceur et envelopper une sorte de logique à l'extérieur.

Eh bien, extraire PostGIS à partir du calcul, même si vous dupliquez la base de données sur chaque nœud du cluster, est toujours une idée très douloureuse.

L'avènement de CI et Spark


- Automatisez-le! - mon deuxième grand point d' intérêt de rofessionalny après enterprayznogo n rogrammirovaniya sur un crapaud ... Et non. Deuxièmement - il est n itstsa, n ASTA et n udingi, alors qu'il y ait un troisième - est n arrêt n processus et leur automatisation. (Moi, en tant que chef p ovar, j'aime que tout soit sur les cookies p . Hashtag # p .)

Le travail manuel comporte trop de dangers. Les gens ne sont pas fiables et font souvent des erreurs, même s'ils font la même chose, il est donc beaucoup plus efficace de passer un peu de temps à formaliser le flux global du projet et à écrire un script qui n'échouera pas lors de l'appel de l'utilitaire pour copier le jeu de données du stockage à long terme vers le stockage en ligne, et ne mélangera pas l'ordre des étapes, que de continuer à marcher le râteau.

La marche du râteau était juste le problème le plus grave qui devait être résolu en premier. Tout d'abord, je me suis déployé dans une petite équipe virtuelle séparéeet configuré l'assembly avec l'exécution de tous les tests afin que l'artefact vérifié soit toujours à portée de main et qu'il ne doive pas être jeté manuellement sur le cluster. La deuxième étape consistait à écrire un wrapper pour démarrer une tâche MR avec l'ensemble de données spécifié et l'ensemble de paramètres sur le cluster directement à partir du même TC, avec la même copie automatique des ensembles de données d'origine vers le cluster et les résultats du calcul dans le magasin de résultats.

Et la troisième étape, qui a pris beaucoup de temps par habitude, a été d'automatiser le déploiement du cluster lui-même, de régler ses paramètres et de démarrer le calcul sur un ensemble de données intégré à Azure Blob Storage. Tout à coup, il y a eu des projets pour lesquels un cluster statique de cinq machines virtuelles a commencé à manquer et / ou dont les ensembles de données ne devraient pas être mélangés avec un vidage d'anciens fichiers sur HDFS.

Azure HDInsight est en faitHortonworks HDP (reste en paix pour lui), et certains de ses paramètres sont définis dans l'API, et certains ne peuvent être enregistrés que via Ambari. Le déploiement d'un cluster en fonction de la charge du cloud peut prendre jusqu'à une heure, et le cycle de réglage, c'est-à-dire la vérification de l'effet de tout ensemble de paramètres sur les performances de notre code, peut prendre une journée entière. La version locale de HDP Sandbox dans la machine virtuelle mange 11 Go de RAM, et elle est monstrueusement exigeante sur le sous-système de disque, donc même le débogage local est extrêmement désagréable et ses paramètres sont légèrement différents de la version cloud. J'ai pris beaucoup de temps pour les expériences, mais au moins j'ai compris comment tout cela fonctionne et que faire si le calcul se bloque soudainement au milieu avec le prochain MOO, car il est également assez désagréable d'analyser les journaux manuellement.

Pendant que je parlais de HDP, un autre programmeur a commencé à unifier les différentes étapes de la préparation des jeux de données sur Apache Spark. Spark a résolu le problème d'écrire / lire constamment des données intermédiaires qui se produisent entre les étapes d'un calcul, et en général, il est conçu en tenant compte de tous les mauvais endroits de la RM, et peut le faire plusieurs fois hors de la boîte. Et le RDD paresseux de Spark est une chose très pratique.

Dans le même temps, j'ai écrit des scripts Azure Templates sur PowerShell pour configurer le nœud de périphérie pour PostGIS - une instance distincte dans le cluster, avec un tas de cœurs et de mémoire pour accélérer les demandes, ainsi qu'une série d'étapes préliminaires pour la préparation des ensembles de données, qui ont d'abord été placés sur son disque local, et puis chargé dans HDFS sur le cluster.

Ainsi, la liaison de script, qui pensait initialement qu'elle fonctionnerait à la fois de manière interactive et en mode batch sur TC en tant que build distinct, a progressivement appris à lancer une combinaison arbitraire d'étapes sur MR, Spark et d'autres packages logiciels que nous n'avons pas utilisés à partir de la suite HDInsight, mais toujours avec paramétrisation rudimentaire. Cependant, le transfert des paramètres de génération vers un référentiel voisin avec un ensemble de fichiers .ini (pour chaque composant de plate-forme et pour chaque étape de processus) et la gestion des modèles de processus dans les branches de ce référentiel se sont révélés être une pratique si pratique que nous l'utilisons toujours.

Déjà des progrès. Avec l'automatisation d'une routine manuelle, le temps de préparation pour le calcul a été réduit de quatre fois, sans parler des erreurs humaines, qui sont devenues beaucoup moins. Mais ce n'est pas encore le moment du calcul lui-même.

Troisième approximation à GeoSpark


Cela a pris environ six mois. À ce moment-là, un ensemble d'heuristiques débogué et testé s'était progressivement accumulé, déjà avec des applications distinctes sur Spark, et non avec des scripts, et certains modèles de processus typiques ont été développés. Il fallait maintenant les optimiser.

Le deuxième programmeur, qui n'avait aucune expérience préalable dans une équipe ou une entreprise, a agi avec ses modules assez simplement - après avoir terminé le transfert d'une heuristique à Spark, il a simplement copié tout le projet et a commencé à remplacer l'ancien algorithme par le nouveau. En conséquence, quand il y avait huit de ces modules parallèles, chacun avec un ensemble de paramètres similaire mais légèrement différent, un peu d'excellente sémantique d'appel - et aussi beaucoup de code de service en double - ils ont commencé à poser un autre problème. Plus il y a de code, plus on passe de temps sur son support, surtout s'il ne cesse d'évoluer tout ce temps. Et en raison du copier-coller constant, les paramètres inutilisés et autres ordures ont commencé à s'accumuler en eux.

Ayant fini avec le problème brûlant de l'automatisation et ayant traité de la configuration des clusters, maintenant je pouvais déjà reprendre les modules de préparation des données et l'heuristique. Pour commencer, j'ai pris tout le code répétitif dans un projet Commons séparé, branché en tant que sous - module git , et dans les modules de calcul, il est devenu plusieurs fois moins un gâchis. J'ai assemblé un modèle pour une heuristique typique, et un nouveau projet en est déjà sorti, sans avoir besoin de remplacer des morceaux de code et sans saleté inutile dans l'histoire des commits. Le développement a commencé à être plus rapide.

Le prochain gros problème à vaincre est venu de la logique de calcul des signaux produit cartésien × POI.

Seul le traitement par lots le transfère à la base de données, mais ne réduit pas le nombre d'opérations, même si la base de données utilise efficacement les index et l'optimisation des requêtes. Il serait logique de ne pas considérer la distance pour ces paires où elle dépasse évidemment le seuil dont nous avons besoin. Mais comment éliminer les paires dont la distance est supérieure au seuil sans calculer cette distance?

Réponse: partitionnez les signaux et les POI sur une grille géométrique.

De plus, la carte thermique est déjà constituée d'une grille de polygones. Et si vous sélectionnez la taille de cellule de cette grille de la bonne manière, alors pour chaque POI du polygone sélectionné, il est tout à fait possible de nous limiter à calculer les distances aux signaux qui tombent dans le même polygone, ses cellules voisines, et c'est tout. Le reste peut être jeté, ils sortiront certainement des limites de la pertinence.

Spark a déjà un outil prêt à l'emploi pour travailler avec des grilles - GeoSpark . Le deuxième programmeur a commencé à l'utiliser, et l'opération préliminaire «tirer l'ensemble de données sur la grille» est apparue. Mais cela ne s'est pas beaucoup amélioré, un problème grave a été remplacé par un autre problème grave.

Maintenant, c'était le problème de la «longue queue» - les utilisateurs, dans lesquels le nombre de signaux est dans les millions. Il n'y en a pas beaucoup, mais s'ils s'accumulent au centre-ville, où le POI est élevé, et ils s'y accumulent, comme par hasard, alors peu importe comment vous partitionnez en géométrie (au moins Voronoi , au moins quadtree ), il y aura toujours polygones où le nombre de comparaisons dépasse un montant raisonnable. Mais vous devez également vérifier les polygones voisins où la densité est aussi élevée.

Et si 99% des partitions avec des polygones à faible saturation fonctionnent rapidement, alors 1% des postes de travail de Spark avec des cellules à haute densité continuent de s'accrocher à la victoire, de manger de la mémoire comme s'ils étaient inconscients et de gâcher toutes les framboises. Spark essaie de tout garder à l'esprit, et s'il y a une forte variation de la taille de la partition dans RDD, alors tout le réglage de la consommation de mémoire s'envole, car il doit être fait pour le plus grand.

Il s'est avéré que 99% du calcul a été accéléré avec des partitions géométriques des centaines de fois, et 1% de la longue queue a réduit l'optimisation entière à presque rien.

En général, la transition vers GeoSpark a produit un gain de cinq fois, mais uniquement sur la taille des exécuteurs qui étaient très peu efficaces en mémoire - et, par conséquent, sur les clusters avec des machines virtuelles coûteuses. En bref, le partitionnement géométrique des géodonnées à haute densité s'est avéré être une impasse.

Et puis il y avait du bonheur en la personne du bureau d'analyse de l'un des plus grands télécoms japonais. Une petite filiale basée sur les données de géolocalisation collectées par la société principale.

Analystes japonais et migration d'Azure vers AWS


Les Japonais ont une mentalité intéressante. Ils ne sont pas pressés eux-mêmes, mais si seulement un gaijin est donné pour se mordre le doigt, les deux mains sont coupées. Ne donnez jamais les dates spécifiques japonaises! Et si vous appelez, prenez au moins trois fois l'offre. Il sera monstrueusement long et difficile de coordonner les termes de référence, et non seulement la fameuse minutie japonaise va interférer, mais aussi la différence de pensée. Il ne reste tout simplement pas le temps de mettre en œuvre la version finale du mandat.

Le projet d'intégrer la "fille" des télécoms japonais a failli tuer notre projet. Les perspectives brillaient de devenir un fournisseur de données exclusif pour le marché publicitaire japonais fou, et l'entreprise est un peu ... euh, je peux me passer de commentaire.

Tout d'abord, pas d'azur. Seulement AWS, seulement hardcore.

Deuxièmement, le front devait être modifié pour répondre à leurs besoins, qui changeaient constamment tout au long du projet. Les spécialistes du marketing de ce bureau voulaient constamment quelque chose qu'ils ne savaient pas eux-mêmes avec certitude et ne pouvaient pas vraiment articuler, et devaient être refaits dix fois par étape, changeant la logique de calcul pour les prochains nouveaux indicateurs à la volée.

Je m'excuse pour la qualité, une capture d'écran du rapport de bug, il n'en reste plus

À un moment donné, j'ai un peu paniqué et j'ai fait un ensemble d '«opérations élémentaires» - environ 15 actions primitives sur RDD avec appel de méthodes de base telles que les jointures, le mappage, la suppression des valeurs par défaut, la somme des valeurs des colonnes - et d'autres petites opérations de ce type - pour rapidement changer la logique de la chaîne de calcul, comme s'il s'agissait d'un ensemble d'instructions SQL.

(Regular Spark SQL est inapplicable dans notre cas car il n'y a ni typage strict ni ensemble de champs strict. Dans le jeu de données, vous pouvez à tout moment ajouter autant de champs supplémentaires que vous le souhaitez, et cela change pendant le déroulement du processus Il est trop difficile de prescrire des métadonnées dans des conditions en constante évolution.)

La tâche de haut niveau était la suivante: choisir une région arbitraire du Japon et construire une carte thermique pour une période de temps arbitraire en utilisant un ensemble arbitraire de catégories avec un tas d'indicateurs pour la décharge. Quel type d'indicateurs, comment les compter - le client lui-même ne l'a pas vraiment compris.

L'ensemble de données de test (c'est-à-dire petit) avec les signaux des utilisateurs pour 2016-2017, sur lequel nous avons dû travailler sur la technologie, est de 5 téraoctets de données, 14 000 000 000 d'enregistrements. Rien qu'à Tokyo, il y a plusieurs millions de POI, et dans le réseau de la région d'Hokkaido, 1 600 000 cellules.

Et les cartes pour les deux mille catégories pour chacune des 47 perfections japonaises devraient être considérées «à la volée», car elles devraient être vendues en tant que service cloud.

Une grande tâche pour briser le cerveau. Quelque part trois ou quatre ordres de grandeur plus élevés que nos capacités d'alors en termes de "vitesse de calcul" et de "volume de données".

Devenu triste, nous avons décidé de faire néanmoins un pré-calcul pour chaque région (merci aux dieux Shinto, les Japonais n'ont pas eu besoin d'unir les régions) et pendant un mois, afin que la carte thermique soit construite selon les scores préalablement préparés. Soit pas en temps réel, mais quelques minutes ou dizaines (pour le centre de Tokyo) minutes. Le pré-calcul a pris plusieurs mois avec des clusters de 25 des machines virtuelles les plus puissantes disponibles dans la région AWS de Tokyo.

Mais pour fonctionner dans AWS, vous avez d'abord dû réécrire l'automatisation sous l'API AWS. Et différents nuages, bien qu'ils offrent des services similaires de l'extérieur, sont complètement différents en interne. Il est bon qu'à ce moment PowerShell ait déjà atteint la version candidate de la version 6, et les scripts de liaison Azur pour déployer le cluster et exécuter le calcul pourraient être portés et exécutés audacieusement sur Linux TeamCity (car le déploiement de serveurs sur Windows dans AWS est une idée ) Plus précisément, ne portez pas, mais ouvrez un script existant sur un moniteur et écrivez une implémentation parallèle pour un autre cloud sur le second.

De plus, AWS est beaucoup plus ancien, et donc plus archaïque qu'Azure, est architectural, et il y a beaucoup plus de travail manuel pour configurer le niveau inférieur de l'infrastructure. Et la vente aux enchères locale pour la vente de ressources informatiques ajoute un casse-tête lorsque vous ne disposez pas des voitures de la bonne taille au prix souhaité et que le client n'alloue pas de budget pour le calcul du prix complet.

Mais l'écosystème Hadoop lui-même dans l'incarnation amazonienne - EMR - est quelque chose de plus proche de la vanille, et travailler avec lui est plus facile qu'avec HDInsight. Eh bien, au moins avec quelque chose, cela s'est avéré plus facile.

Mais pas avec S3. Ici les ennuis sont sortis d'où ils n'ont pas attendu. S3 a des limites non documentées. Par exemple, dans un compartiment, il ne peut pas y avoir plus de ~ 11 000 000 d'objets, car quelque part dans les entrailles profondes de l'API, ils trient les clés dans l'ordre lexicographique pour chaque (chaque!) Demande, et le tampon alloué pour cela ne permet tout simplement pas le tri plus de lignes, surtout si elles sont longues. Pour accélérer le calcul, nous n'avons pas fusionné les partitions à la fin, et à un moment donné, nous sommes tombés sur cette limite, après quoi le processus s'est simplement arrêté.

Selon l'esprit, la fusion doit être effectuée, et il existe même un outil - l'utilitaire s3-dist-cp, mais son utilisation est un casse-tête distinct. Les prédateurs pour les extraterrestres ont écrit l'utilitaire à coup sûr, il se comporte de manière contre-intuitive. Et il a une faille fatale - sous le fichier fusionné, vous avez besoin d'autant d'espace sur HDFS que tous les originaux. Et pour fusionner des dizaines de milliers de fichiers de partition de centaines d'octets à des dizaines de mégaoctets, répartis sur un cluster de 25 machines, cela durera très longtemps.

Cependant, déjà avec un million d'objets dans le compartiment, S3 commence à lui trotter tranquillement les requêtes. Et dans d'éventuelles conditions de cohérence, c'est généralement un désastre - Spark, sans attendre le prochain bureau le nombre de fois convenu, peut tomber. Il existe une solution - utilisez le module complémentaire Amazon EMRFS, mais il fonctionne au-dessus de DynamoDB, et c'est une chose très coûteuse. Et avec leurs propres limites sur le nombre de demandes par seconde.

En bref, dans des conditions de manque de temps total, nous avons décidé de revenir au schéma statique - déployer un cluster permanent sur des instances d'une taille assez petite (bien que coûteuse, mais moins chère que DynamoDB), fusionner tous les téraoctets des jeux de données d'origine et calculés en HDFS dessus, et lire les cartes localement.

Mais la torsion de l'intrigue suivante était l'exigence pour les Japonais de passer de la grille hexagonale générée à Japan Mesh - la méthode standard de partitionnement géographique pour eux avec des cellules rectangulaires qui ne dépendent que des coordonnées du point. Une très bonne chose, car elle vous permet d’abandonner l’étape lourde de calcul consistant à «tirer des signaux sur la grille».

L'inconvénient est que le maillage Japan Mesh ne s'applique qu'au Japon et aux territoires insulaires qu'il prétend être, mais pas au reste du monde. Mais au moins pour les Japonais, il est devenu possible d'abandonner le GeoSpark lent et de partitionner les signaux de manière uniforme sans référence à la géométrie externe. Et avec le départ de la "longue queue", le calcul s'est de nouveau immédiatement accéléré à 10.

Il est malheureux que cela se soit produit après que nous ayons tous compris les hexagones, dépensant beaucoup d'argent et de temps en vain. Un cluster avec des téraoctets d'ensembles de données préparés a simplement été jeté.

Et en tout cas, quelque part au milieu du travail, les Japonais ont tout de même demandé de transférer toute l'infrastructure d'un compte AWS à un autre. Et comme si vous ne vous souciez pas de tout le travail effectué sur la configuration. Eh bien, j'ai réussi à créer un script pour le modèle CloudFormation au moment de la transition, donc la migration s'est déroulée de manière plus ou moins fluide.

Dernière cerise sur le gâteau, les Japonais ont finalement décidé que le front ne les abandonnait pas, et ils tireraient les calculs manuellement à la demande de leurs clients, donc merci à nous pour les algorithmes (pour la première fois nous les avons tous documentés en détail - et en avons trouvé quelques-uns) erreurs), et pour l'instant. Eh bien ... bonne chance et à plus tard.

Brrr Je me souviens de ce projet avec horreur et frisson.

Ash Nazg Durbatuluk, Ash Nazg Gimbatul, Ash Nazg Trakatuluk, Ag Burzum Ishi Krimpatul !!


Mais du positif, en plus de documenter tous les algorithmes, il y a eu aussi des améliorations générales.

Nous avons appris un étudiant en Java Junior, et il a mené une étude sur un tas de bibliothèques géographiques, à la suite de quoi il a finalement réussi à choisir la bonne et à la jeter hors de l'environnement PostGIS.

Les tentatives précédentes ont échoué en raison d'une mauvaise précision. Au rayon de trois kilomètres, les Haversins nous donnent une erreur déjà perceptible, et la plupart des bibliothèques que nous avons essayé de prendre dès la première fois étaient moche aux latitudes nord de Saint-Pétersbourg, entraînant des trous ou un double chevauchement dans la grille. Et nous, Finlandais, sommes des clients fréquents, il est donc essentiel que tout fonctionne correctement à leurs latitudes.

Jusqu'à ce que nous réalisions que nous avions besoin d'une bibliothèque avec un géoïde normal (de préférence le même que dans PostGIS, WGS84), les résultats n'étaient pas en accord avec les résultats attendus. Mais après le passage à GeographicLib, le goulot d'étranglement sous la forme de connexions Postgre a été éliminé et la dernière étape du calcul de la vitesse a été accélérée 40 fois. Golovnyak est parti avec la configuration supplémentaire d'une instance RDS distincte sous la base et en y téléchargeant un vidage avec POI, qui a été déplacé vers les jeux de données habituels dans S3. Unification!

En même temps, le même élève a déterré et corrigé l'erreur même qui faisait que les cartes semblaient plus pâles qu'elles ne l'étaient en réalité. Eh bien, quand il y a une tâche sans limite de temps, j'envie les étudiants.

Un autre point important. Une fois, pour la énième fois, en regardant les scripts de liaison qui appellent un module Spark après l'autre, je pensais, mais avec quel genre de diable les court-circuitons-nous?

Pourquoi enregistrer des résultats intermédiaires à chaque fois en S3 ou HDFS, si le RDD final du module précédent peut simplement être redirigé vers l'entrée du suivant dans la chaîne. Aussitôt dit, aussitôt fait, MetaRunner a été écrit en quelques heures. La présence de communs a beaucoup aidé à cela, les modules étaient alors assez standardisés, d'autant plus que les paramètres de chacun des modules étaient déjà dans le même fichier tasks.ini, avec les préfixes clés correspondant à leurs noms.
Votre attention est présentée avec un schéma de principe de la carte (la dernière étape avant de sortir au recto, mais pas la version finale), écrit sur les opérations élémentaires:

Organigramme du processus de préparation de la carte thermique

Si vous vous débarrassez de 24 appels intermédiaires vers HDFS, ce calcul est spécifiquement accéléré environ 50 fois.

Mais que se passe-t-il si vous ajoutez une prise en charge variable au modèle de processus afin de ne pas avoir à régénérer le fichier tasks.ini à chaque fois que vous modifiez les paramètres dans le magasin de propriétés?

- Ash Nazg! Ai-je crié. Des collègues se regardèrent avec perplexité. Un mec a un toit à cause de ces Japonais, mais bon, ça arrive.
"Ash nazg ... burzum-ishi krimpatul", grognai-je grogna (cela ne fonctionnait pas très bien), et je me rendis chez PM pour discuter de la fusion des 15 (le nombre d'heuristiques et d'utilitaires auxiliaires augmentait progressivement) des modules de calcul en un seul référentiel.

Si nous court-circuitons les modules entre eux, alors ne travaillez plus avec la doublure de tous les JAR individuels dans le chemin de classe de l'étincelle, et laissez l'ensemble complet de la logique Locomizer brevetée (et nos opérations auxiliaires) être assemblé en un seul JAR gras. En même temps et localement, il sera désormais possible de s'exécuter, sans cluster. Et ce qui est important, la logique d'analyse des tâches.ini peut être transférée des liaisons PowerShell au code Java, où la substitution de variables est beaucoup plus simple.

Collègues hennissant sur la proposition d'appeler le projet "L'anneau de la toute-puissance", - Un anneau - mais un peu de pathos sain ne fera jamais de mal.

Ayant saisi le moment de la prochaine ronde de coordination sans fin des savoirs traditionnels sur le front, j'ai rassemblé tous les modules en tas. Maven est un outil avancé pour résoudre les dépendances dans un projet multi-modules, il était donc possible de nettoyer les derniers morceaux de code en double, d'unifier les versions de toutes les bibliothèques et de créer des options de construction pour les environnements locaux et cloud. De plus, chaque module reste dans son propre sous-projet, et son auteur peut y travailler de manière indépendante, sans interférer avec le reste.

Soit dit en passant, je considère une telle approche avec la cristallisation des abstractions et la construction d'une sorte d'architecture à partir d'un ensemble existant d'entités homogènes plus qu'une tentative de concevoir à l'avance un niveau abstrait et de le mettre en œuvre dans des tâches particulières. Sans pratiques et schémas d'utilisation établis, il est inutile de concevoir une architecture - tous les cas ne peuvent être prévus à l'avance et les comportements des utilisateurs du système peuvent différer radicalement des idées du concepteur.

Avec la logique unifiée du traitement des paramètres, il a été possible de créer un modèle d'objet unifié distinct pour la configuration du module et de vérifier normalement la validité et la cohérence des configurations des modules les uns avec les autres dans le même processus.Ceci est particulièrement important avec les jeux de données au format CSV - le contrôle du nombre et de l'ordre des champs dans chaque enregistrement RDD, ainsi que l'exactitude du transfert de l'ensemble de données lui-même de la sortie d'un module à l'entrée de plusieurs suivants, reposent entièrement sur le côté appelant. Et s'il y a un point de contrôle, cela peut déjà être bien fait.
Pourquoi n'allons-nous pas plus haut et ne travaillons-nous pas avec RDD et non avec des trames de données? Pour la même raison que nous n'utilisons pas Spark SQL. Mais en plus, l'implémentation sur Spark est la dernière et dernière étape du code, qui commence par du livre blanc, est entièrement débogué en Python, et seulement ensuite optimisée en quelques étapes pour la version la plus productive. Et plus les primitives de la bibliothèque de base sont proches, plus le code s'exécute généralement plus rapidement.

... si les mains du développeur sortent de ses épaules et que sa tête est brillante. Théoriquement.

Il s'avère que dans nos conditions, il est beaucoup plus facile de piloter la ligne du CSV d'origine sous la forme d'un texte natif Hadoup compact (sous le capot, c'est juste un tableau d'octets), et de décrire uniquement les colonnes que l'opération actuelle connaît, et uniquement pour cela. En outre, selon les résultats des expériences, les trames de données donnent une surcharge de consommation de mémoire supérieure à la nécessité d'analyser CSV à l'entrée de chaque opération et de les compresser en texte à la sortie. Eh bien et pourtant - il est important pour nous de conserver la possibilité de partitionner manuellement les RDD intermédiaires après chaque étape, car les nouveaux ensembles de données du magasin peuvent se mélanger avec eux (cela est clairement visible dans le diagramme), donc vous devez toujours descendre d'un niveau, peu importe comment vous souhaitez rester au niveau livre blanc logique.

Mais dans le code "bas niveau" de Java, il y a aussi des avantages. Par exemple, si vous décrivez les paramètres d'opération (ainsi que les RDD attendus et générés) dans les métadonnées, vous pouvez générer automatiquement à la fois la documentation et un exemple de configuration et ne les écrivez plus manuellement. Et les quais seront toujours pertinents, après chaque build.

Le fichier de configuration tasks.ini lui-même, à partir d'un ensemble hétérogène de paramètres pour chaque module, s'est immédiatement transformé en programme dans une sorte de langage de programmation déclaratif. Pas très beau, mais logique en interne et relativement lisible par l'homme. Le finir en DSL réel avec sa propre syntaxe n'est pas un problème, mais je ne l'ai pas fait comme inutile. Mais un peu plus tard, il a néanmoins ajouté une vue à JSON pour le futur front avec un éditeur visuel.

Un processus court-circuité a en moyenne reçu trois à cinq fois plus rapidement qu'une chaîne d'appels individuels à des travaux Spark.

Pas cent fois, car maintenant, dans le cadre du même travail Spark, des étapes de tâches de complexité de calcul et de saturation de données différentes pouvaient être mélangées. Par conséquent, le réglage fin des paramètres de cluster pour chacune des parties d'un processus en plusieurs étapes a perdu toute signification pratique. Mais progressivement, et pour une telle option, certains modèles généraux ont été trouvés qui permettaient de sélectionner des préréglages de tailles de cluster, en fonction uniquement de la taille de l'ensemble de données initial et du nombre total d'étapes dans le modèle de processus de traitement.

Pour résumer cette étape, à la fin de notre travail avec les Japonais, nous avions déjà des outils assez développés:

  • , ,
  • , , DSL ,
  • , — ,
  • AWS, .

Mais ce qui n'a pas fonctionné, c'est le front. L'ancienne interface utilisateur Web de Locomizer est désespérément dépassée, nous n'avons jamais réussi à mettre le nouveau japonais dans un état sain avant de l'abandonner complètement. Oui, et le code backend pour cette interface utilisateur, écrit avec mon pied arrière gauche par une nuit sombre d'octobre, je n'ai pas pu peigner jusqu'à la fin simplement en raison du grand volume.

Optimisation et géocatarse avec Uber H3


Après avoir expiré, nous sommes revenus à des projets privés. L'ambiance après les Japonais était, franchement, toute l'équipe était très moyenne.

Mais je me suis finalement débarrassé de la nécessité de maintenir un back-up à l'avant avec son Bogomersssky, holm, holm, spring. (Ceci est mon opinion personnelle. EE, je n'aime pas juste un peu moins, car il a moins d'autogie et de défauts implicites; donc cela n'a pas d'importance en plus de ce que la foutue entreprise est d'écrire des REST).

Il y avait un temps pour regarder à l'intérieur de chaque module avec une dépendance.
— , . , , , . - . — . , — .
Non pas que j'aurais regardé le code de mes pairs de manière inattentive. Tout le monde est engagé dans la tâche qui lui est confiée et, bien qu'elle soit exécutée par lui avec le résultat souhaité, n'interférez pas avec le développeur qui fait son travail. Si l'algorithme fonctionne correctement, et cela est confirmé par des tests, alors tout va bien. Selon la rapidité du travail, - c'est acceptable, ou autrement - la décision est prise par le PM.

Je n'interviens que lorsque je reconnais un risque élevé de soutien supplémentaire dans la décision prise par le développeur lors de la mise en œuvre d'une nouvelle tâche. Et les modules anciens et moches, écrits sous le Tsar Gorokh par quelqu'un qui avait longtemps quitté le projet, mais nécessaires pour les affaires, seront maintenus à un niveau viable tel qu'il est, et peu importe à quel point il en sent. Cela semble cynique, mais je suis pragmatique, pas idéaliste, le résultat du travail est plus important pour moi que la beauté du code.

Mais parfois, il est nécessaire d’apurer la dette technique afin de ne pas enterrer le projet sous son propre poids.

Spark est une bibliothèque de très haut niveau. Il vous permet d'effectuer des opérations sur RDD de différentes manières, ce qui donne le même résultat, et chaque méthode peut avoir plusieurs morceaux d'excellentes options. Vous devez lire attentivement la description de chacun d'eux et, en cas de doute, remonter dans la source pour comprendre ce qui est optimal dans quel cas. Le résultat sera le même, mais la différence de vitesse de calcul peut être plusieurs fois, et si la logique d'une heuristique déploie une centaine de lignes de code sur Spark, alors vous devez être particulièrement prudent pour utiliser les moyens les plus appropriés de transformation des données.

Langages de haut niveau - ils sont tels que vous font penser de manière abstraite.

Mais en même temps, le développeur doit être conscient du faible niveau, peu importe à quel point il monte en abstractions élevées. Par exemple, tout lambda transmis à la méthode .map (), à l'intérieur de laquelle la mémoire est allouée à un objet en gras, est rappelé pour chaque enregistrement et réalloue le même objet, et aucune des machines virtuelles Java existantes n'aime les allocations répétées en gras.

Et si vous pensez à la prise en charge du code, ce serait bien d'avoir des morceaux de l'algorithme qui sont connectés par une logique interne, mais en même temps complètement isolés pour certaines valeurs de paramètres, isoler du reste du code, surtout si ces morceaux sont au début ou à la fin de l'algorithme. Ils peuvent généralement être retirés dans une opération distincte, en même temps les tests avec une couverture complète de tous les cas deviendront plus courts.

Auparavant, il était prématuré de gérer l'optimisation, mais maintenant le moment est venu, et pendant quelques mois, je suis parti avec ma tête dans une immersion passionnante dans les entrailles des modules de calcul avec du code de profilage écrit sur deux ans par mes collègues.

Lorsque j'y ai plongé, One Ring a eu 29 opérations (certains modules en contiennent plusieurs). Quand il est apparu - 43, et chacun plus vite que l'original, de quelques pour cent à des dizaines de fois. Mais de façon plus valable, ces opérations qui étaient auparavant étouffées par des données sur des partitions de 10 000 éléments, maintenant facilement mâchées sur des morceaux dans un million d'enregistrements. À certains endroits, j'ai dû sacrifier la flexibilité et la lisibilité du code, à certains endroits, cela a coûté un simple remplacement de .map () par .mapPartition (), mais le code a cessé de planter.

Il n'y avait qu'un seul goulot d'étranglement - le geofencing dans une région arbitraire. C'était encore une solution hybride bizarre avec un maillage externe. Il était possible d'utiliser Japan Mesh pour le Japon, mais pour le reste du monde, il était nécessaire de rechercher une variante appropriée d'une grille dynamique, qui ne dépend que des coordonnées du point et est pratique à utiliser.

Une telle option a été trouvée - Uber H3 .

Si je comprends bien, l'arbre hexagonal est crypté sous le nom H3 - et c'est une grille géographique avec de grandes fonctionnalités. Il est stable sur toute la plage de coordonnées, monstrueusement rapide (le code natif est appelé), donne des cellules de taille uniforme sans lacunes sur tout le terrain et vous permet de faire un tas d'options différentes pour couvrir les polygones, les points et les chemins. En outre, une cellule de grille hexagonale a un nombre minimal de voisins, et le niveau suivant couvre les sept cellules de la précédente strictement au-dessus du centre de la cellule sous-jacente, ce qui est pratique lors de la construction de cartes d'agrégation.

Avec la transition vers H3, il semble que le puzzle soit complètement développé.

Si nous comparons avec ce qu'il était au début, il y a 2,5 ans, puis des semaines qui ont été passées sur une carte de chaleur malheureuse sur un ensemble de données à quelques millions de signaux, nous sommes arrivés aux minutes qui sont dépensées sur des dizaines de cartes avec des ensembles de données, la taille de l'analyste de données n'y prête pas beaucoup d'attention (vous devez vous plaindre lorsqu'il définit le préréglage trop haut pour la taille du cluster si l'écriture du résultat dans S3 prend plus de temps que le calcul lui-même). Et il ne regarde plus TC lui-même, il obstrue simplement la matrice des paramètres quelque part chez lui, et tire le nombre requis de builds nécessaires avec le python.

Ajoutez une nouvelle opération - il vous suffit d'implémenter correctement la classe Operation (vous pouvez également utiliser Scala si vous le souhaitez), de l'encapsuler avec des métadonnées, de l'inclure dans votre configuration, puis One Ring déterminera si vous appelez la nouvelle heuristique ou si vous traitez correctement dans la chaîne.

Eh bien, tout fonctionne à la fois localement et dans AWS. Il sera également dans un autre cloud s'il prend en charge S3, et Spark peut être tiré via Livy là - bas - et nous nous sommes débarrassés de toutes les autres dépendances externes.

Dehors tout blanc


- Gandalf?!

Mais nous n'avons toujours pas de façade pour lancer des processus flexibles. Et les modèles de ces processus eux-mêmes doivent être écrits à l'ancienne - à la main dans VSCode, mais je voulais être une souris dans un éditeur similaire à Visio. Quelque chose comme ça: j'ai même fait un petit service REST dans le cadre de One Ring, qui a tout ce dont vous avez besoin pour écrire un tel éditeur, mais la dernière fois que j'ai travaillé sur le front était il y a environ 10 ans, et non dans le cadre des tendances actuelles. Ce n'est pas pour JSF que je le rivette, ce ne sera même pas rétro, mais déjà une sorte de nécro. Ce serait bien d'en faire un SPA statique sur quelque chose de moderne. Seulement, je n'ai aucune idée de quoi. Mon intérêt personnel égoïstepour révéler le code One Ring

Interface Mocap pour l'édition d'un processus



(Je vais terminer le référentiel avec du contenu, mais vous pouvez le regarder maintenant), j'espère, c'est clair. Et s'il y a quelqu'un assez courageux pour s'attaquer à cette tâche , j'écrirai une tâche technique saine avec des spécifications.
Mais en général, nous, l'équipe des ingénieurs de données, ne voulons pas garder l'outil fini dans notre placard. Nous en sommes sûrs: il nous sera utile non seulement. Et pas seulement pour les besoins du SIG, mais en général tout traitement en rafale d'ensembles de données avec des étapes de traitement paramétrables.
Dans le dernier article (ou quelques articles, encore une fois, quelque chose prend trop de temps), je vous dirai comment créer, exécuter, développer et utiliser One Ring pour vos tâches de recherche.

* Le code source One Ring OSS n'inclut pas d'algorithmes heuristiques propriétaires Locomizer propriétaires. Mais son référentiel contiendra des interfaces et des descriptions, selon lesquelles les implémentations gratuites de ces heuristiques peuvent être recréées en utilisant la méthode de salle blanche, c'est-à-dire sans invite de mon côté pour le code.

Remerciements


... à ses collègues Gregory pomadchin pour des commentaires de fond sur le sujet, et sshikov pour une évaluation indépendante de la lisibilité du texte, ainsi qu'à Anton dartov Zadorozhny pour un retour inattendu sur l'article précédent de la série.

Source: https://habr.com/ru/post/fr485988/


All Articles