Werken met heel veel XML files
Binnen PoliticalMashup werken we met allerlei soorten files, maar proberen alles om te zetten naar XML. Uit de XML files halen we dan de data. Dit leidt vaak tot erg lange wachttijden of je moet een speciale custom oplossing bedenken. We illustreren dit aan de hand van een voorbeeld.
We hebben een dikke 27.000 moties in XML, alle tussen de 3 en 4K, met elk zo’n 81 regels. Een simpele vraag is om voor elke motie haar kamerstuknummer uit te printen. Dat kan met de XPath expressie //hiddenkamerstuknr. De wachttijden voor deze simpele vraag lopen erg uiteen
Update: MonetDB-XQuery
Eerste tests met MonetDB/XQuery geven zeer positieve resultaten. Zie hieronder.
Voorbeeld
Hieronder de eerste 10 regels van zo’n motie in XML. We zien alleen nog metadata. Het kamerstuknummer van deze motie is dus 27830 .
<?xml version="1.0" encoding="ISO-8859-1" ?>
<document id="1802274"
template="Doc Kamerstukken Moties"
templateID="333"
timestamp="03-04-2008 08:34:03">
<hiddendatum>2001.07.05</hiddendatum>
<hiddentitel>Materieelprojecten</hiddentitel>
<hiddenkamerstuknr>|27830 |</hiddenkamerstuknr>
<hiddenkamerstukvolgnr>|003|</hiddenkamerstukvolgnr>
<hiddenekltr/>
<hiddenhoofdstuk/>
<hiddenraad/>
<status><alt omschrijving="status">Definitief</alt></status>
De tijden
Saxon
Met Saxon8 kunnen we de XPath functie collection() gebruiken. Dat is handig voor ons.
De code wordt dan:
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
version='2.0'>
<xsl:output method='xml' indent='yes'/>
<xsl:template match='/'>
<xsl:for-each select='collection("/scratch/marx/Parlando/PolitiekeData/MotiesTweedeKamer/XML")//hiddenkamerstuknr'>
<xsl:value-of select='.'/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Het resultaat op de directory met alle 27.000 moties was een out-of-memory error. Op een directory met slechts 10.000 moties duurde het
bash-3.00$ echo `ls xml1000/` |wc -w
10000
bash-3.00$ time java -Xmx1024M -jar ~/bin/saxon8.jar empty.xml saxontestcol.xsl > out.xml
real 1m39.002s
user 0m14.942s
sys 0m3.413s
Je kan natuurlijk ook alle moties aan elkaar plakken, en de vraag aan die nieuwe file stellen. Je krijgt dan een file van 112 Mb, en Saxon geeft keurig antwoord.
bash-3.00$ ls -lh allemoties.xml
-rw-rw-rw- 1 marx ii 101M Apr 14 19:47 allemoties.xml
bash-3.00$ time java -Xmx1024M -jar ~/bin/saxon8.jar allemoties.xml saxontest.xsl >out.xml
real 0m20.704s
user 0m16.157s
sys 0m1.132s
Xsltproc
Xsltproc is een xslt 1.0 processor en dus lang niet zo krachtig als Saxon. De collection() functie zit er niet in. Op de grote file gaat het dus prima: 2 keer zo snel als Saxon zowel in wachtijd (real) als in CPU tijd (user):
bash-3.00$ time xsltproc saxontest.xsl allemoties.xml >out.xml
real 0m8.860s
user 0m7.934s
sys 0m0.927s
We kunnen natuurlijk ook over al die 27K motie files heen loopen, maar dat gaat wel erg lang duren:
bash-3.00$ time for i in XML/*; do xsltproc saxontest.xsl $i >>out1.xml; done
real 2m17.461s
user 0m54.170s
sys 1m19.449s
bash-3.00$ time xsltproc saxontest.xsl allemoties.xml >out.xml
real 0m8.675s
user 0m7.741s
sys 0m0.936s
Met een Java programma als Saxon moet je al helemaal niet met zo’n truc beginnen:
bash-3.00$ time for i in XML/*; do java -jar ~/bin/saxon8.jar $i saxontest.xsl >> out1.xml; done
real 317m24.284s
user 277m9.756s
sys 24m29.174s
bash-3.00$
Conclusie
Een echte oplossing is er dus nog niet. We willen Saxon gebruiken wegens zijn XSLT en XPath 2.0 kracht, maar dan zijn we verplicht alle files aan elkaar te plakken. Maar dat schaalt niet als we met Handelingen files gaan werken. xsltproc werkt prima op grote files, maar heeft weer die beperkte uitdrukkingskracht.
Misschien is MonetDB wel een oplossing voor dit geval…, zie hun tutorial.
Update: MonetDB-XQuery
We hebben MonetDB gebruikt en zijn al erg onder de indruk. Het inladen van de files gaat snel en makkelijk, en het bevragen gaat helemaal snel. Hier zijn 2 voorbeelden.
Query fn:collection(”MotiesTweedeKamer”)//hiddenkamerstuknr
Trans 3.123 msec
Shred 0.000 msec
Query 67.723 msec
Print 225.214 msec
Timer 446.198 msec
This is 100x faster than XSLTProc and 1000x faster than Saxon in the one big document scenario. But, these times do not include the time it takes to parse the XML documents.
In the second example we ask how many moties are filed in each year. MonetDB answers in less than a second.
let $col := fn:collection(”MotiesTweedeKamer”)
let $years := fn:distinct-values(
for $date in $col//hiddendatum
return fn:substring(fn:string($date),1,4))
for $y in $years
order by $y ascending
return
Trans 13.915 msec
Shred 0.000 msec
Query 361.062 msec
Print 1.116 msec
Timer 393.358 msec
22-01-2009 om 00:05 uur
Dag Maarten,
Ik denk dat je de performance nog iets kunt verbeteren door in de xpath zelf de positie van het kamerstuknummer aan te geven (dus ‘/document/hiddenkamerstuknr’) in plaats van de xslt processor zelf de hele nodetree te laten doorzoeken (dmv ‘//hiddenkamerstuknr’).