Reputation: 11
In Gunicorn, each worker by default loads the entire application code. This causes increased memory usage and occasional OOM (out of memory errors).
I am looking to enable the --preload option of gunicorn so that workers refer to memory of master process, thus saving memory used and avoiding OOM errors as well.
I started by adding the --preload flag, however on measuring the RSS and shared memory (using psutil) of individual workers, I found there to be no difference as compared to when I deploy without --preload.
I discovered a concept called Copy-on-Read in Instagram's disabling garbage collector article (https://medium.com/binu-22feet/dismissing-python-garbage-collection-at-instagram-37faf66828ad) indicating how CPython's reference counting and garbage collection causes a Copy-on-Write even on a read.
Following the article, I did they same thing that they did, however even that did not had any difference for my app when compared to deploying without --preload.
How do I get the workers to share memory?
Sample Code:
app.yaml:
runtime: python311
instance_class: F2
entrypoint: gunicorn -b :$PORT -w 4 --preload --config gunicorn.conf.py app_main:app
automatic_scaling:
min_idle_instances: 1
app_main.py
> import os
> import gc
> import atexit
> from flask import Flask
>
> gc.set_threshold(0)
> atexit.register(os._exit, 0)
>
> app = Flask(__name__)
>
> @app.route('/')
> def hello():
> return "Hello, World!"
>
> if __name__ == '__main__':
> app.run()
gunicorn.conf.py
> import psutil
> import logging
> from google.cloud import logging as cloud_logging
>
> def post_fork(server, worker):
> # Disable GC inside worker processes as well
> gc.set_threshold(0)
> atexit.register(os._exit, 0) # from IG's analysis
>
> try:
> client = cloud_logging.Client()
> client.setup_logging()
> except Exception as e:
> logging.basicConfig(level=logging.INFO)
>
> process = psutil.Process()
> pid = process.pid
>
> mem = process.memory_info().rss / (1024 ** 2)
> shared = process.memory_info().shared / (1024 ** 2)
>
> logging.info(f"Process {pid} memory: {mem:.2f} MB")
> logging.info(f"Process {pid} shared memory: {shared:.2f} MB")
>
> \# Log memory of master process
> mpid = psutil.Process(pid).ppid()
> mp = psutil.Process(mpid)
>
> mmem = mp.memory_info().rss / (1024 ** 2)
> mshared = mp.memory_info().shared / (1024 ** 2)
>
> logging.info(f"Master {mpid} memory: {mmem:.2f} MB")
> logging.info(f"Master {mpid} shared memory: {mshared:.2f} MB")
In all the experiments I have run so far, the shared memory is always 0 for all workers and even master process.
Upvotes: 0
Views: 51