Wyciek pamięci w aplikacji Google App Engine / Datastore / Flask / Python
Zbudowałem prostą witrynę agregującą wiadomości, w której użycie pamięci we wszystkich moich instancjach App Engine rośnie, aż do osiągnięcia limitu, a tym samym do zamknięcia.
Zacząłem eliminować wszystko z mojej aplikacji, aby uzyskać minimalną, odtwarzalną wersję. Oto, co mam teraz:
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
Statystyki pokazują typowy wzór piłokształtny: przy uruchamianiu instancji dochodzi do 190 - 210 Mb, następnie po niektórych żądaniach, ale NIE WSZYSTKICH żądaniach, użycie pamięci wzrasta o 20 - 30 Mb. (Nawiasem mówiąc, odpowiada to z grubsza szacunkowemu rozmiarowi wyników zapytania, chociaż nie jestem pewien, czy są to istotne informacje). Dzieje się tak, dopóki nie przekroczy 512 Mb, kiedy zostanie zamknięty. Zwykle dzieje się to około 50-100 żądania wysłanego do „/”. W międzyczasie żadne inne prośby nie są kierowane do niczego innego.
Teraz, jeśli wyeliminuję cykl „for” i pozostanie tylko zapytanie, problem zniknie, użycie pamięci pozostanie na poziomie 190 Mb, bez wzrostu nawet po ponad 100 żądaniach.
gc.collect () na końcu nie pomaga. Próbowałem też spojrzeć na różnicę w statystykach tracemalloc na początku i na końcu funkcji, nie znalazłem nic przydatnego.
Czy ktoś doświadczył czegoś podobnego, proszę? Jakieś pomysły, co może się tu nie udać? Jakie dodatkowe testy / badania możesz polecić? Czy jest to prawdopodobnie problem z Google App Engine / Datastore, nad którym nie mam kontroli?
Dziękuję Ci.
Odpowiedzi
@Alex w drugiej odpowiedzi przeprowadził całkiem niezłe badania, więc podążę za tym zaleceniem: spróbuj skorzystać z biblioteki NDB . Wszystkie wywołania z tą biblioteką muszą być opakowane w menedżera kontekstu, który powinien gwarantować czyszczenie po zamknięciu. To może pomóc rozwiązać problem:
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)
Teraz, jeśli wyeliminuję cykl „for” i pozostanie tylko zapytanie, problem zniknie, użycie pamięci pozostanie na poziomie 190 Mb, bez wzrostu nawet po ponad 100 żądaniach.
query.fetch() zwraca iterator, a nie rzeczywistą tablicę wyników
https://googleapis.dev/python/datastore/latest/queries.html#google.cloud.datastore.query.Query.fetch
Patrząc na kod źródłowy, wygląda na to, że ten iterator zawiera kod do pobierania kolejnych stron zapytania. Więc pętla for zmusza go do pobrania wszystkich stron wyników. W rzeczywistości nie sądzę, aby faktycznie cokolwiek pobierał, dopóki nie zaczniesz iterować. Dlatego właśnie usunięcie pętli for miałoby znaczenie
Niestety poza tym nie jestem pewien, ponieważ podczas przekopywania się przez kod źródłowy dość szybko natrafiasz na kody pośredniczące GRPC i nie jest jasne, czy problem występuje.
Jest to pytanie podobne do twojego, gdzie pytający znalazł wyciek pamięci związany z tworzeniem instancji datastore.Client(). Jak zbadać wyciek pamięci podczas korzystania z bibliotek Google Cloud Datastore Python?
Ostatecznie zostało to powiązane z problemem w GRPC, który powodował wyciek GRPC, jeśli nie zostanie zamknięty https://github.com/grpc/grpc/issues/22123
Miejmy nadzieję, że wskazuje to właściwy kierunek