Rò rỉ bộ nhớ trong ứng dụng Google App Engine / Datastore / Flask / Python

Sep 15 2020

Tôi đã xây dựng một trang web tổng hợp tin tức đơn giản, trong đó việc sử dụng bộ nhớ của tất cả các phiên bản App Engine của tôi tiếp tục tăng cho đến khi đạt đến giới hạn và do đó sẽ bị đóng lại.

Tôi đã bắt đầu loại bỏ mọi thứ khỏi ứng dụng của mình để có được một phiên bản có thể tái tạo tối thiểu. Đây là những gì tôi có bây giờ:


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
    

Số liệu thống kê cho thấy một mô hình răng cưa điển hình: ví dụ khi khởi động, nó tăng lên 190 - 210 Mb, sau đó theo một số yêu cầu, nhưng KHÔNG PHẢI TẤT CẢ các yêu cầu, mức sử dụng bộ nhớ tăng 20 - 30 Mb. (Nhân tiện, điều này gần tương ứng với kích thước ước tính của kết quả truy vấn, mặc dù tôi không thể chắc chắn đây là thông tin có liên quan.) Điều này tiếp tục xảy ra cho đến khi vượt quá 512 Mb, khi nó bị tắt. Nó thường xảy ra vào khoảng yêu cầu thứ 50 - 100 đối với "/". Không có yêu cầu nào khác được thực hiện cho bất kỳ điều gì khác trong thời gian chờ đợi.

Bây giờ, nếu tôi loại bỏ chu kỳ "cho" và chỉ còn lại truy vấn, vấn đề sẽ biến mất, việc sử dụng bộ nhớ vẫn ở mức 190 Mb cố định, không tăng ngay cả sau hơn 100 yêu cầu.

gc.collect () ở cuối không giúp được gì. Tôi cũng đã thử xem xét sự khác biệt về số liệu thống kê tracemalloc ở đầu và cuối của hàm, tôi không tìm thấy bất kỳ điều gì hữu ích.

Có ai có kinh nghiệm bất cứ điều gì tương tự, xin vui lòng? Bất kỳ ý tưởng những gì có thể sai ở đây? Bạn có thể đề xuất những thử nghiệm / điều tra bổ sung nào? Đây có thể là vấn đề Google App Engine / Datastore mà tôi không kiểm soát được?

Cảm ơn bạn.

Trả lời

1 yedpodtrzitko Sep 17 2020 at 02:48

@Alex trong câu trả lời khác đã thực hiện một nghiên cứu khá tốt, vì vậy tôi sẽ theo dõi đề xuất này: hãy thử sử dụng Thư viện NDB . Tất cả các lệnh gọi với thư viện này phải được bao bọc trong một trình quản lý ngữ cảnh, điều này sẽ đảm bảo việc dọn dẹp sau khi đóng. Điều đó có thể giúp khắc phục sự cố của bạn:

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

Bây giờ, nếu tôi loại bỏ chu kỳ "cho" và chỉ còn lại truy vấn, vấn đề sẽ biến mất, việc sử dụng bộ nhớ vẫn ở mức 190 Mb cố định, không tăng ngay cả sau hơn 100 yêu cầu.

query.fetch() trả về một trình lặp, không phải một mảng kết quả thực tế

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

Nhìn vào mã nguồn, có vẻ như trình lặp này có mã để tìm nạp các trang tiếp theo của truy vấn. Vì vậy, bạn buộc nó phải tìm nạp tất cả các trang của kết quả. Trên thực tế, tôi không nghĩ rằng nó thực sự tìm nạp bất cứ thứ gì cho đến khi bạn bắt đầu lặp lại. Vì vậy, đó sẽ là lý do tại sao loại bỏ vòng lặp for của bạn sẽ tạo ra sự khác biệt

Thật không may, tôi không chắc chắn, vì khi bạn tìm hiểu mã nguồn, bạn khá nhanh chóng gặp phải các sơ khai GRPC và không rõ liệu vấn đề có nằm ở đó hay không.

Có câu hỏi này tương tự như câu hỏi của bạn, nơi người hỏi phát hiện ra một lỗ hổng bộ nhớ liên quan đến việc khởi tạo datastore.Client(). Làm cách nào để điều tra rò rỉ bộ nhớ khi sử dụng thư viện Python của Google Cloud Datastore?

Điều này cuối cùng có liên quan đến một sự cố trong GRPC nơi GRPC sẽ bị rò rỉ nếu nó không được đóng lại https://github.com/grpc/grpc/issues/22123

Hy vọng rằng điều này sẽ giúp bạn đi đúng hướng