So starten Sie ein Haustierprojekt und erhalten keine Vorteile

So starten Sie ein Haustierprojekt und erhalten keine Vorteile


TL; DR

Der Artikel beschreibt die Verwendung von Haustierprojekten als eine Möglichkeit, Fähigkeiten zu erhalten und zu verbessern. Der Autor hat eine PHP-Bibliothek zur Installation von FIAS aus XML-Dateien erstellt.


Zweck


Ich wechsle selten den Job, daher wird jede Aufgabe aufgrund des natürlichen Wunsches jeder Organisation nach festen Prozessen zur Routine. Einerseits ist es für ein Unternehmen von Vorteil, einen solchen Zustand aufrechtzuerhalten, andererseits bedeutet dies für mich entweder einen vollständigen Verlust oder eine Überalterung der Fähigkeiten. PHP entwickelt sich rasant und infolgedessen wächst auch die potenzielle Verzögerung rasant. Schließlich wissen wir alle, dass es heutzutage für einen Programmierer schwierig ist, einen guten Job zu finden, ohne Kenntnisse über Elasticsearch, RabbitMQ, Kafka und andere Technologien, die in meiner täglichen Arbeit nicht oft vorkommen.


Nachdem ich die nächste typische Site gestartet hatte, entschied ich, dass es Zeit war, etwas zu ändern. Ich wollte meine Arbeit nicht ändern, aber ich erinnerte mich, wie der Sprecher auf einer der Konferenzen empfahl, sein eigenes optionales Projekt, das sogenannte Haustierprojekt, für das Training zu verwenden. Die Methode schien angemessen und ich beschloss, es zu versuchen.


Aufgabenauswahl


Die Wahl der Aufgabe erwies sich als der schwierigste Teil des Unternehmens. Es fiel nichts Besonderes ein: Einige Dienste, wie ein Job-Parser, der einfach auf einem vertrauten Stack implementiert werden kann. Ich gab die Idee des Projekts für einige Monate auf, bis ich versehentlich die Nachrichten über den Hackathon des Finanzministeriums sah. Es wurde vorgeschlagen, eine der offenen Datenquellen zu verwenden, um einen Service zu erstellen. Unter anderem wurde auch das Federal Information Address System (FIAS) angegeben. Leider war der Hackathon bis dahin bereits beendet.


Ich habe zum ersten Mal von FIAS erfahren, aber die Aufgabe schien interessant zu sein. Überzeugen Sie sich selbst: Rund 30 GB XML-Dateien, rund 60 Millionen Zeilen in der Datenbank und darüber hinaus könnte sich die Bibliothek später als nützlich für die Arbeit erweisen. Es gab einige fertige Lösungen auf Github, aber das hat mich nicht aufgehalten. Im Gegenteil, basierend auf ihrer Analyse stellte ich zusätzliche Anforderungen, die meine Implementierung hervorheben würden.


Mit Blick auf die Zukunft stieß ich auf weniger Schwierigkeiten als erwartet.


Aufgabenstellung


90% des Erfolgs ist die korrekte Angabe des Problems. Nach mehreren Jahren Vorlagenarbeit war es ziemlich schwierig, sich das Problem klar zu formulieren. Ich wollte gerade anfangen zu arbeiten, aber schon in dem Prozess hätte sich alles von selbst geklärt. Nach einer Stunde des Aufschubs schrieb ich schließlich: Erstellen Sie eine Bibliothek in PHP, um FIAS-Daten zu importieren.


Später, nach einem Vorgeschmack, habe ich ein paar zusätzliche Anforderungen hinzugefügt:


  • Implementierung in PHP ohne Verwendung von Drittanbieter-Dienstprogrammen, ausschließlich Code in PHP und Erweiterungen von PECL,
  • Import aller Daten aus dem FIAS-Set,
  • vollständiger Installations- und Aktualisierungszyklus: Suche nach der erforderlichen Version, Empfang des Archivs, Entpacken, Schreiben in die Datenbank,
  • maximale Flexibilität: die Möglichkeit, den Speicherort zu ändern, Daten vor der Aufzeichnung zu ändern, das Notwendige zu filtern usw.
  • Die Bibliothek sollte sich problemlos in bestehende Projekte integrieren lassen.

FIAS


FIAS hat eine offizielle Website , auf der wir die Definition und den Zweck der Erstellung eines Systems angeben


Das Bundesinformationsadressensystem (FIAS) ist das Landesinformationssystem, das die Bildung, Pflege und Nutzung des Landesadressregisters vorsieht.

Der Zweck der Schaffung des FIAS ist die Bildung einer einzigen Bundesressource, die zuverlässige, einheitliche, öffentlich zugängliche, strukturierte Adressinformationen enthält. Dank der Implementierung von FIAS können diese Informationen kostenlos über das Internet auf dem offiziell registrierten FIAS-Portal abgerufen werden.

Materialien mit einer Beschreibung sind sowohl auf der FIAS-Website als auch auf dem Habré völlig ausreichend, daher werde ich mich nicht darauf konzentrieren.


Kurz gesagt. FIAS gibt es in zwei Formaten: FIAS und KLADR. Die zweite ist veraltet und wird nicht mehr verwendet. Informationen werden entweder in DBF oder in XML gespeichert. Jede Änderung in der Zusammensetzung des FIAS ist mit einer neuen Version gekennzeichnet. Sie können entweder ein Paket mit vollständigen Daten anfordern, die aktuell sind, oder nur Änderungen zwischen den beiden Versionen enthalten. Links bietet einen SOAP-Dienst. Das Paket ist ein RAR-Archiv, das Dateien mit speziell geformten Namen enthält. Sie bestehen aus einem Präfix, einem Datensatznamen und einem Erstellungsdatum. Es gibt zwei Arten von Präfixen: AS_ für Dateien, aus denen Daten zur Datenbank hinzugefügt werden müssen, und AS DEL für Dateien, deren Daten aus der Datenbank gelöscht werden sollen.


FIAS enthält folgende Daten:


  • Register der adressbildenden Elemente (dies ist die Grafik der Adressen: Regionen, Städte und Straßen),
  • Adresselemente zur Identifizierung adressierbarer Objekte (Hausnummer und Hausdaten),
  • Informationen über Grundstücke
  • Informationen über die Räumlichkeiten (Wohnungen, Büros, Zimmer usw.),
  • Angaben zum normativen Dokument, das die Grundlage für die Zuordnung des Namens zum Adresselement bildet.

Sowie mehrere Wörterbücher
  • eine Liste der möglichen Werte der Intervalle der Häuser (regelmäßig, gerade, ungerade),
  • eine Liste der relevanten Status eines Eintrags eines Adresselements durch den KLADR4.0-Klassifizierer,
  • eine Liste relevanter Status eines Eintrags eines Adresselements nach FIAS,
  • eine Liste der vollständigen, abgekürzten Namen der Arten von Adresselementen und ihrer Klassifizierungsebenen,
  • Liste der Gebäudetypen,
  • Liste der möglichen Eigentumsarten
  • Liste der Operationscodes mit adressierbaren Objekten,
  • eine Liste möglicher Immobilienbedingungen,
  • Liste der Arten von Räumlichkeiten oder Büros,
  • Liste der Zimmertypen,
  • Liste möglicher Zustände (Zentren) von Adressobjekten von Verwaltungseinheiten,
  • Arten von Regulierungsdokumenten.

Die Datenstruktur ist im Dokument beschrieben, das im Abschnitt Aktualisierungen zu finden ist .


Letztendlich haben wir einen ziemlich einfachen und linearen FIAS-Installationsalgorithmus:


  • vom SOAP-Dienst einen Link zum Archiv und die aktuelle Versionsnummer erhalten,
  • Archiv herunterladen,
  • auspacken
  • alle Daten aus Dateien mit dem Präfix AS_ in die Datenbank schreiben,
  • alle Daten aus Dateien mit dem Präfix AS DEL aus der Datenbank löschen (ja, das ist richtig, während der Installation müssen Sie auch einige Daten löschen),
  • Notieren Sie sich die Nummer der installierten Version.

Und nicht weniger einfacher Update-Algorithmus:


  • vom SOAP-Dienst eine Liste mit Versionsnummern und Links zu Dateien mit Änderungen erhalten,
  • Wenn die aktuelle Version in der lokalen Datenbank die neueste ist, beenden Sie die Ausführung.
  • einen Link zum Archiv mit den Änderungen zur nächsten Version erhalten,
  • Archiv herunterladen,
  • auspacken
  • alle Daten aus Dateien mit dem Präfix AS_ in die Datenbank schreiben,
  • alle Daten aus Dateien mit dem Präfix AS DEL aus der Datenbank entfernen,
  • notieren Sie sich die Nummer der aktualisierten Version,
  • kehren Sie zum ersten Schritt zurück.

FIAS hinterlässt widersprüchliche Eindrücke. Einerseits: vollständige Automatisierung des gesamten Prozesses, offene Formate, gute Dokumentation. Auf der anderen Seite: eine seltsame Entscheidung, proprietäres RAR für offene Daten zu verwenden; Unterschiede zwischen Dokumentation und Realität (hauptsächlich im Zusammenhang mit obligatorischen Attributen), die viele kleine, aber unangenehme Probleme verursachen; Gelegentlich kommen Archive, die unter Linux nicht entpackt werden können. Einige Deltas zwischen den Versionen belegen 4 bis 5 GB.


Architektur


Jede Bibliothek sollte auf einer Grundidee basieren, einem Kern, um den der Rest der Funktionalität erweitert wird. Das Muster der Aufgabenkette schien mir die beste Wahl für die Rolle einer solchen Idee zu sein. Erstens ist es ideal: Mehrere sequenzielle Operationen, die eine Person durchgeführt hätte, wenn sie FIAS manuell installieren wollte, sind für den Entwickler offensichtlich und passen gut in kleine Klassen, die im SOLID-Stil geschrieben sind. Zweitens lässt sich eine solche Kette in nahezu jeder Phase sehr leicht durch neue Operationen erweitern, was eine gute Flexibilität bietet. Drittens wollte ich schon lange meine eigene Implementierung schreiben.


Zusätzlich zu den Vorgängen habe ich mehrere Dienste erstellt, die mit DI übertragen werden können. Sie ermöglichen es Ihnen, den Code wiederzuverwenden, die Implementierung für einfache Systemaufgaben (Herunterladen einer Datei, Entpacken eines Archivs, Schreiben in eine Datenbank und andere) zu ersetzen und dank Versehen eine gute Testabdeckung zu erzielen.


Folglich enthält die Bibliothek vier Haupttypen von Objekten, für die der Verantwortungsbereich klar definiert ist:


  • Services - Bereitstellung von Tools für die Ausführung von Systemaufgaben auf niedriger Ebene,
  • Statusobjekt - speichert Informationen für die Übertragung zwischen Operationen,
  • Operationen - unter Verwendung der Dienste und Zustände implementieren sie den atomaren Teil der Geschäftslogik,
  • Operationskette - Führt Operationen aus und überträgt den Status zwischen ihnen.

Durch die Verknüpfung von Operationen und Diensten, die von der Bibliothek bereitgestellt werden, können Sie auf einfache Weise eine neue Kette abrufen oder die vorhandene Kette nur mit Konfigurationsdateien ergänzen.


Frameworks


Mit langen Pausen und ständigen Umgestaltungen arbeitete ich anderthalb Jahre an der Bibliothek.


Die erste relativ stabile Version war in zwei Monaten Arbeit abends fertig. Tatsächlich könnte es unabhängig vom Framework existieren und alles Notwendige enthalten: ein Eingabeskript zur Ausführung in der Konsole, einen DI-Container, ein Add-On über PDO, einen eigenen Logger und Migrationen der Datenbankstruktur - worauf ich sehr stolz war.


Natürlich lehnten ihre Kollegen sie gnadenlos ab.


Das Hauptargument dagegen war die mangelnde Unterstützung für populäre Rahmenbedingungen. Niemand wollte einen separaten Wrapper für die Bibliothek schreiben. Aus diesem Grund habe ich den teuersten Fehler der Zeit gemacht: Ich begann, sowohl die Standalone-Version als auch einzelne Wrapper für jedes Framework zu unterstützen. Echte FIAS-Dateien unterscheiden sich von den Angaben in der Dokumentation. Jedes Mal, wenn es erforderlich war, beispielsweise nicht null in der Spaltenbeschreibung zu entfernen oder hinzuzufügen, musste ich Änderungen an drei Repositorys vornehmen. Aufgrund des langwierigen Prozesses wurde die Arbeit für weitere sechs Monate unterbrochen.


Das Gefühl der Unvollständigkeit quälte mich die ganze Zeit und zwang mich nach einem blutigen Kampf mit Faulheit, wieder eine neue Version zu entwerfen. Zunächst entschied ich, dass niemand eine eigenständige Bibliothek benötigt. Das bedeutet, dass Sie alle Dienste, die Frameworks bereitstellen, aus dem Paket streichen und durch Schnittstellen ersetzen sollten. Also gingen wir unter die Lupe: Ein Eingabeskript zur Ausführung in der Konsole, ein DI-Container, ein Add-On über PDO, unsere eigenen Logger- und Datenbankstrukturmigrationen. Als Nächstes habe ich mich entschieden, für jedes Framework separate Pakete zu erstellen, die alle Teile vom Haupt-Skript zu einem funktionierenden Skript verbinden und spezifische Implementierungen von Diensten bereitstellen.


Der entscheidende Punkt war das Modell. Das ständige Aktualisieren heterogener Objektmengen in mehreren Repositories wollte nicht. Gleichzeitig bekam ich bei der Hauptarbeit ein Projekt über Symfony. Nachdem ich mich schnell damit vertraut gemacht hatte, entschied ich, dass die Codegenerierung die nützlichste Funktion von SF ist und alle meine Probleme lösen wird. Ich habe im Hauptpaket eine yaml-Datei erstellt, die eine deklarative Beschreibung der FIAS-Daten enthält. Dann habe ich Codegeneratoren hinzugefügt, die bestimmte Klassen für Modelle basierend auf dieser Beschreibung erstellen: Doctrine-Entitäten für Symfony- und Eloquent-Objekte für Laravel. Während der Entwicklung von Generatoren wurde mir klar, dass Zweigvorlagen dafür nicht geeignet waren, und ich entschied mich für eine spezielle Lösung - den Nette PHP Generator .


Als Proof of Concept habe ich Bundles für Laravel und Symfony erstellt . Da ich länger mit dem zweiten gearbeitet habe, werde ich alles Weitere in seinem Zusammenhang beschreiben.


Die Infrastruktur


Die meisten meiner Kampfprojekte waren auf veraltete Technologien ausgerichtet, daher konnte ich auf keinem von ihnen moderne Code-Analysatoren verwenden. Nachdem ich die alte Unterdrückung beseitigt hatte, installierte und konfigurierte ich alle Tools zur Codequalitätskontrolle, die ich nutzen konnte:



Integrierte Validierung in Github mit Travis . Abschließend fügte er eine Docker-Datei hinzu, um eine lokale Entwicklerumgebung mit einer make-Datei zu erstellen, die die grundlegenden Befehle für den Container enthält (Starten von Überprüfungen, Tests, Erstellen von Modellen usw.).


Lernergebnisse


PHP 7


Bevor ich mit der Arbeit an der Bibliothek begonnen habe, habe ich die neuen Funktionen von PHP 7 nie wirklich genutzt. Sie sind schön: von strengen Typen bis zu einer deutlichen Steigerung der Produktivität. Besonderer Dank geht an die Entwickler für den Null-Koaleszenz-Operator. Ich habe nach der Einführung eines Operators keinen so gravierenden Rückgang der Codebasis festgestellt.


Rar


Überraschenderweise gab es in PECL ein Paket für die Arbeit mit RAR . Normalerweise werden solche Erweiterungen nicht als vertrauenswürdig eingestuft, und ich versuche, sie zu vermeiden. Es stellte sich als verdächtig stabil heraus: Es wurde problemlos in 7.2 installiert, es konnte relativ schnell und mit geringem RAM-Verbrauch große Archive entpacken (6 GB werden in 10-20 Minuten entpackt, abhängig von den verfügbaren Systemressourcen). Ich fürchte immer noch, dass dies eine Manifestation von Murphys Gesetz ist.


Xmlreader


Das Lesen von riesigen XML-Dateien ist keine triviale Aufgabe. Und wieder kam die PECL-Erweiterung zu Hilfe - XmlReader . Ich habe seine Leistungsfähigkeit nicht sofort erkannt, aber in mehreren Ansätzen in Verbindung mit dem Symfony-Serializer angepasst, um Daten aus FIAS-Dateien schnell und kostengünstig abzurufen. Bibliotheksseitig implementiert das Reader-Objekt die Iterator-Schnittstelle, die nacheinander XML-Zeichenfolgen zurückgibt, die einem Datensatz in der Datei entsprechen. Mit dem Symfony-Serializer werden diese Zeichenfolgen in Objekte konvertiert. Eine 20-GB-Datei kann in 3-4 Minuten gelesen werden, wenn nicht mehr als 50 MB RAM verwendet werden.


In die Datenbank schreiben


Natürlich habe ich mit assoziativen Arrays mit Daten und umfangreichen Tabellenbeschreibungen begonnen. Der Code verwandelte sich schnell in einen Hash von Konfigurations- und Konvertierungsklassen.


Die Magie der Doktrin zeigte, wie Objekte sich selbst beschreiben können. Ich habe mich für denselben Ansatz entschieden, aber gleichzeitig meine eigene Implementierung des Schreibens von Daten in die Datenbank mithilfe von PDO aufgegeben. Stattdessen habe ich eine Speicherschnittstelle erstellt, die Methoden zur Verarbeitung von Objekten beschreibt. Basierend auf der Entitätsklasse entscheidet eine bestimmte Speicherimplementierung genau, wie und wo die Daten geschrieben werden sollen. Dieser Ansatz machte es einfach, eine Vielzahl von Speichern zu verbinden: von MySQL bis zu CSV-Dateien.


Dateneinfügeoptimierung


Ich habe den ersten Import nach 48 Stunden abgebrochen. Es wurde deutlich, dass Sie den Prozess des Einfügens von Daten optimieren müssen.


Zuerst habe ich zu den Spalten vom Typ ugid für in PostgreSql integrierte Primärschlüssel gewechselt . Das Schreiben in eine uuid-Spalte mit einem Index ist viel schneller als das Schreiben in eine Zeichenfolge.


Danach habe ich alle unkritischen Indizes und Fremdschlüssel aufgegeben, da die Sorge um die Datenintegrität vollständig auf der Seite des FIAS-Teams liegt.


Dann habe ich die Speicheroberfläche überarbeitet, sodass das externe Skript es explizit über den Abschluss des Imports informieren kann. Dies ermöglichte die Verwendung von Bulk-Insert, was die Aufnahme zeitweise beschleunigte. Auf der Suche nach Informationen bin ich neben query_to_xml auch auf den Befehl copy query_to_xml . Es hatte zwei große Nachteile: Erstens muss der PostgreSql-Benutzer über Leseberechtigungen für die Datei verfügen, die ich nicht garantieren kann, und zweitens ging die Möglichkeit verloren, die Daten im Skript vor dem Schreiben zu ändern.


Trotz dieser Änderungen betrug die Importdauer mehr als 30 Stunden. Eine radikale Änderung des Ansatzes war erforderlich.


Parallele Prozesse


Das Internet ist voll mit Artikeln über Asynchronität in PHP. Meine Wahl fiel auf Amp . Es lief einfach nicht asynchron. Erstens wurde der Code schnell zu einem furchterregenden Blatt mit Rückrufen und nicht offensichtlichen Anrufen (dies ist wahrscheinlich meine Schuld, nicht die asynchrone Vorgehensweise). Zweitens musste ich auf die Verwendung von Standard-ORMs verzichten, da nicht blockierende Aufrufe der Datenbank über ein spezielles Framework erforderlich sind. Drittens, obwohl es Bedingungen gibt, unter denen PostgreSql Zeilen parallel einfügen kann, sind sie äußerst schwierig zu erfüllen. Infolgedessen beobachtete ich nach 5 Stunden Arbeit, wie alle meine asynchronen Anforderungen auf der Datenbankseite zwangsweise "synchronisiert" wurden.


Der Import ist jedoch gut in parallele Prozesse unterteilt: mehrere völlig unabhängige Aufgaben, die keine gemeinsamen Ressourcen haben, für die sie konkurrieren könnten, und Daten, die sie austauschen könnten. Außerdem habe ich im Rahmen eines Threads einen schönen und linearen Code erhalten.


Das erste Mal habe ich mich für die Parallel- Erweiterung entschieden. Es gibt einen schwerwiegenden Fehler: Der Interpreter muss mit Unterstützung für ZTS (Zend Thread Safety) erstellt werden. Da ZTS in normalen Web-Skripten nicht funktioniert, müsste man zwei verschiedene Versionen des Interpreters haben. Eine ohne ZTS für das Web, die zweite mit ZTS für die Installation von FIAS. Die potenzielle Leistungssteigerung überwog diese Unannehmlichkeit, insbesondere angesichts der Tatsache, wie einfach es ist, einen neuen Docker-Container zusammenzubauen und in Verbindung mit dem alten zu verwenden. Leider führte das Starten von Symfony in einem neuen Thread zum Überlauf des PHP-Stacks, und ich war nicht bereit, den DI-Container und die praktische Konfiguration abzulehnen.


Schließlich fand ich den Symfony-Prozess . Tatsächlich wird ein neuer Prozess für den angegebenen Konsolenbefehl gestartet und dessen Fertigstellung überwacht. Ich musste zwei zusätzliche Ketten hinzufügen. Der erste lädt das Archiv herunter, entpackt es und leitet parallele Prozesse zur Datenverarbeitung ein. Der zweite Befehl entnimmt eine Liste von Dateien aus dem Befehlszeilenargument und schreibt deren Inhalt in die Datenbank.


Aufgrund der mangelnden Erfahrung mit parallelen Prozessen habe ich anscheinend alle Anfängerfehler gemacht.


Zum Beispiel hat mein Initiierungsprozess die Fertigstellung der Kinder in einer Endlosschleife überprüft, und natürlich hat er unangemessen viel Prozessorressourcen dafür ausgegeben. Der Sleep-Aufruf zwischen den Iterationen half.


In der ersten Implementierung wurden Dateien ungleichmäßig auf die Prozesse verteilt. Die zwei größten fielen in einen Strom, der mehr als 20 Stunden lang verarbeitet wurde. In der zweiten Implementierung habe ich einen speziellen Manager hinzugefügt, der Dateien basierend auf der Zeit verteilt, die zum Importieren erforderlich ist. Jetzt werden die Prozesse gleichmäßig geladen.


Nach diesen Änderungen konnte ich die Vollversion von FIAS in 16-20 Stunden importieren, abhängig von den Serverressourcen. Nicht so gut, wie wir es uns wünschen, aber ich arbeite weiter an der Optimierung. Der nächste Schritt ist eine vollständige Ablehnung von PostgreSql zugunsten von Elasticsearch.


Schlussfolgerungen


War es das wert? Zwei Jahre Arbeit an einer Bibliothek, die noch nie in ein Kampfprojekt geraten ist?


Ja, vollständig.


Ich habe trotzdem meinen Job gewechselt. Während eines Rundgangs durch ein Dutzend Interviews beantwortete ich viele knifflige Fragen nur dank meines Lieblingsprojekts.


Die Panik, die PHP im Sterben liegt, wird immer größer. Ich werde die Tatsache nicht verbergen, dass ich selbst über die Migration in eine andere Sprache nachgedacht habe.


Nachdem ich die enorme Arbeit gesehen hatte, die das PHP-Team in Version 7 gesteckt hatte; er war durch persönliches Beispiel überzeugt, wie reif die Sprache wurde und wie reich das Ökosystem ist, das es gewachsen hat; Ich kann mit Sicherheit sagen, dass die Gerüchte über den Tod von PHP stark übertrieben sind. Und dies ist nur der Anfang: In Zukunft warten wir auf JIT, sofortige Asynchronität und vieles mehr.

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


All Articles