Pérdida de memoria en la aplicación Google App Engine / Datastore / Flask / Python

Sep 15 2020

Creé un sitio de agregación de noticias simple, en el que el uso de memoria de todas mis instancias de App Engine sigue creciendo hasta alcanzar el límite y, por lo tanto, se apaga.

Comencé a eliminar todo de mi aplicación para llegar a una versión mínima reproducible. Esto es lo que tengo ahora:


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
    

Las estadísticas muestran un patrón típico de dientes de sierra: en el inicio de la instancia, va a 190-210 Mb, luego, en algunas solicitudes, pero NO TODAS las solicitudes, el uso de memoria aumenta en 20-30 Mb. (Esto, por cierto, corresponde aproximadamente al tamaño estimado de los resultados de la consulta, aunque no puedo estar seguro de que sea información relevante). Esto sigue sucediendo hasta que supera los 512 Mb, cuando se apaga. Por lo general, ocurre alrededor de la solicitud 50 a 100 a "/". Mientras tanto, no se realizan otras solicitudes a ninguna otra persona.

Ahora, si elimino el ciclo "para", y solo queda la consulta, el problema desaparece, el uso de la memoria permanece en 190 Mb planos, sin aumento incluso después de más de 100 solicitudes.

gc.collect () al final no ayuda. También intenté ver la diferencia en las estadísticas de tracemalloc al principio y al final de la función, no he encontrado nada útil.

¿Alguien ha experimentado algo similar, por favor? ¿Alguna idea de lo que podría salir mal aquí? ¿Qué pruebas / investigaciones adicionales puede recomendar? ¿Es posible que se trate de un problema de Google App Engine / Datastore del que no tengo control?

Gracias.

Respuestas

1 yedpodtrzitko Sep 17 2020 at 02:48

@Alex en la otra respuesta hizo una investigación bastante buena, así que seguiré con esta recomendación: intente usar la biblioteca NDB . Todas las llamadas con esta biblioteca deben estar envueltas en un administrador de contexto, que debería garantizar la limpieza después del cierre. Eso podría ayudar a solucionar su 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

Ahora, si elimino el ciclo "para", y solo queda la consulta, el problema desaparece, el uso de la memoria permanece en 190 Mb planos, sin aumento incluso después de más de 100 solicitudes.

query.fetch() devuelve un iterador, no una matriz real de los resultados

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

Mirando el código fuente, parece que este iterador tiene código para buscar las siguientes páginas de la consulta. Entonces, el bucle for lo fuerza a buscar todas las páginas de los resultados. De hecho, no creo que realmente obtenga nada hasta que comiences a iterar. Entonces esa sería la razón por la que eliminar su bucle for marcaría la diferencia

Desafortunadamente, más allá de eso, no estoy seguro, ya que a medida que busca en el código fuente, rápidamente se encuentra con los stubs de GRPC y no está claro si el problema está allí.

Existe esta pregunta que es similar a la suya, donde el autor de la pregunta encontró una pérdida de memoria relacionada con la instanciación datastore.Client(). ¿Cómo debo investigar una pérdida de memoria cuando uso las bibliotecas Python de Google Cloud Datastore?

Esto finalmente se vinculó a un problema en GRPC donde GRPC se filtraría si no se cierra https://github.com/grpc/grpc/issues/22123

Con suerte, esto te indicará la dirección correcta.