gae-filestore (Írható/olvasható virtuális fájlrendszer Google AppEngine-re)

A Google AppEngine egyik elég erős megszorítása, hogy a telepített alkalmazás csak olvasni tudja a fájlrendszert. Új fájlokat nem hozhatunk létre, illetve nem módosíthatjuk a fájlokat. Bármilyen adattárolásra az AppEngine által biztosított adatbázist, a Datastore-t kell használnunk. Igazából ha jobban meggondoljuk, egy webalkalmazás többnyire valóban csak az adatbázist használja, így sokszor tényleg nincs szükség fájlrendszerre, de azért néha jól jön. Egy projektem kapcsán épp ilyesmire volt szükségem, ezért készítettem egy igazán egyszerű virtuális fájlrendszer implementációt Google AppEngine-hez. A rendszer működésének megértéséhez először írnék kicsit a Datastore-ról, annak is az alacsony szintű megvalósításáról.

Az AppEngine Datastore-ja egy BigTable alapú nem relációs adatbázis rendszer. Bár pontosan nem tudom, hogy a Google alkalmazások (GMail, Google Maps, a kereső, stb.) is ezt a megvalósítást használják-e, erről nem találtam konkrét leírást, de mindenesetre ez valami nagyon hasonló dolog kell hogy legyen. A rendszer használatára két lehetőségünk van. Egyfelől kapunk egy szabványos JPA/JDO réteget, így J2EE alkalmazásainkat változtatás nélkül, vagy kisebb változtatásokat követően futtathatjuk AppEngine-en. A másik lehetőség a low-level API használata, ami közvetlen hozzáférést biztosít a DataStore-hoz, így sokkal hatékonyabb, ugyanakkor kicsivel kényelmetlenebb is, mint a szabványos réteg. Én ez utóbbiról írnék kicsit bővebben. A Datastore adattárolásának alapja az Entity. A relációs adatbázis kezelőkhöz hasonlítva az Entity az adatbázis rekordnak felel meg, azzal a különbséggel, hogy az Entity-nek nincs fix szerkezete. Ebből a szempontból az Entity inkább olyan mint egy asszociatív tömb, vagy Java terminológiával élve Map. Egy ilyen Entity-be szabadon helyezhetünk el név-érték párokat. A másik nagyon fontos komponens a Key. A Key az entitás elsődleges azonosítója. A Key 3 féle módon épülhet fel. Generálhatjuk egy string-ből vagy long-ból, ezen felül definiálhatunk egy kulcs típust (kind), ami kb. a tábla megfelelője, és végül definiálhatjuk úgy a kulcsot, hogy ezeken felül egy szülő kulcsot is megadunk. Ez utóbbi megoldással a kulcsok és ezzel az Entity-k hierarchiába szervezhetőek. Ha összeállítottunk egy Entity-t, azt egyetlen metódussal lerakhatjuk a datastore-ra, és onnan a Key segítségével vissza is olvashatjuk. Amennyiben nem a Key alapján szeretnénk visszanyerni az entitást, úgy létre kell hoznunk egy Query-t. A Query segítségével az Entitás bármely értékére szűrhetünk az alapműveletek (kisebb, nagyobb, egyenlő, stb.) használatával. Az ilyen keresések segítésére definiálhatunk indexeket, de erre sincs feltétlenül szükség, hiszen a rendszer a lekérdezések alapján automatikusan legenerálja a szükséges indexeket. Az eredményként kapott entitás listát bármely eleme alapján rendezhetjük is, és nagyjából ennyi. Ha több entitást szeretnénk összekapcsolni (JOIN), VAGY kapcsolatot kialakítani a szűrési feltételekben, vagy bármi hasonlót tenni, amire egy relációs adatbázis amúgy önmagában képes, azt kódból kell megvalósítanunk. Cserébe viszont az egész rendszer masszívan elosztott, sémamentes, flexibilis és gyors. Most hogy megismertük a Datastore-t, lássuk a virtuális fájlrendszer működését.

A virtuális fájlrendszer legfelső szintű eleme a DataStoreFile entitás. Ezek az entitások testesítik meg az állományokat és a könyvtárakat egyaránt. Egy ilyen entitásnak 4 attribútuma van. A fájl típusa (könyvtár v. fájl), az utolsó módosítás ideje, a fájl mérete, és a fájlt tartalmazó útvonal. A fájl entitás egyedi azonosítója annak elérési útja, így ez alapján nagyon könnyen elérhetőek a fájl metaadatai. Mivel a metaadatokra sokszor szükség lehet, ezért a fájl entitás mentésekor és betöltésekor a memcache-ben is tároljuk, így annak további elérése már nagyon gyorsan megy a cache-ből. A fájl tényleges tartalmának tárolása Blob-okban történik. A Blob tulajdonképpen egy byte tömb, aminek mérete maximálisan 1 Mb lehet AppEngine esetén. Én 512 Kb méretű blokkokat használtam, amiket sorszámmal azonosít a rendszer. Az egyes blokkok a fájl entitás alatt helyezkednek el (a blokk kulcsa a fájl entitás kulcsának leszármazottja). Amikor adatokat írunk vagy olvasunk a fájlból, a rendszer a fájlmutató alapján meghatározza a blokk sorszámát ahol az adat van, majd ebben módosítja a megfelelő adatot. Egyszerű kialakítás, mégis kényelmes és hatékony.

A kód szokásos módon a Google Code-on megtalálható a http://code.google.com/p/gae-filestore/ címen.