Довольно много времени прошло после моей последней статьи про nginx под windows, неделя nginx закончилась. Стоит поправить это упущение.

Иногда так случается, что вдруг появилось свободное время, но для чего-то путного его не хватает, а просто полазить в интернетах, почитать хабр всячески повышать свою квалификацию совсем не хочется.
Чтобы сделать все-таки что-нибудь полезного, решил заняться анализом логов с некоторых серверов одного проекта, насколько удастся впихнуть это в пару свободных минут.

После небольшого разбора и оценки в сравнении с результатами предыдущего анализа, заметил одну странность — абсолютная скорость отдачи nginx упала в среднем от 5 до 15%.

Объяснить, чем это вызвано с налета никак не удавалось, больших изменений вроде не было, объемы данных тоже настолько не выросли. Да и на отдаче динамики сильных изменений немного.

Покрутив логи и так и сяк, зацепился за отдачу маленькой статики — выяснилась одна закономерность: чем длиннее путь (url) — тем «медлительней» становился nginx (независимо от размера файла).

Итак после нескольких экспериментов, имеем следующие факты:
  • скорость отдачи падает прямо пропорционально увеличению длины пути до файла
  • скорость практически не зависит от длинны URL, т.е. если URL короткий, но увеличиваем длину root/alias, скорость отдачи падает также, т.е. это все-таки длинна пути, а не URL
  • ну и наконец, поиграв с путями файла, а именно его вложенности, выяснилось, что скорость отдачи падает в зависимости от количества поддиректорий, и не зависит от длины как-таковой. Т.е. файл «D:\...\ms-ms-ms-ms-ms-ms-ms-ms\test.gif» отдается много быстрее «D:\...\ms\ms\ms\ms\ms\ms\ms\ms\test.gif»

И тут пришло озарение — я вспомнил, что в этом проекте изменилась файловая структура, и вложенность до некоторой статики и динамики, отдаваемой файлом (по redirect), увеличилась на два-три, а местами до пяти каталогов.

Компилировать debug-версию под профайлер-анализатор было лень, будем искать «дедуктивным» способом.

Измеряем с помощью siege среднюю скорость отдачи «test.gif» — порядка 5500 rps (nginx 4 workers, concurency 15).

Создаем тестовый location с root «D:\...\ms\ms\ms\ms\ms\ms\ms\ms», где сегмент пути ms повторяется 60 раз и запрашиваем URL «test-loc/test.gif».
Снова измеряем среднюю скорость отдачи — уже 900 rps.

Возможно это что-то где-то на уровне проверки легальности пути. Пробуем замерить 404 для несуществующего файла с URL «test-loc/test-file-not-exists.gif» — скорость вырастает до 13167 rps и что странно вложенность пути уже не имеет значения.

Включаем дебаг в логгировании nginx, и затем детально анализируем логи в сравнении малая — большая вложенность пути. В среднем время от «http request line» до «http filename» одинаково, но запись «http static fd» для длинного пути уже ощутимо отстает.

Довольно странно, поэтому пытаемся проверить что происходит, используя процесс-мониторинг.
Под спойлером имеем следующую картину
15:23:43,2205361  nginx.exe 9780  CreateFile  D:\ SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2206208  nginx.exe 9780  QueryDirectory  D:\Projects  SUCCESS Filter: Projects, 1: Projects
15:23:43,2206430  nginx.exe 9780  CloseFile D:\ SUCCESS 
15:23:43,2207337  nginx.exe 9780  CreateFile  D:\Projects  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2207844  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv SUCCESS Filter: app_srv, 1: app_srv
15:23:43,2208009  nginx.exe 9780  CloseFile D:\Projects  SUCCESS 
15:23:43,2208728  nginx.exe 9780  CreateFile  D:\Projects\app_srv SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2209238  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp SUCCESS Filter: tmp, 1: tmp
15:23:43,2209383  nginx.exe 9780  CloseFile D:\Projects\app_srv SUCCESS 
15:23:43,2210075  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2210615  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms  SUCCESS Filter: ms, 1: ms
15:23:43,2210764  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp SUCCESS 
15:23:43,2211466  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2211995  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms SUCCESS Filter: ms, 1: ms
15:23:43,2212141  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms  SUCCESS 
15:23:43,2212813  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2213326  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms  SUCCESS Filter: ms, 1: ms
15:23:43,2213462  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms SUCCESS 
15:23:43,2214193  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2214677  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms SUCCESS Filter: ms, 1: ms
15:23:43,2214806  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms  SUCCESS 
# ... 106 lines ... #
15:23:43,2265328  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2265851  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Filter: ms, 1: ms
15:23:43,2265980  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS 
15:23:43,2266665  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2267165  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Filter: ms, 1: ms
15:23:43,2267291  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS 
15:23:43,2267976  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2268489  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Filter: ms, 1: ms
15:23:43,2268622  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS 
15:23:43,2269314  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2269814  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Filter: ms, 1: ms
15:23:43,2269946  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS 
15:23:43,2270615  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2271135  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Filter: ms, 1: ms
15:23:43,2271264  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS 
15:23:43,2271989  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2272525  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Filter: ms, 1: ms
15:23:43,2272654  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS 
15:23:43,2273343  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2273876  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Filter: ms, 1: ms
15:23:43,2274005  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS 
15:23:43,2274743  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2275246  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Filter: ms, 1: ms
15:23:43,2275375  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS 
15:23:43,2276034  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2276531  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Filter: ms, 1: ms
15:23:43,2276657  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS 
15:23:43,2277332  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:23:43,2277819  nginx.exe 9780  QueryDirectory  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms SUCCESS Filter: ms, 1: ms
15:23:43,2277944  nginx.exe 9780  CloseFile D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms  SUCCESS 
15:23:43,2278987  nginx.exe 9780  CreateFile  D:\Projects\app_srv\tmp\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\ms\test.gif SUCCESS Desired Access: Generic Read, Disposition: Open, Options: Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened


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

Лезем в исходники nginx, довольно быстро находим в [src]\os\win32\ngx_files.c функцию ngx_open_file, которая только в случае успешного открытия файла (а мы помним про быстрый 404), вызывает другую функцию ngx_win32_check_filename (проверка легальности имени файла).
Эту же функцию ngx_win32_check_filename вызывает и другая функция ngx_file_info. Ну и рассмотрим под лупой наш кандидат на роль «ручного тормоза».

Сперва идет проверка правильности пути сегментов, исключения NTFS streams (":"), точек и пробелов в конце и т.д. Но в этом теле цикла проверкой не осуществляются никакие системные вызовы.

Идем дальше и видим вызов GetLongPathNameW в блоке, проверяющем длинное имя файла.

Для пробы закомментируем эту проверку, компилируем nginx — в результате имеем 9100 rps, там где ранее nginx отдавал 900 rps.
Видимо Максим когда делал эту проверку не знал (ваш покорный слуга кстати тоже), что Windows возвращает длинное имя файла рекурсивно, т.е. для каждого подкаталога (и как видим эта информация не кэшируется в OS).

К сожалению, таким образом (т.е. полностью) не стоит убирать эту проверку, чтобы исключить доступ к файлам по коротким именам 8.3 (вида «testpa~1/testfi~2.gif»), а лучшей функции делающей тоже самое, я к сожалению не знаю.
Однако можно добавить выше установку специального флажка, проверкой существования символа "~", только при наличии которого мы и будем осуществлять проверку длинного пути, вызывая GetLongPathNameW.

В результате при минимальных изменениях имеем рост скорости от 2 до 10 раз (зависит от вложенности и размеров файла) и в разы меньшую нагрузку на дисковую подсистему.

Ну и как обычно сравнения производительности, до (первый и третий столбцы помечены **NF) и после.
Первые два — для короткого (12 сегментов) пути.
Следующие два — для длинного (55 сегментов) пути.
Path, Segments Short, 12 **NF Short, 12 Long, 55 **NF Long, 55
Transactions 16732 hits 28380 hits 2962 hits 26820 hits
Availability 100.00 % 100.00 % 100.00 % 100.00 %
Elapsed time 2.96 secs 2.96 secs 2.97 secs 2.97 secs
Data transferred 7.21 MB 12.23 MB 1.28 MB 11.56 MB
Response time 0.00 secs 0.00 secs 0.00 secs 0.00 secs
Transaction rate 5645.07 trans/sec 9591.08 trans/sec 996.04 trans/sec 9030.30 trans/sec
Throughput 2.43 MB/sec 4.13 MB/sec 0.44 MB/sec 3.89 MB/sec
Concurrency 14.92 14.72 14.80 14.12
Successful transactions 16732 28380 2962 26820
Failed transactions 0 0 0 0
Longest transaction 0.11 0.11 0.12 0.12
Shortest transaction 0.00 0.00 0.00 0.00
Измерение siege (concurency: 15), win7 — i5-2400 cpu @ 3.10GHz (4 core), gif-статика: 452 байт (+ header), 4 воркера nginx.

У себя в branch nginx-mod я уже интегрировал этот этот PR.
Будет время, сделаю changeset для оригинального hg-репозитария и отправлю CR в nginx-dev.

Комментарии (29)


  1. UdarEC
    08.10.2015 15:47

    Очень интересная особенность оказалась.


  1. dMetrius
    08.10.2015 16:13
    -1

    nginx, windows?

    Version of nginx for Windows uses the native Win32 API (not the Cygwin emulation layer). Only the select() connection processing method is currently used, so high performance and scalability should not be expected. Due to this and some other known issues version of nginx for Windows is considered to be a beta version.


    1. TimsTims
      09.10.2015 00:02
      +2

      Посмотрите его первую статью http://habrahabr.ru/post/260133/
      Там автор довольно неплохо скомпилил nginx для хорошей работе на Windows с блэкджеком и шлюзами


  1. baldr
    08.10.2015 16:13

    А может кто-нибудь проверить — есть ли и в исходниках для *nix похожая функция ngx_linux_check_filename?
    На хабре есть isysoev — возможно он что-нибудь прокомментирует?


    1. sebres
      08.10.2015 16:24
      +2

      ngx_open_file в *nix-ах это define на стандартный open

      #define ngx_open_file(name, mode, create, access)     open((const char *) name, mode|create|O_BINARY, access)
      
      Но там полно другого чего, например подозреваю что директивы типа «disable_symlinks» могут натянуть ручник на некоторых FS.
      Хотя в *nix-ах он получше оттестирован, хотя бы из-за его распространенности.


    1. isysoev
      08.10.2015 16:30
      +9

      В стандартных файловых системах Линукса, к счастью, нет способа обратиться к файлу миллионами разных способов, поэтому нет и необходимости в ngx_linux_check_filename().


  1. isysoev
    08.10.2015 16:30
    +1

    В стандартных файловых системах Линукса, к счастью, нет способа обратиться к файлу миллионами разных способов, поэтому нет и необходимости в ngx_linux_check_filename().


  1. ildarz
    08.10.2015 17:39

    К сожалению, таким образом (т.е. полностью) не стоит убирать эту проверку, чтобы исключить доступ к файлам по коротким именам 8.3 (вида «testpa~1/testfi~2.gif»),


    А можете пояснить для чайников, зачем эта проверка вообще нужна?


    1. sebres
      08.10.2015 17:45

      Конкретно доступ к файлу под коротким именем? Думается, чтобы исключить перебор (типа брута и т.д.).
      В принципе по той же причине, по какой выключают directory listing.


      1. ildarz
        08.10.2015 18:02

        Я просто в целом логику работы не понимаю в данном случае.

        находим в [src]\os\win32\ngx_files.c функцию ngx_open_file, которая только в случае успешного открытия файла (а мы помним про быстрый 404), вызывает другую функцию ngx_win32_check_filename (проверка легальности имени файла).


        Т.е. мы сначала открываем файл, и, если операция успешна, проверяем длинное имя. Зачем? Что должно дальше происходить по результатам этой проверки?

        P.S. Btw, генерация 8.3 имен в винде отключается. Может, просто имеет смысл включить это в системные требования к установке и не париться с проверкой?


        1. isysoev
          08.10.2015 18:33
          +1

          Например, в такой конфигурации

          location /authorization/ {
             auth_basic ...
             fastcgi_pass  ...
          }

          запрос "/author~1/script.php" не будет обработан в этом location'е.
          Поэтому разрешаются только обращения по полному имени.


  1. intnzy
    08.10.2015 21:02

    Несколько оффтопик, но меня ужасно интересует — в чем необходимость именно такой связки nginx-win? Именно не для тестирования а реальные use cases.


    1. baldr
      08.10.2015 21:10
      +1

      Автор писал об этом в первой статье


    1. sebres
      08.10.2015 21:13

      Именно такой, это какой?
      Если это намек в сторону IIS, appache или любимое подставить, то у меня будет встречный вопрос «Когда оне будут такие же шустрые, динамичные и гибкие (во всех смыслах) как nginx?»
      Т.е. для реального use case с преф-ом и девочками, а не поиграться и не для портала на 10 человек аля sharepoint?

      Я беру nginx не потому, что он есть под linux — а потому-что он nginx. И не беру IIS, потому что он IIS.


      1. intnzy
        08.10.2015 22:40

        Про nginx более-менее понятно. Непонятно 2 часть. Почему тогда Windows? Или nginx сервает ASP?


        1. Evengard
          09.10.2015 01:58

          Сейчас кстати ASP.NET сервается хорошо и из под Линукса. У самого такой сетап.


        1. sebres
          09.10.2015 13:53

          Потому что не сам себе режиссер. Например, в корпоративном секторе от виндовс часто тупо никуда не деться.

          Или nginx сервает ASP?
          Что во фразе «и не беру IIS» было не понятно…


  1. maksqwe
    08.10.2015 21:07

    «Видимо Максим когда делал эту проверку не знал (ваш покорный слуга кстати тоже), что Windows возвращает длинное имя файла рекурсивно, т.е. для каждого подкаталога (и как видим эта информация не кэшируется в OS).»
    Кстати, а где вы это узнали? Msdn, судя по всему, молчит по этому поводу.
    Или методом «Для пробы закомментируем эту проверку...»?


    1. sebres
      08.10.2015 21:14

      Загляните под спойлер, где процесс-мониторинг.


  1. maksqwe
    08.10.2015 22:48
    +2

    Прочитал статью не заглянув туда. Действительно, не очевидное поведение системы, хотя логичное в данном случае.
    И вы натолкнули меня на мысль проверить кое-что.
    Являюсь пользователем Qt как для домашних так и для рабочих проектов уже около 4 лет, иногда даже принимают мои патчи.
    Данный системный вызов(«GetLongPathName») используется так же в основном модуле библиотеки при возврата пути к папки в которой система по-умолчанию хранит временные файлы, упущу некоторый код, в общем это:

    QString QFileSystemEngine::tempPath()
    {
    QString ret;
    wchar_t tempPath[MAX_PATH];
    const DWORD len = GetTempPath(MAX_PATH, tempPath);
    if (len) { // GetTempPath() can return short names, expand.
    wchar_t longTempPath[MAX_PATH];
    const DWORD longLen = GetLongPathName(tempPath, longTempPath, MAX_PATH);
    ret = longLen && longLen < MAX_PATH?
    QString::fromWCharArray(longTempPath, longLen):
    QString::fromWCharArray(tempPath, len);
    }

    }

    То есть при каждом вызове данной функции будет вызываться GetLongPathName().
    Потом почитал в каких случаях GetTempPath() возвращает «короткий» путь, оказывается в XP и то иногда нет(?) путь действительно возвращается «короткий», почему так делалось можно почитать в относительно старой статьи в достаточно известном блоге — http://blogs.msdn.com/b/oldnewthing/archive/2004/12/24/331750.aspx
    В Windows > XP этот путь возвращается полный. Отчего при каждом вызове данного метода вызываем не нужный достаточно медленный системный вызов.

    Потому добавив код:

    wchar_t * pwc = wcschr(tempPath, L'~');
    if (pwc) { // GetTempPath() can return short names, expand.
    wchar_t longTempPath[MAX_PATH];
    const DWORD longLen = GetLongPathName(tempPath, longTempPath, MAX_PATH);
    ret = longLen && longLen < MAX_PATH?
    QString::fromWCharArray(longTempPath, longLen):
    QString::fromWCharArray(tempPath, len);
    }
    else {
    ret = QString::fromWCharArray(tempPath, len);
    }

    И погоняв несколько бенчмарков выяснилось что в среднем прирост скорости до 5 раз.
    На выходных оформлю патчик и кину на кодревью в Qt для этого случая и в пару других мест в коде.

    P.S. Теги не пашут, нет кармы чтобы нормально код оформить.


    1. Maccimo
      09.10.2015 17:09

      Нельзя так делать.
      Ответ почему — в том же блоге, что и заметка про TEMP: http://blogs.msdn.com/b/oldnewthing/archive/2004/04/14/113052.aspx


      1. sebres
        09.10.2015 17:14

        Ну да Novell, Windows NT etc. все еще рулят…


        1. Maccimo
          09.10.2015 17:27

          То, что баг трудновоспроизводим в современном окружении, вряд ли может быть оправданием для его игнорирования.
          От того, что современная версия Windows называется «Windows 10», последняя не перестаёт быть наследницей Windows NT.


          1. maksqwe
            09.10.2015 17:39

            Спасибо за замечание. Очень жаль что так получается, надо почитать поразбиратсья с этим.


      1. maksqwe
        09.10.2015 20:18

        Прочитал эту заметку и, честно говоря, не понял почему так делать не нужно, особенно в случае когда берется значение этих системных переменных:
        — The path specified by the TMP environment variable.
        — The path specified by the TEMP environment variable.
        — The path specified by the USERPROFILE environment variable.
        — The Windows directory.

        «Windows NT adds the hexadecimal hash overflow technique.» Что это за техника я тоже не понял и не нашел что-либо о ней.
        Разве что действительно в редких случаях сети netware будут выдавать не полное имя файла… Судя по их сайту они вообще с 2005 года портируют свою система на Linux-based ядро.

        Зато в мсдне написано как формируются 8.3 формат имени:
        https://support.microsoft.com/en-us/kb/142982
        и там еще внизу написано:
        Applies to
        Microsoft Windows Millennium Edition
        Microsoft Windows 98 Standard Edition
        Microsoft Windows 95
        Microsoft Windows NT Server 4.0 Standard Edition
        Microsoft Windows NT Workstation 4.0 Developer Edition

        Так что считаю данное изменение имеет значение, особенно когда прирост в скорости не абстрактная величина, граничащая со случайностью, в 1.1 — 1.2 раза, а вполне себе в разы. Пока не сможете рассказать что имелось ввиду под «Windows NT adds the hexadecimal hash overflow technique.»


        1. maksqwe
          09.10.2015 20:38

          Уже сам нашел. https://usn.pw/blog/gen/2015/06/09/filenames/

          The file extension is truncated to 3 characters, and (if longer than 8 characters) the file name is truncated to 6 characters followed by ~1. SomeStuff.aspx turns into SOMEST~1.ASP.
          If these would cause a collision, ~2 is used instead, followed by ~3 and ~4.
          Instead of going to ~5, the file name is truncated down to 2 characters, with the replaced replaced by a hexadecimal checksum of the long filename — SomeStuff.aspx turns into SOBC84~1.ASP, where BC84 is the result of the (previously-)undocumented checksum function.

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


          1. maksqwe
            09.10.2015 20:45

            Потому то и майкрософтовцы сами и советуют на виндо-серверах отключать генерацию 8.3 формата.
            https://technet.microsoft.com/en-us/library/ff633453%28v=ws.10%29.aspx

            Issue
            In addition to the normal file names, the server is creating short, eight-character file names with a three-character file extension (8.3 file names) for all files.

            Impact
            Creating short file names in addition to the normal, long file names can significantly decrease file server performance.

            Странно что по умолчанию не выключено. Хотя это критично лишь к файл-серверам.


  1. Maccimo
    09.10.2015 17:21

    Проверка на тильду — костыль и потенциальные грабли, не стоит так делать.
    Её наличие в коротком пути в общем случае никто не гарантирует: http://blogs.msdn.com/b/oldnewthing/archive/2004/04/14/113052.aspx
    Лучше, как советовали в комментариях выше, попробовать отключить генерацию 8.3: https://support.microsoft.com/en-us/kb/121007


    1. sebres
      09.10.2015 18:39
      +1

      Лучше, как советовали в комментариях выше, попробовать отключить генерацию 8.3
      Во первых, отключить простите где? На хосте корпоративного клиента? С удовольствием посмотрю на то как вы там админов уговаривать будете…

      Во вторых, «Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP: 8.3 aliasing cannot be disabled for specified volumes until Windows 7 and Windows Server 2008 R2.» Взято из Naming Files, Paths, and Namespaces (Windows)

      В третих, одно другому совершенно не мешает.

      Проверка на тильду — костыль и потенциальные грабли, не стоит так делать.
      Вы вероятно имеете ввиду «Not all file systems follow the tilde substitution convention». Этой фразе 20-ть лет в обед исполняется. Ну и отключайте это для тех obscure-obsolete FS.

      Кроме того перебор коротких путей без «tilde substitution convention» не имеет большого смысла.
      А защита внутренних location (пример Игоря выше про php-скрипт) достигается совершенно другим способом: не нужно иметь глобальных location позволяющих вернуть любой файл системы. Это моветон.