{"id":20653801,"url":"https://github.com/lukehsiao/axum-fastapi","last_synced_at":"2025-07-29T03:06:24.382Z","repository":{"id":193802651,"uuid":"689148728","full_name":"lukehsiao/axum-fastapi","owner":"lukehsiao","description":"Simple servers to benchmark FastAPI vs Axum with Postgres","archived":false,"fork":false,"pushed_at":"2024-05-01T13:54:53.000Z","size":899,"stargazers_count":14,"open_issues_count":13,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-19T15:09:33.428Z","etag":null,"topics":["axum","benchmark","fastapi","postgres","python","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lukehsiao.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-09-08T23:45:20.000Z","updated_at":"2025-03-23T08:30:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"371e5075-cb5f-4cac-aec7-3cc7ce414d2c","html_url":"https://github.com/lukehsiao/axum-fastapi","commit_stats":null,"previous_names":["lukehsiao/axum-fastapi"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lukehsiao/axum-fastapi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukehsiao%2Faxum-fastapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukehsiao%2Faxum-fastapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukehsiao%2Faxum-fastapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukehsiao%2Faxum-fastapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukehsiao","download_url":"https://codeload.github.com/lukehsiao/axum-fastapi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukehsiao%2Faxum-fastapi/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267621582,"owners_count":24116900,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-29T02:00:12.549Z","response_time":2574,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axum","benchmark","fastapi","postgres","python","rust"],"created_at":"2024-11-16T17:48:20.259Z","updated_at":"2025-07-29T03:06:24.309Z","avatar_url":"https://github.com/lukehsiao.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    📊\u003cbr\u003e\n    FastAPI vs Axum Benchmark with Postgres\n\u003c/h1\u003e\n\u003cdiv align=\"center\"\u003e\n    \u003cstrong\u003eA simple comparison of Python/FastAPI/SQLAlchemy vs Rust/Axum/sqlx.\u003c/strong\u003e\n\u003c/div\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\nThis repo contains two implementations of a _very_ simple web server.\n\n**Contents**\n- [What the servers do](#what-the-servers-do)\n- [The FastAPI server](#the-fastapi-server)\n- [The Full Async FastAPI server](#the-full-async-fastapi-server)\n- [The Axum server](#the-axum-server)\n- [Modifying the code](#modifying-the-code)\n- [Example Benchmark Results](#example-benchmark-results)\n  - [FastAPI](#fastapi)\n  - [FastAPI (async)](#fastapi-async)\n  - [Axum](#axum)\n  - [Flamegraphs](#flamegraphs)\n- [What about with more uvicorn workers?](#what-about-with-more-uvicorn-workers)\n- [What about coordinated omission?](#what-about-coordinated-omission)\n  - [FastAPI](#fastapi-1)\n  - [Axum](#axum-1)\n- [Running your own](#running-your-own)\n  - [Example setup](#example-setup)\n- [Complaints?](#complaints)\n- [License](#license)\n\n## What the servers do\n\nIn both cases, the server fetches users from the `users` table with the following query and returns the results.\n\n```\nSELECT * FROM \"users\" ORDER BY user_id LIMIT 100\n```\n\nPostgres database is seeded with 2000 users using the script in `scripts/init_db.sh`.\nIt is run with docker, and configured to support up to 1000 connections (though both servers only use connection pools of size 5).\n\n[SQLAlchemy](https://docs.sqlalchemy.org/en/20/core/pooling.html)\n\n\u003e This is why it’s perfectly fine for create_engine() to default to using a QueuePool of size five without regard to whether or not the application really needs five connections queued up - the pool would only grow to that size if the application actually used five connections concurrently, in which case the usage of a small pool is an entirely appropriate default behavior.\n\nIn Rust, we set the `max_connections` to 5 to match.\n\n## The FastAPI server\n\nThe FastAPI server is modeled almost directly from the [FastAPI tutorial on SQL databases](https://fastapi.tiangolo.com/tutorial/sql-databases/).\nWhen benchmarking, we run it with `uvicorn` and a single worker (the default).\nWhile this may seem somewhat unfair (throughput and latency improve with more workers), this is [FastAPI's recommendation](https://fastapi.tiangolo.com/deployment/server-workers/) when running in docker on k8s, as many people do.\n\n\u003e In particular, when running on Kubernetes you will probably not want to use Gunicorn and instead run a single Uvicorn process per container...\n\nIncreasing the number of workers to `N` improves throughput and latency, but also multiplies memory usage by `N`, as each worker runs its own process.\nAs is typically done with FastAPI, we use SQLAlchemy and Pydantic for structured responses.\n\n## The Full Async FastAPI server\n\nThis FastAPI server takes a different, more optimal approach of doing _everything_ asynchronously.\nIt deviates more from the FastAPI tutorial, but is also _very_ simple, and actually more structurally similar to the Axum server.\nWhen benchmarking, we still run it with `uvicorn` and a single worker (the default).\n\n## The Axum server\n\nThe Axum server is modeled almost directly from the [Axum example for sqlx and postgres](https://github.com/tokio-rs/axum/tree/503d31976f8504bba76d9ff6d3b20738eb0f3385/examples/sqlx-postgres/src).\n\nAlthough Rust does have ORMs (e.g., [diesel](https://diesel.rs/), [SeaORM](https://www.sea-ql.org/SeaORM/)), the compile-time checking of SQLx means that many applications get by without a full-fledged ORM.\nThis repository could be modified to use diesel as well, since [Axum provides similar examples](https://github.com/tokio-rs/axum/tree/503d31976f8504bba76d9ff6d3b20738eb0f3385/examples/diesel-async-postgres).\nBut, that is left as an exercise to the reader.\n\n## Modifying the code\n\nIn both cases, the code is extremely basic, and should be easy to tweak and experiment with.\n\n## Example Benchmark Results\n\nOn my personal PC with 64 GB of DDR5 RAM and a Ryzen 7 7800X3D (8-core, 16-thread), I saw the following.\nServer and postgres all running locally.\n\nHere's a table comparing the results\n\n| Metric                  | FastAPI | FastAPI (async) |   Axum  |\n| :---------------------- | ------: | --------------: | ------: |\n| Throughput (rps)        |   `612` |          `2267` | `15363` |\n| 50% latency (ms)        |  `15.4` |           `2.2` |   `0.6` |\n| 99% latency (ms)        |  `29.1` |           `2.5` |   `0.9` |\n| 99.9% latency (ms)      |  `33.4` |           `3.1` |   `1.0` |\n| Peak Memory Usage (MiB) |    `78` |            `69` |    `11` |\n| Peak CPU Usage (%)      |   `7.0` |           `5.9` |  `15.9` |\n\nComparing to the synchronous FastAPI baseline specifically, we find the following improvements (×).\n\n| Metric                  | FastAPI | FastAPI (async) |   Axum  |\n| :---------------------- | ------: | --------------: | ------: |\n| Throughput (×)          |     `1` |          `3.70` |  `25.1` |\n| 50% latency (1/×)       |     `1` |           `7.0` |  `25.7` |\n| 99% latency (1/×)       |     `1` |          `11.7` |  `32.3` |\n| 99.9% latency (1/×)     |     `1` |          `10.8` |  `33.4` |\n| Peak Memory Usage (1/×) |     `1` |           `1.1` |   `7.1` |\n| Peak CPU Usage (×)      |     `1` |           `0.8` |   `2.3` |\n\n### FastAPI\n\n#### Details\n```\noha -n 50000 -c 10 --disable-keepalive http://localhost:8000/\nSummary:\n  Success rate: 100.00%\n  Total:        81.7200 secs\n  Slowest:      0.0383 secs\n  Fastest:      0.0051 secs\n  Average:      0.0163 secs\n  Requests/sec: 611.8453\n\n  Total data:   490.14 MiB\n  Size/request: 10\n  Size/sec:     6.00 MiB\n\nResponse time histogram:\n  0.005 [1]     |\n  0.008 [29]    |\n  0.012 [1328]  |■■\n  0.015 [20848] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.018 [18842] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.022 [3972]  |■■■■■■\n  0.025 [2614]  |■■■■\n  0.028 [1685]  |■■\n  0.032 [533]   |\n  0.035 [124]   |\n  0.038 [24]    |\n\nResponse time distribution:\n  10.00% in 0.0130 secs\n  25.00% in 0.0141 secs\n  50.00% in 0.0154 secs\n  75.00% in 0.0173 secs\n  90.00% in 0.0217 secs\n  95.00% in 0.0249 secs\n  99.00% in 0.0291 secs\n  99.90% in 0.0334 secs\n  99.99% in 0.0374 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0001 secs, 0.0000 secs, 0.0005 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0004 secs\n\nStatus code distribution:\n  [200] 50000 responses\n```\n\n### FastAPI (async)\n\n#### Details\n```\noha -n 50000 -c 10 --disable-keepalive http://localhost:8000/\nSummary:\n  Success rate: 100.00%\n  Total:        22.0537 secs\n  Slowest:      22.0526 secs\n  Fastest:      0.0019 secs\n  Average:      0.0044 secs\n  Requests/sec: 2267.1906\n\n  Total data:   490.14 MiB\n  Size/request: 10\n  Size/sec:     22.22 MiB\n\nResponse time histogram:\n   0.002 [1]     |\n   2.207 [49993] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n   4.412 [1]     |\n   6.617 [0]     |\n   8.822 [0]     |\n  11.027 [0]     |\n  13.232 [0]     |\n  15.437 [0]     |\n  17.642 [1]     |\n  19.848 [1]     |\n  22.053 [3]     |\n\nResponse time distribution:\n  10.00% in 0.0021 secs\n  25.00% in 0.0021 secs\n  50.00% in 0.0022 secs\n  75.00% in 0.0022 secs\n  90.00% in 0.0024 secs\n  95.00% in 0.0024 secs\n  99.00% in 0.0025 secs\n  99.90% in 0.0031 secs\n  99.99% in 2.7683 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0000 secs, 0.0000 secs, 0.0005 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0004 secs\n\nStatus code distribution:\n  [200] 50000 responses\n```\n\n### Axum\n\n#### Details\n```\noha -n 50000 -c 10 --disable-keepalive http://localhost:8000/\nSummary:\n  Success rate: 100.00%\n  Total:        3.2546 secs\n  Slowest:      0.0014 secs\n  Fastest:      0.0003 secs\n  Average:      0.0006 secs\n  Requests/sec: 15362.6923\n\n  Total data:   490.14 MiB\n  Size/request: 10\n  Size/sec:     150.60 MiB\n\nResponse time histogram:\n  0.000 [1]     |\n  0.000 [3]     |\n  0.001 [813]   |■\n  0.001 [24488] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.001 [19610] |■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.001 [4344]  |■■■■■\n  0.001 [650]   |\n  0.001 [74]    |\n  0.001 [8]     |\n  0.001 [4]     |\n  0.001 [5]     |\n\nResponse time distribution:\n  10.00% in 0.0006 secs\n  25.00% in 0.0006 secs\n  50.00% in 0.0006 secs\n  75.00% in 0.0007 secs\n  90.00% in 0.0007 secs\n  95.00% in 0.0008 secs\n  99.00% in 0.0009 secs\n  99.90% in 0.0010 secs\n  99.99% in 0.0013 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0000 secs, 0.0000 secs, 0.0004 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0003 secs\n\nStatus code distribution:\n  [200] 50000 responses\n```\n\n### Flamegraphs\n\nFor the curious, there are [flamegraphs](https://www.brendangregg.com/flamegraphs.html) provided from my machine in the directories of the servers.\nFor rust, it was collected by running the benchmark and using [cargo-flamegraph](https://github.com/flamegraph-rs/flamegraph), while for python, it was collected using [py-spy](https://github.com/benfred/py-spy).\n\n## What about with more uvicorn workers?\n\nIf I run\n\n```\nuvicorn app.main:app --log-level critical --host 0.0.0.0 --port 8000 --workers 16\n```\n\nboth the memory usage and CPU usage increase (e.g, up to ~1200 MiB).\n\nThen, the results look like\n\n```\noha -n 50000 -c 10 --disable-keepalive http://localhost:8000/\nSummary:\n  Success rate: 100.00%\n  Total:        4.7476 secs\n  Slowest:      0.0030 secs\n  Fastest:      0.0006 secs\n  Average:      0.0009 secs\n  Requests/sec: 10531.5539\n\n  Total data:   490.14 MiB\n  Size/request: 10\n  Size/sec:     103.24 MiB\n\nResponse time histogram:\n  0.001 [1]     |\n  0.001 [11841] |■■■■■■■■■■■■■■\n  0.001 [26594] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.001 [7971]  |■■■■■■■■■\n  0.002 [2927]  |■■■\n  0.002 [481]   |\n  0.002 [121]   |\n  0.002 [35]    |\n  0.002 [12]    |\n  0.003 [12]    |\n  0.003 [5]     |\n\nResponse time distribution:\n  10.00% in 0.0008 secs\n  25.00% in 0.0008 secs\n  50.00% in 0.0009 secs\n  75.00% in 0.0010 secs\n  90.00% in 0.0012 secs\n  95.00% in 0.0013 secs\n  99.00% in 0.0016 secs\n  99.90% in 0.0021 secs\n  99.99% in 0.0027 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0001 secs, 0.0000 secs, 0.0010 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0005 secs\n\nStatus code distribution:\n  [200] 50000 responses\n```\n\nThis is a significant improvement in both throughput and latency.\nNot quite a linear improvement with 16× more processes, and still slower than Axum.\n\n## What about coordinated omission?\n\n_WARNING: Unlike the other results, this was done on a machine with only 16GB of DDR4 RAM and a AMD Ryzen 3700X._\n\n`oha`, the load generator I'm using, does support compensating for [coordinated omission](https://redhatperf.github.io/post/coordinated-omission/).\nBut, if I do so, it _really_ makes FastAPI look bad.\nSo bad, that I'd highly suspect I'm doing something wrong, but haven't dug into it yet.\n\nHere's what it looks like with `-q 10000` and `--latency-correction`:\n\n| Metric           |  FastAPI |   Axum |\n| :--------------- | -------: | -----: |\n| Throughput (rps) |    `317` | `9920` |\n| 50% latency (ms) |  `75000` | `16.2` |\n| 99% latency (ms) | `151000` | `40.4` |\n\nI think you'll agree that this looks crazy, and suggests there is something I should tweak about the setup.\nIf you have ideas, please reach out!\n\n### FastAPI\n\n```\n❯ oha -n 50000 -c 10 --disable-keepalive --latency-correction -q 10000 http://localhost:8000/\nSummary:\n  Success rate: 100.00%\n  Total:        157.5955 secs\n  Slowest:      152.5937 secs\n  Fastest:      0.0147 secs\n  Average:      76.0228 secs\n  Requests/sec: 317.2680\n\n  Total data:   490.90 MiB\n  Size/request: 10.05 KiB\n  Size/sec:     3.11 MiB\n\nResponse time histogram:\n    0.015 [1]    |\n   15.273 [4820] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n   30.531 [4859] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n   45.788 [5246] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n   61.046 [5362] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n   76.304 [5037] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n   91.562 [4983] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  106.820 [5207] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  122.078 [4564] |■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  137.336 [5088] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  152.594 [4833] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n\nResponse time distribution:\n  10% in 15.7830 secs\n  25% in 38.8800 secs\n  50% in 75.2023 secs\n  75% in 113.5457 secs\n  90% in 136.7149 secs\n  95% in 145.2185 secs\n  99% in 151.1093 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0001 secs, 0.0000 secs, 0.0011 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0003 secs\n\nStatus code distribution:\n  [200] 50000 responses\n```\n\n### Axum\n\n```\n❯ oha -n 50000 -c 10 --disable-keepalive --latency-correction -q 10000 http://localhost:8000/\nSummary:\n  Success rate: 100.00%\n  Total:        5.0403 secs\n  Slowest:      0.0415 secs\n  Fastest:      0.0020 secs\n  Average:      0.0199 secs\n  Requests/sec: 9920.0133\n\n  Total data:   490.90 MiB\n  Size/request: 10.05 KiB\n  Size/sec:     97.40 MiB\n\nResponse time histogram:\n  0.002 [1]     |\n  0.006 [2400]  |■■■■■\n  0.010 [1570]  |■■■\n  0.014 [9299]  |■■■■■■■■■■■■■■■■■■■■\n  0.018 [14379] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.022 [4971]  |■■■■■■■■■■■\n  0.026 [3931]  |■■■■■■■■\n  0.030 [4365]  |■■■■■■■■■\n  0.034 [2941]  |■■■■■■\n  0.038 [1462]  |■■■\n  0.042 [4681]  |■■■■■■■■■■\n\nResponse time distribution:\n  10% in 0.0105 secs\n  25% in 0.0137 secs\n  50% in 0.0162 secs\n  75% in 0.0265 secs\n  90% in 0.0371 secs\n  95% in 0.0394 secs\n  99% in 0.0404 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0000 secs, 0.0000 secs, 0.0011 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0004 secs\n\nStatus code distribution:\n  [200] 50000 responses\n```\n\n## Running your own\n\nI've provided a [Justfile](https://just.systems/man/en/) to help run things the way I did.\nSpecifically, you can set up the database with `just initdb` (you'll need docker and postgres installed).\nYou can run a server with `just python` or `just rust`.\nYou can run the benchmark with `just oha`.\nNote that the number of workers, `C`, can be increased depending on how many threads your CPU has.\nIf you do too many, `oha` will behave oddly.\nI did so using [tmux](https://github.com/tmux/tmux/wiki), but multiple shells will also work.\nMonitor the system utilization of `uvicorn` or `rust-axum` however you please; I recommend [btm](https://clementtsang.github.io/bottom/0.9.6/) with the filter `cpu\u003e0 and (uvicorn or rust-axum or docker or oha)` on the Process Widget for a nice view.\n\n### Example setup\n\n\u003cdiv align=\"center\"\u003e\n\n![screenshot](assets/in-action.png)\n\n\u003c/div\u003e\n\n## Complaints?\n\nBenchmarks are hard.\nIf you think something is wrong or unfair, please let me know!\n\n## License\n\nThis repository is distributed under the terms of the Blue Oak license.\nAny contributions are licensed under the same license, and acknowledge via the [Developer Certificate of Origin](https://developercertificate.org/).\n\nSee [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukehsiao%2Faxum-fastapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukehsiao%2Faxum-fastapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukehsiao%2Faxum-fastapi/lists"}