Rust 2026-10-15·Ievgenii Svyryd

Benchmark, runda 2: Rust, Go i frameworki Rusta pod profilerem

Go i siedem aplikacji w Rust (axum, actix-web, Rocket, Loco, Cot oraz Rustango na axum i actix) na tych samych zasadach, z uruchomioną tylko testowaną aplikacją, oraz profile CPU, wywołań systemowych i ruchu sieciowego, które pokazują, na co każda z nich zużywa czas.

Benchmark, runda 2: Rust, Go i frameworki Rusta pod profilerem

To runda 2 uczciwego benchmarku. Runda 2 obejmuje tylko aplikacje kompilowane: Go i siedem aplikacji w Rust. Dodaje trzy frameworki Rusta (Rocket, Loco, Cot) oraz profile CPU, wywołań systemowych i ruchu sieciowego, które pokazują, na co każda aplikacja zużywa czas.

Pomiary z 14 października 2026 na Apple M3 Pro (maszyna wirtualna Dockera z 12 CPU i 16 GB).

  • 441/s odśmiecania Go na indeksie z cache
  • 3 vs 9 rund do Postgresa na stronę szczegółów: Go vs sqlx z pingiem przy pobraniu
  • +45% Rustango na actix, strona indeksu, z jednym wycinkiem puli na workera
  • 6.3–22.8 MiB pamięć pod obciążeniem, wszystkie osiem aplikacji
  • 0 błędów lub odpowiedzi innych niż 200 w 144 przebiegach

Konfiguracja

Ten sam blog co w rundzie 1: 10 autorów, 30 tagów, 1 000 wpisów, 2 600 powiązań wpis-tag i 10 000 komentarzy. Sześć tras: indeks (20 najnowszych wpisów), strona szczegółów wpisu ze wszystkimi komentarzami, strona tagu, indeks i strona szczegółów z cache stron w Redis oraz /compute (suma liczb pierwszych poniżej 20 000, bez bazy danych).

Osiem aplikacji:

  • Go: net/http z biblioteki standardowej, pgx v5, go-redis
  • axum + sqlx i actix + sqlx: surowy sqlx z tekstem SQL z Go, szablony Tera
  • Rocket: rocket_db_pools (sqlx) i rocket_dyn_templates (Tera)
  • Rustango na axum i na actix-web: ORM Rustango i jego cache stron
  • Loco: SeaORM, Tera
  • Cot: ORM Cota, szablony askama

Zasady:

  • Aplikacja dostaje 4 CPU (przypięte do rdzeni 0–3) i 2 GB RAM, z 4 workerami. PostgreSQL 16 i Redis 7 działają na rdzeniach 4–7, generator obciążenia oha na rdzeniach 8–11.
  • Działa tylko testowana aplikacja; pozostałe są zatrzymane. Między aplikacjami liczba połączeń z Postgresem wraca do wartości bazowej.
  • Pule: maks. 16 połączeń, min. 4.
  • Przed jakimkolwiek obciążeniem każda strona każdej aplikacji jest porównywana z HTML-em z Go. Niezgodność przerywa przebieg.
  • Bez kompresji. 5 s rozgrzewki, potem trzy przebiegi po 20 s na endpoint przy 50 połączeniach; liczy się mediana.

Stanowisko testowe

DOCKER NETWORK · NO COMPRESSION oha load generator 8 9 10 11 CORES 8–11 App under test 4 CPUs · 2 GB · 4 workers 0 1 2 3 CORES 0–3 · PINNED Databases Postgres · MySQL · Redis 4 5 6 7 CORES 4–7 HTTP 50 conns SQL · cache 16 conns ✓ HTML parity vs Go, before any load PER ENDPOINT 5 s run 1 · 20 s run 2 · 20 s run 3 · 20 s WARM-UP → MEDIAN OF 3 · HTTP 200 ONLY DOCKER NETWORK oha load generator CORES 8–11 8 9 10 11 HTTP 50 conns ✓ HTML parity vs Go App under test 4 CPUs · 2 GB · 4 workers CORES 0–3 0 1 2 3 SQL · cache · 16 conns Databases PostgreSQL · MySQL · Redis CORES 4–7 4 5 6 7 PER ENDPOINT 20 s 20 s 20 s 5 S WARM-UP → MEDIAN OF 3

To samo stanowisko co w rundzie 1. Runda 2 używa tylko PostgreSQL i Redis (bez MySQL).

Wersje

  • Go 1.25.14, pgx v5.7.2, go-redis v9.7.0
  • rustc 1.99.0; axum 0.8.9, actix-web 4.16.0, tokio 1.53, sqlx 0.8.6, Tera 1.20.1
  • Rocket 0.5.1 (sqlx 0.7.4, hyper 0.14)
  • Loco 1.2.0 (SeaORM 2.0.4, sqlx 0.9.0)
  • Cot 0.7.0 (sqlx 0.9.0, askama 0.16.1)
  • Rustango bez pingu przy pobraniu, z cache stron przechowującym surowe bajty i jednym połączeniem z puli na żądanie (wniosek 5)

Ustawienia, których nie dało się wyrównać

UstawienieAplikacjeDlaczego
Pula pinguje Postgresa przy każdym pobraniu połączenia (sqlx test_before_acquire)Rocket, Loco, CotBrak ustawienia, które to wyłącza. W axum + sqlx, actix + sqlx i Rustango jest wyłączone.
10 połączeń w puli zamiast 16CotCot nie ma ustawień puli.
Jeden wycinek puli na workera (4 z 16 połączeń) i jedno połączenie Redis na workeraactix + sqlx, Rustango · actixWspólna pula jest wolniejsza na actix-web (zob. wniosek 6).
sqlx 0.7.4 i hyper 0.14RocketWymuszone przez Rocket 0.5.1.
5, 5 i 7 zapytań na stronę indeksu, szczegółów i tagu zamiast 3, 3 i 4CotJego API zapytań nie potrafi wyrazić zapytań z Go (zob. wniosek 9).
askama zamiast TeraCotSilnik szablonów Cota.

Wyniki

Żądania na sekundę, im więcej, tym lepiej.

Strony bez cache

Ten sam HTML, 50 połączeń, PostgreSQL przy każdym żądaniu

req/s · higher is better
  • Go
  • Rust
  • Rustango
  • Loco
  • Cot
Indeks
Go 8 741 req/s
axum + sqlx 14 140 req/s
actix + sqlx 14 408 req/s
Rocket 13 316 req/s
Rustango · axum 14 686 req/s
Rustango · actix 17 401 req/sfastest
Loco 10 290 req/s
Cot 2 018 req/s
Szczegóły
Go 22 607 req/s
axum + sqlx 22 842 req/s
actix + sqlx 21 630 req/s
Rocket 24 561 req/s
Rustango · axum 25 788 req/s
Rustango · actix 27 292 req/sfastest
Loco 15 635 req/s
Cot 9 282 req/s
Tag
Go 6 386 req/s
axum + sqlx 6 467 req/s
actix + sqlx 6 628 req/s
Rocket 6 836 req/s
Rustango · axum 7 090 req/sfastest
Rustango · actix 7 056 req/s
Loco 5 604 req/s
Cot 3 065 req/s

Mediana z trzech przebiegów po 20 s przy 50 połączeniach. Działała tylko testowana aplikacja. 0 odpowiedzi innych niż 200, 0 błędów.

Strony z cache

Serwowane z cache stron w Redis, bez SQL

req/s · higher is better
  • Go
  • Rust
  • Rustango
  • Loco
  • Cot
Indeks, z cache
Go 91 839 req/s
axum + sqlx 198 669 req/s
actix + sqlx 226 887 req/s
Rocket 138 224 req/s
Rustango · axum 174 501 req/s
Rustango · actix 231 975 req/sfastest
Loco 153 243 req/s
Cot 190 245 req/s
Szczegóły, z cache
Go 141 574 req/s
axum + sqlx 214 871 req/s
actix + sqlx 240 161 req/s
Rocket 155 691 req/s
Rustango · axum 192 897 req/s
Rustango · actix 242 609 req/sfastest
Loco 170 158 req/s
Cot 203 119 req/s

Mediana z trzech przebiegów po 20 s przy 50 połączeniach. Działała tylko testowana aplikacja. 0 odpowiedzi innych niż 200, 0 błędów.

Obliczenia

Liczby pierwsze poniżej 20 000, bez bazy danych

req/s · higher is better
  • Go
  • Rust
  • Rustango
  • Loco
  • Cot
Go 16 580 req/sfastest
axum + sqlx 14 963 req/s
actix + sqlx 14 578 req/s
Rocket 14 696 req/s
Rustango · axum 14 799 req/s
Rustango · actix 14 710 req/s
Loco 14 184 req/s
Cot 15 330 req/s

Mediana z trzech przebiegów po 20 s przy 50 połączeniach. Działała tylko testowana aplikacja. 0 odpowiedzi innych niż 200, 0 błędów.

Opóźnienie, p50 / p99 w ms:

AplikacjaIndeksSzczegółyTagIndeks, z cacheSzczegóły, z cacheObliczenia
Go5.50 / 9.961.91 / 5.027.61 / 11.720.35 / 2.730.24 / 2.052.37 / 10.75
axum + sqlx3.49 / 4.712.14 / 3.607.42 / 13.890.24 / 0.530.22 / 0.513.35 / 6.07
actix + sqlx3.32 / 6.382.21 / 4.307.23 / 13.230.20 / 0.500.19 / 0.453.41 / 4.97
Rocket3.66 / 5.831.97 / 3.587.14 / 11.550.34 / 0.700.30 / 0.673.40 / 6.14
Rustango · axum3.28 / 5.651.83 / 3.556.90 / 11.180.27 / 0.590.25 / 0.523.38 / 6.09
Rustango · actix2.80 / 4.511.77 / 3.376.91 / 11.530.20 / 0.480.19 / 0.453.41 / 3.96
Loco4.62 / 10.842.99 / 6.348.67 / 14.520.30 / 0.790.28 / 0.623.48 / 6.90
Cot23.35 / 50.365.28 / 8.0215.57 / 27.060.25 / 0.500.23 / 0.523.27 / 5.85

Pamięć

Pamięć pod obciążeniem

Pamięć rezydentna kontenera aplikacji na indeksie bez cache

MiB · lower is better
  • Go
  • Rust
  • Rustango
  • Loco
  • Cot
Go 22.8 MiB
axum + sqlx 7.6 MiB
actix + sqlx 7.2 MiB
Rocket 8.7 MiB
Rustango · axum 6.9 MiB
Rustango · actix 6.3 MiBlowest
Loco 12.0 MiB
Cot 22.6 MiB

docker stats, próbka z najwyższym CPU spośród sześciu pobranych podczas pierwszego przebiegu indeksu.

Najwięcej zużywają Go i Cot. Indeks Cota przy każdym żądaniu ładuje wszystkie 1 000 wpisów wraz z treścią (wniosek 9).

Wnioski

Każdy wniosek mówi, co zmierzono, dlaczego tak się dzieje i jak to wykazano. Profile to pojedyncze przebiegi po 25 s na tym samym stanowisku, jedna aplikacja naraz, więc ich req/s nieco różnią się od uczciwego przebiegu powyżej. Jeśli przyczyna nie jest udowodniona, jest to zaznaczone.

1. Obliczenia: Go jest szybsze o 8–17%

Co. Go obsługuje 16 580 req/s na /compute; aplikacje w Rust 14 184–15 330.

Dlaczego. Obie skompilowane pętle mają 8 instrukcji, 64-bitowy sdiv i jedno sprawdzenie dzielenia przez zero. Różnica leży w kolejności instrukcji, którą wybiera LLVM. To, który szczegół mikroarchitektury CPU spowalnia tę kolejność, jest nieudowodnione: maszyna wirtualna Dockera nie ma liczników sprzętowych.

Jak wykazano. Sama pętla, na jednym rdzeniu, poza HTTP:

Pętla liczb pierwszych, jedno wywołanie

Jeden rdzeń, bez HTTP

mikrosekundy · lower is better
  • Go
  • Rust
Go 194 mikrosekundylowest
Rust 224 mikrosekundy
Rust, kolejność instrukcji z Go 194 mikrosekundylowest

Rust z dokładnie taką kolejnością instrukcji jak w Go działa w czasie Go. Reszta każdego żądania jest tańsza w Rust: 8 µs wobec 14 µs w Go.

Opóźnienie. Go ma niższe p50, ale wyższe p99. Średnie opóźnienie to 50 / req/s dla każdej aplikacji, więc średnie idą za przepustowością; różnica tkwi w rozrzucie. Go obsługuje żądania poza kolejnością: p10 0.56 ms, p99 10.75 ms. Aplikacje w Rust obsługują niemal w kolejności przybycia: axum + sqlx p10 1.85 ms, p99 6.07 ms. Dlaczego p99 w Go jest blisko 10 ms, jest nieudowodnione.

2. Strony z cache: garbage collector Go

Co. Na indeksie z cache Go obsługuje 91 839 req/s; axum + sqlx 198 669.

Dlaczego. Go wykonuje na tej trasie 441 odśmiecań na sekundę, a pauzy stop-the-world sumują się do około 10% czasu. Reszta to CPU na żądanie: 27.2 µs w Go wobec 14.6 µs w axum + sqlx.

Jak wykazano. Podniesienie GOGC usuwa większość różnicy:

Go, indeks z cache, dostrojony GC

Przed: domyślne GOGC=100. Przebiegi profilujące.

beforeafter
GOGC=400, req/s 97 289 req/s 144 083 req/s +48%
GC prawie wyłączony, req/s 97 289 req/s 159 734 req/s +64%
GC prawie wyłączony, p99 2.48 ms 0.90 ms −64%

Na co idzie pozostałe CPU, na żądanie na indeksie z cache:

Goaxum + sqlx
CPU aplikacji27.2 µs14.6 µs
Ścieżka zapisu w jądrze10.6 µs6.9 µs
Wywołania systemowe10.7 (6.6 read, 2.9 write)4.8 (2.0 recvfrom, 1.4 writev)
CPU Redisa6.8 µs2.5 µs
Ruch do Redisago-redis: jeden GET na żądaniejedno połączenie, GET-y z wielu żądań zebrane w jednym pipeline

Go: CPU na żądanie z cache

łącznie 27.2 µs

  • 39%Ścieżka zapisu w jądrze, 10.6 µs
  • 61%Cała reszta, 16.6 µs

axum + sqlx: CPU na żądanie z cache

łącznie 14.6 µs

  • 47%Ścieżka zapisu w jądrze, 6.9 µs
  • 53%Cała reszta, 7.7 µs

3. Strona szczegółów: rundy do Postgresa

Co. Strona szczegółów: Go 22 607, axum + sqlx 22 842, Rocket 24 561, Rustango · axum 25 788 req/s. Wszystkie wysyłają te same trzy zapytania.

Dlaczego. Różni się liczba rund do Postgresa. pgx wysyła jedną na zapytanie. sqlx dodaje jedną, gdy połączenie jest pobierane z puli (ping przy pobraniu), i jedną, gdy do niej wraca. axum + sqlx pobiera połączenie na każde zapytanie, więc płaci za każde zapytanie; Rocket i Rustango pobierają jedno połączenie na stronę, więc płacą raz. Każda dodatkowa runda kosztuje około 10 µs CPU Postgresa.

Jak wykazano. Przechwycenie protokołu Postgresa pod obciążeniem i zliczenie komunikatów ReadyForQuery na stronę:

Rundy do Postgresa na stronę szczegółów

Przechwycenie ruchu, te same trzy zapytania; ping = ping sqlx przy pobraniu

rundy · lower is better
  • Go
  • Rustango
  • Rust
Go · pgx 3 rundylowest
Rustango 4 rundy
Rocket 5 rundy
axum + sqlx, ping wyłączony 6 rundy
axum + sqlx, ping włączony 9 rundy

Strona indeksu pokazuje te same liczby.

axum + sqlx: wyłączony ping przy pobraniu

Przebiegi profilujące

beforeafter
Szczegóły, req/s 18 222 req/s 23 472 req/s +29%
Szczegóły, CPU Postgresa 166.6 µs/req 134.3 µs/req −19%
Indeks, req/s 12 140 req/s 14 419 req/s +19%
Indeks, CPU Postgresa 237.9 µs/req 208.5 µs/req −12%

Trzy rundy mniej oszczędzają 29–32 µs CPU Postgresa na stronę: około 10 µs każda.

Go ma najmniej rund, ale tu nie jest najszybsze: jego garbage collector zatrzymuje je na tej trasie przez około 9% czasu.

4. Strona indeksu: GC i renderowanie szablonów

Co. Strona indeksu: Go 8 741 req/s, axum + sqlx 14 140.

Dlaczego. Dwie zmierzone przyczyny. Znowu garbage collector Go (pauzy sumują się do około 8% czasu na tej trasie) oraz renderowanie szablonów: html/template w Go używa refleksji i kosztuje 167 µs na żądanie; serde + Tera w Rust kosztują 72 µs.

Jak wykazano.

Go, indeks bez cache, dostrojony GC

Przed: domyślne GOGC=100. Przebiegi profilujące.

beforeafter
GOGC=400, req/s 9 359 req/s 12 848 req/s +37%
GC prawie wyłączony, req/s 9 359 req/s 13 967 req/s +49%

Renderowanie szablonów, strona indeksu

CPU na żądanie, z profilu CPU

mikrosekundy · lower is better
  • Go
  • Rust
Go · html/template 167 mikrosekundy
Rust · serde + Tera 72 mikrosekundylowest

Na co axum + sqlx zużywa CPU na indeksie, według jego profilu (części się pokrywają: alokacje zachodzą wewnątrz sqlx i Tera):

  • sqlx: około 112 µs na żądanie, z czego około 25 µs to ping, gdy połączenie wraca do puli
  • malloc z glibc: około 56 µs
  • sendto w jądrze: 38 µs

5. Rustango: co zmieniły poprawki

Co. Profile Rustango sprzed tych poprawek pokazały cztery koszty. Trzy są naprawione:

Koszt przedUdział w CPUPoprawka
Pobranie połączenia z puli przy każdym zapytaniu19–35%Jedno połączenie z puli na żądanie: szczegóły +26%
Ping do Postgresa przy każdym pobraniu8–16%Ping domyślnie wyłączony
CachePageLayer przechowuje strony jako JSON + base64~12–20% na trafienie w cacheStrony przechowywane jako surowe bajty
Querysety budowane i kompilowane od nowa przy każdym żądaniu~2%Jeszcze nienaprawione

Rustango · axum, przed → po poprawkach

Przebiegi profilujące, jedna aplikacja naraz

beforeafter
Indeks, req/s 11 215 req/s 15 849 req/s +41%
Szczegóły, req/s 17 267 req/s 28 097 req/s +63%
Indeks, CPU aplikacji 286.6 µs/req 220.8 µs/req −23%
Szczegóły, CPU aplikacji 164.7 µs/req 110.4 µs/req −33%

W uczciwym przebiegu Rustango na actix-web i na axum to dwie najszybsze aplikacje na wszystkich trzech stronach bez cache.

Otwarte. Na indeksie z cache Rustango · axum obsługuje 174 501 req/s wobec 198 669 w axum + sqlx. Tej różnicy jeszcze nie sprofilowano.

6. actix-web: wspólna pula jest wolna

Co. W rundzie 1 aplikacje actix-web zostawały w tyle za axum na stronach bez cache i używały mniej niż 300% ze swojego budżetu 400% CPU.

Dlaczego. Przy jednej wspólnej puli każde zapytanie budzi wątek innego runtime'u. Danie każdemu workerowi actix własnego wycinka puli (4 z 16 połączeń) i własnego połączenia Redis rozwiązuje problem. Mechanizm, czyli to, że gniazdo jest obsługiwane przez runtime, który je otworzył, to wnioskowanie; efekt jest zmierzony.

Jak wykazano. Te same aplikacje, wspólna pula wobec wycinków na workera:

Wspólna pula → jeden wycinek puli na workera

Przebiegi profilujące

beforeafter
actix + sqlx
Indeks 11 094 req/s 15 108 req/s +36%
Szczegóły 18 080 req/s 23 330 req/s +29%
Indeks, z cache 192 604 req/s 238 884 req/s +24%
Rustango · actix
Indeks 12 052 req/s 17 454 req/s +45%
Szczegóły 23 032 req/s 28 693 req/s +25%
Indeks, z cache 190 889 req/s 237 962 req/s +25%
Przełączenia kontekstu na żądanieWspólna pulaPula na workera
actix + sqlx, indeks4.041.45
actix + sqlx, szczegóły3.571.64
Rustango · actix, indeks2.700.81
Rustango · actix, szczegóły2.341.03

Około 2.5× mniej przełączeń kontekstu na żądanie. Więcej workerów nie pomaga: 8 workerów ze wspólną pulą osiąga 11 904 req/s na indeksie actix + sqlx (4 workery, wspólna: 11 094).

7. Rocket: mocny na stronach z bazą, najwolniejszy z cache

Co. Rocket: szczegóły 24 561 req/s, przed Go i aplikacjami z surowym sqlx; indeks z cache 138 224, najwolniejszy wynik w Rust.

Dlaczego. Na stronach z bazą Rocket pobiera jedno połączenie na stronę i wykonuje 5 rund (wniosek 3). Na stronach z cache zużywa 24.1 µs CPU na żądanie wobec 14.6 µs w axum + sqlx, przy 8.1 wywołaniach systemowych na żądanie wobec 4.8. Profil pokazuje dodatkowy writev (2.24 na żądanie wobec 1.36) i rywalizację w malloc; wywołań futex jest 2.88 na żądanie wobec 0.91.

Jak wykazano. Rozliczenie CPU, liczniki wywołań systemowych i profile CPU na indeksie z cache.

8. Loco

Co. Loco: indeks 10 290, szczegóły 15 635, indeks z cache 153 243 req/s.

Dlaczego. SeaORM wysyła tyle samo zapytań co Go. Na stronach z cache Loco zużywa 21.0 µs na żądanie: o 2.4 µs więcej w malloc i o 1.2 µs więcej w middleware niż axum + sqlx. Na stronach z bazą pingu przy pobraniu nie da się wyłączyć (wniosek 3 pokazuje jego koszt w sqlx).

Jak wykazano. Rozliczenie CPU i profile CPU na indeksie z cache. Stron Loco z bazą nie profilowano, więc to, jaką część tej różnicy wyjaśnia ping, jest nieudowodnione.

9. Cot: granicę wyznacza API zapytań

Co. Cot: indeks 2 018, szczegóły 9 282, tag 3 065 req/s, ale indeks z cache 190 245.

Dlaczego. API zapytań Cota ma tylko filter, limit i offset: brak ORDER BY, JOIN, IN i COUNT. Aby pokazać 20 najnowszych wpisów, indeks ładuje wszystkie 1 000 wpisów wraz z treścią. Strony potrzebują 5, 5 i 7 zapytań zamiast 3, 3 i 4. Pamięć pod obciążeniem to 22.6 MiB. Strony z cache nie dotykają ORM i są szybkie: 16.4 µs CPU na żądanie.

Jak wykazano. Liczba zapytań z kodu, pamięć z uczciwego przebiegu, rozliczenie CPU na indeksie z cache.

10. Pamięć

Zob. wykres pamięci powyżej. Sześć aplikacji w Rust używa 6.3–12.0 MiB pod obciążeniem. Go używa 22.8 MiB. Cot używa 22.6 MiB; jego indeks ładuje każdy wpis przy każdym żądaniu (wniosek 9).

11. Wspólne koszty

Dwa koszty pojawiają się w każdej aplikacji w Rust i nie należą do żadnego frameworka:

  • Renderowanie Tera: około 18–20% CPU na stronach bez cache
  • malloc z glibc: około 12%

Szybszy alokator, taki jak mimalloc lub jemalloc, mógłby zmniejszyć ten drugi. To nieprzetestowane.

Ograniczenia

  • Jedna maszyna: Apple M3 Pro. Inne procesory mogą ułożyć wyniki inaczej, zwłaszcza w obliczeniach (wniosek 1).
  • Obciążenie zdominowane przez odczyty: bez zapisów, bez transakcji.
  • Aplikacje działają w maszynie wirtualnej Dockera na macOS, nie na Linuksie bare-metal. Maszyna wirtualna nie ma sprzętowych liczników wydajności.

Podsumowanie: dlaczego Rustango na actix-web stało się dużo szybsze

Rustango · actix, strona indeksu

Trzy przebiegi: runda 1, przebieg profilujący i uczciwy przebieg tej rundy

req/s · higher is better
Runda 1 (0.60.6) 6 341 req/s
Poprawki, wspólna pula 12 052 req/s
Poprawki, pula na workera 17 401 req/sfastest

Dwie zmiany, w dwóch miejscach.

1. Poprawki w Rustango. Pomagają zarówno na axum, jak i na actix-web:

  • Każda kolumna jest dekodowana raz, według swojego typu. Wcześniej dekodowanie zajmowało 46% CPU.
  • Brak pingu przed każdym pobraniem z puli: jedna runda do Postgresa mniej na zapytanie.
  • Jedno połączenie z puli na żądanie: zapytania jednej strony dzielą jedno połączenie, zamiast każde osobno pobierać połączenie i pingować przy zwrocie.

2. Konfiguracja actix-web. axum uruchamia jeden wspólny, wielowątkowy runtime Tokio. actix-web uruchamia jednowątkowy runtime na każdego workera. Przy jednej puli wspólnej dla wszystkich workerów gniazdo połączenia pozostaje obsługiwane przez runtime, który je otworzył, więc gdy inny worker używa tego połączenia, każdy odczyt i zapis budzi tamten wątek. Aplikacja wykonuje wtedy 2.5× więcej przełączeń kontekstu i używa około 280% ze swoich 4 CPU. Jeden wycinek puli (4 z 16 połączeń) i jedno połączenie Redis na workera usuwają te przeskoki między wątkami:

  • Rustango · actix: indeks 12 052 → 17 454, szczegóły 23 032 → 28 693, indeks z cache 190 889 → 237 962 req/s
  • actix + sqlx: indeks 11 094 → 15 108 req/s
  • 8 workerów ze wspólną pulą nie pomogło: 11 904 req/s na indeksie actix + sqlx

Przyspieszenie i spadek liczby przełączeń kontekstu są zmierzone. Własność gniazd to najlepsze wyjaśnienie, ale nie jest udowodnione bezpośrednio. axum nie ma tego problemu, bo wszystkie jego zadania działają w jednym wspólnym runtime.

W praktyce: na actix-web twórz pulę połączeń z bazą w każdym workerze, a nie raz dla wszystkich workerów.

Kontakt →