NGINX: Maksymalna ilość równoczesnych połączeń

17 Feb 2020

best-practices  http  nginx 

Share on:

Zastanawiałeś się kiedyś, ile maksymalnie równoczesnych połączeń jest w stanie obsłużyć serwer NGINX? A także jaki wpływ na ich ilość mają dyrektywy worker_processes, worker_connections oraz worker_rlimit_nofile?

Spójrz na poniższe równanie:

worker_processes * worker_connections = max connections

Zgodnie z tym: jeśli uruchomisz 4 procesy robocze (workery) z ustawioną wartością 4096 połączeń na proces roboczy, będziesz w stanie obsłużyć maksymalnie 16 384 połączeń. Oczywiście ustawienia te są ograniczone przez jądro (maksymalna liczba połączeń, maksymalna liczba otwartych plików lub maksymalna liczba procesów).

W tym miejscu polecam przeczytać świetny artykuł: Understanding socket and port in TCP, w celu pełniejszego zrozumienia gniazd i portów TCP. Warto także zaznajomić się z tym wyjaśnieniem opisującym maksymalną liczbę otwartych połączeń TCP, jakie może utworzyć system GNU/Linux.

Jednak czy powyższe równanie w sposób definitywny określa maksymalną liczbę połączeń? Tak, jednak jest pewna rzecz warta wyjaśnienia. W wielu artykułach dostępnych w Internecie widziałem, jak niektórzy administratorzy tłumaczą sumę wartości dyrektyw worker_processes oraz worker_connections bezpośrednio na maksymalną liczbę klientów, którzy mogą być obsługiwani jednocześnie.

Moim zdaniem jest to duży błąd, ponieważ niektórzy klienci (np. przeglądarki) otwierają szereg równoległych połączeń (na potwierdzenie moich słów, zerknij na to krótkie wyjaśnienie). Klienci zwykle ustanawiają od 4 do 8 połączeń TCP, aby równolegle pobierać zasoby (aby pobierać różne komponenty strony internetowej, na przykład obrazki, skrypty itp.). Takie zachowanie zwiększa efektywną przepustowość i zmniejsza opóźnienia.

Jest to limit dla protokołu HTTP/1.1, który zgodnie z RFC wynosi 6-8 równoczesnych połączeń. Najlepszym rozwiązaniem w celu poprawy wydajności (bez aktualizacji sprzętu i użycia pamięci podręcznej między klientem a aplikacją, tj. CDN, Varnish) jest użycie protokołu w wersji HTTP/2 (RFC 7540 [IETF]) zamiast HTTP/1.1. Jak wiemy, protokół HTTP/2 multipleksuje wiele żądań w jednym połączeniu i nie ma on standardowego limitu ilości połączeń, jednak definiuje ich ilość w następujący sposób: “It is recommended that this value (SETTINGS_MAX_CONCURRENT_STREAMS) be no smaller than 100” (zgodnie z RFC 7540).

Ponadto należy wiedzieć, że dyrektywa worker_connections obejmuje wszystkie połączenia na proces roboczy, tj. połączenia do gniazd nasłuchiwania, połączenia wewnętrznej komunikacji między procesami, połączenia z serwerami proxy i połączenia z serwerami warstwy backend’u, a nie tylko połączenia przychodzące od klientów.

Ciekawostka: Każde połączenie obsługiwane przez proces roboczy, który jest w stanie uśpienia, potrzebuje 256 bajtów pamięci.

Liczba połączeń jest szczególnie ograniczona przez maksymalną ilość plików, która może zostać otwarta (RLIMIT_NOFILE w systemach GNU/Linux).

O deskryptorach plików oraz uchwytach plików możesz poczytaj tutaj oraz tutaj. W tym artykule będę stosował zamiennie oba terminy.

Powodem takiego zachowania jest to, że system operacyjny potrzebuje pamięci do zarządzania każdym otwartym deskryptorem pliku, a jak wiemy, pamięć jest zasobem, który można bardzo szybko wysycić. Oczywiście powyższe ograniczenie wpływa tylko na limity dla bieżącego procesu. Granice bieżącego procesu są również przekazywane procesom potomnym, jednak każdy proces ma wartość niezależną od procesu głównego.

Aby zmienić limit maksymalnych deskryptorów plików (które mogą być otwarte przez pojedynczy proces roboczy), możesz również edytować dyrektywę worker_rlimit_nofile. Dzięki temu NGINX zapewnia bardzo potężne możliwości dynamicznej konfiguracji bez ponownego uruchamiania serwera.

Maksymalna ilość otwartych deskryptorów plików nie jest jedynym ograniczeniem liczby połączeń — pamiętaj także o parametrach jądra dotyczących sieci (stosu TCP/IP), maksymalnej liczbie procesów a także przepustowości sieci i wydajności samej maszyny, na której działa NGINX.

Jeżeli chodzi o jasne wskazanie maksymalnej ilości otwartych deskryptorów plików, oficjalna dokumentacja jest tutaj bardzo niejednoznaczna. Mówi ona jedynie, że worker_rlimit_nofile jest ograniczeniem maksymalnej liczby otwartych plików dla procesów roboczych. Uważam, że jest to związane z jednym procesem roboczym, a nie ze wszystkimi.

Jeśli ustawisz RLIMIT_NOFILE na 25 000, a worker_rlimit_nofile na 12 000, NGINX ustawia (tylko dla procesów roboczych) maksymalny limit otwartych plików jako wartość dyrektywy worker_rlimit_nofile — czyli 12 000. Jednak proces główny będzie miał nadal ustawioną wartość określoną za pomocą RLIMIT_NOFILE. Domyślnie worker_rlimit_nofile nie jest ustawiona, więc NGINX ustawia wartość początkową maksymalnej liczby otwartych plików na podstawie limitów systemowych.

Przykład:

# Dla GNU/Linux (lub /usr/lib/systemd/system/nginx.service):
grep "LimitNOFILE" /lib/systemd/system/nginx.service
LimitNOFILE=5000

grep "worker_rlimit_nofile" /etc/nginx/nginx.conf
worker_rlimit_nofile 256;

   PID       SOFT HARD
 24430       5000 5000
 24431        256 256
 24432        256 256
 24433        256 256
 24434        256 256

Moim zdaniem poleganie na wartości RLIMIT_NOFILE (i alternatywach w innych systemach) jest bardziej zrozumiałe i przewidywalne, ponieważ jest to jakby limit graniczny. Szczerze mówiąc, tak naprawdę nie ma znaczenia, który sposób wybierzesz, jednak należy zawsze pamiętać o tym, jaki priorytet ma każde z rozwiązań i gdzie leżą ograniczenia każdego z nich.

Jeśli nie ustawisz dyrektywy worker_rlimit_nofile, ilość deskryptorów plików używanych przez NGINX będzie określona ustawieniami systemu operacyjnego.

Swoją drogą, prawdopodobieństwo wyczerpania się deskryptorów plików jest minimalne, jednak może być dużym problemem przy obsłudze naprawdę sporego ruchu. Zależy także od rodzaju i ilości pozostałych procesów działających na serwerze.

Podsumowując, ile maksymalnie deskryptorów plików może otworzyć NGINX? Może otworzyć/używać do dwóch deskryptorów plików na pełne połączenie:

Spójrz na poniższe przykłady:

1) Jeden uchwyt do połączenia z klientem i jeden uchwyt do obsługi plików, w tym wypadku dla pliku statycznego serwowanego bezpośrednio przez NGINX:

# 1 połączenie, 2 uchwyty plików

                     +-----------------+
+----------+         |                 |
|          |    1    |                 |
|  CLIENT <---------------> NGINX      |
|          |         |        ^        |
+----------+         |        |        |
                     |      2 |        |
                     |        |        |
                     |        |        |
                     | +------v------+ |
                     | | STATIC FILE | |
                     | +-------------+ |
                     +-----------------+

2) Jeden uchwyt do obsługi połączenia z klientem i jeden uchwyt dla otwartego gniazda zdalnego lub lokalnego hosta/procesu:

# 2 połączenia, 2 uchwyty plików

                     +-----------------+
+----------+         |                 |         +-----------+
|          |    1    |                 |    2    |           |
|  CLIENT <---------------> NGINX <---------------> BACKEND  |
|          |         |                 |         |           |
+----------+         |                 |         +-----------+
                     +-----------------+

3) Dwa uchwyty dla dwóch jednoczesnych połączeń od tego samego klienta (1, 4), jeden uchwyt dla połączenia z innym klientem (3), dwa uchwyty dla plików statycznych (2, 5) i jeden uchwyt dla otwartego gniazda do zdalnego lub lokalnego hosta/procesu:

# 4 połączenia, 6 uchwytów plików

                  4
      +-----------------------+
      |              +--------|--------+
+-----v----+         |        |        |
|          |    1    |        v        |  6
|  CLIENT <-----+---------> NGINX <---------------+
|          |    |    |        ^        |    +-----v-----+
+----------+    |    |        |        |    |           |
              3 |    |      2 | 5      |    |  BACKEND  |
+----------+    |    |        |        |    |           |
|          |    |    |        |        |    +-----------+
|  CLIENT  <----+    | +------v------+ |
|          |         | | STATIC FILE | |
+----------+         | +-------------+ |
                     +-----------------+

W dwóch pierwszych przykładach: możemy przyjąć, że NGINX potrzebuje dwóch deskryptorów plików do obsługi pełnego połączenia (i dla każdego używa dwóch połączeń). W trzecim przykładzie NGINX nadal potrzebuje dwóch uchwytów dla każdego pełnego połączenia (także, jeśli klient korzysta z połączeń równoległych).

Zgodnie z powyższym, uważam, że poprawna wartość dyrektywy worker_rlimit_nofile powinna być większa niż wartość dyrektywy worker_connections.

Moim zdaniem bezpieczna wartość worker_rlimit_nofile (i limitów systemowych) to:

# Jeden uchwyt dla jednego połączenia:
worker_connections + (shared libs, log files, event pool, etc.) = worker_rlimit_nofile

# Dwa uchwyty dla jednego połączenia:
(worker_connections * 2) + (shared libs, log files, event pool, etc.) = worker_rlimit_nofile

Prawdopodobnie tyle plików może otworzyć każdy worker i maksymalna ilość deskryptorów plików, które jest w stanie otworzyć NGINX, powinna mieć wartość większą niż liczba połączeń na proces roboczy (zgodnie z powyższą formułą).

W większości artykułów i samouczków widzimy, że ten parametr (dla przypomnienia, który ustawiamy na proces roboczy) ma wartość podobną do maksymalnej liczby (lub nawet więcej) wszystkich otwartych plików, jakie może otworzyć serwer NGINX. Jeśli założymy, że dotyczy on każdego procesu roboczego, wartości te są całkowicie zawyżone.

Jednak po głębszej refleksji uważam, że są one w miarę racjonalne, ponieważ pozwalają jednemu workerowi na użycie wszystkich deskryptorów plików, tak, aby nie ograniczał się do innych procesów roboczych, jeśli zostaną np. zamknięte lub stanie się z nimi cokolwiek niedobrego. Pamiętaj jednak, że nadal jesteśmy ograniczeni przez liczbę połączeń przypadających na proces roboczy.

Tak więc, przechodząc dalej, maksymalna liczba otwartych plików przez NGINX powinna wynosić:

(worker_processes * worker_connections * 2) + (shared libs, log files, event pool, etc.) = max open files

Dzięki czemu, aby obsłużyć 16 384 połączeń (4096 połączeń na każdy worker), mając na uwadze inne deskryptory plików używane przez NGINX, a także możliwość wykorzystania maksymalnie dwóch deskryptorów plików na połączenie, rozsądna wartość maksymalnej liczby deskryptorów plików w tym przypadku może wynosić 35 000. Myślę, że taka wartość jest wystarczająca.

Pamiętaj także o następujących zasadach:

Na koniec przykład:

nginx: master process         = LimitNOFILE (35,000)
  \_ nginx: worker process    = LimitNOFILE (35,000), worker_rlimit_nofile (10,000)
  \_ nginx: worker process    = LimitNOFILE (35,000), worker_rlimit_nofile (10,000)
  \_ nginx: worker process    = LimitNOFILE (35,000), worker_rlimit_nofile (10,000)
  \_ nginx: worker process    = LimitNOFILE (35,000), worker_rlimit_nofile (10,000)

                              = master (35,000), all workers:
                                                 - 140,000 by LimitNOFILE
                                                 - 40,000 by worker_rlimit_nofile

W celu dodatkowego zgłębienia wiedzy polecam Optimizing Nginx for High Traffic Loads.