Xml-ben nagy fájlok base64-gyel kódolva

Fórumok

Már 1 napot eltöltöttem ezzel a feladattal és nem igazán látok megoldást.

SOAP: Jaxb 2.3.3 + Spring.

Kb. 400 megás fájlokat kellene XML-ben visszaadnom base64 kódolással.

Valahogy így néz ki az XSD ezen része: 

type="xs:base64Binary" xmime:expectedContentTypes="application/octet-stream"

 

A Java-ban így van beállítva:

.setDoc(new DataHandler(new FileDataSource(f)));

 

Jelenleg olyan 4G-át eszik 1db 400 megás fájlnál.

Jó lenne ha streamelné inkább mint betölti memóriába a teljes fájlt stb.
com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.class:

try {
    ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(1024);
    InputStream is = this.dataHandler.getDataSource().getInputStream();
    baos.readFrom(is);
    is.close();
    this.data = baos.getBuffer();
    this.dataLen = baos.size();
} catch (IOException var3) {
    this.dataLen = 0;
}

Mert a főnökség viszont szeretné, ha mindez elmenne <1G rammal. Ha lehet 512 megával.

Ahogy debugoltam úgy látom alapból betölti a teljes fájlt. Majd azon még dolgozik a base64 kódolás miatt és úgy rakja be az XML-be. Gondolom a Content-Lenght-et is így állítja be (de ez nem lenne fontos).

Van ötletetek valami megoldásra?

Hozzászólások

Szerkesztve: 2022. 09. 28., sze – 17:03

Megírod kézzel a kódolást, egyszerre egy buffernyit kódolsz. rfc4648

A legtöbb XML lib a text node-okat be kell töltse egyben memóriába. Nyilván nem ismerem mindet, de amit ismerek az ilyen. Tehát a BASE64 string egyben a memóriában lesz, ami már eleve bukó. Hogy hogyan lesz ebből ennek a többszöröse, azt nem tudom a kód elemzése nélkül, de gondolom az XML lib a belsejében eszképel meg ilyenek, és persze ezt is objektumokba csinálja stream helyett.

Ahogy én csinálnám:

Generálnék XML-t memóriába úgy, hogy a bazi nagy Base64 helyett placeholder lenne ott. Például: <izé bizé="PLACEHOLDER_BASE64"/>

Kvázi pszeudókóddal:

Writer wr=response.getWriter();
wr.write(xmlPrefix);
Base64ToWriter(inputStream, wr);
wr.write(xmlPostfix);

Hasonló elv működhet akkor is ha több placeholder van, csak akkor kicsit bonyolultabb.

Szerk.: a parser oldalon még nagyobb lesz a baj, mert olyan API-t sem ismerek, ami nem töltené be egybe a szöveges entitásokat. (Egyébként ez béna, egy teljesítménykritikus alkalmazásban már okozott ez problémát a praxisomban. Úgy kellene működnie az XML parsernek, hogy van egy ) Tehát benn lesz a base64 változat RAM-ban.

Ha így kerülne be az XML-be a fájl:

<fájl>

<darab>base64darab</darab>

<darab>base64darab</darab>

....

<darab>base64darab</darab>

</fájl>

Akkor SAX api-val kicsi RAM-ban meg lehetne oldani. Még ekkor is fennáll a probléma, hogy a GC-t megpörgeti, mert a string ojjektumok létrejönnek, csak legalább nem egyszerre. Így simán belefér az egész néhány mega RAM-ba és a darabok méretével skálázódik.

Igen, ez a file + chunk jellegu egymasbaagyazott megoldas is teljesen jo lehet. Sajnos az implementalasnal itt sem usszuk meg feltetlen azt hogy valamifele trukkel beazonositjuk hogy a fragmensek utani lezaro xml tag-eket es azt utokag kiirjuk... de ez a megoldas is faszan skalazodik, jaja.

még mindig jaxb.

amíg jaxb-t használsz (DOM) addig sose szabadulsz meg a memóriafoglalástól.

streamben kell az xml-t megépítened.

lehet úgy csinálnám hogy jaxb-ből legyen meg serializálva a többi rész és ezt az attachmentet beleszúrnám (a streambe) ahova kell.

Gábriel Ákos

Én első körben jelezném a főnökség felé, hogy ezt gondolják át újra, mert az egy dolog, hogy te szívni fogsz az implementációval, de az összes kliens is... (ha te vagy a kliens, akkor pedig az API gazdáját keresném)

Szerkesztve: 2022. 09. 29., cs – 09:47

Megoldás lehet, ha nem beágyazva küldöd, hanem attachment-ként.

Szerk.: attachment-ként úgy is el lehet küldeni, hogy beágyazva legyen az adott helyre, de ehhez támogatnia kell ezt a végpontnak is. Itt nézheted meg miként kell: MTOM. A szerver oldalon ez az egyszerűbb, mert csak engedélyezni kell egy flag beállítással.

Szerkesztve: 2022. 09. 29., cs – 08:28

Ezt a memóriában eleve felejtős, 400Mb mellé odateszed még a base64 enkódolt string-et akkor elfogyott az 1Gb-od.

Ez így jaxb-vel nem fog menni. Valamilyen stream-es módszerrel tudod megcsinálni, pl kiírod az outputra stream-re az xml-t, és amikor kell, feldolgozod a fájlt, azt is stream-ként, és kiírod. Az xml-ből csinálhatsz jaxb-vel string-et, a fájl nélkül, de utánna ezt az xml string-et neked még fel kell dolgoznod, mert a base-64 enkódolot fájl a közepén kell visszaadnod. Tehát visszaírod a felét, ott el van vágva valami placeholder mentén, kiírod a fájlt, és visszaadod a másik felén.

1. Tehát van egy XML string-ed fájl nélkül.

2. Kiírod az outputstream-re az xml felét a fájl contenting

3. Kiírod a fájlcontent-et (stream-ekkel, ne tartsd bent a memóriában az egészet, se a bájttömböt, se a base64 enkódolt string-et)

4. Kiírod az xml stream másik felét.

Tudom, ez így egy hülye kavarás, de a feladat is furcsa.

De ha lehet attachmentként küldeni (de azt is stream-ként megadva), akkor +1 arra.

JAXB az DOM-ot használ, nem? Tehát az egészet benyalja mindenestül. Streamelned kellene valahogyan.

Erre az alkalmazás kódjától függetlenül is lehet egy jó kis POC-ot írni.

Amivel még kínlódni fogsz az a köztes proxy-k max response size-a.

Én ha lehet úgy csinálnám hogy legyen valahova "eltárolva" akár egy S3-ba (vagy minio-ba) vagy akármilyen webserverre staticba a nagy dög file és az URL-t adnám át az XML-ben.
 

Gábriel Ákos

"a főnökség..."

Kolléga tetszik lenni? :D

Azért van deja vu érzésem, mert ugyanilyen probléma volt nálunk is, és rám szoktak így mutogatni a kollégák, amikor nem értik az elvárást, és persze nem is kérdezik az okát.

Első kérdés: egyáltalán miért akarunk egy XML-be ekkorát csomagolni?

- így szoktuk, már az ügyfél is így várja. 

Következmény: a debug-hoz valami K drága XML editor kell, mert semmi más nem nyitja meg. Gondolom az ügyfél fejlesztője is hasonlót szív, de persze nem beszélünk vele hátha lenne közösen jobb megoldás.

 

Amiért kisebb memória kéne: mert minden új cuccunk kubernetes tetején fut és nem viccesek a worker node méretével összemérhető konténerek. Utána meg megy a sírás, hogy a timeout-ot is vegyük nagyobbra, mert egy ekkora állományt ugye idő átvinni. Health check, firewall, tcp, miegymás

Amiért szívás, hogy egyetlen tranzakció vitéz ennyit: és mi van, ha bejön 10? Ki képes a hívási láncban ennyit pufferelni?

Szerkesztve: 2022. 09. 29., cs – 09:37

Mert a főnökség viszont szeretné, ha mindez elmenne <1G rammal. Ha lehet 512 megával.

Kezdj el keresni valami jobb melóhelyet, ha a főnökség ennyire belepofázik abba, hogy mit és hogy kell megoldani, majd megoldják ők.

--

Az interfészen nem lehet faragni? Merthogy: https://www.w3.org/TR/SOAP-attachments/

> Kezdj el keresni valami jobb melóhelyet, ha a főnökség ennyire belepofázik abba, hogy mit és hogy kell megoldani, majd megoldják ők.

Azért ez nem rakétatudomány, nem lehetne csak simán megcsinálni és nem rinyálni? Persze, ha van kivel beszélni, megváltoztati a protokollt akkor érdemes. De semmi lehetetlenről nincs szó, meg lehet csinálni a feladatot, _kissé_ túlreagálás volna csak ezért felmondani.

Szerkesztve: 2022. 09. 29., cs – 10:38

Irsz valamit ami eleve streamelve olvassa fel a file-t es teszi at base64-be on-the-fly? Mondjuk 4096*3-as chunkokban felolvasod es 4096*4-es chunkokban letarolod, oszt ennyi. Ha fel tudsz olvasni 3*N byteot es kiiord 4*N byteba, akkor azok osszefuzhetoek base64-ben is minden tovabbi nelkul definicio szerint. Csak az utolso (2/3 valoszinuseggel 3-mal nem oszthato merettel rendelkezo) bytekupac eseten van az hogy mar nem tudod tovabbfuzni, vagyhat nem feltetlenul a legjobb otlet. De akkor meg mar ugyis vege van a filenak :)

Ez a "felolvasod a file-t majd memoriaban atteszed base64-re es utana lestreameled valahova" az minden programnyelvben/kornyezetben elkepesztoen unhatekony,  nemhogy egy olyan kornyezetben amit eleve memoriazabalonak teremtett a joisten. Ellenben az a szep hogy magat az alapproblemat problemat O(1) memory footprinttel is meg lehet oldani (szoval ha az lenne a feladat hogy egy atmega* mikrokontroller + sdkartya + tcp/ip offload engine kombinacioval oldjam meg, akkor meg lehet csinalni). Es hat azert egy base64 encoder nem egy agysebeszet.

Szerk: es raadasul a streameleses valtozat meg gyorsabb is lesz mert a disk input dma es az ethernet downstream dma tud egyszerre dolgozni. Es nem is kell hozza annyi effort ;)

Ráadásul bármekkora lehet így a fájl, és több párhuzamos kérést is le tud reagálni majd a program. Ha a fájlt bárhol egyben a memóriában akarja tartani, akkor az zsákutca.

Hat, igen ;) szoval en ugy csinalnam hogy a kedvenc javas xml-manipulalo kornyezetemmel eloallitanam az xml-t szerializalva, ugy hogy a file helyet uresen hagyom (kb mintha 0 byte lenne). Ekkor a szerialzialt xml stringbe, lesz egy <file></file> blokk (vagy barmi ezzel ekvivalens). Majd rakeresnek hogy hol van a file helye, kiirnam az xml elso felet a downstream-re, majd johet a file -> base64 stream, majd a szerializalt xml maradeka. Jol skalazodik, nincs vele baj, es minimalis (effektive zero) memory footprintje van.

Mondjuk a kliensben a beolvasás érdekes lesz. :)

Lol ;) Hol lenne igy 2022-ben barmifele bongeszo-elmeny ha barkitis erdekelnenek ilyen uri huncutsagok hogy hogy is fog a kliens reagalni barmire :))

Első és legfontosabb, hogy ne JAXB-vel dolgozd fel. 

"Jelenleg olyan 4G-át eszik 1db 400 megás fájlnál."

Mit csinálsz az XML beolvasásán kívül? Milyen memóriaszemeted marad? Irreális a 4GB, ha csak egy 400 MB-os fájl olvasol fel. Profiloztad már?

Mi a pontos feladat? A JAXB egy kiváló absztrakciót ad objektum-XML leképezésre, de ennek ára van, nem hatékony.

Használhatsz StaX-ot, ha hatékonyabb XML feldolgozást szeretnél, de ekkor elveszik csomó kényelmi funkció a JAXB-hez képest.

A hatterben vsz az van hogy a 400megas filebol csinal ugye egy 530+ megas base64 tombot, az megjelenik a hierachikus xml objektumban megegyszer, majd az meg megjelenik a szerializalt xml-ben is. Es akkor hipszhopsz mar 2 giganal tartunk. Ez az ami siman lehet realis es egy egyszeri naiv implementacio (megintcsak: programnyelvtol/kornyezettol fuggetlenul) vsz ezt csinalna. Azaz pl c + libxml komboval is itt tartanank ha esz nelkul, csak ugy megszokasbol dolgoznank. Es akkor jon a hab a tortan, a java osszes pazarekolasa sem segit... 

Javában a stringek belső ábrázolása többnyire betűnként 2 bájt (UTF-16-szerű). Ez még odavág egy kétszeres szorzót a base64 ábrázolásnak :-) Talán az újabb JRE implementációkban van erre optimalizáció, de nem vagyok biztos benne.

Aztán még a transzkódolás is rendkívül költséges, egyszer ebbe is beleástam magamat. De az legalább nem RAM, ezért a legtöbb esetben a többi mellett már fel sem tűnik, hogy mennyire pazarló. Ha komolyan optimalizálni kellene egy ilyet, akkor nyers bájt folyamot használnék és a base64 enkóderből is csinálnék egy olyat ami ASCII kódolással bájt folyamba tudja írni a kimenetet, tehát String objektum nem keletkezik egy sem. És persze mindezt néhány újrahasznosított bufferben. Így már majdnem olyan hatékony volna a program mint mikrovezérlőn :-)

Hat, igen, ez vicces... es mi lesz ha realizaljak majd hogy utf-16 helyett itt az ideje utf-32re atterni... mertkulonben a padlizsant evo tort-katamaran csuklovoros unikornis emotat nem lehet letarolni :) Na, akkor kesz egy parszaz meganyi adaton dokgozo, egyebkent O(1) memoriaigenyu feladatra keves a 8 giga :) 

Zsengébb koromban tervezgettem egy saját nyelvet csinálni és abban 32 bites lett volna a string ábrázolása. Mert az UTF vacakolások egyszerűen túl bonyolultak amiatt, hogy nem fix a karakterek hossza. Javában is a 16 bites ábrázolás miatt lehetséges olyan karakter ami nem egy char, és emiatt egy rakás kivétel kerülne a minőségi kódokba, amit kezelni kell. Amit persze nem csinálnak meg a programozók - a többség nem is tudja, hogy kellene - és emiatt szerintem lehetnek még aknák a rendszerben. Biztos nem vagyok benne, mert 100%ig én sem értem hogy pontosan mit hogyan kellene csinálni :-)

Nincs is jobb mint mikor kapsz egy random kódolási exceptiönt valami lib mélyéről, azán keresgetheted, hogy valaki úgy vágott félbe egy stringet, hogy a karakter két 16 bites része külön stringbe került.

Szóval nem szeretem azokat a rendszerket amikben lehetséges aknák vannak, és a változó hosszúságú kódolás pont ilyen. Ezért kellene 32 biten tárolni egy karaktert.

Hat igen, ez marcsak egy ilyen kompromisszumos jatek... gyorsan, olcson es jot nem lehet csinalni ;) De cserebe az mar jo ha mi dontjuk el hogy melyiket dobjuk el. Jo az a 32 bites karakterkeszlet is, ha van ra eroforras. Aztan marcsak a typedef-ekre, alignmentekre meg ilyesmikre kell figyelni oszt minden rendben lesz!

Igaz, emiatt gyanús, hogy ezek az optimalizációk ki vannak náluk kapcsolva, vagy régebbi JRE-n vannak, különben nehéz volna megmagyarázni a mért számait. Egy heap dumpban meg lehetne nézni, hogy hány példányban vannak tárolva a stringek és egyenként mennyi overheaddel. Volt itt egy topik ahol elkalandoztunk Java HTTP szerver performance mérés irányba, és akkor elég mélyen beleástam ennek a bugyraiba, hogy micsoda bonyolultságok vannak a stringek kezelésének mélyén. És persze találtam nagyon nem optimális (CPU-ban mérve) implementációt is a JRE-ben, amiről voltak blog posztok is, hogy már dolgoznak rajta. Egyszer majd minden optimális lesz.

Köszönöm a hozzászólásokat. Most a fájlcsatolás megoldás lett felkínálva. Bízom benne elfogadják.

Valamennyire kötött a kezem. Csak annyit tudok, amire a főnökség rábólint. Eddig ez volt a spec., hogy base64 kódolással xml-be. Kicsit csodálkoztam rajta, de elkészítettem.

Közben kiderült, hogy mekkora fájlok vannak. Így csináltunk memória teszteket, akkor bukott ki ez.

A másik oldallal nem én tartom a kapcsolatot, azt a fölöttem lévők teszik.

Most javasoltam több megoldást. Meglátjuk mi lesz elfogadva.

Igen, ez jogos, hogy egy méret felett azzal a problémával is foglalkozni kell, hogy megszakadt kapcsolat esetén újraindítható legyen a letöltés. Amire a HTTP-nek van protokollja, csak használni kell. Az eredeti XML esetén problémás, hogy a kész XML-t cache-elni kell, vagy minden kérésre az egészet előállítani, vagy úgy megvalósítani a szerializálást, hogy képesek legyünk a közepéből bármely darabot újra előállítani kérésre. Ez utóbbi nagyon brrrr, nem akarnám magamnak a feladatot. Ha már HTTP, akkor a fájlt töltsük le fájlként, erre van kitalálva és csak egy megfelelő chunkokat támogató handlert kell mögé tenni és kész a tökéletes megoldás.