DAV-ABDA / eRezept-Referenzvalidator

eRezept-Referenzvalidator auf Basis des HAPI-FHIR-Validators
Apache License 2.0
20 stars 8 forks source link

Lange Laufzeit beim Prüfen großer TA7 FHIR Dateien #35

Open HeikoBensch opened 2 years ago

HeikoBensch commented 2 years ago

Die Verarbeitung einer großen FHIR Datei 971 MB mit 19000 ERezepte dauert ca. 2,5 bis 3 h.

Systemumgebung: Linux VM: 32 GB, 4 Cores Java 11

Aufruf java -Xmx32000m -jar TA7Validator/reference-validator-cli-0.9.9.jar ./grosseFhirDatei.xml

Wie kann die Ausführungsdauer verkürzt werden.

DarthGizka commented 2 years ago

Das Thema hatten wir vor einigen Monaten bereits abgehandelt... Der dem Referenzvalidator zugrundeliegende HAPI-Validator hat Probleme bei der Verarbeitung solcher großen Dateien; uAG-Teilnehmer berichteten Laufzeiten zwischen 30 Minuten und mehreren Stunden.

Die Lösung des Problems liegt darin, vor der Übergabe großer TA7-Dateien an den Validator die Base64-Blöcke zu entfernen und durch kleine Stummel zu ersetzen (z.B. mittels eines XML-Pull-Parsers wie System.Xml.XmlReader in .NET). Die resultierende Datei hat dann nur noch einen Bruchteil der ursprünglichen Größe.

Die extrahierten Base64-Blöcke werden i.d.R. zusammen mit entsprechender Referenzinformation (entry.fullUrl, entry.resource.id, Blocktyp) in einer geeigneten Datenstruktur für die ohnehin notwendige weitere Verarbeitung vorgehalten. Die Form der Vorverarbeitung, die Form der Stummel (i.e. mit oder ohne zusätzliche Id-Information) sowie Vorhalten und Verarbeitung der extrahierten Blöcke sind extrem spezifisch für das jeweilige verarbeitende System, und diese Vorverarbeitung ist nicht notwendigerweise Java-basiert. Daher muß jede TA7 verarbeitende Stelle auf eigene Weise den Referenzvalidator in ihr System integrieren; die Extraktion der Base64-Blöcke ist nur ein winziger Nebenaspekt dieser Integration.

HeikoBensch commented 2 years ago

Danke probiere ich aus.

DarthGizka commented 2 years ago

Wir sollten dieses Thema noch eine Weile offenhalten. Ich bin mir sicher, daß wir mit der geballten Expertise der uAG-Teilnehmer die Validierung großer TA7-Dateien noch um mindestens eine weitere Größenordnung beschleunigen können. Einige der dafür in Frage kommenden Maßnahmen können u.U. kleinere Anpassungen in der nächsten Edition des Referenzvalidators erfordern.

DarthGizka commented 2 years ago

ACHTUNG: HAPI hat momentan offensichtlich ein Skalierungsproblem beim Resolvieren von Referenzen auf Bundle-Einträge, wobei ab ca. 1000 Einträgen die notwendige Zeit geometrisch zu wachsen scheint statt linear. Dieser Effekt dominiert alle anderen um Größenordnungen. Bis zur Lösung dieses Problems bringen andere Beschleunigungsmaßnahmen - e.g. Schrumpfen der Blobs oder Verwendung von URI-Referenzen ('urn:uuid:{uuid}') anstelle von relativen Pseudo-URLs ('{resource-type}/{id}') - wenig oder gar nichts.

HeikoBensch commented 2 years ago

Kann ich bestätigen. Wir haben die ganzen Base64 Blöcke durch Stummel ersetzt. Die Datei ist zwar deutlich kleiner geworden, allerdings konnte ich keine Signifikante Verbesserung feststellen. Die Validierung dauert immer noch 2 bis 3 Stunden. Ticket lass ich erstmal offen.

DarthGizka commented 2 years ago

tl;dr: Validierungszeit für 1 GiB verkürzt von mehreren Stunden auf < 1 Minute

Bezüglich des quadratischen Laufzeitverhaltens habe ich bei HAPI ein Issue aufgemacht; bei Interesse kann man die Details dort nachlesen:

Validation of bundle entry references is O(n²) and it can make validation of a file take hours

Zum Erkunden möglicher Lösungen durch Anpassung der Profile (e.g. Weglassen der Referenzliste) gibt es jetzt ein Thema auf Stack Overflow:

Do (document) bundle entries always have to be referenced or referencing?

Momentan sieht es so aus, als bräuchte man nur den Bündeltyp von 'document' in 'collection' zu ändern, um die Liste der Referenzen weglassen zu können. Grahame Grieve hat auch schon reagiert; wir stehen noch in E-Mail-Kontakt.

Bis zum Eintreffen der einen oder anderen Lösung kann die Validierung auch mit den existierenden Validatoren (HAPI oder Referenz) mit vertretbaren Zeitaufwand durchgeführt werden: die Berechnung von f(100 x) dauert 100 100 Mal so lange wie die Berechnung von f(x). Also lassen wir f(100 x) im Regal und berechnen stattdessen 100 f(x).

Ausführlicher: sowohl die Liste der Referenzen als auch die E-Rezept-Daten enthaltenden Bündeleinträge bilden zusammenhängende Blöcke innerhalb der TA7-Datei. Diese kann man einfach ausschneiden und in kleineren Portionen wieder einsetzen. Die Umsetzung ist größtenteils eine Übung im Indizieren: Durchwandern des XML mit einem Pull-Parser wie XmlReader (.NET) oder XMLStreamReader (StAX) und Aufzeichnen von Start und Länge der relevanten Elementtexte im XML.

Da die kleinen Dateien durch Zusammensetzen von Teilen des Originaltextes entstehen, besteht hier keine Gefahr von Verfälschungen (wie etwa beim Einsatz von XmlWriter oder DOM); selbst der letzte Whitespace-Krümel wird getreulich an den Validator weitergeleitet.

Die einzige echte Verfälschungsmöglichkeit liegt in der Verkürzung der Base64-Klumpen. Bei diesen muß die lokale Verarbeitung - die ja ohnehin erfolgt - auch den vollen Prüfumfang des Validators realisieren, also Beweis der Abwesenheit von verbotenen Zeichen etc. pp.

Hier ist die Ausgabe eines entsprechenden LINQPad-Skripts für eine TA7-Datei mit knapp 2^30 Bytes (1 GiB). Der Referenzvalidator 0.9.9 - eingewickelt in com.sun.net.httpserver.HttpServer - lief auf meinem Java-Notebook (8-Kern Xeon mit Ubuntu 20.04 und JRE 11), angebunden mit Gigabit-Ethernet an das Notebook mit dem LINQPad-Skript (Windows 11, .NET 6). Beim Umstückeln der TA7-Datei habe ich die Base64-Klumpen auf 4 Zeichen verkürzen lassen, um die Bremswirkung des Netzwerks zu minimieren. Parameter wie Stückelgröße und Anzahl der zum Validieren zu verwendenden logischen Kerne wurden empirisch auf beste Performance im vorliegenden Fall getrimmt.

 2.217 ms File.ReadAllText("d:\Test\ARZFHR22017.gross_24984.xml") -> 1.073.740.767 Zeichen
 2.572 ms parse_XML() -> 24.984 Rezepte
     2 ms DBG_pruefe_Referenzen_vs_Rezeptindex()
   636 ms baue_TA7() für alles (mit kompletten Blobs, zwecks Vergleich mit Original-XML)
    28 ms baue_TA7() für alles (Blobs verkürzt auf 4 Zeichen) -> 97.266.111 Zeichen
    19 ms 391 x baue_TA7() à 64 Rezepte -> 98.591.721 Zeichen

18:43:05.674 >>> Parallelvalidierung (11)
18:43:55.301 Task-Ende (35 bearbeitet)
18:43:55.489 Task-Ende (36 bearbeitet)
18:43:55.521 Task-Ende (35 bearbeitet)
18:43:55.646 Task-Ende (35 bearbeitet)
18:43:55.774 Task-Ende (36 bearbeitet)
18:43:55.951 Task-Ende (35 bearbeitet)
18:43:55.982 Task-Ende (36 bearbeitet)
18:43:56.067 Task-Ende (37 bearbeitet)
18:43:56.165 Task-Ende (35 bearbeitet)
18:43:56.242 Task-Ende (35 bearbeitet)
18:43:56.243 Task-Ende (36 bearbeitet)
18:43:56.243 <<< Parallelvalidierung (11)

50.570 ms -> 129 ms/Validierung, 2,02 ms/Rezept (I/O: 98.591.721 Zeichen, 1.950 k/s)

56.657 ms Gesamtzeit

Ergo kann die ARZsoftware-Genossenschaft jedem E-Rezept-Ansturm gelassen entgegensehen. ;-)

HeikoBensch commented 2 years ago

Danke für die Ausführungen. Wir probieren das aus.

DarthGizka commented 2 years ago

Grahame Grieve hat die relevanten Stellen im HAPI-Validator überarbeitet und das Ergebnis als Pull-Request bereitgestellt:

ok well I spent some time looking for bottlenecks and addressing them. https://github.com/hapifhir/org.hl7.fhir.core/pull/817 is the result. Are you in a position to give that a go and see how it goes? I think it's around 2 orders of magnitude faster, but mileage varies

Hier wäre es wichtig, im Sandkasten eine Version des Referenzvalidators mit diesen Patches zu bauen, damit wir Grahame Feedback bzgl. der Performance geben können. Ich kann auch eine Testdatei (ZIP mit 4 MiB, expandiert 93,2 MiB) bereitstellen, die aus einer TA7-Datei mit 1 GiB (24984 Rezepte) durch 'Verstummeln' der Blobs erzeugt wurde.

DarthGizka commented 2 years ago

Neuere Neuigkeit: die Fixes sind jetzt im Master, und dürften dann auch automatisch im nächsten turnusmäßigen Release des HAPI-Validators enthalten sein (https://github.com/hapifhir/org.hl7.fhir.core/releases). Sobald die 5.6.47 verfügbar ist, werde ich entsprechende Tests damit fahren und hier berichten.

DarthGizka commented 2 years ago

Release 5.6.47 ist jetzt verfügbar. Damit dauert Validieren der großen Datei aus dem Stückelungstest jetzt nur noch 30 Minuten. Das ist zwar noch kein Grund, den Stückeler zu verschrotten (zumal Ausnutzen aller Prozessorkerne ja weiterhin attraktiv bleibt), aber es ist auf jeden Fall ein erheblicher Fortschritt.

FHIR Validation tool Version 5.6.47 (Git# 567a9b2ce7bd). Built 2022-05-27T17:24:34.812Z (23 hours old)
  Java:   11.0.15 from /usr/lib/jvm/java-11-openjdk-amd64 on amd64 (64bit). 16012MB available
  Paths:  Current = /home/knightowl/Testdaten, Package Cache = /home/knightowl/.fhir/packages
  Params: -tx n/a -version 4.0.1 -level warnings -ig de.gkvsv.erezeptabrechnungsdaten#1.1.0 -ig dav.kbv.sfhir.cs.vs#1.0.3 ARZFHR22017.gross_24984.xml
  Jurisdiction: United States of America
Loading
  Load FHIR v4.0 from hl7.fhir.r4.core#4.0.1 - 4575 resources (00:02.0139)
  Load hl7.terminology#3.1.0 - 4117 resources (00:00.0768)
  Terminology server null - Version n/a: No Terminology Server (00:00.0000)
  Load de.basisprofil.r4#0.9.13 - 128 resources (00:00.0091)
  Load KBV.Basis#1.1.3 - 68 resources (00:00.0055)
  Load KBV.ITA.FOR#1.0.3 - 14 resources (00:00.0015)
  Load kbv.ita.erp#1.0.2 - 28 resources (00:00.0025)
  Load de.gematik.erezept-workflow.r4#1.1.1 - 49 resources (00:00.0039)
  Load de.abda.eRezeptAbgabedatenBasis#1.1.0 - 49 resources (00:00.0028)
  Load de.gkvsv.erezeptabrechnungsdaten#1.1.0 - 36 resources (00:00.0016)
  Load dav.kbv.sfhir.cs.vs#1.0.3 - 24 resources (00:00.0004)
  Get set...  go (00:00.0626)
Validating
  Validate ARZFHR22017.gross_24984.xml
Validate Bundle against http://hl7.org/fhir/StructureDefinition/Bundle..........20..........40..........60..........80.........|
...
Done. Times: Loading: 00:03.0821, validation: 28:51.0702. Memory = 7Gb

Success: 0 errors, 0 warnings, 1 notes
  Information: All OK

Verwendete Kommandozeile (unter Windows ^ verwenden anstelle von \):

java -server -jar ~/jar/validator_cli-5.6.47.jar \
    -tx n/a \
    -version 4.0.1 \
    -level warnings \
    -ig de.gkvsv.erezeptabrechnungsdaten#1.1.0 \
    -ig dav.kbv.sfhir.cs.vs#1.0.3 \
    ARZFHR22017.gross_24984.xml

Das Fixup-Paket dav.kbv.sfhir.cs.vs-1.0.3-json.tgz* muß manuell in den lokalen FHIR-Paket-Cache (~/.fhir/packages) implantiert werden, da es im Gegensatz zu den anderen Paketen nicht automatisch von Simplifier geladen werden kann.

Weglassen des Fixup-Pakets ergab bei meinen Tests keinerlei Änderungen der Ausgabe, zumal Meldungen der Stufe 'Information' ja ohnehin via -level warnings unterdrückt waren. YMMV.

*) kann aus dem JAR des Referenzvalidators herauskopiert werden (z.B. mit Total Commander)