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

The memory usage piles up over the time and leads to OOM #1624

Closed
6 of 9 tasks
prav2019 opened this issue Jun 25, 2020 · 86 comments
Closed
6 of 9 tasks

The memory usage piles up over the time and leads to OOM #1624

prav2019 opened this issue Jun 25, 2020 · 86 comments

Comments

@prav2019
Copy link

prav2019 commented Jun 25, 2020

First check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google "How to X in FastAPI" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.
  • After submitting this, I commit to one of:
    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
    • I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
    • Implement a Pull Request for a confirmed bug.

Example

Here's a self-contained, minimal, reproducible, example with my use case:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}

Description

  • Open the browser and call the endpoint /.
  • It returns a JSON with {"Hello": "World"}.
  • But I expected it to return {"Hello": "Sara"}.

Environment

  • OS: [e.g. Linux / Windows / macOS]:
  • FastAPI Version [e.g. 0.3.0]:

To know the FastAPI version use:

python -c "import fastapi; print(fastapi.__version__)"
  • Python version:

To know the Python version use:

python --version

Additional context

Tracemalloc gave insight on the lines , that are top consumers of memory: (top one seems to be below line in uvicorn)
/usr/local/lib/python3.6/site-packages/uvicorn/main.py:305:
Line:
loop.run_until_complete(self.serve(sockets=sockets))

@prav2019 prav2019 added the question Question or problem label Jun 25, 2020
@prav2019
Copy link
Author

same issue i faced @prav2019

Any solution to overcome that @Riki-1mg

@Riki-1mg
Copy link

Riki-1mg commented Jun 25, 2020

No @prav2019,
are you using aiohttp as http clients in your service ?
#1623

@prav2019
Copy link
Author

No @Riki-1mg

@teymour-aldridge
Copy link

teymour-aldridge commented Jun 26, 2020

@app.get("/")
def read_root():
  return {"Hello": "World"}

Surely the expected behaviour here is to return {"Hello": "World"}?

If you want this function to return {"Hello": "Sara"} you'd probably need to do something like:

@app.get("/")
def read_root():
  return {"Hello": "Sara"}

Further, I can't reproduce your error on my "machine" (it's sitting on a cloud somewhere). You can see the full details here but everything looks to be working fine.

I suspect that this is specific to your operating system setup, etc. Would you please provide some more info needed/useful to know in context of how to reproduce the error?

  • How much RAM does your machine have?
  • How much memory is each function using (ideally include all the debugging output).
    This wil look something like the below (example from the Python documentation)
[ Top 10 ]
<frozen importlib._bootstrap>:716: size=4855 KiB, count=39328, average=126 B
<frozen importlib._bootstrap>:284: size=521 KiB, count=3199, average=167 B
/usr/lib/python3.4/collections/__init__.py:368: size=244 KiB, count=2315, average=108 B
/usr/lib/python3.4/unittest/case.py:381: size=185 KiB, count=779, average=243 B
/usr/lib/python3.4/unittest/case.py:402: size=154 KiB, count=378, average=416 B
/usr/lib/python3.4/abc.py:133: size=88.7 KiB, count=347, average=262 B
<frozen importlib._bootstrap>:1446: size=70.4 KiB, count=911, average=79 B
<frozen importlib._bootstrap>:1454: size=52.0 KiB, count=25, average=2131 B
<string>:5: size=49.7 KiB, count=148, average=344 B
/usr/lib/python3.4/sysconfig.py:411: size=48.0 KiB, count=1, average=48.0 KiB
  • Does your out-of-memory error include a traceback? Please include that if that's the case.

@prav2019
Copy link
Author

@teymour-aldridge : here is the debug statistics:
Memory Statistics
Top 10 Files
/usr/local/lib/python3.6/site-packages/uvicorn/main.py:305: size=1652 KiB (+1499 KiB), count=4597 (+4172), average=368 B KB

/usr/local/lib/python3.6/site-packages/starlette/applications.py:136: size=1288 KiB (+1173 KiB), count=2290 (+2086), average=576 B KB

/usr/local/lib/python3.6/threading.py:347: size=943 KiB (+854 KiB), count=1836 (+1657), average=526 B KB

/usr/local/lib/python3.6/queue.py:145: size=919 KiB (+835 KiB), count=1783 (+1619), average=528 B KB

/usr/local/lib/python3.6/asyncio/locks.py:233: size=885 KiB (+807 KiB), count=9633 (+8771), average=94 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:82: size=788 KiB (+717 KiB), count=6876 (+6264), average=117 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:77: size=751 KiB (+684 KiB), count=2289 (+2086), average=336 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:146: size=725 KiB (+662 KiB), count=15984 (+14611), average=46 B KB

/usr/local/lib/python3.6/site-packages/sqlalchemy/engine/result.py:376: size=657 KiB (+590 KiB), count=10490 (+9426), average=64 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:285: size=609 KiB (+555 KiB), count=4589 (+4183), average=136 B KB

Scanned Lines that consumes more memory
uvicorn/main.py

loop.run_until_complete(self.serve(sockets=sockets))
starlette/applications.py

scope["app"] = self
python3.6/threading.py

waiters_to_notify = _deque(_islice(all_waiters, n))
python3.6/queue.py

self.not_empty.notify()
asyncio/locks.py

self._waiters = collections.deque()
http/httptools_impl.py

self.parser = httptools.HttpRequestParser(self)
http/httptools_impl.py

self.config = config
http/httptools_impl.py

self.parser.feed_data(data)
engine/result.py

for obj_elem in elem[4]
http/httptools_impl.py

self.timeout_keep_alive, self.timeout_keep_alive_handler

@prav2019
Copy link
Author

@teymour-aldridge the above grows and cause OOM after a while

@teymour-aldridge
Copy link

@prav2019 I can't reproduce the bug on either my machine or on a cloud-hosted linux container; this leads me to believe that the problem is in the way that your machine/environment setup.

In the issue template, it asks for the following fields – would you mind filling them in?

OS: [e.g. Linux / Windows / macOS]:
FastAPI Version [e.g. 0.3.0]:

Also, there's a "checklist" at the top of the issue which you should fill out!

@prav2019
Copy link
Author

@teymour-aldridge , this usually happens when there are some traffic over a period of time. Usually this is happening in our prod environment.
OS: using docker image: python 3.6 [so debian ] **Linux
FastAPI version: fastapi[all]==0.20.0

@prav2019
Copy link
Author

@teymour-aldridge , current status:
Top 10 Files
/usr/local/lib/python3.6/site-packages/uvicorn/main.py:305: size=3650 KiB (+1922 KiB), count=10158 (+5349), average=368 B KB

/usr/local/lib/python3.6/site-packages/starlette/applications.py:136: size=2851 KiB (+1504 KiB), count=5069 (+2673), average=576 B KB

/usr/local/lib/python3.6/threading.py:347: size=2104 KiB (+1110 KiB), count=4083 (+2155), average=528 B KB

/usr/local/lib/python3.6/queue.py:145: size=2049 KiB (+1087 KiB), count=3974 (+2109), average=528 B KB

/usr/local/lib/python3.6/asyncio/locks.py:233: size=1948 KiB (+1017 KiB), count=21295 (+11208), average=94 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:82: size=1744 KiB (+920 KiB), count=15226 (+8037), average=117 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:77: size=1663 KiB (+877 KiB), count=5068 (+2673), average=336 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:146: size=1598 KiB (+839 KiB), count=35181 (+18488), average=47 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:285: size=1349 KiB (+712 KiB), count=10163 (+5364), average=136 B KB

/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py:216: size=1288 KiB (+676 KiB), count=30112 (+15818), average=44 B KB

@teymour-aldridge
Copy link

0.20.0 is a somewhat old version of FastAPI, what happens if you use the latest release instead?

@prav2019
Copy link
Author

@teymour-aldridge haven't tried with latest version ? but i like to give it a shot, if there were any memory fix done with the versions after 0.20.0

@prav2019
Copy link
Author

@teymour-aldridge updated to latest version, will report back, once I see the result, Thanks

@prav2019
Copy link
Author

@teymour-aldridge I checked on our staging, after updating to latest versions [fast api and uvicorn], the memory issue still exists

@prav2019
Copy link
Author

prav2019 commented Jun 30, 2020

@teymour-aldridge can this one work, i saw this in uvicorn documentation:
--limit-max-requests - Maximum number of requests to service before terminating the process. Useful when running together with a process manager, for preventing memory leaks from impacting long-running processes.

@teymour-aldridge
Copy link

teymour-aldridge commented Jun 30, 2020

@prav2019 I don't know, it might do. How many requests are you handling and how many machines do you have?

@prav2019
Copy link
Author

@teymour-aldridge or trying to add gunicorn!!!

@curtiscook
Copy link

im running into the same issue - memory usage slowly builds over time, runnign on gunicorn with 4 uvicorn workers

@erikreppel
Copy link

+1, seeing the same issue

fastapi==0.55.1
uvicorn==0.11.5
gunicorn==19.10.0

Gunicorn + uvicorn worker class

@curtiscook
Copy link

Reading through the uvicorn code.. adding the max-requests effectively just restarts the server as soon as you hit some arbitrary number of requests?

https://github.com/encode/uvicorn/blob/e77e59612ecae4ac10f9be18f18c47432be7909a/uvicorn/main.py#L537-L539

I can't find any good documentation on what this number should be either 500? 1000? 10k? 100k?

If anyone has any experience/advice here, I'm all ears

@prav2019
Copy link
Author

prav2019 commented Aug 12, 2020

@curtiscook The max-requests restarts the service completely, we need to configure workers to keep one running always, when we restart another, was able to solve memory issue, but got into one more, now sometimes I get multiple requests to workers with same data and each worker creates new entry into database.

@drisspg
Copy link

drisspg commented Aug 19, 2020

@prav2019 So what exactly solved your OOM issue, was it setting the max-requests?

@curtiscook
Copy link

Hi,

I actually have not solved my memory leak issue but it's small enough to not be a huge concern. I'm also seeing the memory leak in other async processes so it might be an issue with long running event loops in async python?

@curtiscook The max-requests restarts the service completely, we need to configure workers to keep one running always, when we restart another, was able to solve memory issue, but got into one more, now sometimes I get multiple requests to workers with same data and each worker creates new entry into database.

That's what I thought it might do. Not really a great solution then :(

@teymour-aldridge
Copy link

I would have thought it would be pretty tricky to have a memory leak in Python. Perhaps an underlying issue in the interpreter (C is very good for memory leaks :D) or a C extension module?

Anyway this has peaked my interest, so I'll try and investigate a little to see what's causing the problem.

Is this only happening in Docker containers, or can it be reproduced across a number of devices? I'm not experiencing this issue on my machine, having left a FastAPI process running for a few days (nothing unusual happened).

It's generally a good move to use ephemeral (short-lived) processes to run applications and regularly recycle them in order to reduce the risk/impact a memory leak (which tend to build up over time) can have.

@curtiscook
Copy link

I haven't tested outside of docker containers/ heroku docker containers

@teymour-aldridge
Copy link

Ah. I'll try and do some profiling. Unfortunately my time is pretty scarce these days with the number of different projects I'm working on but fingers crossed.

@binbinah
Copy link

binbinah commented Aug 21, 2020

+1

image

python 3.6
fastapi==0.60.1
uvicorn==0.11.3

uvicorn main:app --host 0.0.0.0 --port 8101 --workers 4
docker:2 core 2GB memory,CentOS Linux release 7.8.2003 (Core)

client call the function below per minute, and server memory usage slowly builds over time.

...
from fastapi import BackgroundTasks
...

@router.get('/tsp/crontab')
def tsp_crontab_schedule(topic: schemas.AgentPushParamsEnum,
                         background_tasks: BackgroundTasks,
                         api_key: str = Header(...)):
    crontab = CrontabMain()

    if topic == topic.schedule_per_minute:
        background_tasks.add_task(crontab.schedule_per_minute)

@binbinah
Copy link

binbinah commented Aug 21, 2020

update:

I add async before def and it worked
refer: #596 (comment)

@router.get('/tsp/crontab')
def tsp_crontab_schedule(topic: schemas.AgentPushParamsEnum..)
    pass

to

@router.get('/tsp/crontab')
async def tsp_crontab_schedule(topic: schemas.AgentPushParamsEnum..)
    pass

@prav2019
Copy link
Author

prav2019 commented Nov 9, 2020

#1624 (comment) . The max request fixed OOM, as it restarts server, but it opened to lot of concurrency issues.

@prav2019
Copy link
Author

did replacing def with async def helped, because I tried long back it didn't!!!!!

@ConMan05
Copy link

ConMan05 commented Apr 6, 2022

If you're in a hurry and need a quick and temporary solution for now.

--max requests 1 --workers 10

This helped me. You can get 10 simultaneous request where each worker will be restarted when request is finished. Thus the memory will be released.

@curtiscook
Copy link

Hello guys I and my colleagues had a similar issue and we solved it. image

After profiling we found out the coroutines created by uvicorn did not disappear but remain in the memory (health check request, which basically does nothing could increase the memory usage). This phenomenon was only observed in the microservices that were using tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim-2021-10-02 as base image. After changing the Image, the memory did not increased anymore.

  • if you are using tiangolo/uvicorn-gunicorn-fastapi as base docker image, try building from python official image. [it worked for us]
  • if it doesn't work, profile your own reason. the script below may help you.
# [Memory Leak Profiler]
# REF: https://tech.gadventures.com/hunting-for-memory-leaks-in-asyncio-applications-3614182efaf7

def format_frame(f):
    keys = ["f_code", "f_lineno"]
    return OrderedDict([(k, str(getattr(f, k))) for k in keys])

def show_coro(c):
    data = OrderedDict(
        [
            ("txt", str(c)),
            ("type", str(type(c))),
            ("done", c.done()),
            ("cancelled", False),
            ("stack", None),
            ("exception", None),
        ]
    )
    if not c.done():
        data["stack"] = [format_frame(x) for x in c.get_stack()]
    else:
        if c.cancelled():
            data["cancelled"] = True
        else:
            data["exception"] = str(c.exception())
    return data

async def trace_top20_mallocs(sleep_time = 300):
    """
    See https://docs.python.org/ko/3/library/tracemalloc.html
    """
    # has_snap_shot_before = False

    initial_snapshot = (
        tracemalloc.take_snapshot()
    )  # copy.deepcopy(tracemalloc.take_snapshot())
    while True:
        if tracemalloc.is_tracing():
            snapshot = tracemalloc.take_snapshot()
            top_stats = snapshot.compare_to(
                initial_snapshot, "lineno"
            )  # snapshot.statistics("lineno")
            print(f"[ TOP 20 ] diff {datetime.now()}")
            traces = [str(x) for x in top_stats[:20]]
            for t in traces:
                print(t)
            await asyncio.sleep(sleep_time)


async def show_all_unfinished_coroutine_status(sleep_time=200):
    cnt = 0
    while True:
        await asyncio.sleep(sleep_time)
        tasks = asyncio.all_tasks()
        if len(tasks) != cnt:

            for task in tasks:
                formatted = show_coro(task)
                print(json.dumps(formatted, indent=2))
            cnt = len(tasks)
        print(len(tasks))


loop = asyncio.get_running_loop()
asyncio.ensure_future(trace_top20_mallocs(), loop=loop)
asyncio.ensure_future(show_all_unfinished_coroutine_status(), loop=loop)

This is great. I'm 90% sure that the issue you found is the one that I was experiencing. As one of the early posters on this issue, I haven't noticed this issue anymore--but there were a few things that have happened since... Namely:

  1. I've been upgrading FastAPI
  2. I moved from uvicorn to hypercorn since hypercorn supports HTTP/2

It's possible that I still have a memory leak, but it's not as detrimental as 2 years ago.

Re:

--max requests 1 --workers 10. This helped me. You can get 10 simultaneous request where each worker will be restarted when request is finished. Thus the memory will be released.

I don't think this is a viable solution since I believe this blocks the event loop and relies on multiprocessing, which skips out on one of the major benefits of the ASGI server (not getting [thread/process]bound with a single worker)

I don't know if @tiangolo has any thoughts? I feel like we're finally closer to being able to close this issue

@Xcompanygames
Copy link

Xcompanygames commented Apr 13, 2022

I'm having a massive leak with tensorflow + inference with dockerized fastapi + uvicorn server. anyone met that? (I'm on a machine with 120GB RAM)

@evaldask
Copy link

While it didn't completely solve memory pile up over time, using gunicorn_conf.py attached below made the increase minimal over time.

import os

host = os.getenv("HOST", "0.0.0.0")
port = os.getenv("PORT", "8000")

# Gunicorn config variables
loglevel = os.getenv("LOGLEVEL", "error")
workers = int(os.getenv("WORKERS", "2"))
bind = f"{host}:{port}"
errorlog = "-"
logconfig = "/logging.conf"

@JorgeRuizDev
Copy link

JorgeRuizDev commented May 8, 2022

@Xcompanygames Consider Using ONNX instead of TF as it's usually faster and more reliable.

I'm having a memory leak, but i think is because the inference data stays on memory / gets dupped at some point.
I'll update later if the issue is not related with the inference process.

Update: I wasn't closing correctly the onnx inference session.
The memory accumulation is almost unnoticeable now!

@Bears-Eat-Beets
Copy link

Bears-Eat-Beets commented May 11, 2022

Can confirm I am experiencing the same issue. Using Python 3.10 + FastAPI + Hypercorn[uvloop] with 2 workers.
The FastAPI project is brand new, so there isn't any tech debt that could possibly be the cause - no models, schemas or anything fancy being done here.

[tool.poetry.dependencies]
python = "^3.9"
celery = "^5.2.3"
neo4j = "^4.4.3"
hypercorn = {extras = ["uvloop"], version = "^0.13.2"}
fastapi = "^0.77.1"

The Docker container starts at around 105.8 MiB of RAM usage when fresh.

After running a Locust swarm (40 Users) all hitting an endpoint that returns data ranging from 200KB to 8MB - the RAM usage of the Docker container grows (and sometimes shrinks, but mostly grows) until I get an OOM exception. The endpoint retrieves data from the Neo4J database and closes the driver connection cleanly each time.

I had some success making the function async def even though there was nothing to await on. But it seems that FastAPI is still holding onto some memory somewhere... caching?

I'm curious why this topic isn't more popular; surely everyone would be experiencing this. Perhaps we all notice it due to our endpoints returning enough data for us to notice the increase in usage, whereas the general user would most times only return a few KB at a time.

Additional details:
Docker CMD

CMD ["hypercorn", "app.main:app", "--bind", "0.0.0.0:8001", "--worker-class", "uvloop", "--workers", "2"]

@Atheuz
Copy link

Atheuz commented May 11, 2022

I've also experienced this, I don't really understand what causes it.

@SarloAkrobata
Copy link

@tiangolo Is there any updates on this? Thank you.

@JP-Globality
Copy link

JP-Globality commented Jun 9, 2022

I'm also noticing that memory keeps on increasing when hitting my service with 20 virtual users as part of a performance test locally. Am using python3.7 + gunicorn + fastapi + uvicorn inside a docker container.

image

@fredrikaverpil
Copy link

fredrikaverpil commented Jul 18, 2022

What is the deal with the original issue of not returning {"Hello": "Sara"}?
Was the original issue edited and now doesn't make sense in this regard?

EDIT: oh, okay. I see. The author included the bug report template without editing it. Sorry for the noise.

@astrojuanlu
Copy link

Many folks are affected by this issue so definitely something is happening, but it could as well be that the problem is in the user code and not in fastapi. So I suggest that, to make things easier for the maintainers, if you're affected by this issue

  1. Try to give as many details as possible. How is your setup? What versions/Docker images are you using? What Python version, operating system, gunicorn version, etc?
  2. Detail in what timeframe does your issue appear - weeks, days, hours? Does making more requests to the server accelerate the issue? Does the issue still appear if zero requests are made?
  3. Have a look at memray to better understand your program or use @Apiens method above. Share your results in Gist or similar platforms.

Memory issues are tricky but without a good reproducer, it will be impossible for the maintainers to declare whether this is still a problem or not, and if it is, to fix it.

@Lujeni
Copy link

Lujeni commented Jul 20, 2022

If this can help with debugging, using pyenv was able to reproduce the memory leak under python 3.8.X using multiple middlewares.

2243 memory blocks: 3820.5 KiB
  File "/home/lujeni/.pyenv/versions/3.8.13/lib/python3.8/asyncio/locks.py", line 257
    self._waiters = collections.deque()
8563 memory blocks: 3725.5 KiB
  File "/home/lujeni/.pyenv/versions/3.8.13/lib/python3.8/asyncio/events.py", line 81
    self._context.run(self._callback, *self._args)
6773 memory blocks: 3470.5 KiB
  File "/home/lujeni/.pyenv/versions/3.8.13/lib/python3.8/asyncio/base_events.py", line 1859
    handle._run()
22091 memory blocks: 2841.9 KiB
  File "/home/lujeni/.pyenv/versions/uep-moulinex-3.8.13/lib/python3.8/site-packages/starlette/middleware/base.py", line 30
    await self.app(scope, request.receive, send_stream.send)
20978 memory blocks: 2390.5 KiB
  File "/home/lujeni/.pyenv/versions/3.8.13/lib/python3.8/asyncio/base_events.py", line 431
    task = tasks.Task(coro, loop=self, name=name)
4381 memory blocks: 2375.4 KiB
  File "/home/lujeni/.pyenv/versions/3.8.13/lib/python3.8/asyncio/base_events.py", line 570
    self._run_once()
[...]

Via python 3.9.x or 3.10.x there is no issue


from fastapi import FastAPI, Request
import time

app = FastAPI()


@app.middleware("http")
async def middle_1(request: Request, call_next):
    return await call_next(request)


@app.middleware("http")
async def middle_2(request: Request, call_next):
    return await call_next(request)


@app.middleware("http")
async def middle_3(request: Request, call_next):
    return await call_next(request)

@app.middleware("http")
async def middle_4(request: Request, call_next):
    return await call_next(request)

@app.middleware("http")
async def middle_5(request: Request, call_next):
    return await call_next(request)


@app.get("/")
def read_root():
    return {"Hello": "World"}

@tranvannhat
Copy link

same issue

@pkucmus
Copy link

pkucmus commented Oct 22, 2022

I see the same thing across all my services using different FastApi/Uvicorn/Gunicorn and Sentry SDK versions.
This particular one is running:

  • Python 3.10
  • starlette 0.17.1
  • fastapi 0.75.1
  • sentry-sdk 1.5.0 (maybe it's causing issues)
  • gunicorn 20.1.0
  • uvicorn 0.17.6

And is receiving short-lived requests that trigger longer but relatively short background tasks (not like I made myself a Celery out of it). Another thing I can think of is my ignorance around the subject of database connections where we go something like

engine = create_async_engine(settings.database_dsn)
session_factory = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
Session = async_scoped_session(session_factory, scopefunc=current_task)
Base = declarative_base()


@asynccontextmanager
async def get_db():
    session = Session()
    try:
        yield session
        await session.commit()
    except:
        await session.rollback()
        raise
    finally:
        await session.close()

and then use get_db with a with or Depends(get_gb) (I'm very unsure how to work with a DB and FastAPI but that's another thing), but when I had this service on sync SQLAlchemy I was running into many issues where the connections were not going back to the connection pool, resulting in timeouts when waiting for a db connection.

Using Gunicorn with the uvicorn.workers.UvicornWorker worker, workers set to 4

Here's the memory usage (the spike on 10/21 is where I increased a replica count and it immediately hogged a lot of memory):
image
(green is memory %, red is CPU %)

to put it in traffic context (not that any correlation can be seen):
image

I wanted to limit requests for gunicorn to refresh the workers but I'm getting Error while closing socket [Errno 9] Bad file descriptor which seems related to benoitc/gunicorn#1877

Please tell me if I can help i.e. by providing more data.

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Nov 27, 2022

We solved a memory leak on uvicorn 0.18.3, and there were some things solved on FastAPI since the mentioned versions.

Can someone confirm this issue is still happening with the latest uvicorn and FastAPI?

@amchelmer
Copy link

Had an OOM issue whilst running Uvicorn 0.17.2 using tiangolo's pre-built image. The issue hasn't reappeared after switching to a self-built image based on Python 3.10.

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Nov 28, 2022

There are 44 participants on this issue, maybe more subscribed. Can we please focus, and not bother people with information that is not relevant?

If someone can reply my last question, I can proceed from there.

@LLYX
Copy link

LLYX commented Nov 30, 2022

There are 44 participants on this issue, maybe more subscribed. Can we please focus, and not bother people with information that is not relevant?

If someone can reply my last question, I can proceed from there.

image

I'm still having this issue, the first part of that (from roughly 85% utilization to 100%) was on FastAPI 0.85.0 and Uvicorn 0.17.6/Gunicorn 20.1.0/Starlette 0.20.4. The first big drop (down to ~65% utilization) was when my autoscaler kicked in from 1 to 4, and the second big drop (down to ~20%) was when I turned off autoscaling and upgraded a single instance from 512MB to 2GB of memory. Alongside the instance upgrade, I also updated FastAPI to 0.87.0, Uvicorn to 0.20.0, and Starlette to 0.21.0, and it seems like my memory usage is still creeping up (from a base usage of ~380MB to currently 525MB and still growing), which would've hit 100% memory utilization again if I stayed at the previous instance size).

The instance is hosted by Render, and it does health check requests.

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Dec 1, 2022

Are you able to share an MRE?

@evaldask
Copy link

evaldask commented Dec 1, 2022

I upgraded to:

fastapi = "^0.88.0"
starlette = "^0.22.0"
uvicorn = {extras = ["standard"], version = "^0.20.0"}
gunicorn = "^20.1.0"

and the issue still persists (you can see a restart when an instance reached 600mb RAM usage):
image

pyproject.toml
[tool.poetry.dependencies]
python = ">=3.11,<3.12"
fastapi = "^0.88.0"
starlette = "^0.22.0"
uvicorn = {extras = ["standard"], version = "^0.20.0"}
gunicorn = "^20.1.0"
lightgbm = "^3.3.3"
jaeger-client = "^4.8.0"
prometheus-client = "^0.15.0"
scipy = "1.9.3"

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Dec 1, 2022

Are you able to share a minimal, reproducible, example?

@lorosanu
Copy link

lorosanu commented Dec 3, 2022

Would this simple-api sample help?

As far as I can tell

  • the memory increase is most visible on invalid requests (not exclusively though, but maybe this can point you in the right direction?)
  • it is not the fault of pydantic validation
  • it is not the fault of uvicorn

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Dec 3, 2022

it is not the fault of pydantic validation
it is not the fault of uvicorn

I don't think the arguments used there are enough to discard those.

Thanks for the MRE. 👍

EDIT: I still cannot reproduce it: lorosanu/simple-api#1 (comment).
EDIT2: I can see the increase.

andreihalici added a commit to andreihalici/googlefinance-stocks-info that referenced this issue Dec 24, 2022
Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #9082 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests