Skip to content

Commit

Permalink
Increase FastAPI thread pool and make configurable
Browse files Browse the repository at this point in the history
The FastAPI thread pool size can now be configured by setting the environment variable DAF_BUTLER_SERVER_THREAD_POOL_SIZE.

It turns out the default value was 10, which is way too low for an I/O-bound service like Butler server.
  • Loading branch information
dhirving committed Dec 6, 2024
1 parent eabd6ff commit c6227a8
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 9 deletions.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ remote = [
]
server = [
"fastapi",
"safir >= 3.4.0"
"safir >= 3.4.0",
"pydantic-settings"
]
test = [
"pytest >= 3.2",
Expand Down
18 changes: 12 additions & 6 deletions python/lsst/daf/butler/remote_butler/server/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@

from __future__ import annotations

import dataclasses
import os
from pydantic_settings import BaseSettings, SettingsConfigDict


@dataclasses.dataclass(frozen=True)
class ButlerServerConfig:
class ButlerServerConfig(BaseSettings):
"""Butler server configuration loaded from environment variables.
Notes
Expand All @@ -43,12 +41,20 @@ class ButlerServerConfig:
`ButlerRepoIndex`.
"""

static_files_path: str | None
model_config = SettingsConfigDict(env_prefix="daf_butler_server_")

static_files_path: str | None = None
"""Absolute path to a directory of files that will be served to end-users
as static files from the `configs/` HTTP route.
"""

thread_pool_size: int = 40
"""
Maximum number of concurrent threads that may be spawned by FastAPI for
synchronous handlers.
"""


def load_config() -> ButlerServerConfig:
"""Read the Butler server configuration from the environment."""
return ButlerServerConfig(static_files_path=os.environ.get("DAF_BUTLER_SERVER_STATIC_FILES_PATH"))
return ButlerServerConfig()
16 changes: 14 additions & 2 deletions python/lsst/daf/butler/remote_butler/server/_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@

__all__ = ("create_app",)

from collections.abc import Awaitable, Callable
from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import asynccontextmanager

import anyio
import safir.dependencies.logger
from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles
Expand All @@ -54,7 +56,17 @@ def create_app() -> FastAPI:
"""Create a Butler server FastAPI application."""
config = load_config()

app = FastAPI()
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
# Set the size of the threadpool used internally by FastAPI for
# synchronous handlers. Because most Butler server endpoints are sync,
# this effectively sets the maximum number of concurrent requests that
# can be handled.
# See https://github.com/encode/starlette/issues/1724#issuecomment-1179063924

Check failure on line 65 in python/lsst/daf/butler/remote_butler/server/_server.py

View workflow job for this annotation

GitHub Actions / call-workflow / lint

W505

doc line too long (85 > 79 characters)
anyio.to_thread.current_default_thread_limiter().total_tokens = config.thread_pool_size
yield

app = FastAPI(lifespan=lifespan)

# A single instance of the server can serve data from multiple Butler
# repositories. This 'repository' path placeholder is consumed by
Expand Down
1 change: 1 addition & 0 deletions requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ httpx
# Butler server dependencies sufficient for unit tests
fastapi
safir >= 3.4.0
pydantic-settings

0 comments on commit c6227a8

Please sign in to comment.