Pérdida de memoria en la aplicación Google App Engine / Datastore / Flask / Python
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
@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)
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.