7

Ich denke, es gibt einen Speicherverlust in der ndb-Bibliothek, aber ich kann nicht finden, wo.Speicherleck in Google Ndb-Bibliothek

Gibt es eine Möglichkeit, das unten beschriebene Problem zu vermeiden?
Haben Sie eine genauere Vorstellung von Tests, um herauszufinden, wo das Problem liegt?


Das ist, wie ich das Problem reproduziert:

I mit 2 Dateien eine minimalistische Google App Engine-Anwendung erstellt.
app.yaml:

application: myapplicationid 
version: demo 
runtime: python27 
api_version: 1 
threadsafe: yes 


handlers: 
- url: /.* 
    script: main.APP 

libraries: 
- name: webapp2 
    version: latest 

main.py:

# -*- coding: utf-8 -*- 
"""Memory leak demo.""" 
from google.appengine.ext import ndb 
import webapp2 


class DummyModel(ndb.Model): 

    content = ndb.TextProperty() 


class CreatePage(webapp2.RequestHandler): 

    def get(self): 
     value = str(102**100000) 
     entities = (DummyModel(content=value) for _ in xrange(100)) 
     ndb.put_multi(entities) 


class MainPage(webapp2.RequestHandler): 

    def get(self): 
     """Use of `query().iter()` was suggested here: 
      https://code.google.com/p/googleappengine/issues/detail?id=9610 
     Same result can be reproduced without decorator and a "classic" 
      `query().fetch()`. 
     """ 
     for _ in range(10): 
      for entity in DummyModel.query().iter(): 
       pass # Do whatever you want 
     self.response.headers['Content-Type'] = 'text/plain' 
     self.response.write('Hello, World!') 


APP = webapp2.WSGIApplication([ 
    ('/', MainPage), 
    ('/create', CreatePage), 
]) 

ich die Anwendung hochgeladen, rief einmal /create.
Danach erhöht jeder Aufruf von / den von der Instanz verwendeten Speicher. Bis es wegen des Fehlers Exceeded soft private memory limit of 128 MB with 143 MB after servicing 5 requests total stoppt.

Exemple von Diagramm zur Speicherauslastung (Sie können das Speicherwachstum und Abstürze sehen): enter image description here

Hinweis: Das Problem kann mit einem anderen Rahmen als webapp2, wie web.py

+2

wohl [ndb in-context-Cache] (https://cloud.google.com/appengine/docs/python/ndb/cache) erwarte ich. –

+0

Ich weiß nichts über Python, aber wenn ich Ihren Code lese, würde ich sagen, dass Ihnen der Speicher ausgeht, weil Ihre 'ndb.put_multi' versucht, 100 Entitäten in einer einzigen Transaktion einzufügen. Das verursacht wahrscheinlich, dass viel Speicher zugewiesen wird. Das Überschreiten des Grenzwerts für den privaten Speicher ist wahrscheinlich darauf zurückzuführen, dass Ihre Transaktionen noch ausgeführt werden, wenn die nächste Anforderung zur Speicherauslastung hinzugefügt wird. Dies sollte nicht auftreten, wenn Sie zwischen den Anrufen eine Weile warten (bzw. warten, bis die Transaktion abgeschlossen ist). Außerdem sollte App Engine eine zusätzliche Instanz starten, wenn die Antwortzeiten drastisch ansteigen. – konqi

+0

@DanielRoseman "Der In-Kontext-Cache bleibt nur für die Dauer eines einzelnen Threads bestehen." Wenn Sie den Kontextcache löschen oder eine Richtlinie zum Deaktivieren der Zwischenspeicherung festlegen, erhöht sich die Speicherbelegung langsamer, das Leck bleibt jedoch bestehen. – greg

Antwort

4

Nach weiteren Untersuchungen, und mit Hilfe eines Google-Ingenieurs, habe ich zwei Erklärungen zu meinem Speicherverbrauch gefunden.

Context und Gewinde

ndb.Context ist ein "thread local" Objekt und wird erst dann gelöscht, wenn eine neue Anforderung in dem Faden kommen. So hält der Thread zwischen Anfragen fest. Viele Threads können in einer GAE-Instanz vorhanden sein und es kann Hunderte von Anfragen erfordern, bevor ein Thread ein zweites Mal verwendet wird und sein Kontext gelöscht wird.
Dies ist kein Speicherverlust, aber die Größe des Kontexts im Speicher kann den verfügbaren Speicher in einer kleinen GAE-Instanz überschreiten.

Umgehung:
Sie können nicht die Anzahl der Threads in einer GAE-Instanz verwendet konfigurieren. Daher ist es am besten, jeden Kontext möglichst klein zu halten. Vermeiden Sie den Kontextcache und löschen Sie ihn nach jeder Anfrage.

Ereigniswarteschlange

Es scheint, dass NDB nicht, dass Ereignis-Warteschlange nach einer Anforderung geleert wird garantiert. Auch dies ist kein Speicherleck. Aber lassen Sie Futures in Ihrem Thread-Kontext, und Sie sind zurück zum ersten Problem.

Umgehung:
Wrap alle Code, der mit @ndb.toplevel NDB verwenden.

+0

Greg, hat der Google-Techniker Ihnen einen Hinweis gegeben, ob dies ein beabsichtigtes Verhalten oder ein Fehler ist? Es scheint mir ein Fehler zu sein. –

+0

Wie löschen Sie den Kontext nach jeder Anfrage? – diogovk

+0

Ich habe alle oben genannten Schritte durchgeführt und mich sogar an den Google-Support zu diesem Problem gewandt ... und sie erkennen nicht einmal an, dass es existiert. Ich bekomme immer noch ein Leck, das so extrem ist, dass ein Prozess, der wenig mehr tut, ibn durch NDB-Einträge iteriert und die Ergebnisse in die Warteschlange stellt, 500M Speicher innerhalb von ein paar Minuten verliert. Irgendwelche anderen möglichen Erklärungen? – Sniggerfardimungus

3

reproduziert wird Es ist ein bekanntes Problem mit NDB. Sie können über it here und es ist ein Werk around here lesen:

Die Nicht-Determinismus mit fetch_page beobachtet die Iterationsreihenfolge von eventloop.rpcs zurückzuführen ist, die datastore_rpc.MultiRpc.wait_any() und apiproxy_stub_map geben wird. __check_one wählt den letzten rpc aus dem Iterator aus.

Abrufen mit page_size von 10 ist ein RPC mit count = 10, Limit = 11, eine Standardtechnik, um das Backend zu zwingen, genauer zu bestimmen, ob es mehr Ergebnisse gibt. Dies ergibt 10 Ergebnisse, aber aufgrund eines Fehlers in der Art, wie der QueryIterator entschlüsselt wird, wird ein RPC hinzugefügt, um den letzten Eintrag zu holen (unter Verwendung des erhaltenen Cursors und Anzahl = 1). NDB gibt dann den Batch von Entitäten zurück, ohne diesen RPC zu verarbeiten. Ich glaube, dass dieser RPC nicht ausgewertet wird, bis er zufällig ausgewählt wird (wenn MultiRpc ihn vor einem notwendigen RPC verbraucht), da er den Client-Code nicht blockiert.

Problemumgehung: Verwenden Sie iter(). Diese Funktion hat dieses Problem nicht (Anzahl und Limit sind identisch). iter() kann als Workaround für die Leistungs- und Speicherprobleme verwendet werden, die mit der oben erwähnten Abrufseite verbunden sind.

+0

Ich habe diese Threads gelesen, aber die Verwendung von 'iter()' verhindert das Speicherleck nicht. – greg

+0

Sie sollten Ihre Ergebnisse in den Threads veröffentlichen, damit die Ingenieure sie sehen können. – Ryan

+0

Greg, nette Unterhaltung mit dir in Paris. Ich würde vorschlagen, den Code stattdessen mit "iter()" zu bearbeiten und Beweise für das Speicherleck zu liefern. – Riccardo

1

Eine mögliche Abhilfe ist context.clear_cache() und GC.Collect() auf get-Methode zu verwenden.

def get(self): 

    for _ in range(10): 
     for entity in DummyModel.query().iter(): 
      pass # Do whatever you want 
    self.response.headers['Content-Type'] = 'text/plain' 
    self.response.write('Hello, World!') 
    context = ndb.get_context() 
    context.clear_cache() 
    gc.collect()