apache2 недостаточно памяти при обслуживании больших статических файлов

Вопрос или проблема

Я запускаю сервер apache2 с несколькими виртуальными хостами. Он работает на достаточно ограниченном в ресурсах raspberry pi 4. Особенно проблема в ОП (4 ГБ). Трафика не так много, я в основном использую его для себя, чтобы размещать nextcloud, сайт для потокового вещания и некоторые статические файлы.

Проблема в статических файлах. Всякий раз, когда я или кто-то другой загружает файл, кажется, что весь файл загружается в ОП. Это не проблема для небольших файлов, но всё, что больше 500 МБ, вызывает серьёзные проблемы, заполняя SWAP и в конечном итоге приводя к OOM убийству apache.

Я никак не могу понять, как сделать так, чтобы файлы просто стримились с диска, вместо кэширования их в памяти перед отправкой. Это может быть жизнеспособным вариантом, если у вас есть 128 ГБ ОП на полноценном сервере, но на моём бедном pi это невозможно.

Итак, как заставить apache просто стримить файл с диска? И почему он загружает файл в оперативную память вообще? диск I/O намного быстрее, чем сеть, поэтому нет смысла кэшировать файл с диска в память.

Я попробовал:

  • переключиться с mpm_prefork на mpm_event.
  • EnableSendfile On, что, похоже, ничего не дает.
  • mod_xsendfile с XSendFile On
  • Искать другие решения, включая
    • это, не применимо, потому что один рабочий использует всю оперативную память для загрузки одного большого файла в ОП
    • это, неприемлемо, потому что, хотя я и запускаю PHP, проблема заключается в get-запросах к статическим файлам на диске, а не в PHP. Также не могу установить больше ОП.
    • это, где я даже не понимаю, о чем они говорят с этим конфигурационным файлом. Также это не проблема PHP.
    • Многое другое, связанное с большими загрузками на PHP и запросами PUT, и другими PHP штуками.

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

Один из моих виртуальных хостов, для которого существует данная проблема:

<IfModule mod_ssl.c> <VirtualHost *:443> ServerName host.tld ServerAdmin [email protected] DocumentRoot /var/www/web CustomLog ${APACHE_LOG_DIR}/access.log combined # Создать более приятный и современный вид списка IndexOptions FancyIndexing IconsAreLinks FoldersFirst EnableSendfile On EnableMMAP Off <IfModule mod_xsendfile.c> <Directory "/var/www/web"> XSendFile On XSendFilePath /var/www/web </Directory> </IfModule> <IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^/\.well-known/carddav /nextcloud/remote.php/dav [R=301,L] RewriteRule ^/\.well-known/caldav /nextcloud/remote.php/dav [R=301,L] RewriteRule ^/\.well-known/webfinger /nextcloud/index.php/.well-known/webfinger [R=301,L] RewriteRule ^/\.well-known/nodeinfo /nextcloud/index.php/.well-known/nodeinfo [R=301,L] RewriteRule ^/ocm-provider/?$ index.php [QSA,L] </IfModule> <IfModule mod_headers.c> # запрещаем встраивание с non https Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains" # предотвращаем интерпретацию текстовых файлов как javascript Header onsuccess unset X-Content-Type-Options Header always set X-Content-Type-Options "nosniff" # включаем фильтр защиты от межсайтовых скриптов Header onsuccess unset X-XSS-Protection Header always set X-XSS-Protection "1; mode=block" # предотвратить индексацию сайта поисковыми системами Header onsuccess unset X-Robots-Tag Header always set X-Robots-Tag "noindex, nofollow" # предотвращаем встраивание в другие сайты Header onsuccess unset X-Frame-Options Header always set X-Frame-Options "SAMEORIGIN" Header onsuccess unset X-Permitted-Cross-Domain-Policies Header always set X-Permitted-Cross-Domain-Policies "none" # предотвратить отправку браузером данных реферера при нажатии на ссылки Header onsuccess unset Referrer-Policy # Header всегда установлена Referrer-Policy “no-referrer” Header всегда установлена Referrer-Policy “same-origin” </IfModule> # Частная, защищённая паролем директория <Location "/private"> # аутентификация AuthType basic AuthName "My Server" # Cache учетных данных с socache AuthBasicProvider socache dbd # также необходимо для кэширования: говорить кэшу кэшировать dbd-запросы AuthnCacheProvideFor dbd AuthDBDUserPWQuery "SELECT password FROM authn WHERE user = %s" # блокировать всех не пользователей. Require valid-user </Location> <Location /nextcloud/> Require all granted AllowOverride All Options FollowSymLinks MultiViews <IfModule mod_dav.c> Dav off </IfModule> # отключить базовую аутентификацию для этого Satisfy Any </Location> # Настройки SSL SSLEngine on SSLCertificateFile /etc/letsencrypt/live/host.tld/cert.pem SSLCertificateChainFile /etc/letsencrypt/live/host.tld/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/host.tld/privkey.pem #Include /etc/letsencrypt/options-ssl-apache.conf # Добавить имя vhost к записям в логе: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common </VirtualHost> </IfModule> # vim: syntax=apache ts=4 sw=4 sts=4 sr et 

Мои включённые модули:

access_compat.load authn_core.load authz_core.load autoindex.load dir.conf headers.load mpm_event.load proxy.load reqtimeout.conf security3.load socache_shmcb.load status.load alias.conf authn_dbd.load authz_host.load dbd.load dir.load mime.conf negotiation.conf proxy_fcgi.load reqtimeout.load setenvif.conf ssl.conf unique_id.load alias.load authn_file.load authz_user.load deflate.conf env.load mime.load negotiation.load proxy_http.load rewrite.load setenvif.load ssl.load xsendfile.load auth_basic.load authn_socache.load autoindex.conf deflate.load filter.load mpm_event.conf proxy.conf proxy_wstunnel.load security3.conf socache_memcache.load status.conf 

mpm_event.conf:

<IfModule mpm_event_module> StartServers 2 MinSpareThreads 25 MaxSpareThreads 75 ThreadLimit 64 ThreadsPerChild 25 MaxRequestWorkers 150 MaxConnectionsPerChild 0 </IfModule> 

apachectl -V:

Server built: 2024-07-17T18:57:26 Server's Module Magic Number: 20120211:126 Server loaded: APR 1.7.0, APR-UTIL 1.6.1 Compiled using: APR 1.7.0, APR-UTIL 1.6.1 Архитектура: 64-битная Server MPM: event многопоточный: да (фиксированное количество потоков) форк: да (переменное количество процессов) Сервер скомпилирован с… -D APR_HAS_SENDFILE -D APR_HAS_MMAP -D APR_HAVE_IPV6 (IPv4-совмещённые адреса включены) -D APR_USE_PROC_PTHREAD_SERIALIZE -D APR_USE_PTHREAD_SERIALIZE -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT -D APR_HAS_OTHER_CHILD -D AP_HAVE_RELIABLE_PIPED_LOGS -D DYNAMIC_MODULE_LIMIT=256 -D HTTPD_ROOT="/etc/apache2" -D SUEXEC_BIN="/usr/lib/apache2/suexec" -D DEFAULT_PIDLOG="/var/run/apache2.pid" -D DEFAULT_SCOREBOARD="logs/apache_runtime_status" -D DEFAULT_ERRORLOG="logs/error_log" -D AP_TYPES_CONFIG_FILE="mime.types" -D SERVER_CONFIG_FILE="apache2.conf" 

/proc/meminfo при загрузке 2-гигабайтного статического файла с помощью curl. Текст, когда завершилось соединение ssh, примерно через 10 секунд после начала загрузки. К этому времени curl еще не получил ни одного байта.

MemTotal: 3880848 kB MemFree: 190000 kB MemAvailable: 479864 kB Buffers: 52840 kB Cached: 458780 kB SwapCached: 63000 kB Active: 502232 kB Inactive: 2665432 kB Active(anon): 318696 kB Inactive(anon): 2479580 kB Active(file): 183536 kB Inactive(file): 185852 kB Unevictable: 26756 kB Mlocked: 26756 kB SwapTotal: 4095996 kB SwapFree: 2418916 kB Dirty: 52 kB Writeback: 192 kB AnonPages: 2626292 kB Mapped: 172496 kB Shmem: 134752 kB KReclaimable: 292096 kB Slab: 411324 kB SReclaimable: 292096 kB SUnreclaim: 119228 kB KernelStack: 8768 kB PageTables: 22876 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 6036420 kB Committed_AS: 8174592 kB VmallocTotal: 133143592960 kB VmallocUsed: 27632 kB VmallocChunk: 0 kB Percpu: 3216 kB AnonHugePages: 0 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB FileHugePages: 0 kB FilePmdMapped: 0 kB CmaTotal: 65536 kB CmaFree: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB 

Ответ или решение

Теория

Вы столкнулись с проблемой нехватки памяти при обслуживании больших статических файлов сервером Apache2 на Raspberry Pi 4 с ограниченной оперативной памятью (4 ГБ). Проблема заключается в том, что когда пользователь пытается скачать большой файл, он загружается в оперативную память (RAM). Это приводит к заполнению SWAP и в конечном итоге к завершению работы Apache из-за ошибки Out of Memory (OOM). В отличие от обработки малых файлов, загрузка больших файлов в RAM становится непрактичной на устройстве с ограниченными ресурсами. В идеале, файлы должны считываться с диска и передаваться непосредственно клиенту, минимизируя использование RAM.

Обычно, Apache2 и другие HTTP-серверы кэшируют данные в оперативную память для увеличения скорости обслуживания пользователей. На высокоразмерных серверах такая стратегия оправдана, однако в вашем случае это приводит к перегрузке системы.

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

Пример

В вашей конфигурации Apache2 включены настройки для оптимизации работы с большими файлами, например:

  • EnableSendfile On: предполагает использование функции sendfile для отправки файлов напрямую из файловой системы в сетевой стек.
  • EnableMMAP Off: отключает отображение файлов в память, что может снизить количество используемой RAM.
  • mod_xsendfile: модуль для Apache, который позволяет серверу считывать и отправлять файлы из файловой системы.

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

Применение

  1. Понимание работы sendfile: Хотя вы включили EnableSendfile On, необходимо убедиться, что конкретная файловая система и железо поддерживают технологию sendfile. На некоторых конфигурациях это может не работать эффективно, особенно на ARM-процессорах, используемых в Raspberry Pi.

  2. Оптимизация MPM (Multi-Processing Module):

    • Переход на mpm_event — это хороший шаг, поскольку он эффективнее управляет потоками. Однако настройка параметров MPM может улучшить работу. Рассмотрите возможность уменьшения MaxRequestWorkers и других параметров потоков, чтобы адаптировать их к вашим ограничениям RAM.
  3. Настройка заголовков и кэширования:

    • Хотя не все заголовки непосредственно влияют на использование RAM, использование таких опций, как Cache-Control и Expires, может помочь оптимизировать передачу данных через сетевые протоколы, минимизировать нагрузку на сервер.
  4. Модули и плагины:

    • Убедитесь, что другие модули, такие как mod_deflate и mod_proxy, не активированы, если их функции вам не нужны, поскольку они могут дополнительно нагружать систему.
  5. Использование lighttpd или nginx: Несмотря на ваше нежелание добавлять nginx в конфигурацию, эта замена могла бы принести пользу. Nginx славится своей возможностью эффективно управлять большими файлами с минимальной загрузкой памяти. Он изначально проектировался для отправки статического контента.

  6. Мониторинг и логирование:

    • Проводите регулярный анализ логов для отслеживания использования ресурсов. Обратите внимание на временные качки использования памяти, связанные с загрузкой файлов, чтобы понять, какие операции потребляют больше всего памяти.
  7. Использование RPi в качестве шлюза:

    • Вариантом может быть использование Raspberry Pi в качестве прокси для перенаправления запросов на более мощный сервер или облачное решение, которое будет управлять большими файлами.

Эти шаги помогут уменьшить нагрузку на оперативную память вашего Raspberry Pi и избежать возникновения ошибок OOM при подаче больших статических файлов. Экспериментируя с конфигурацией, следите за изменениями в логах и наблюдайте за ресурсами, чтобы находить наиболее эффективные решения для вашего сервера.

Оцените материал
Добавить комментарий

Капча загружается...