Archiv pro měsíc: Říjen 2013

Solaris (SmartOS) TCP tuning, nginx a chyba phantom event for closed and removed socket

Nedávno se mi v error logu nginxu objevila řada následujících chyb:

2013/09/14 21:05:01 [alert] 26115#0: phantom event 0001 for closed and removed socket 21
2013/09/29 08:52:59 [alert] 49045#0: phantom event 0004 for closed and removed socket 17
2013/09/30 16:51:37 [alert] 49045#0: phantom event 0001 for closed and removed socket 46
2013/10/09 11:01:15 [alert] 92940#0: phantom event 0004 for closed and removed socket 28

Samozřejmě jsem nejprve Googloval a v prvních pár dotazech našel odpověď. Mám ale ve zvyku nejprve pochopit, co vlastně tomu serveru cpu do konfigurace a proč to vyřešit právě takto a ne jinak. Zkoumal jsem tedy dál a snad i pochopil, v čem je vlastně jádro problému.

Proč vlastně tento problém vzniká?

  • Nginx je asynchronní server. Veškeré operace zpracovává během iterací tzv. event loop-u (také nazývaného IO loop).
  • Solaris nginxu předává eventy (připojení klientů) tzv. poll-em pomocí zařízení /dev/poll.
  • Chyba vzniká nekonzistencí při práci nginxu s /dev/poll, který vrací eventy hromadně v jedné iteraci.
  • Výchozí limit eventů předaných kernelem nginxu je pro /dev/poll nastaven na 32.
  • Nginx při práci s jednotlivými eventy otevírá jejich sockety postupně. Když chce zpracovat další, musí současný socket zavřít. Pokud tedy kernel předá nginxu až 32 eventů (spojení s klienty), při větším vytížení serveru se může stát, že event již není aktivní (klient se odpojil).
  • Dá se to vyřešit tím, že nginx donutíme přijímat od kernelu vždy jen jeden event během iterace. V confu to nastavíme parametrem devpoll_events.
  • Na výkonu nginxu by se to mělo projevit jen neznatelně.
events {
    devpoll_events 1;
}

Jaký je skutečný důvod vzniku problému?

Ačkoliv tímto nastavením zařídíme, aby si už nginx nestěžoval do logu, příslovečný pes je zakopaný někde jinde. Kernel má totiž limity pro maximální počet navázaných a příchozích spojení, které jsou v základu poměrně malé. Pokud server obdrží nový požadavek na spojení, který se nevejde do bufferu, starší spojení se zahodí.

Parametry tcp_conn_req_max_q a tcp_conn_req_max_q0

Parametry tcp_conn_req_max_q a tcp_conn_req_max_q0 určují maximální počet požadavků, které mohou být přijmuty na jednu IP adresu a jeden port. tcp_conn_req_max_q je maximální počet příchozích spojení, které mohou být akceptovány (accepted) na IP a port. tcp_conn_req_max_q0 je maximální počet „polovičně otevřených“ (half-open) TCP spojení, které mohou existovat na portu. Parametry jsou odděleny kvůli efektivnímu blokování SYN segment denial of service útoků.

Jak zjistit, zda byly limity překročeny?

netstat -s|grep ListenDrop
  • Pokud je tcpListenDrop nenulový, je potřeba navýšit tcp_conn_req_max_q.
  • Pokud je tcpListenDropQ0 nenulový, je potřeba navýšit tcp_conn_req_max_q0.

Ale počkat! Není to zase tak úplně jednoduché. Navýšením tcp_conn_req_max_q na příliš vysoké hodnoty můžeme otevřít cestu pro SYN segment denial of service útoky. Solaris to má ale díky oddělení parametrů dobře vymyšleno. Navyšujte tcp_conn_req_max_q v násobcích 256. Použijte tcp_conn_req_max_q0 pro zvýšení počtu „polovičně otevřených“ TCP spojení. Pokud nginx nebo jiný server nemůže zpracovávat spojení dostatečně rychle, zvýšení tcp_conn_req_max_q0 může zabránit tomu, že se klienti nebudou schopni připojit. Spojení klientů zůstávají v „polovičně otevřeném“ stavu, dokud je nginx nezpracuje.

Jak limity nastavit?

ndd -set /dev/tcp tcp_conn_req_max_q 4096
ndd -set /dev/tcp tcp_conn_req_max_q0 8192

Pozor! Po nastavení limitu je potřeba restartovat nginx nebo jiné aplikace, kterých se to týká! Také tato nastavení se vyresetují restartem systému. Jak je nastavit permanentně si řekneme ale až v příštím článku 🙂

Zdroje