Z nginx sendfile oraz tcp_nopush Przesyłam pliki statyczne metodą „zero-copy” z systemu plików do gniazda, co pozwala zauważalnie zmniejszyć obciążenie procesora oraz liczbę pakietów. Przy prawidłowych ustawieniach obie dyrektywy zwiększają wydajność transmisji, zmniejszają obciążenie systemowe i stanowią podstawę do prawidłowej optymalizacji serwera nginx w zakresie zasobów i plików do pobrania.
Punkty centralne
- Zero-Copy za pomocą funkcji sendfile: mniej kopii, większa przepustowość
- tcp_nopush buforuje pakiety: większe ramki, mniejsze obciążenie
- Połączenie liczy się: sendfile + tcp_nopush + tcp_nodelay
- Przypadki użycia ustalić priorytety: zasoby statyczne, duże pliki do pobrania
- Testy w przypadku NFS/SMB: zmierzyć wpływ, w razie potrzeby wyłączyć sendfile
Dlaczego funkcja `sendfile` zapewnia tak dużą wydajność w serwerze NGINX
Aktywuję sendfile, ponieważ jądro może wysyłać pliki bezpośrednio przez stos sieciowy, bez konieczności wykonywania dodatkowych operacji kopiowania w przestrzeni użytkownika. Ta ścieżka typu „zero-copy” ogranicza zmiany kontekstu i oszczędza cykle procesora, zwłaszcza gdy wielu klientów jednocześnie pobiera treści statyczne. Korzyści odnoszą duże pliki, takie jak obrazy, CSS, JavaScript czy archiwa, ponieważ transfer danych przebiega bardziej płynnie i przy mniejszym obciążeniu. Również pamięci podręczne systemu działają wydajniej, ponieważ występuje mniej operacji pamięciowych, a jądro kontroluje ścieżkę danych. Korzyści są najbardziej widoczne w lokalnych systemach plików, dlatego najpierw dokonuję pomiarów właśnie tam, zanim przejdę do bardziej nietypowych konfiguracji.
Czym dokładnie zajmuje się tcp_nopush i kiedy najlepiej się sprawdza
Z tcp_nopush Proszę system, aby wysyłał pakiety TCP dopiero wtedy, gdy są one odpowiednio wypełnione, zamiast wysyłać zbyt wcześnie małe segmenty. W systemie Linux odpowiada to opcji TCP_CORK, w systemie FreeBSD – TCP_NOPUSH, a w obu przypadkach liczba pakietów zmniejsza się w sposób wymierny. Dyrektywa ta nie sprowadza opóźnienia do minimum, ale ma na celu uzyskanie lepszego stosunku danych użytkowych do obciążenia dodatkowego. Celowo stosuję tcp_nopush w przypadku plików statycznych, ponieważ właśnie tam spójne strumienie danych zapewniają największy wzrost wydajności. Bez sendfile tcp_nopush pozostaje bezskuteczny, dlatego zawsze włączam oba ustawienia razem.
sendfile i tcp_nopush w parze: tak ustalam podstawy
Połączenie sendfile a opcja tcp_nopush ogranicza liczbę kopii i grupuje pakiety, dzięki czemu jeden serwer na rdzeń procesora może obsłużyć znacznie więcej równoległych transferów. Konfiguruję obie te opcje na poziomie kontekstu http i często dodaję jeszcze tcp_nodelay, aby ostatnia część strumienia mogła przepłynąć bez opóźnień. Ważne jest przeprowadzanie testów z wykorzystaniem rzeczywistego ruchu, ponieważ rozmiary pakietów, MTU i klienci różnią się, a optymalna równowaga może się nieznacznie zmieniać w zależności od obciążenia. W przypadku katalogów statycznych zazwyczaj wystarcza aktywacja globalna, natomiast w przypadku dynamicznych tras odpowiedzi zwracam uwagę na skutki tej konfiguracji. Takie połączenie stanowi solidną podstawę dla dalszych kroków optymalizacji nginx, które zostaną wprowadzone w późniejszym etapie.
| dyrektywa | Cel | Typowe działanie | Uzależnienie |
|---|---|---|---|
| włącz sendfile | Zero-Copy z pliku do gniazda | Mniejsze obciążenie procesora, większa przepustowość | Lokalny system plików – idealne rozwiązanie |
| tcp_nopush włączone | Wypełniać paczki, obniżać koszty ogólne | Mniejsza liczba segmentów na plik | Działa tylko z funkcją `sendfile` |
| tcp_nodelay włączone | Wysyłanie ostatnich bajtów bez oczekiwania | Szybkie zakończenie transferu | Dodano tcp_nopush |
W ten sposób tcp_nodelay współdziała z tcp_nopush
Aktywuję tcp_nopush, aby wysłać początek transferu w większych pakietach, a jednocześnie włączyć opcję tcp_nodelay, aby zakończenie transferu nie utknęło. Oba ustawienia oddziałują na różne fazy przepływu danych i nie kolidują ze sobą, gdy NGINX dostarcza pliki za pomocą funkcji sendfile. Szczególnie w przypadku wielu małych plików opcja tcp_nodelay zapobiega niepotrzebnemu oczekiwaniu klienta z powodu niewielkiej ilości pozostałych danych. Najpierw testuję tę kombinację w środowisku stagingowym, obserwuję czasy RTT i rozmiary segmentów, a następnie porównuję je z metrykami z środowiska produkcyjnego. W ten sposób zapewniam wydajność na początku i szybkość na końcu transferu.
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
}
Typowe scenariusze zastosowania: sytuacje, w których dyrektywy mają silny wpływ
Dla dużych Pliki do pobrania W przypadku plików takich jak filmy, archiwa czy obrazy ISO ścieżka „zero-copy” jądra znacznie skraca czas przetwarzania przez procesor na każdy transfer. W konfiguracjach podobnych do CDN, zawierających wiele plików CSS, JS i czcionek, funkcja tcp_nopush pozwala zaoszczędzić segmenty, zwiększając w ten sposób wykorzystywaną przepustowość na każdy gniazd. Na stronach WordPress z dobrze zoptymalizowanym buforowaniem większość żądań dotyczy zasobów statycznych, dlatego efekt ten widać tam bardzo szybko. Korzyści odnoszą również artefakty kompilacji, obrazy kontenerów lub instalatory, o ile znajdują się lokalnie, a nie są dostarczane przez niestabilny system plików sieciowy. Kto spodziewa się szczytów obciążenia, dzięki tej parze rozwiązań uzyska dużą stabilność z dostępnego sprzętu.
Przykład praktyczny: NGINX dla WordPressa z buforowaniem i zasobami
W konfiguracjach WordPressa ustawiam sendfile, tcp_nopush i tcp_nodelay ustawione globalnie, zasoby statyczne dostarczam bezpośrednio, a PHP-FPM dla ścieżek dynamicznych utrzymuję w całkowitej separacji. Dodaję sensowne nagłówki pamięci podręcznej dla obrazów, arkuszy CSS i skryptów JavaScript, aby przeglądarki generowały mniej cykli wymiany danych. W przypadku dostarczania odpowiedzi typu strumieniowego zwracam uwagę na interakcję z buforowaniem i testuję, jak rozmiary fragmentów wpływają na opóźnienie i przepustowość; w tym kontekście przydatny jest przegląd dotyczący Strumieniowe przesyłanie odpowiedzi w częściach. W przypadku treści tekstowych stosuję kompresję, unikając jednak niepotrzebnego kompresowania plików binarnych. Dzięki temu przepływ żądań pozostaje stabilny, obciążenie procesora jest niewielkie, a czas do pierwszego bajtu (Time-to-First-Byte) jest krótki.
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
gzip_types text/css application/javascript image/svg+xml;
server {
listen 80;
server_name blog.example.com;
root /var/www/blog;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg|woff2?)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
}
}
Kiedy celowo wyłączam funkcję `sendfile`
Przełączam sendfile gdy pliki znajdują się w systemach NFS, SMB lub rozproszonych systemach plików, które w moich testach wykazały gorszą przepustowość. Niektóre sterowniki lub opóźnienia w ścieżce pamięci masowej niwelują korzyści płynące z technologii Zero-Copy, dlatego decydujące znaczenie mają pomiary. W przypadku sporadycznych anomalii sieciowych najpierw wyłączam tcp_nopush, aby ograniczyć skutki, zanim zacznę kwestionować samą funkcję sendfile. Również nietypowe błędy jądra lub starsze stosy mogą być powodem tymczasowego przejścia na klasyczną ścieżkę odczytu i zapisu. Ważne jest, aby wprowadzanie zmian odbywało się stopniowo i było poparte danymi pomiarowymi.
Źródła błędów, na które zwracam uwagę
Najpierw sprawdzam, czy tcp_nopush jest przypadkowo włączona, podczas gdy sendfile pozostaje wyłączone, ponieważ w takim przypadku to ustawienie nie przynosi żadnego efektu. W przypadku ścieżek dynamicznych obserwuję, czy dodatkowe buforowanie zwiększa opóźnienie, i rozważam korzyści w stosunku do czasu reakcji. W sieciach o wysokim opóźnieniu sprawdzam, czy większe pakiety rzeczywiście pomagają, czy też muszę dopracować rozmiary segmentów i ustawienia Keep-Alive. Również konfiguracja MTU oraz funkcje odciążania karty sieciowej mogą wyraźnie wpłynąć na wynik. Przejrzyste logi, próbki pcap oraz skorelowane metryki systemowe szybko wskazują mi, gdzie należy wprowadzić korekty.
Kompleksowe podejście do wydajności NGINX: kolejne elementy regulacyjne
Oprócz sendfile Opłaca się ustawić odpowiednią liczbę worker_processes i worker_connections, aby nie ograniczać sztucznie liczby gniazd. W systemie Linux korzystam z epoll i dbam o wystarczającą liczbę deskryptorów plików, aby szczyty obciążenia nie powodowały wąskich gardeł. W przypadku treści tekstowych włączam gzip lub Brotli i sprawdzam, czy poziom kompresji nie obciąża nadmiernie procesora. Na poziomie transportu utrzymuję połączenia otwarte przez dłuższy czas i optymalizuję Keep-Alive, co opisano w przewodniku Optymalizacja Keep-Alive dostarcza praktycznych wskazówek. Protokół TLS, ponowne wykorzystanie sesji oraz HTTP/2 lub HTTP/3 dopełniają tę konfigurację i zapewniają wysoki poziom równoległości przy umiarkowanym opóźnieniu.
Ograniczenia i przypadki szczególne: TLS, HTTP/2/3 i proxy
Biorę pod uwagę, że sendfile technicznie dotyczy to wyłącznie niezaszyfrowanych ścieżek do plików lub specjalnych funkcji jądra. W przypadku klasycznego TLS NGINX szyfruje bajty w przestrzeni użytkownika, co powoduje utratę korzyści wynikających z technologii zero-copy; nowoczesne jądra mogą częściowo przenieść szyfrowanie do jądra, co przywraca ten efekt, ale nie jest to dostępne w każdej konfiguracji. W przypadku HTTP/2 dane są przechowywane w ramkach, kilka odpowiedzi współdzieli jedno połączenie TCP, a NGINX aktywnie pakuje bajty – w tym przypadku sendfile ma mniejsze znaczenie. HTTP/3 opiera się na protokole UDP/QUIC i znów działa według innych zasad, więc wzrost wydajności osiągam raczej dzięki buforom, kontroli przeciążenia i odpowiednio dobranym rozmiarom fragmentów. Jako Odwrotne proxy funkcja sendfile działa tylko wtedy, gdy faktycznie udostępniam pliki z lokalnego systemu plików; odpowiedzi z proxy_pass lub fastcgi_pass i tak przechodzą przez przestrzeń użytkownika. Dlatego ściśle oddzielam zasoby od ścieżki dynamicznej, aby w jak największym stopniu wykorzystać metodę „zero-copy”.
Właściwe zrozumienie kompresji: gzip/Brotli a gzip_static
Gdy NGINX kompresuje treści w locie, musi odczytać plik, przetworzyć go i zapisać wynik – przy czym traci sendfile swoją zaletę. W przypadku zasobów statycznych korzystam więc, tam gdzie to możliwe, wstępnie skompresowane Pliki (np. .gz lub .br) i przekazuję je bezpośrednio do dostarczenia. W ten sposób zachowana zostaje ścieżka „zero-copy”, ponieważ NGINX może przekazać wstępnie skompresowany plik tak samo jak każdy inny zasób. W przypadku treści tekstowych, rzadko modyfikowanych, osiągam w ten sposób oszczędność mocy obliczeniowej procesora oraz stabilną przepustowość bez utraty czasu transmisji. W przypadku plików binarnych i formatów już skompresowanych unikam kompresji w czasie wykonywania – tutaj liczy się wyłącznie przepustowość operacji wejścia/wyjścia, a funkcje `sendfile` oraz `tcp_nopush` w pełni wykorzystują swoje zalety.
AIO, directio i Page Cache: wzorce dla małych i dużych plików
Łączę sendfile z asynchronicznym wejściem/wyjściem i bezpośrednim dostępem do dysku, aby osiągnąć optymalną wydajność w zależności od rozmiaru pliku. Małe i średnie pliki korzystają z pamięci podręcznej stron jądra i pozostają na ścieżce sendfile. Natomiast bardzo duże pliki mogą wyprzeć tę pamięć podręczną; w takim przypadku odczytuję je celowo za pomocą kierunek poza pamięcią podręczną i korzystam z wątków AIO. Dzięki temu odciążam pamięć i utrzymuję niskie opóźnienia dla innych żądań. Typowy wzorzec wygląda następująco:
http {
# Ścieżka standardowa: Zero-Copy z pamięci podręcznej stron
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Duże pliki: pomijanie pamięci podręcznej i odczyt asynchroniczny
aio threads;
directio 4m; # działa tylko dla plików >= 4 MiB
output_buffers 1 512k; # Bufory dla ścieżek directio
sendfile_max_chunk 1m; # Równowaga obciążenia przy wysokim obciążeniu
}
Dzięki takiemu rozłożeniu małe zasoby pozostają niezwykle wydajne, podczas gdy bardzo duże transfery nie przeciążają pamięci operacyjnej. Ważne: directio wyłącza ścieżkę sendfile dla odpowiednich plików – dokładnie tak, jak zamierzałem w przypadku obsługi dużych plików.
Sprawiedliwość i kontrola przepływu pod obciążeniem
W okresach dużego obciążenia chcę uniknąć sytuacji, w której pojedynczy strumień monopolizuje procesor lub gniazdo. Ustawiam sendfile_max_chunk, aby NGINX zwracał jądro po osiągnięciu określonej liczby bajtów i pozostawił miejsce dla innych połączeń. W zakresie zarządzania przepustowością pomocne są limit_rate oraz limit_rate_after, na przykład w celu ograniczenia prędkości pobierania plików zbiorczych, podczas gdy zasoby interfejsu użytkownika działają sprawnie. Dzięki odłóż_wyjście ustalam, od jakiej wielkości odpowiedzi NGINX rozpocznie wysyłanie – w połączeniu z opcją tcp_nopush zapewniam w ten sposób prawidłowe dzielenie pakietów. Ponadto zwracam uwagę na lingering_close, aby pozostałe pakiety mogły zostać poprawnie przesłane, a gniazdo nie zostało nagle zamknięte.
Systemy plików, odczyt z wyprzedzeniem i ścieżki dostępu do pamięci masowej
Ponieważ sendfile W przypadku korzystania z pamięci podręcznej stron dużą rolę odgrywa system plików, na którym opiera się aplikacja. Sprawdzam wartości odczytu z wyprzedzeniem (Readahead) i ustawiam je tak, aby sekwencyjne operacje odczytu dużych plików nie ulegały spowolnieniu, nie wypierając przy tym mniejszych zasobów. Na ext4 lub xfs Obserwuję, jak dobrze prefetching i harmonogram I/O współgrają z moim wzorcem przepustowości. W przypadku systemów plików sieciowych (NFS/SMB) rygorystycznie testuję parametry rsize/wsize, buforowanie i opóźnienia, ponieważ nawet niewielkie odchylenia neutralizują korzyści płynące z technologii zero-copy. Moja zasada pozostaje niezmienna: najpierw maksymalnie wykorzystać ścieżki lokalne, a dopiero potem ostrożnie dostosowywać stosy zewnętrzne – i zawsze przedkładać wyniki pomiarów nad intuicję.
Pragmatyczne dostosowanie stosu sieciowego i odciążania karty sieciowej
W przypadku dużej liczby połączeń polegam na automatycznej regulacji buforów w nowoczesnych stosach, ale w razie potrzeby dostosowuję bufory wysyłania i odbierania. Funkcje odciążania karty sieciowej, takie jak TSO, GSO i GRO, zauważalnie zmniejszają obciążenie procesora; podczas pomiarów zachowuję jednak ostrożność, ponieważ przechwycone pakiety mogą być zafałszowane przez proces odciążania (pozornie niewielka liczba bardzo dużych segmentów). Dlatego koreluję pcap‑Traces z metrykami z NGINX i jądra, aby oddzielić rzeczywiste rozmiary pakietów od artefaktów związanych z offloadem. W przypadku skoków opóźnień na chwilę przerywam testy, wyłączając offload, dokumentuję różnicę, a następnie decyduję, co przyniesie większe korzyści w trybie ciągłej pracy.
Szablony konfiguracyjne dla poszczególnych lokalizacji: selektywne włączanie i wyłączanie
Zostawiam sobie możliwość, sendfile w zależności od ścieżki lub typu pliku. W przypadku katalogów statycznych pozostaje włączona, natomiast w przypadku ścieżek strumieniowych lub dynamicznych wyłączam ją selektywnie, gdy priorytet mają bufory lub filtry (np. kompresja). Krótki przykład:
server {
listen 80;
server_name static.example.com;
root /var/www/static;
# Zasoby statyczne: Zero-Copy
location /assets/ {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
expires 7d;
}
# Treści dynamiczne lub strumieniowanie: elastyczność przed trybem Zero-Copy
location /api/ {
sendfile off;
proxy_pass http://app_upstream;
}
}
To rozdzielenie zapobiega utracie korzyści z jednej strony tylko dlatego, że inna ścieżka stawia szczególne wymagania.
Zakresy, wycinki i duże katalogi
W przypadku dużych obiektów Zasięg-Wykorzystuję zalety żądań: klient pobiera tylko niezbędne fragmenty, a połączenia pozostają stabilne. W katalogach treści zawierających bardzo duże pliki chętnie dzielę transfery na logiczne segmenty – obciążenie serwera rozkłada się bardziej równomiernie, a sytuacje awaryjne, takie jak przerwania, pochłaniają mniej czasu. W scenariuszach buforowania zapobiegam zjawisku „Thundering Herds“, sensownie buforując odpowiedzi, ale nie zatrzymując sztucznie małych fragmentów i pozostałych danych. Kluczową rolę odgrywa tu współdziałanie z tcp_nopush: utrzymuję duże segmenty początkowe, ale nie pozwalam, by segmenty końcowe czekały.
Strategia pomiarowa i testowa: wiarygodne wykazanie skutków
Optymalizacje popieram testami, których wyniki da się powtórzyć. Po stronie serwera analizuję profile wykorzystania procesora, $request_time, 1 TP 4 Tbajtów wysłanych, aktywne połączenia i zmiany kontekstu. W sieci mierzę rozmiary segmentów, retransmisje i rozkład RTT; koreluję przechwycone pakiety ze statystykami gniazd, aby uwzględnić efekty odciążenia. Po stronie klienta porównuję TTFB, First Contentful Paint oraz czasy pobierania przy realistycznych wartościach RTT i przepustowościach. Zmieniam MTU, ustawienia Keep-Alive oraz rozmiary plików, aby nie patrzeć wyłącznie na krzywe w najlepszym scenariuszu. Na koniec, na podstawie twardych danych liczbowych, oceniam, czy sendfile/tcp_nopush zapewniają pożądaną stabilność i wydajność w danym obciążeniu – i dopracowuję ustawienia, aż tak się stanie.
Szczegóły protokołu HTTP, które mają znaczenie: Range i streaming
Używam Zasięg-Żądania dotyczące dużych plików, dzięki czemu klienci pobierają tylko niezbędne fragmenty, a połączenia pozostają stabilne. Zwłaszcza w przypadku przeskakiwania w filmach i wznawiania pobierania prawidłowa obsługa zakresów bajtów pomaga w racjonalnym rozłożeniu przepustowości; dodatkowe informacje można znaleźć na stronie poświęconej Żądania HTTP typu Range. W przypadku ciągłych odpowiedzi z rosnącą treścią testuję strategie strumieniowania i dbam o to, by bufory nie przechowywały danych nieumyślnie zbyt długo. Przy tym uwzględniam pamięć podręczną i ustawiam odpowiednie nagłówki, aby serwery proxy i przeglądarki działały poprawnie. Zwracam uwagę na interakcję z tcp_nopush, ponieważ rozmiary pakietów i czas wykonania operacji flush mają bezpośredni wpływ na postrzeganą szybkość działania.
Krótkie podsumowanie
Z sendfile W ten sposób efektywnie przekazuję pliki bezpośrednio do jądra, a dzięki tcp_nopush zapewniam sensowne wypełnienie pakietów, zanim zaczną obciążać łącze. Obie dyrektywy wzajemnie się uzupełniają, podczas gdy tcp_nodelay dostarcza ostatni bajt bez opóźnienia. Sprawdzam działanie w warunkach rzeczywistego ruchu, zwracam uwagę na ścieżkę przechowywania, MTU, Keep-Alive i kompresję oraz konsekwentnie dokonuję pomiarów. W przypadku obciążeń typu WordPress i CDN korzyści są widoczne szczególnie szybko, ponieważ wiele żądań dotyczy zasobów statycznych. Kto celowo wykorzystuje te ustawienia, osiąga większą przepustowość na rdzeń, zmniejsza obciążenie systemowe i tworzy rezerwy na rzeczywiste szczyty obciążenia.


