Kebocoran memori di aplikasi Google App Engine / Datastore / Flask / Python

Sep 15 2020

Saya telah membangun situs agregator berita sederhana, di mana penggunaan memori dari semua instance App Engine saya terus berkembang hingga mencapai batas dan karena itu ditutup.

Saya sudah mulai menghilangkan semuanya dari aplikasi saya untuk mencapai versi minimal yang dapat direproduksi. Inilah yang saya miliki sekarang:


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
    

Statistik menunjukkan pola gigi gergaji yang khas: saat startup, nilainya menjadi 190 - 210 Mb, lalu atas beberapa permintaan, tetapi TIDAK SEMUA permintaan, penggunaan memori meningkat sebesar 20 - 30 Mb. (Ngomong-ngomong, ini kira-kira sesuai dengan perkiraan ukuran hasil kueri, meskipun saya tidak yakin ini adalah info yang relevan.) Ini terus terjadi hingga melebihi 512 Mb, saat dimatikan. Ini biasanya terjadi sekitar permintaan ke-50 - ke-100 untuk "/". Sementara itu, tidak ada permintaan lain yang dibuat untuk hal lain.

Sekarang, jika saya menghilangkan siklus "untuk", dan hanya kueri yang tersisa, masalahnya akan hilang, penggunaan memori tetap pada 190 Mb, tidak ada peningkatan bahkan setelah 100+ permintaan.

gc.collect () di bagian akhir tidak membantu. Saya juga mencoba melihat perbedaan statistik tracemalloc di awal dan akhir fungsi, saya belum menemukan sesuatu yang berguna.

Adakah yang pernah mengalami hal serupa? Ada ide apa yang mungkin salah di sini? Tes / investigasi tambahan apa yang dapat Anda rekomendasikan? Apakah ini mungkin masalah Google App Engine / Datastore yang tidak dapat saya kendalikan?

Terima kasih.

Jawaban

1 yedpodtrzitko Sep 17 2020 at 02:48

@Alex di jawaban lain melakukan penelitian yang cukup bagus, jadi saya akan menindaklanjuti dengan rekomendasi ini: coba gunakan Perpustakaan NDB . Semua panggilan dengan pustaka ini harus digabungkan ke dalam pengelola konteks, yang harus menjamin pembersihan setelah penutupan. Itu dapat membantu memperbaiki masalah Anda:

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

Sekarang, jika saya menghilangkan siklus "untuk", dan hanya kueri yang tersisa, masalahnya akan hilang, penggunaan memori tetap pada 190 Mb, tidak ada peningkatan bahkan setelah 100+ permintaan.

query.fetch() mengembalikan iterator, bukan larik hasil sebenarnya

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

Melihat kode sumbernya, sepertinya iterator ini memiliki kode untuk mengambil halaman berikutnya dari kueri. Jadi Anda memaksa for-loop untuk mengambil semua halaman dari hasil. Sebenarnya saya tidak berpikir itu benar-benar mengambil apa pun sampai Anda mulai mengulang. Jadi itulah mengapa menghapus for-loop Anda akan membuat perbedaan

Sayangnya di luar itu saya tidak yakin, karena saat Anda menggali kode sumber, Anda cukup cepat menemukan stub GRPC dan tidak jelas apakah masalahnya ada di sana.

Ada pertanyaan yang serupa dengan pertanyaan Anda, di mana penanya menemukan kebocoran memori yang terkait dengan pembuatan instance datastore.Client(). Bagaimana cara saya menyelidiki kebocoran memori saat menggunakan library Python Google Cloud Datastore?

Ini akhirnya terkait dengan masalah di GRPC di mana GRPC akan bocor jika tidak ditutup https://github.com/grpc/grpc/issues/22123

Semoga ini mengarahkan Anda ke arah yang benar