<html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><div><span class="Apple-style-span" style="font-family: Times; font-size: 16px; "><pre style="word-wrap: break-word; white-space: pre-wrap; ">Keywords: freebsd tcp optimization tune speed socket mbuf sendfile sysctl
From: Сысоев Игорь Владимирович &lt;<a href="http://www.sysoev.ru">http://www.sysoev.ru</a>>
Date: Mon, 1 Oct 2007 14:31:37 +0000 (UTC)
Subject: FreeBSD для обслуживания 100-200 тысяч соединений

[[<a href="http://rutube.ru/tracks/125719.html?v=23351111db90ae8beace6a841dd2a8f4%EE%C1%D3%D4%D2%CF%CA%CB%C1">http://rutube.ru/tracks/125719.html?v=23351111db90ae8beace6a841dd2a8f4%EE%C1%D3%D4%D2%CF%CA%CB%C1</a> Видеоролик доклада]]
<br></pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## мои дополнения отмечены ######## в начале строки&nbsp;</pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## в самом низу даны "итоговые" файлы конфигурации /boot/loader.conf /etc/sysctl.conf и ядер для i386 и amd64</pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">
Стенограмма выступления Игоря Сысоева с конференции РИТ-2007.


mbuf clusters

FreeBSD хранит сетевые данные в mbuf clusters, размер каждого 2Кб, но из
них используется только около 1500 байт (по размеру Ethernet пакета).

mbufs

Для каждого mbuf кластера нужен "mbuf", который имеет размер 256 байт и
нужен для организации связи цепочек из mbuf кластеров. В mbuf можно поместить
полезную информацию в районе 100 байт, но это не всегда используется.

Если в машине 1Гб и больше памяти, то по умолчанию будет создано 25 тыс. mbuf кластеров,
что не всегда достаточно.

При ситуации исчерпания числа свободных mbuf кластеров FreeBSD попадает в 
состояние zonelimit и перестает отвечать на запросы по сети, 
в top это выглядит как "zoneli". Единственная возможность как-то повлиять на 
ситуацию - это зайти с локальной консоли и перезагрузить систему, уничтожить
процесс находящийся в состоянии "zoneli" невозможно. Для Linux 2.6.x данная проблема
тоже характерна, причем работать переставала даже консоль.

        PID USERNAME  THR PRI NICE   SIZE    RES STATE    TIME   WCPU COMMAND
        13654 nobody      1   4    0 59912K 59484K zoneli 209:26  0.00% nginx

Для выхода из этой ситуации существует патч возвращающий приложению ошибку ENOBUFS, 
сигнализирующий о попадании в состояние "zoneli", после чего программа может 
закрыть лишние соединения. К сожалению патч пока не принят в состав FreeBSD.


Состояние задействованных mbuf кластеров можно посмотреть командой:

        >netstat -m
           4/1421/1425 mbufs in use (current/cache/total)
           0/614/614/25600 mbuf clusters in use (current/cache/total/max)

   
Увеличение числа mbuf кластеров во FreeBSD 6.2 можно произвести в любой момент
через параметр kern.ipc.nmbclusters:

        sysctl kern.ipc.nmbclusters=65536

Для более ранних версий FreeBSD число mbuf кластеров можно было установить только
на этапе загрузки:

        /boot/loader.conf:
            kern.ipc.nmbclusters=65536
        
            25000 mbuf clusters = 55M
            32768 mbuf clusters = 74M
            65536 mbuf clusters = 144M

25000 mbuf кластеров занимают примерно 50Мб памяти, 32000 - 74 Мб, 65000 -
144Мб (рост по степени 2). 65000 - пограничное значение, превышать которое не
рекомендуется, без предварительного расширения адресного пространства 
доступного ядру.


++ Увеличение памяти, доступной ядру

Увеличение адресного пространства ядра, которое на i386 платформе - 1Гб. 
Для увеличения до 2Гб, в файле конфигурации ядра необходимо указать:

            options KVA_PAGES=512
<br></pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## для amd64 оказалось недостаточно указать vm.kmem_size_max=1G в /boot/loader.conf,&nbsp;</pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## (параметр был просто игнорирован) по-этому это надо сделать в файле конфигурации ядра:&nbsp;</pre><pre style="word-wrap: break-word; white-space: pre-wrap; "><span class="Apple-style-span" style="font-family: Times; white-space: normal; "><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## options VM_KMEM_SIZE=1073741824</pre><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;"><span class="Apple-style-span" style="font-family: Times; white-space: normal; "><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## options VM_KMEM_SIZE_MAX=1073741824</pre></span></span></font></div></span></pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">На платформе amd64 KVA всегда 2G, увеличить к сожалению в настоящее время нельзя.

Кроме увеличения виртуального адресного пространства можно увеличить лимит 
физической памяти, которую может использовать ядро (по умолчанию 320Мб). Увеличим
до 1Гб:

        /boot/loader.conf:
            vm.kmem_size=1G
######## для обеих платформ добавить vm.kmem_size_max=1G</pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">
Затем выделим из него 575Мб под mbuf кластера:

        sysctl -w kern.ipc.nmbclusters=262144


++ Установление соединения. syncache и syncookies

Примерно 1000 байт расходуется на одно соединение.
Примерно 100 байт для одной записи на незаконченное соединение в syncache.
Всего можно держать в памяти информацию об около 15000 соединениях.

Параметры syncache можно посмотреть через "sysctl net.inet.tcp.syncache" (доступен
в режиме только для чтения):

    # sysctl net.inet.tcp.syncache 
        net.inet.tcp.syncache.bucketlimit: 30
        net.inet.tcp.syncache.cachelimit: 15359
        net.inet.tcp.syncache.count: 29
        net.inet.tcp.syncache.hashsize: 512
        net.inet.tcp.syncache.rexmtlimit: 3

Изменить параметры syncache можно только на этапе загрузки ядра:

    /boot/loader.conf:
        net.inet.tcp.syncache.hashsize=1024
        net.inet.tcp.syncache.bucketlimit=100

Когда новое соединение не помещается в переполненный syncache, FreeBSD переходит в  
режим "syncookies" (TCP SYN cookies). Включается возможность такого перехода через:

        sysctl net.inet.tcp.syncookies=1

Заполненность syncache и статистику по syncookies можно посмотреть через команду:

        netstat -s -p tcp
            ...
            2079088720 syncache entries added
            ...
            > 0 dropped
            2058506523 completed
            > 0 bucket overflow
            > 0 cache overflow
            ...
            0 cookies sent
            0 cookies received


После того как соединение принято оно попадает в "listen socket queue".
Статистику можно посмотреть командой

        netstat -Lan
            Current listen queue sizes (qlen/incqlen/maxqlen)
            Proto Listen         Local Address         
            tcp4  43/0/4096      *.80                   
            tcp4  0/0/128        *.22

4096 - размер очереди (максимум 65тыс.)
43 - заполненность очереди в данный момент (приложение не вытащило из очереди).

Увеличение размера очереди производится через:

        sysctl kern.ipc.somaxconn=4096


После того как соединение принято для него FreeBSD создает структуры связанные 
с сокетами (sockets).

Увеличить максимальное число открытых сокетов во FreeBSD 6.2, во время работы,
можно через:

        sysctl kern.ipc.maxsockets=204800

    В более ранних версиях:

        /boot/loader.conf:
            kern.ipc.maxsockets=204800

Посмотреть состояние можно командой:

        >vmstat -z
        ITEM      SIZE     LIMIT      USED      FREE    REQUESTS  FAILURES
        ...
        socket:    356,   204809,    48041,   114869, 4292783585,        0
        ...
        inpcb:     180,   204820,    63956,   121460, 4283258030,        0
        tcpcb:     464,   204800,    48015,   114897, 4283258030,        0


++ tcb hash

Если машина обрабатывает несколько десятков тысяч соединений то tcb hash позволяет быстро
определять принадлежность пришедшего пакета к определенному соединению.

По умолчанию размер tcb hash - 512 элементов.
Текущий размер можно посмотреть через sysctl (только для чтения): 

        sysctl net.inet.tcp.tcbhashsize

Изменить можно на этапе загрузки:

        /boot/loader.conf:
            net.inet.tcp.tcbhashsize=4096

++ Файлы

Приложения работают не с сокетами, а с файлами. По этому для каждого 
сокета нужна структура, которая описывает файл. Увеличить можно через:

        sysctl kern.maxfiles=204800
        sysctl kern.maxfilesperproc=200000

kern.maxfiles - всего файлов в системе
kern.maxfilesperproc - максимальное число файлов на один процесс.

Параметры можно менять на работающей системе, но они не отразятся на уже запущенных
процессах. Поэтому в nginx были добавлены директивы позволяющие менять число
открытых файлов для уже запущенных процессов (после инициирования операции переконфигурации) 

        nginx.conf:
            worker_rlimit_nofile  200000;
            events {
              worker_connections  200000;
            }
      
++ receive buffers
Буферы для приема данных. По умолчанию 64Kб, если нет загрузки больших объемов данных,
то можно уменьшить до 8Кб (меньше вероятность переполнения при DoS атаке).

        sysctl net.inet.tcp.recvspace=8192

Для nginx:

        nginx.conf:
            listen 80 default rcvbuf=8k;


++ send buffers

Буферы для отправки данных. По умолчанию 32K. Если скачиваются данные небольшого объема
или недостаток mbuf кластеров, можно уменьшить:

        sysctl net.inet.tcp.sendspace=16384

Для nginx:

        nginx.conf:
            listen 80 default sndbuf=16k;

В ситуации когда сервер записал в сокет данные, но клиент не хочет их забирать, 
после таймаута по закрытию соединения в ядре данные будут держаться еще
несколько минут. В nginx если директива для принудительного сброса всех данных
после закрытия по таймауту.

        nginx.conf:
                reset_timedout_connections  on;
    
++ sendfile
Еще один механизм для экономии mbuf кластеров - это sendfile, он использует 
память буферов ядра с данными файлов одновременно для передачи этих данных в 
сетевую карту, без промежуточного заполнения лишних буферов.

В nginx включается:

        nginx.conf:
            sendfile on;

На i386 по умолчанию для систем с 1 или более Гб памяти будет выделено 6656 
sendfile буферов. Этого вполне достаточно. На платформе amd64 более оптимальный
вариант реализации и sfbufs буферы не нужны.

Статистика:

        i386>netstat -m
        ...
        190/510/6656 sfbufs in use (current/peak/max)
        
        amd64>netstat -m
        ...
        0/0/0 sfbufs in use (current/peak/max)

При переполнении sendfile буфера процесс замирает в состоянии "sfbufa", но
ситуация достаточно быстро приходит в норму после увеличения размера буфера.

        PID USERNAME  THR PRI NICE   SIZE    RES STATE    TIME   WCPU COMMAND
        13654 nobody      1   4    0 59912K 59484K sfbufa 209:26  5.00% nginx

Увеличение размера через:

        /boot/loader.conf:
            kern.ipc.nsfbufs=10240

++ TIME_WAIT

После того как соединение закрывается сокет переходит в состояние TIME_WAIT
В этом состоянии он может находится по умолчанию в течение 60 секунд.
Время можно изменить через sysctl (в миллисекундах деленных на 2, 2 x 30000 MSL = 60 секунд):

        sysctl net.inet.tcp.msl=30000

Во FreeBSD 6.2 TIME_WAIT сокеты обрабатываются отдельно (нужна лишь
часть информации 48 байт из 1 Кб. Ограничение вне лимита
kern.ipc.maxsockets), число их регулируется параметром:

        sysctl net.inet.tcp.maxtcptw=40960

Статистика:

        >vmstat -z
        ITEM      SIZE     LIMIT      USED      FREE    REQUESTS  FAILURES
        ...
        tcptw:      48,    41028,    15941,    25087, 1045159949,   438573


++ TCP/IP ports

По умолчанию исходящие соединения инициируются с диапазона портов 49152-65535 (16 тыс.).
Их неплохо увеличить (1024-65535):

        sysctl net.inet.ip.portrange.first=1024
        sysctl net.inet.ip.portrange.last=65535

Для использования портов по порядку, вместо случайной выборки (для
исключения ошибки повторного коннекта с одного порта до отработки
TIME_WAIT):

        sysctl net.inet.ip.portrange.randomized=0

Во FreeBSD 6.2 появилась возможность не создания состояния TIME_WAIT для 
соединений в рамках localhost:

        sysctl net.inet.tcp.nolocaltimewait=1
    </pre><pre style="word-wrap: break-word; white-space: pre-wrap; "><br></pre><pre style="word-wrap: break-word; white-space: pre-wrap; "><br></pre><pre style="word-wrap: break-word; white-space: pre-wrap; "><br></pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## /usr/src/sys/amd64/conf/CUSTOM </pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">include                GENERIC

device                pf
device                pflog
device                pfsync
device                carp

options                ALTQ
options                ALTQ_CBQ
options                ALTQ_RED
options                ALTQ_RIO
options                ALTQ_HFSC
options                ALTQ_CDNR
options                ALTQ_PRIQ
options                ALTQ_NOPCC

#options                QUOTA

options                IPSEC
#options                IPSEC_FILTERGIF
device                crypto
device                cryptodev

options                DEVICE_POLLING
#options                HZ=1000

#options                SCHED_ULE

#options                KVA_PAGES=512

options         VM_KMEM_SIZE=1073741824
options         VM_KMEM_SIZE_MAX=1073741824

options                PANIC_REBOOT_WAIT_TIME=60

ident                CUSTOM
</pre><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;"><br></span></font></div><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;"><span class="Apple-style-span" style="font-family: Times; white-space: normal; "><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## /usr/src/sys/i386/conf/CUSTOM </pre><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;"><br></span></font></div></span></span></font></div><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;"><div>include<span class="Apple-tab-span" style="white-space:pre">                </span>GENERIC</div><div><br></div><div>device<span class="Apple-tab-span" style="white-space:pre">                </span>pf</div><div>device<span class="Apple-tab-span" style="white-space:pre">                </span>pflog</div><div>device<span class="Apple-tab-span" style="white-space:pre">                </span>pfsync</div><div>device<span class="Apple-tab-span" style="white-space:pre">                </span>carp</div><div><br></div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ</div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ_CBQ</div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ_RED</div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ_RIO</div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ_HFSC</div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ_CDNR</div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ_PRIQ</div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>ALTQ_NOPCC</div><div><br></div><div>#options<span class="Apple-tab-span" style="white-space:pre">                </span>QUOTA</div><div><br></div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>IPSEC</div><div>#options<span class="Apple-tab-span" style="white-space:pre">                </span>IPSEC_FILTERGIF</div><div>device<span class="Apple-tab-span" style="white-space:pre">                </span>crypto</div><div>device<span class="Apple-tab-span" style="white-space:pre">                </span>cryptodev</div><div><br></div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>DEVICE_POLLING</div><div>#options<span class="Apple-tab-span" style="white-space:pre">                </span>HZ=1000</div><div><br></div><div>#options<span class="Apple-tab-span" style="white-space:pre">                </span>SCHED_ULE</div><div><br></div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>KVA_PAGES=512</div><div><br></div><div>options <span class="Apple-tab-span" style="white-space:pre">        </span>VM_KMEM_SIZE=1073741824</div><div>options <span class="Apple-tab-span" style="white-space:pre">        </span>VM_KMEM_SIZE_MAX=1073741824</div><div><br></div><div>options<span class="Apple-tab-span" style="white-space:pre">                </span>PANIC_REBOOT_WAIT_TIME=60</div><div><br></div><div>ident<span class="Apple-tab-span" style="white-space:pre">                </span>CUSTOM</div><div><br></div><div><br></div><div><span class="Apple-style-span" style="font-family: Times; white-space: normal; "><pre style="word-wrap: break-word; white-space: pre-wrap; ">######## /boot/loader.conf </pre><pre style="word-wrap: break-word; white-space: pre-wrap; ">verbose_loading="YES"
loader_logo="beastie"
#ng_ether_load="YES"
#linux_load="YES"
accf_data_load="YES"
accf_http_load="YES"

net.inet.tcp.syncache.hashsize=1024
net.inet.tcp.syncache.bucketlimit=100
net.inet.tcp.tcbhashsize=4096
kern.ipc.nsfbufs=10240
vm.kmem_size=1G
vm.kmem_size_max=1G
</pre><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;"><br></span></font></div><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;">######## /etc/sysctl.conf </span></font></div><div><font class="Apple-style-span" face="-webkit-monospace"><span class="Apple-style-span" style="white-space: pre-wrap;"><div>net.inet.tcp.blackhole=1</div><div>net.inet.udp.blackhole=1</div><div>kern.ipc.nmbclusters=262144</div><div>kern.ipc.somaxconn=4096</div><div>kern.ipc.maxsockets=204800</div><div>kern.maxfiles=204800</div><div>kern.maxfilesperproc=200000</div><div>net.inet.ip.portrange.first=1024</div><div>net.inet.ip.portrange.last=65535</div><div>net.inet.ip.portrange.randomized=0</div><div>net.inet.tcp.recvspace=8192</div><div>net.inet.tcp.sendspace=16384</div><div>net.inet.tcp.maxtcptw=40960</div><div>net.inet.tcp.msl=30000</div><div>net.inet.tcp.syncookies=1</div><div>net.inet.tcp.nolocaltimewait=1</div><div>net.inet.tcp.fast_finwait2_recycle=1&nbsp;</div><div><br></div><div><br></div><div>######## после всех мытарств, работа системы весьма порадовала. </div><div>######## комменты только приветствуются. </div><div>######## огромное пожелание к Игорю включить сей опус с поправками и комментариями (кои лично меня ОЧЕНЬ интересуют) в документацию на своём сайте. </div><div><br></div><div>######## данные конфиги являются результатом печального опыта. если не указывать vm.kmem_size_max, ядру нехватит памяти, для всей этой красоты. </div><div>######## успехов </div><div><br></div></span></font></div></span></div></span></font></div></span></div></body></html>