Comment démarrer un projet pour animaux de compagnie et ne pas obtenir d'avantages

Comment démarrer un projet pour animaux de compagnie et ne pas obtenir d'avantages


TL; DR

L'article décrit l'utilisation du projet pour animaux de compagnie comme un moyen de maintenir et d'améliorer les compétences. L'auteur a créé une bibliothèque PHP pour installer FIAS à partir de fichiers XML.


But


Je change rarement de poste, par conséquent, étant donné le désir naturel de chaque organisation pour des processus fixes, toute tâche se transforme en routine. D'une part, il est avantageux pour une entreprise de maintenir un tel état, d'autre part, pour moi, cela signifie soit une perte totale, soit une obsolescence des compétences. PHP se développe rapidement et, par conséquent, le décalage potentiel augmente également rapidement. Enfin, nous savons tous qu'aujourd'hui, il est difficile pour un programmeur de trouver un bon emploi sans connaître Elasticsearch, RabbitMQ, Kafka et d'autres technologies qui n'apparaissent pas souvent dans mon travail quotidien.


Après avoir démarré le prochain site typique, j'ai décidé qu'il était temps de changer quelque chose. Je ne voulais pas changer mon travail, mais je me suis souvenu que lors d'une des conférences, le conférencier avait recommandé d'utiliser son propre projet facultatif, le soi-disant projet pour animaux de compagnie, pour la formation. La méthode semblait appropriée et j'ai décidé de l'essayer.


Sélection des tâches


Le choix de la tâche s'est avéré être la partie la plus difficile de l'entreprise. Rien de spécial n'est venu à l'esprit: certains services, comme un analyseur de tâches qui peut être facilement implémenté sur une pile familière. J'ai abandonné l'idée du projet pendant plusieurs mois, jusqu'à ce que je voie accidentellement la nouvelle du hackathon du ministère des Finances. Il a suggéré d'utiliser l'une des listes de sources de données ouvertes pour créer un service. Entre autres, le Federal Information Address System (FIAS) a également été indiqué. Malheureusement, le hackathon était déjà terminé à ce moment-là.


J'ai découvert FIAS pour la première fois, mais la tâche semblait intéressante. Jugez par vous-même: environ 30 Go de fichiers XML, environ 60 millions de lignes dans la base de données et, en outre, la bibliothèque pourrait plus tard s'avérer utile dans le travail. Il y avait des solutions toutes faites sur Github, mais cela ne m'a pas arrêté. Au contraire, sur la base de leur analyse, j'ai fait des exigences supplémentaires qui mettraient en évidence ma mise en œuvre.


Pour l'avenir, je constate que j'ai rencontré beaucoup moins de difficultés que prévu.


Énoncé de tâche


90% du succès est l'énoncé correct du problème. Après plusieurs années de travail sur les modèles, il était assez difficile de formuler clairement le problème. Je voulais juste commencer à travailler, mais déjà dans le processus, tout se serait éclairci par lui-même. Après une heure de lutte avec la procrastination, j'ai finalement écrit: créer une bibliothèque en PHP pour importer des données FIAS.


Plus tard, après un avant-goût, j'ai ajouté quelques exigences supplémentaires:


  • implémentation en PHP sans utiliser d'utilitaires tiers, exclusivement du code en PHP et des extensions de PECL,
  • importation de toutes les données de l'ensemble FIAS,
  • cycle complet d'installation et de mise à jour: recherche de la version nécessaire, réception de l'archive, déballage, écriture dans la base de données,
  • flexibilité maximale: possibilité de changer l'emplacement de stockage, de modifier les données avant l'enregistrement, de filtrer le nécessaire, etc.,
  • La bibliothèque doit être facilement intégrée aux projets existants.

FIAS


FIAS a un site officiel qui nous donne la définition et le but de la création d'un système


Le Federal Information Address System (FIAS) est le système d'information de l'État fédéral qui prévoit la formation, la maintenance et l'utilisation du registre d'adresses de l'État.

La création de la FIAS a pour but la formation d'une ressource fédérale unique contenant des informations d'adresse structurées fiables, uniformes et accessibles au public. Grâce à la mise en œuvre de FIAS, ces informations peuvent être obtenues gratuitement via Internet sur le portail FIAS officiellement enregistré.

Les matériaux avec une description sont tout à fait suffisants à la fois sur le site Web de la FIAS et sur le Habré , donc je ne me concentrerai pas sur cela.


Bref. FIAS est disponible en deux formats: FIAS et KLADR. Le second est obsolète et n'est plus utilisé. Les informations sont stockées soit dans DBF, soit dans XML. Chaque changement dans la composition de FIAS est marqué d'une nouvelle version. Vous pouvez demander soit un package avec des données complètes à jour ou contenant uniquement des modifications entre les deux versions. Links fournit un service SOAP. Le package est une archive RAR contenant des fichiers avec des noms spécialement formés. Ils se composent d'un préfixe, d'un nom de jeu de données et d'une date de génération. Il existe deux types de préfixes: AS_ pour les fichiers dont les données doivent être ajoutées à la base de données et AS DEL pour les fichiers dont les données doivent être supprimées de la base de données.


FIAS contient les données suivantes:


  • registre des éléments de formation d'adresses (c'est le graphe des adresses: régions, villes et rues),
  • des éléments d'adresse identifiant les objets adressables (numéro et données de la maison),
  • informations sur les terrains
  • des informations sur les locaux (appartements, bureaux, chambres, etc.),
  • des informations sur le document normatif, qui est la base de l'attribution du nom à l'élément d'adresse.

Ainsi que plusieurs dictionnaires
  • une liste des valeurs possibles des intervalles des maisons (régulier, pair, impair),
  • une liste des statuts de pertinence d'une entrée d'un élément d'adresse selon le classificateur KLADR4.0,
  • une liste des statuts pertinents d'une entrée d'un élément d'adresse par FIAS,
  • une liste de noms complets et abrégés des types d'éléments d'adresse et de leurs niveaux de classification,
  • liste des types de bâtiments,
  • liste des types de propriété possibles
  • liste des codes d'opérations avec objets adressables,
  • une liste des conditions immobilières possibles,
  • liste des types de locaux ou de bureaux,
  • liste des types de chambres,
  • liste des statuts (centres) possibles des objets d'adresse des unités administratives,
  • types de documents réglementaires.

La structure des données est décrite dans le document, qui se trouve dans la section des mises à jour .


En fin de compte, nous avons un algorithme d'installation FIAS assez simple et linéaire:


  • obtenir du service SOAP un lien vers l'archive et le numéro de version actuel,
  • télécharger l'archive,
  • déballer
  • écrire dans la base de données toutes les données des fichiers avec le préfixe AS_,
  • supprimer de la base de données toutes les données des fichiers avec le préfixe AS DEL (oui, c'est vrai, lors de l'installation, vous devez également supprimer certaines données),
  • notez le numéro de la version installée.

Et un algorithme de mise à jour non moins simple:


  • obtenir du service SOAP une liste avec les numéros de version et des liens vers des fichiers avec des modifications,
  • si la version actuelle de la base de données locale est la dernière, arrêtez l'exécution,
  • obtenir un lien vers l'archive avec les modifications apportées à la prochaine version,
  • télécharger l'archive,
  • déballer
  • écrire dans la base de données toutes les données des fichiers avec le préfixe AS_,
  • supprimer de la base de données toutes les données des fichiers avec le préfixe AS DEL ,
  • noter le numéro de la version mise à jour,
  • revenir à la première étape.

FIAS laisse des impressions contradictoires. D'une part: automatisation complète de l'ensemble du processus, formats ouverts, bonne documentation. De l'autre: une décision étrange d'utiliser le RAR propriétaire pour les données ouvertes; les différences entre la documentation et la réalité (principalement liées aux attributs obligatoires), qui causent de nombreux petits mais désagréables problèmes; il arrive parfois des archives qui ne peuvent pas être décompressées sous Linux; certains deltas entre les versions occupent 4-5 Go.


L'architecture


Chaque bibliothèque doit être basée sur une idée de base, un noyau autour duquel le reste des fonctionnalités se développera. Le schéma de la «chaîne de fonctions» me semblait le meilleur choix pour le rôle d'une telle idée. Tout d'abord, il est idéal: plusieurs opérations séquentielles qu'une personne aurait effectuées s'il avait voulu installer FIAS manuellement sont évidentes pour le développeur et s'intègrent bien dans de petites classes écrites dans le style SOLID. Deuxièmement, une telle chaîne se développe très facilement avec de nouvelles opérations à presque toutes les étapes, ce qui offre une bonne flexibilité. Troisièmement, je souhaite depuis longtemps écrire ma propre implémentation.


En plus des opérations, j'ai créé plusieurs services qui peuvent être transférés via DI. Ils vous permettent de réutiliser le code, de remplacer facilement l'implémentation pour des tâches système de bas niveau (télécharger un fichier, décompresser une archive, écrire dans une base de données et autres) et offrent une bonne couverture de test grâce aux maquettes.


En conséquence, la bibliothèque contient quatre principaux types d'objets, pour chacun desquels le domaine de responsabilité est clairement défini:


  • services - fournir des outils pour effectuer des tâches système de bas niveau,
  • objet d'état - stocke les informations à transmettre entre les opérations,
  • opérations - en utilisant les services et en indiquant qu'ils implémentent la partie atomique de la logique métier,
  • chaîne d'opérations - effectue des opérations et transfère l'état entre elles.

En utilisant la liaison des opérations et des services fournis par la bibliothèque, vous pouvez facilement obtenir n'importe quelle nouvelle chaîne ou compléter la chaîne existante en utilisant uniquement des fichiers de configuration.


Cadres


Avec de longues pauses et un refactoring constant, j'ai travaillé sur la bibliothèque pendant un an et demi.


La première version relativement stable était prête en deux mois de travail le soir. En fait, il pouvait exister séparément du framework et contenir tout le nécessaire: un script d'entrée à exécuter dans la console, un conteneur DI, un add-on sur PDO, son propre logger et des migrations de structure de base de données - dont j'étais très fier.


Bien sûr, ses collègues l'ont rejetée sans pitié.


Le principal argument contre cela était le manque de soutien pour les cadres populaires. Personne ne voulait écrire un wrapper séparé pour la bibliothèque. Pour cette raison, j'ai fait l'erreur la plus coûteuse dans le temps: j'ai commencé à prendre en charge la version autonome et les wrappers individuels pour chaque framework. Les vrais fichiers FIAS sont différents de ce qui est écrit dans la documentation. Chaque fois qu'il était nécessaire de supprimer ou d'ajouter, par exemple, non nul dans la description de la colonne, je devais apporter des modifications à trois référentiels. En raison de la lenteur du processus, le travail a calé pendant encore six mois.


Le sentiment d'incomplétude m'a tourmenté tout ce temps et après une bataille sanglante avec la paresse m'a obligé à revenir à la conception d'une nouvelle version. Pour commencer, j'ai décidé que personne n'avait besoin d'une bibliothèque autonome, ce qui signifie que vous devriez supprimer tous les services qui fournissent des cadres du package, en les remplaçant par des interfaces. Nous sommes donc passés sous le couteau: un script d'entrée à exécuter dans la console, un conteneur DI, un module complémentaire sur PDO, notre propre enregistreur et des migrations de structure de base de données. Ensuite, j'ai décidé de créer des packages séparés pour chaque framework, qui relieront toutes les parties du main à un script de travail et fourniront des implémentations spécifiques de services.


Le point clé était le modèle. La mise à jour constante d'ensembles hétérogènes d'objets dans plusieurs référentiels ne le voulait pas. En même temps, au travail principal, j'ai eu un projet sur Symfony. Après une rapide connaissance, j'ai décidé que la fonctionnalité la plus utile de SF est la génération de code et qu'elle résoudra tous mes problèmes. J'ai créé un fichier yaml dans le package principal, qui contient une description déclarative des données FIAS. Ensuite, j'ai ajouté des générateurs de code qui créent des classes spécifiques pour les modèles basés sur cette description: entités Doctrine pour Symfony et objets Eloquent pour Laravel. Lors du développement des générateurs, j'ai réalisé que les modèles de brindilles n'étaient pas adaptés à cela, et j'ai opté pour une solution spécialisée - Nette PHP Generator .


Comme preuve de concept, j'ai créé des bundles pour Laravel et Symfony . Comme j'ai travaillé plus longtemps avec le second, je décrirai tout ce qui suit dans son contexte.


L'infrastructure


La plupart de mes projets de combat ont été écrits sur des technologies obsolètes, donc je ne pouvais pas utiliser d'analyseurs de code modernes sur aucun d'entre eux. Pour me débarrasser de l'oppression héritée, j'ai installé et configuré tous les outils de contrôle de la qualité du code que je pouvais:



Validation intégrée dans Github à l'aide de Travis . Enfin, il a ajouté un fichier Docker pour créer un environnement de développement local avec un fichier make contenant les commandes de base du conteneur (lancement des vérifications, des tests, création de modèles et autres).


Résultats d'apprentissage


PHP 7


Avant de commencer à travailler sur la bibliothèque, je n'ai jamais vraiment utilisé les nouvelles fonctionnalités de PHP 7 . Ils sont beaux: des types stricts à une augmentation significative de la productivité. Un merci spécial aux développeurs pour l'opérateur de coalescence nulle. Je n'ai pas vu une telle diminution sérieuse de la base de code après l'introduction d'un opérateur.


Rar


Étonnamment, dans PECL, il y avait un package pour travailler avec RAR . En règle générale, ces extensions ne sont pas fiables et j'essaie de les éviter. Il s'est avéré être d'une stabilité suspecte: il a été installé en 7.2 sans problème, il a pu déballer d'énormes archives relativement rapidement et avec une faible consommation de RAM (6 Go sont déballés en 10-20 minutes en fonction des ressources système disponibles). Je crains toujours que ce soit une manifestation de la loi de Murphy.


Xmlreader


La lecture de fichiers xml géants n'est pas une tâche triviale. Et encore une fois, l'extension PECL est venue à la rescousse - XmlReader . Je n'ai pas immédiatement réalisé toute sa puissance, mais dans plusieurs approches, je l'ai adapté en conjonction avec le sérialiseur Symfony pour obtenir rapidement et économiquement des données à partir de fichiers FIAS. Côté bibliothèque, l'objet lecteur implémente l'interface de l'itérateur, qui renvoie séquentiellement des chaînes xml correspondant à un enregistrement du fichier. À l'aide du sérialiseur Symfony, ces chaînes sont converties en objets. Un fichier de 20 Go peut être lu en 3-4 minutes tout en n'utilisant pas plus de 50 Mo de RAM.


Ecrire dans la base de données


Bien sûr, j'ai commencé avec des tableaux associatifs avec des données et des descriptions de table volumineuses. Le code s'est rapidement transformé en hachage de configurations et de classes de conversion.


La magie des entités Doctrine a montré comment les objets peuvent se décrire d'eux-mêmes. J'ai décidé d'utiliser la même approche, mais en même temps de me débarrasser de ma propre implémentation de l'écriture de données dans la base de données à l'aide de PDO. Au lieu de cela, j'ai créé une interface de stockage qui décrit les méthodes de traitement des objets. En fonction de la classe d'entité, une implémentation de stockage particulière décide exactement comment et où écrire les données. Cette approche a facilité la connexion d'une grande variété de stockages: de MySql aux fichiers csv.


Optimisation de l'insertion des données


J'ai interrompu la première importation après qu'elle ait dépassé en 48 heures. Il est devenu évident que vous devez optimiser le processus d'insertion des données.


Tout d'abord, je suis passé aux colonnes de type ugid pour les clés primaires intégrées dans PostgreSql . L'écriture dans une colonne uuid avec un index est beaucoup plus rapide que l'écriture dans une chaîne.


Par la suite, j'ai abandonné tous les index non critiques et les clés étrangères, car le souci de l'intégrité des données est entièrement du côté de l'équipe FIAS.


J'ai ensuite refait l'interface de stockage pour que le script externe puisse l'informer explicitement de la fin de l'importation. Cela a permis l'utilisation d'insert en vrac, ce qui a parfois accéléré l'enregistrement. À la recherche d'informations, je suis également tombé sur la commande de copy avec query_to_xml . Il avait deux gros inconvénients: premièrement, l'utilisateur PostgreSql doit avoir des autorisations de lecture pour le fichier, ce que je ne pouvais pas garantir, et deuxièmement, la possibilité de modifier les données à l'intérieur du script avant la perte d'écriture.


Malgré ces changements, le temps d'importation a dépassé 30 heures. Un changement radical d'approche s'imposait.


Processus parallèles


Internet regorge d'articles sur l'asynchronie en PHP. Mon choix s'est porté sur Amp . Cela n'a tout simplement pas fonctionné de manière asynchrone. Premièrement, le code s'est rapidement transformé en une terrifiante feuille de rappels et d'appels non évidents (c'est probablement ma faute, pas l'approche asynchrone). Deuxièmement, j'ai dû abandonner l'utilisation des ORM standard car des appels non bloquants à la base de données via un cadre spécial sont nécessaires. Troisièmement, bien qu'il existe des conditions dans lesquelles PostgreSql peut insérer des lignes en parallèle, elles sont extrêmement difficiles à remplir. En conséquence, après 5 heures de travail, j'ai observé comment toutes mes demandes asynchrones étaient «synchronisées» de force du côté de la base de données.


Mais l'importation est bien divisée en processus parallèles: plusieurs tâches complètement indépendantes qui n'ont pas de ressources communes pour lesquelles elles pourraient rivaliser et des données qu'elles pourraient échanger. De plus, dans le cadre d'un thread, j'ai reçu un code beau et linéaire.


Le premier, j'ai décidé d'essayer l'extension parallèle . Il a une faille fatale - l'interprète doit être construit avec le support de ZTS (Zend Thread Safety). Étant donné que ZTS ne fonctionne pas dans les scripts Web classiques, il faudrait avoir deux versions différentes de l'interpréteur. Un, sans ZTS, pour le web, le second, avec ZTS, pour installer FIAS. L'amélioration potentielle des performances l'emportait sur cet inconvénient, en particulier compte tenu de la facilité d'assemblage d'un nouveau conteneur Docker et de son utilisation conjointement avec l'ancien. Malheureusement, démarrer Symfony dans un nouveau thread a fait déborder la pile PHP, et je n'étais pas prêt à refuser le conteneur DI et la configuration pratique.


Enfin, j'ai trouvé le processus symfony . En fait, il démarre un nouveau processus pour la commande de console spécifiée et surveille son achèvement. J'ai dû ajouter deux chaînes supplémentaires. Le premier télécharge l'archive, la décompresse et lance des processus parallèles pour le traitement des données. Le second prend une liste de fichiers de l'argument de ligne de commande et écrit leur contenu dans la base de données.


En raison du manque d'expérience avec les processus parallèles, il semble que j'ai commis toutes les erreurs des débutants.


Par exemple, mon processus d'initiation a vérifié l'achèvement des enfants en utilisant une boucle infinie et, bien sûr, il dépensait indécemment beaucoup de ressources processeur pour cela. L'appel de sommeil entre les itérations a aidé.


Dans la première implémentation, les fichiers étaient répartis inégalement entre les processus. Les deux plus grands sont tombés en un seul flux, qui a été traité pendant plus de 20 heures. Dans la deuxième implémentation, j'ai ajouté un gestionnaire spécial qui distribue les fichiers en fonction du temps nécessaire pour les importer. Maintenant, les processus sont chargés uniformément.


Après ces modifications, j'ai pu importer la version complète de FIAS en 16-20 heures, selon les ressources du serveur. Pas aussi bien que nous le souhaiterions, mais je continue de travailler sur l'optimisation. La prochaine étape est un rejet complet de PostgreSql en faveur d'Elasticsearch.


Conclusions


Cela en valait-il la peine? Deux ans de travail sur une bibliothèque qui ne s'est jamais lancée dans un projet de combat?


Oui, complètement.


J'ai quand même changé de boulot. Au cours d'une tournée d'une douzaine d'interviews, je n'ai répondu à de nombreuses questions délicates que grâce à mon projet animal.


La panique que PHP est en train de mourir est de plus en plus forte. Je ne cacherai pas le fait que je pensais moi-même à la migration vers une autre langue.


Après avoir vu l'énorme travail que l'équipe PHP a mis dans la version 7; il a été convaincu par son exemple personnel de la maturité de la langue et de la richesse de l'écosystème qu'elle a développé; Je peux dire en toute sécurité que les rumeurs sur la mort de PHP sont grandement exagérées. Et ce n'est qu'un début: à l'avenir, nous attendons le JIT, l'asynchronie prête à l'emploi et bien plus encore.

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


All Articles