Vazamento de memória no aplicativo Google App Engine / Datastore / Flask / Python

Sep 15 2020

Eu construí um site agregador de notícias simples, no qual o uso de memória de todas as minhas instâncias do App Engine continua crescendo até atingir o limite e, portanto, ser desligado.

Comecei a eliminar tudo do meu aplicativo para chegar a uma versão reproduzível mínima. Isso é o que eu tenho agora:


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
    

As estatísticas mostram um padrão típico de dente de serra: na inicialização da instância, ele vai para 190 - 210 Mb, depois, em algumas solicitações, mas NÃO TODAS as solicitações, o uso de memória aumenta em 20 - 30 Mb. (Isso, aliás, corresponde aproximadamente ao tamanho estimado dos resultados da consulta, embora eu não possa ter certeza de que essa informação seja relevante.) Isso continua acontecendo até ultrapassar 512 Mb, quando é desligado. Geralmente acontece por volta do 50º ao 100º pedido de "/". Nenhum outro pedido é feito a mais nada nesse meio tempo.

Agora, se eu eliminar o ciclo "para" e apenas a consulta permanecer, o problema vai embora, o uso de memória permanece em 190 Mb, sem aumento mesmo após mais de 100 solicitações.

gc.collect () no final não ajuda. Também tentei observar a diferença nas estatísticas de tracemalloc no início e no final da função, mas não achei nada útil.

Alguém experimentou algo semelhante, por favor? Alguma ideia do que pode dar errado aqui? Quais testes / investigações adicionais você pode recomendar? É possivelmente um problema do Google App Engine / Datastore do qual não tenho controle?

Obrigado.

Respostas

1 yedpodtrzitko Sep 17 2020 at 02:48

Na outra resposta, @Alex fez uma pesquisa muito boa, então irei continuar com esta recomendação: tente usar a biblioteca NDB . Todas as chamadas com esta biblioteca devem ser agrupadas em um gerenciador de contexto, que deve garantir a limpeza após o fechamento. Isso pode ajudar a resolver o seu 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

Agora, se eu eliminar o ciclo "para" e apenas a consulta permanecer, o problema vai embora, o uso de memória permanece em 190 Mb, sem aumento mesmo após mais de 100 solicitações.

query.fetch() retorna um iterador, não uma matriz real dos resultados

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

Olhando para o código-fonte, parece que este iterador tem código para buscar as próximas páginas da consulta. Então você for-loop o força a buscar todas as páginas dos resultados. Na verdade, acho que ele não busca nada até que você comece a iterar. Então é por isso que remover seu loop for faria uma diferença

Infelizmente, além disso, não tenho certeza, já que conforme você vasculha o código-fonte, você rapidamente se depara com os stubs GRPC e não fica claro se o problema está lá.

Há uma questão semelhante à sua, em que quem fez a pergunta encontrou um vazamento de memória envolvido na instanciação datastore.Client(). Como devo investigar um vazamento de memória ao usar as bibliotecas Python do Google Cloud Datastore?

Isso acabou sendo vinculado a um problema no GRPC em que o GRPC vazaria se não fosse fechado https://github.com/grpc/grpc/issues/22123

Felizmente, isso aponta para a direção certa