Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Production configuration of gunicorn #551

Closed
sandys opened this issue Sep 19, 2019 · 25 comments
Closed

Production configuration of gunicorn #551

sandys opened this issue Sep 19, 2019 · 25 comments
Labels
question Question or problem question-migrate

Comments

@sandys
Copy link

sandys commented Sep 19, 2019

NOTE: I'm not sure whether i should be filing this here or upstream on starlette. Please let me know if Starlette developers are more knowledgeable about this. However, I'm using fastapi.

What is a good configuration setting for gunicorn in production.

On reading documentation of gunicorn with uvicorn, there are lines like this

"Gunicorn provides a different set of configuration options to Uvicorn, so some options such as --limit-concurrency are not yet supported when running with Gunicorn"

Is it possible for you to share a list of options that work well ? One of the biggest question is around threads - should we set the threads option?
what about stuff like "worker_connections" , "keepalive" (especially in context of async code)

@sandys sandys added the question Question or problem label Sep 19, 2019
@dmontagu
Copy link
Collaborator

dmontagu commented Sep 19, 2019

This might be a reasonable starting point: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/master/python3.7/gunicorn_conf.py

But it doesn't go into much depth on the available options.

The starlette and uvicorn repos would also be good places to ask this question / search for related issues.

@devsetgo
Copy link

I started mine off from a gunicorn example https://github.com/benoitc/gunicorn/blob/master/examples/example_config.py

Then set mine like this - https://github.com/devsetgo/test-api/blob/master/app/gunicorn_cfg.py

From my experience, i have found that I really just need to set workers, bind, port, and for starlette/fastapi the worker class is what makes uvicorn work with gunicorn.

@sandys
Copy link
Author

sandys commented Sep 22, 2019 via email

@devsetgo
Copy link

devsetgo commented Sep 22, 2019

The item I am most concerned with for my production use are the number of workers. For async I've tried a couple different worker classes, but didn't see much change. So I just use uvicorn.workers.UvicornWorker. I set the backlog of connections, but I haven't ever found it to be an issue.

In my opinion, I don't think threads will matter. Threads I presume will matter more if you are kicking on new threads using multi-threading. I've not tried threads with Gunicorn for sync or async, so that could be an incorrect assessment.

I've done some testing of my test-api using WRK and connecting to my server 1000 miles away (1600 Km). So far with 9 workers running (4 cores x 2 + 1) I max out just below 6k requests per second. I find that Traefik and Let'sEncrypt drops that to 2k (research area). A single core (1 x 2 + 1) I can get around 2k requests per second (drops to 1k with Traefik/LetsEncrypt). I eventually plan to do more tests, but this is good enough for my use and horizontal scaling would increase this easily.

@sandys
Copy link
Author

sandys commented Sep 22, 2019 via email

@devsetgo
Copy link

devsetgo commented Sep 22, 2019

Backlog setting came as a recommendation to add from a friend to add. The maximum is 2048 if I remember right, which is what I use as a setting. I've tried a few changes to the setting, but for my own use it has not made any difference.

Haven't used HAProxy, but I was using Nginx for the last 10 years and jwilder/nginx-proxy. Performance was pretty good (better than Traefik), but I find configuration is easier with Traefik + LetsEncrypt and Docker integration. So that is why I have been using it for the last 6 months.

Thanks for the tip on HAProxy, I'll look into it.

Edit - Love the name of your domain. Made me laugh... excellent choice.

@euri10
Copy link
Contributor

euri10 commented Sep 25, 2019

I think most of your questions are answered quite well here @sandys https://medium.com/building-the-system/gunicorn-3-means-of-concurrency-efbb547674b7

@sandys
Copy link
Author

sandys commented Sep 25, 2019 via email

@euri10
Copy link
Contributor

euri10 commented Sep 25, 2019

So the link you pointed out is not very helpful in this specific context.

so sorry it didn't help @sandys , next time I'll google something more useful that will fit your exact needs, hopefully 😜

@sandys
Copy link
Author

sandys commented Sep 25, 2019 via email

@dmontagu
Copy link
Collaborator

dmontagu commented Sep 25, 2019

@sandys Thanks for your feedback/contributions! Please keep sharing as you figure out more of the production tuning stuff, I'm definitely hoping to gain some knowledge from these tuning efforts. (Sorry I can't be more helpful on this point...)

Have you tried asking in the uvicorn / starlette repos? I think there is a broader (and often very technically sophisticated) audience watching those repos.

@sandys
Copy link
Author

sandys commented Sep 25, 2019 via email

@krnr
Copy link

krnr commented Oct 10, 2019

I've been trying to enable pre_request/post_request hooks for Uvicorn worker, but with no avail. They are supported, but why their behavior is different from standard worker?

pre_request set up as usual

loglevel = "debug"
worker_class = "uvicorn.workers.UvicornWorker"

def pre_request(worker, req):
    worker.log.warning('foo')

has no effect. but set as

def pre_request(worker):
    worker.log.warning('UUUU')

results in error: Invalid value for pre_request: <function pre_request at 0x7feb8dee0a60>. so, it's processed, but how? looking at the debug output:

  pre_request: <function pre_request at 0x7f9bb01f5a60>
  post_request: <function validate_post_request.<locals>.<lambda> at 0x7f9bb01f5b70>

why post_request is a lambda function and how to set it?

when I use standard sync worker everything is fine.

@tiangolo
Copy link
Owner

Thanks everyone for the discussion!

@sandys as I understand, threads in Gunicorn are useful for WSGI frameworks that can run in multiple threads, if supported by the rest of the components used. And that way increase concurrency.

With an ASGI framework, it's already handling concurrency on the async loop. In fact, Starlette and FastAPI will run some parts of the code (when you use normal def) in a thread pool.

So, I don't think enabling threads with Uvicorn would help (or maybe even harm).


@krnr that seems to be a separated problem, can you please create a new issue for that?

@sandys
Copy link
Author

sandys commented Oct 28, 2019 via email

@dmontagu
Copy link
Collaborator

dmontagu commented Oct 28, 2019

It might be better to ask questions like this on the gunicorn github than fastapi; I expect the same advice will apply to fastapi as applies to any async server framework.

This is a relatively old comment but I suspect little has changed -- using threads adds a little CPU overhead in exchange for reduced memory consumption. If you have high-memory-usage shared (e.g., global) variables you may see some memory usage benefits. But it is going to depend enormously on the design of your application. Even if other people running fastapi under high load could comment, I don't think they'd be able to give useful advice on these nuanced points without a relatively deep understanding of your application.

I suspect that for most typical fastapi applications, CPU will be more of a bottleneck than memory under high load (again, depends on your application). In that case, using only process-based workers should give the best performance.

@sandys
Copy link
Author

sandys commented Oct 29, 2019

@dmontagu well this seems to be one of those things that we are not getting answers anywhere.

For example, take a look another gunicorn configuration issue - #253 - where fastapi does not play well with per-core settings when run inside docker/kubernetes.

It is my humble opinion - that production configuration is a hugely dangerous thing here. And could become one of those things that hurt us after we have heavy investment in writing fastapi. Because our servers are being under-utilized (multi-core issue) or other problems.

We see that Uber, etc do use fastapi in production. It would be really awesome if we could get some learnings/rules-of-thumb from them.

@dmontagu
Copy link
Collaborator

dmontagu commented Oct 29, 2019

The problem in the issue you linked had nothing to do with the specifics of fastapi or it ignoring settings related to pet-core settings. It was a timeout issue due to a large query, which would have happened with any server.

Again, I don’t think there’s anything specific to FastAPI here in terms of what a production config should look like.

@dmontagu
Copy link
Collaborator

Also, the other issues discussed there with workers restarting are not resolved. I don’t believe that is a production gunicorn config issue, but an issue of cloud config (which is generally going to be idiosyncratic to each application) and/or just bugs in people’s code.

@sandys
Copy link
Author

sandys commented Oct 30, 2019

@dmontagu i disagree here. there's no cloud config here, unless you are referring to docker config. however, given that we are seriously moving to production with fastapi and these are issues we are hitting...im not sure where else should i cry for help.

there is a similar issue being discussed of in context of threads - #603 (comment) . also directly related to production configuration.

@tiangolo
Copy link
Owner

@sandys as I said before, you shouldn't "enable" threads in Gunicorn apart from setting the number of Uvicorn workers.

Gunicorn was created for WSGI applications. It doesn't have a notion of ASGI. So it was made expecting that threads would be the only way to handle concurrency.

FastAPI uses an async event loop, that can be provided by Uvicorn (running uvloop underneath).

FastAPI takes care of running synchronous functions in a threadpool using run_in_executor.

So, FastAPI is already starting threads for you, for each running process (for each app instance).

Being able to handle concurrency directly and the full even loop with uvloop is what gives all the performance benefits. So, starting extra threads in Gunicorn (apart from the ones that each FastAPI instance is already starting) would at best take control out from uvloop, reducing performance, and at worst, could generate issues if you have context-unsafe code.

If you want to hyper-optimize performance more than that, you can check the internals of uvloop, read the docs for asyncio, the PEP for ContextVars and understand how it all works together and is integrated.


Also, let me ask you to double-check your messages. They are looking very critical and demanding. Try to make them more deferent. Everyone here is trying to help you. @euri10, @dmontagu and @devsetgo are just trying to help you, for free. We don't really earn anything from helping you. So, please, be respectful and gentle to the community, that's the least you can do.

@github-actions
Copy link
Contributor

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

@SagiMedina
Copy link

Hey @sandys ,
After a year, any learning points? conclusion regarding using uvicorn and gunicorn in production?
recommendation?

@Gatsby-Lee
Copy link

@sandys I guess your expectation for FastAPI community is not quite higher.
At least, I appreciate to your effort to get what other ppl might look for.
Due to your effort, at least I can see more detailed explanation from @tiangolo :)

Thank you.

@Gatsby-Lee
Copy link

@tiangolo
Thank you very much for your detailed explanation.
It really helps :)

@tiangolo tiangolo changed the title [QUESTION] Production configuration of gunicorn Production configuration of gunicorn Feb 24, 2023
@tiangolo tiangolo reopened this Feb 28, 2023
Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #8087 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Question or problem question-migrate
Projects
None yet
Development

No branches or pull requests

8 participants