Datastore optimieren auf 50k Ops Google AppEngine Quota Limit

Maximal 50.000 Zugriffe auf die API des Datastore gestattet Google täglich in der Free-Version der AppEngine. Ab November 2011 wird mit Google Cloud Service ein neues Preismodell eingeführt, wodurch die Nutzung der Datenbank „BigTable“ durch das 50k-Limit sehr stark beschnitten bzw. verteuert wird.

„Datastore API quota per app per day … 50k free read/write/small“ [AppEngine Pricing

There are 3 categories of Datastore operations [Pricing FAQ]:

    • Write operations (Entity Put, Entity Delete, Index Write) Each of these operations will cost $0.10 per 100k operations.
    • Read operations (Query, Entity Fetch) Each of these operations will cost $0.07 per 100k operations.
    • Small operations (Key Fetch, Id Allocation) Each of these operations will cost $0.01 per 100k operations.

In der GAE-Betaphase waren noch CPU-Zeit, Trafic und Requests die limitierenden Faktoren. Künftig muss die Optimierung (insbesondere auch in der Payed-Version) auf ein Minimum an Datastorezugriffe abzielen, besonders bei Webseiten mit dynamischen Inhalten. Waren früher bis zu 5 Mio dynamische Request pro Monat möglich, sind es jetzt höchstens 1,5 Mio.

Bei den Operations / Zugriffen werden künftig „small ops“ künstiger berechnet. Small Operationen greifen nur auf / über Keys zu. Der Indexzugriff selbst wird extra zum Fetch der Entities abgerechnet. Werden Datensätze nicht gleichzeitig im Bulk eingelesen, dann zählt sogar jeder Fetch einzeln! Die Größe des Fetches ist nicht relevant.

Die 50k Ops sind u.U. schnell weg! Ein paar Cronjobs im Hintergrund erzeugen leicht tausende Zugriffe auf den Datastore pro Tag. Auch Tracking der User kostet ein paar Ops pro HTTP-Request. Teuer wird es für Seiten mit dynamischen Inhalten (z.B. Blogs) und fast unkalkulierbar für asyncrone Kommunikation für Realtime-Apps (z.B. Chats).

Dynamische Seite > 50.000 Hits / Tag?

Wenn die Limits für die anderen APIs bleiben, dann sind imho verschiedene Workarounds für das 50k-Problem denkbar.

1. Möglichkeit – statischer Content

Ein Blog z.B. kann fast zu 100% als statische Seite betrieben werden und muss nicht bei jedem Seitenaufruf die Datenbank nach dem Inhalt befragen. Die Lösung hier ist das erneute Deployment, sobald sich etwas dynamisch ändert. Jede App kann (noch) 1.000 mal am Tag „geändert“ werden, d.h. alle 90 Sekunden! In der app.yaml einfach einen static-Ordner definieren. Kommentare werden dann dynamisch erfasst und erst etwas zeitversetzt angezeigt.

In der AppEngine kann man auch Daten aus eine statischen Datei auslesen, was derzeit nichts extra kostet. Eine PLZ-Geo-Datenbank ändert sich z.B. kaum. Neben Dateien können die Daten auch über eine eigene Library statisch hinterlegt werden (from my.plzgeodb import plztogeo). Einen statischen String kann auch eine Funktion zurückliefern.

Sollen statische Daten möglichst im Cache bleiben, dann lassen sich dafür Templates nutzen. Über die Templatesyntax kann hier sogar eine Suchlogik eingebaut werden.

Änderungen erfolgen bei dieser Methode über ein erneutes Deployment der App. Durch eine kleine Anpassung im SDK mache ich dies automatisiert im Batch. Dafür benötigt man einen Rechner bzw. virtuellen Server, der die Anwendung regelmäßig in Intervallen aktualisiert.

2. Möglichkeit – Direktzugriff per Key

Im günstigsten Fall kann direkt auf einen Datensatz über dessen Key zugegriffen werden. Bei einem Blog kann man die lange URL mittels MD5 Hashwert kürzen oder mehrere Keys mit key_namen = ‚_’+md5(k1+’|’+k2) vereinen.

Der Key für einen Direktzugriff auf einen Datensatz solle sich aus der URL bzw. den Parametern bilden lassen. Die Nutzung von GQL in Abfragen sollte vermieden werden.

3. Möglichkeit – Page cachen

Statt bei jedem Request die komplette Seite dynamisch aus verschiedenen Datensätzen zusammen zu lesen (z.B. Blogbeitrag + mehrere Kommentare), kann dies einmalig erfolgen. Das Ergebnis wird dann als einzelner Datensatz gespeichert (HTML-Seite).

Zusätzlich kann die fertige HTML-Seite neben dem Datastore auch im MemCache zwischengespeichert werden, um die Anzahl der Ops zu schonen.

4. Möglichkeit – Blobstore nutzen

Greift man auf den Datensatz direkt per Key zu, dann kann dieser auch dynamisch als Datei im Blobstore gespeichert werden. Mit files.blobstore.create können Datensätze angelegt und files.blobstore.get_blob_key gelesen werden.

Der Blobstore ist hier ein idealer Ersatz für den Datastore. Jedoch ist mir nicht bekannt, ob Google künftig das Limit von 50.000 Zugriffen pro Tag auf Data- und Blobstore gemeinsam anwendet.

5. Möglichkeit – URL Fetch nutzen

Aktuell dürfen pro Tag 657.000 HTTP-Requests per URL Fetch gemacht werden. Jedoch ist künftig ein 50k Limit auch bei dieser API denkbar.

Die AppEngine ermöglicht mit der Picasa Web Albums Data API das Speichern von Bilder und Kommentaren. Über Picasa lassen sich zusätzlich Listen sowie eine Suche „abbilden“. Die Datensätze werden verschlüsselt als Kommentare zum Dummy-Foto gespeichert.

In der WordPress Mediathek bekommt jeder Blog gratis 3 GB für Bilder und Videos. Derartige Inhalte können von der AppEngine per XmlRcp hochgeladen werden. Ähnlich Picasa können Datensätze verschlüsselt als Blogbeitrag gespeichert werden.

Bei AMAZON S3 (Simple Storage Service) kosten 10.000 GET-Request 0,01 USD und 1 GB Space ab 0,093 USD je Monat.

Fazit: für statische Inhalte gibt es keine Begrenzung auf 50k Request pro Tag. Dynamische Inhalten können gecached und auf andere Datendienst ausgelagert werden. Je nach Implementierung sind auch mehr als 50.000 Request auf dynamischen Content möglich.

__ Update zum Thema Instanz-Cache / Thx @wingi __

Je nach Anwendung kann die komplette Datenbank, der Index zur Volltextsuche oder häufig benötigte Aggregationen direkt im Hauptspeicher des Webservers gecached werden (siehe Kommentare). Bei mir ist das z.B. eine Adressdatenbank mit 30.000 sowie eine Geo-Datenbank mit 10.000 Datensätzen. Der Index für die Volltextsuche belegt ca. 55 MB im Hauptspeicher. Die Volltextsuche im Hauptspeicher ist bei 30.000 Sätzen nicht messbar und erzeugt keine Latenz, sodass die Skripte unter 20 ms ausgeliefert werden.

Das Caching lohnt besonders bei den am häufigsten aufgerufen Seiten. In meinem Fall benötige ich – bis auf den Warmup – keinerlei Datastorezugriffe für die Suche und Übersichtsseiten. Lediglich für die Detailseiten wird der Datastore bemüht, sollte der Datensatz nicht bereits im MemCache sein.

Der Speicherplatzverbrauch im Instanz Cache ist sehr stark abhängig vom Datentyp! Auch wird dadurch die Latenz beim initialen Loading Request beeinflusst. Hier meine Messwerte für meinen Index mit 30.000 Sätzen:

  • StringIO: 3 MB Hauptspeicher / 474 ms Loading Latenz
  • Dict: 19 MB Hauptspeicher / 679 ms Loading Latenz
  • List: 21 MB Hauptspeicher / 776 ms Loading Latenz

Die Loading Latenz ist nur beim Start des Webservers relevant. Für das Instanz Caching gibt es keine pauschale Lösung. Hierarchien – z.B. Land, Bundesland, Ort, PLZ – lege ich als Hashmap (Dict) ab. Für die Volltextsuche über Namen nutze ich StringIO, da sowieso ein Scan über alle Zeilen gemacht wird.

Advertisements

2 Responses to Datastore optimieren auf 50k Ops Google AppEngine Quota Limit

  1. Wenn der Artikel nicht so trocken wäre … viele Fakten aufgezählt. Nur zwischen Punkt 1) und 2) kann man auch Daten in der Instanz einer WebApp speichern.

    Allgemein ist ein guter Mix zwischen Applikationscaching und HTTP-Caching eine gute Lösung.

    • RoHa says:

      Das Caching von Datenbankinhalten im Hauptspeicher einer Instanz macht auch Probleme, da Google den Prozess teilweise nach wenigen Sekunden ohne Request wieder beendet und dann sind auch die temporären Daten weg. Laufen mehrere Instanzen parallel, dann müssen Änderungen auf alle Instanzen verteilt werden.

      Statische Datenbanken aus dem Webspace cache ich normalerweise nicht im Hauptspeicher der Instanz, wenn die Latenz beim Lesen minimal ist. Ich habe z.B. eine Datenbank mit 30.000 Datensätzen als statische CSV-Dateien. Die Datenmenge von 400 MB belegen im Webspace nur 20 MB, da ich die CSV-Dateien in ZIP-Archiven komprimiert speichere.

      Dynamische Inhalte aus Datenbanken (Datastore, Blobstore, SimpleStorage, …) cache ich primär im MemCache und sekundär in der Instanz. Zusätzlich halte ich im MemCache den aktuellen Timestamp oder eine Versionsnummer zum Datensatz. Die Instanz prüft zuerst im MemCache, ob der Datensatz noch aktuell ist. Wenn kein Timestamp gefunden wurde oder dieser sich geändert hat, dann wird der Datensatz in der Instanz durch den neuen ersetzt.  

      Gute Ergebnisse erziele ich mit meinem Datastore-Container. Hier werden mehrere logische Datensätze einer Tabelle physisch in einem Datensatz gespeichert. Dadurch benötige ich kaum Lese-Operationen, da die Daten im Bulk gespeichert sind.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: