Perdita di memoria nell'app Google App Engine / Datastore / Flask / Python

Sep 15 2020

Ho creato un semplice sito di aggregazione di notizie, in cui l'utilizzo della memoria di tutte le mie istanze di App Engine continua a crescere fino a raggiungere il limite e quindi a essere spento.

Ho iniziato a eliminare tutto dalla mia app per arrivare a una versione riproducibile minimale. Questo è quello che ho ora:


app = Flask(__name__)

datastore_client = datastore.Client()

@app.route('/')
def root():
    
    query = datastore_client.query(kind='source')
    query.order = ['list_sequence']
    sources = query.fetch() 
    
    for source in sources:
        pass
    

Le statistiche mostrano un tipico schema a dente di sega: all'avvio dell'istanza, va a 190-210 Mb, quindi su alcune richieste, ma NON TUTTE le richieste, l'utilizzo della memoria aumenta di 20-30 Mb. (Questo, a proposito, corrisponde all'incirca alla dimensione stimata dei risultati della query, anche se non posso essere sicuro che si tratti di informazioni rilevanti.) Questo continua ad accadere finché non supera 512 Mb, quando viene spento. Di solito accade intorno alla 50a - 100a richiesta a "/". Nel frattempo non vengono fatte altre richieste a nient'altro.

Ora, se elimino il ciclo "for" e rimane solo la query, il problema scompare, l'utilizzo della memoria rimane a 190 Mb flat, nessun aumento anche dopo 100+ richieste.

gc.collect () alla fine non aiuta. Ho anche provato a guardare la differenza nelle statistiche di tracemalloc all'inizio e alla fine della funzione, non ho trovato nulla di utile.

Qualcuno ha sperimentato qualcosa di simile, per favore? Qualche idea su cosa potrebbe andare storto qui? Quali test / indagini aggiuntive puoi consigliare? Si tratta forse di un problema di Google App Engine / Datastore di cui non ho alcun controllo?

Grazie.

Risposte

1 yedpodtrzitko Sep 17 2020 at 02:48

@Alex nell'altra risposta ha fatto una ricerca piuttosto buona, quindi seguirò questa raccomandazione: prova a utilizzare la libreria NDB . Tutte le chiamate con questa libreria devono essere inserite in un gestore di contesto, che dovrebbe garantire la pulizia dopo la chiusura. Questo potrebbe aiutarti a risolvere il tuo problema:

ndb_client = ndb.Client(**init_client)

with ndb_client.context():
    query = MyModel.query().order(MyModel.my_column)
    sources = query.fetch()
    for source in sources:
        pass

# if you try to query DataStore outside the context manager, it will raise an error
query = MyModel.query().order(MyModel.my_column)
2 Alex Sep 16 2020 at 20:03

Ora, se elimino il ciclo "for" e rimane solo la query, il problema scompare, l'utilizzo della memoria rimane a 190 Mb flat, nessun aumento anche dopo 100+ richieste.

query.fetch() restituisce un iteratore, non un array effettivo dei risultati

https://googleapis.dev/python/datastore/latest/queries.html#google.cloud.datastore.query.Query.fetch

Guardando il codice sorgente, sembra che questo iteratore abbia il codice per recuperare le pagine successive della query. Quindi il tuo ciclo for lo costringe a recuperare tutte le pagine dei risultati. In effetti non penso che recuperi effettivamente qualcosa finché non inizi a iterare. Quindi sarebbe questo il motivo per cui rimuovere il ciclo for farebbe la differenza

Sfortunatamente oltre a questo non ne sono sicuro, poiché mentre scavi nel codice sorgente ti imbatti abbastanza rapidamente negli stub GRPC e non è chiaro se il problema sia lì.

C'è questa domanda simile alla tua, in cui il richiedente ha trovato una perdita di memoria coinvolta nell'istanza datastore.Client(). Come devo indagare su una perdita di memoria quando utilizzo le librerie Python di Google Cloud Datastore?

Alla fine questo è stato collegato a un problema in GRPC in cui GRPC avrebbe perso se non si chiudeva https://github.com/grpc/grpc/issues/22123

Si spera che questo ti indirizzi nella giusta direzione