Після введення в дію динамічного веб-сервера на базі apach + php + postgresql (так і на базі інших систем теж, якщо чесно), вебмастер часто виявляє, що продуктивність системи починає з більшою чи меншою активністю прагнути до нуля, інколи досягаючи при напливах відвідувачів. Стандартними діями вебмастера при цьому є гарячковий читання документації, пошук в Інтернеті усіляких корисних порад, спілкування у форумах і інші рухи тіла, типові для «комп’ютерного авралу». При цьому дуже часто знаходяться якісь обривки інформації, відповіді із серії «у мене запрацювало, чому в тебе не працює не знаю», суперечливі поради та посилання на мегабайтні исходники, розбір яких загрожує затягнутися на кілька років…

Тому нижче зроблена спроба в компактному вигляді викласти ті речі, які найбільш істотно впливають на продуктивність сервера. Не можна сказати, що цей список не є повним і закінченим, але він може послужити непоганою їжею для роздумів. А якщо у вас є якісь свої напрацювання та ідеї з приводу» — пишіть автору, він постарається врахувати їх в наступній редакції цієї статті…

Настройка php

Настройка php, за великим рахунком, зводиться до включення persistent connections і, відповідно, використання pg_pconnect() замість pg_connect() в скриптах. Для цього у файлі php.ini треба вказати pgsql.allow_persistent = on У деяких форумах зустрічалася рекомендація встановити ще й pgsql.auto_reset_persistent = on для визначення «битих» єднань, але то «биті з’єднання» зустрічаються дуже рідко, то вони проявляються як-то непомітно… Словом, цього можна й не робити. Обмежень за кількістю з’єднань в php можна не встановлювати, залишивши
pgsql.max_persistent = -1
pgsql.max_links = -1

Ефект від переходу на постійні з’єднання дуже помітний, особливо на відвідуваних сайтах. Завантаження відразу падає відсотків на двадцять-тридцять, а то й більше! Тільки не лякайтеся виявляючи в top’е купу postres’ор у стані sbwait…

Налаштування postgresql

Налаштування postgresql здійснюється з допомогою редагування файлу postgresql.conf та подальшого перезапуску постмастера. Незважаючи на те, що деякі параметри можна змінювати «на льоту», практика показала, що повний перезапуск дозволяє набагато точніше і швидше відстежити вплив тих чи інших змін конфігурації на поведінку системи в цілому, так що має сенс використовувати саме цей метод.

Налаштування shared_buffers в postgres’е дуже важлива. Чим більше пам’яті йому виділено, тим більше даних він зможе використовувати не звертаючись до диска. Але якщо пам’яті виділити занадто багато, то іншим процесам її може не вистачити, і вони будуть використовувати файл підкачки. Тому з налаштуванням цього параметра доведеться поекспериментувати — крім усього іншого, дуже багато залежить від конкретики вашого сайту, зокрема від того, наскільки часто запитуються одні і ті ж дані. В якості стартової точки можна спробувати виділити postgres’у 40% пам’яті, якщо він працює на одній машині з веб-сервером, і 70%, якщо це виділений сервер баз даних. Тільки не забудьте, що до того, як вказувати кількість пам’яті в файлі postgresql.conf, вам треба налаштувати операційну систему, аби вона дозволила цю пам’ять забрати, інакше postgresql просто не запуститься, написавши в лог, що не вдалося отримати необхідну пам’ять. Про те як налаштувати виділення необхідної пам’яті в різних ОС докладно написано в документації postgresql. Ця пам’ять виділяється один раз при старті сервера.

Такий корисний параметр — sort_mem. Ця пам’ять використовується для сортування отриманих наборів даних, і велика її кількість корисно, якщо ваші запити часто використовують select … order by… Але з цим параметром треба бути дуже обережним — мало того, що вказане вами кількість пам’яті, що виділяється кожному процесу, так воно ще й може виділятися кілька разів для складних запитів! Так що з цим параметром теж варто «пограти» — спробуйте змінювати його значення в діапазоні, скажімо, від 1 Мб до 128 Кб. Причому іноді результати бувають парадоксальними — зменшення пам’яті веде до підвищення продуктивності, по всій видимості, через створення безлічі маленьких тимчасових файлів, які операційна система успішно кешує у вільній пам’яті.

Якщо завдання, що виконуються на сервері не є критичними і можлива втрата кількох записів при аварії вас не лякає, то варто також скасувати примусову запис на диск результатів кожної транзакції. Робиться це зазначенням fsync = false. При цьому результати ваших змін будуть зберігається в пам’яті і записуватися на диск цілими блоками, що дозволить досить помітно збільшити продуктивність. Але, як було зазначено вище, якщо раптом сервер «впаде», то результати кількох останніх оновлень можуть бути втрачені.

Дуже сильно впливає на швидкість роботи грамотне індексування таблиць. Про індекси можна писати (і вже написано багато, але основний принцип — індексувати треба ті поля, які використовується для вибірки даних (перевіряються в where). Складові індекси (в яких використовується кілька полів) працюють, якщо відбір відбувається з умовою and, якщо використовується or, то треба створювати кілька окремих індексів.

Однак для маленьких таблиць (скажімо, до 500 рядків) перебір майже завжди виявляється швидше, ніж використання індексів. Тут можна застосувати маленьку хитрість: в postgresql.conf вказати enable_seqscan = false (це заборонить перебір для тих таблиць, у яких є індекси) і видалити всі індекси в маленьких таблицях (індекси, автоматично створювані для первинних ключів, можна видалити, використовуючи drop constraint).

Непоганий виграш в продуктивності може дати і оптимізація самих sql запитів, особливо тих, які використовуються найчастіше. Для того, щоб їх можна обчислити в скриптах перенумерувати всі запити і перед кожним викликом pg_query() записувати в лог (або таблицю) номер запиту. А потім просто проаналізувати лог… Для того, щоб подивитися як буде виконуватися запит можна (треба!) використовувати команду explain. Врахуйте, що в деяких випадках навіть просте перегрупування умов вибірки в секції where може змінити план виконання запиту!

У деяких випадках може допомогти і використання представлень (views). Справа в тому, що при видачі звичайного запиту sql сервер його аналізує, створює план виконання і потім виконує. А якщо використовується уявлення, аналіз і складання плану проводиться тільки при його створенні. Якщо запити виконуються часто, то зекономлений час роботи процесора може виявитися дуже помітним. Не кажучи вже про те, що запити в скриптах стануть набагато коротше і наочніше…

Практично у всіх посібниках (і в документації postgresql) можна зустріти рекомендації регулярно запускати vacuum analyze, для стиснення таблиць. Рекомендація правильна і корисна, але недостатня. Практика показала, що без більш-менш регулярних запусків vacuum full analyze продуктивність системи поступово падає, причому чим далі, тим більше. Різниця між vacuum і vacuum full полягає в тому, що full фізично переписує на диску всю таблицю таким чином, щоб в ній не залишалося «дірок» від віддалених або оновлених записів. Але його недолік в тому, що під час роботи таблиця повністю блокується, що може призвести до проблем на популярному сервері — почне накопичуватися чергу запитів, які очікують доступу до бази, кожен запит вимагає пам’яті, пам’ять закінчується, починається запис в swap, з-за відсутності пам’яті сам vacuum теж починає використовувати swap і все починає працювати дуже-дуже повільно.

Тут можна використовувати наступний трюк — під час роботи vacuum’а перенаправляти відвідувачів на сторінку з поясненнями, що йде профілактика і сервер відновить свою роботу через кілька хвилин. При використанні веб-сервера apache це легко робиться за допомогою mod_rewrite: ваш оптимізуючий скрипт при запуску створює, а при закінченні роботи видаляє файл /home/site/optimizer.pid, а в apache включається mod_rewrite і вказується
rewritecond /home/site/optimizer.pid -f
rewriterule .* /optimization_message.html

Для того щоб зменшити час, протягом якого відвідувачі не можуть дістатися до вашого сайту, можна перенаправляти відвідувачів тільки в той час, коли оптимізуються великі і частоиспользуемые таблиці, а решта «чистити» паралльно з роботою сайту. Якщо дані в базі оновлюються дуже часто, то можна, скажімо, щогодини запускати vacuum analyze, а раз на добу — vacuum full analyze. Як правило, «час недоступності» сервера при такому підході можна скоротити до однієї-двох хвилин навіть на дуже великих сайтах.

Крім того, треба врахувати, що vacuum не оптимізує індекси, тому після відпрацювання vacuum full analyze варто запускати ще й reindex.

Зрозуміло, дуже великий вплив на продуктивність надає і структура вашої бази даних, але ця область дуже сильно залежить від конкретної задачі і в цій статті порушуватися не буде.

Налаштування apache

Конфігураційний файл apache, як правило, знаходиться в /usr/local/apache/conf/httpd.conf, а змусити сервер його перечитати можна з допомогою проргаммы /usr/local/apache/bin/apachectl.

Основною метою подальших рекомендацій є визначення і обмеження кількості «апач», що виконуються в кожен момент часу (передбачається, що сам сервер у вас вже налаштований і працездатний). Справа в тому, що (умовно) на кожного відвідувача сайту «витрачається» процес apache, і кожен такий процес витрачає пам’ять і процесорний час. Тому якщо у вас буде дуже багато запущених серверів, то оперативної пам’яті не вистачить, а використання swap’а сильно уповільнює роботу сайту. З іншого боку, якщо запущених серверів мало, то при заході користувача буде витрачатися час на створення нового процесу, що знову ж таки призводить до затримок і витрати процесорного часу.

З вищесказаного ясно, що з одного боку бажано завжди мати деякий запас запущених серверів для обслуговування відвідувачів, а з іншого — треба вбивати зайві сервера, щоб не займати занадто багато пам’яті. Зрозуміло, що конкретні цифри залежать від відвідуваності вашого сайту і доступних ресурсів, тому наведені нижче цифри варто використовувати тільки як основу, коригуючи їх за необхідності.

Максимальна кількість «апач», які можуть бути запущені одночасно визначається параметром maxclients. Це число має трохи перевищувати максимальну кількість відвідувачів, які можуть в якийсь момент часу опинитися у вас на сайті. У той же час, якщо охочих до вас потрапити багато, а ресурсів сервера для їх обслуговування не вистачає, то надмірно високе число запущених серверів тільки загальмує всю роботу. Тому бажано встановити якесь розумне обмеження, скажімо 150 або 200.

Час, протягом якого сервер чекає відгуків від клієнта визначається параметром timeout. Обриви зв’язку іноді трапляються і якщо браузер відвідувача звернувся до вашого сервера, який не отримав відповіді і послав повторний запит (скажімо, користувач натиснув reload), то у вас запустяться два «апача», причому один з них буде просто висіти і протягом зазначеного часу чекати, коли відвідувач підтвердить своє бажання подивитися сторінку. За замовчуванням цей параметр встановлено в 300 секунд, але значно більш ефективним виявилося знизити його до 30.

Включення підтримки keepalive може помітно полегшити життя. Справа в тому, що в «звичайному» режимі для передачі кожного файлу клієнту потрібно встановити з’єднання, і якщо у вас на сторінці, наприклад, є 10 картинок, то доведеться встановлювати і розривати 10 сполук для їх передачі. А в режимі keepalive сервер після передачі файлу з’єднання не розриває і наступні запити цього клієнта обробляє, використовуючи вже встановлене з’єднання. Таким чином економиться час на встановлення та розрив з’єднань, причому для популярних сайтів ця різниця може бути дуже помітна!

Для keepalive сполук, точно також як і для звичайних, треба встановити timeout за допомогою параметра keepalivetimeout. Так як сервер, встановив з’єднання з клієнтом, недоступний для інших клієнтів поки з’єднання не буде розірвано, занадто велике значення може призвести до купи серверів, нічого не роблять і просто чекають не захоче їх завантажити клієнт ще що-небудь. Причому в цей же час натовп нових відвідувачів може виявити, що ваш сайт не відповідає, так як досягнуто максимальну кількість дозволених «апачів»… Найбільш практичним значенням параметра keepalivetimeout є щось між десятьма і двадцятьма секундами.

Як відомо, тривале використання якоїсь програми може призвести до «витоків пам’яті» або якихось інших ресурсів. Щоб уникнути таких проблем є два параметри: maxkeepaliverequests і maxrequestsperchild. Перший параметр відповідає за примусове «вбивство» процесу після обробки зазначеного числа keepalive запитів, а другий — після зазначеного числа «звичайних» запитів. В принципі, на абсолютній більшості систем витоків пам’яті бути не повинно і ці параметри можна зробити досить великими — по кілька тисяч. Але на всяк випадок постежите за поведінкою сервера — не виключено, що «витоку» виявляться в котрійсь із бібліотек, які ви використовуєте. Найзручніше рухатися «знизу вгору» — спочатку встановити значення невеликими, скажімо, 100 і 50, а потім їх збільшувати, спостерігаючи за поведінкою сервера.

Ну і ще три параметри, які регулюють кількість запущених процесів: startservers, minspareservers і maxspareservers. Перший, при старті сервера запускає вказане число «апачів». Другий визначає мінімальне число бездельничающих в очікуванні нового клієнта серверів, а третій — їх максимальну кількість. В якості першого кроку можна спробувати, скажімо, 25, 2 і 10, а далі подивитися на завантаженість сайту…

Перевірка результатів

Найбільш простим методом швидко оцінити вплив зроблених вами змін в налаштуваннях є команда top. У верхній частині вікна при її роботі виводиться корисна статистична інформація, приблизно така:

last pid: 40772; load averages: 0.52, 0.50, 0.50 up 23+17:53:40 09:51:01
233 processes: 1 running, 231 sleeping, 1 zombie
cpu states: 21.2% user, 0.0% nice, 6.4% system, 0.4% interrupt, 72.0% idle
mem: 367m active, 239m inact, 123m wired, 48m cache, 112m buf, 107m free
swap: 1024m total, 13m used, 1011m free, 1% inuse
В першу чергу, треба звертати увагу на load averages — чим вище числа, тим гірше. В ідеалі, в нормальному стані вони не повинні перевищувати одиниці. Наступне, до чого варто придивитися — це використання файлу підкачки. Праворуч від рядка swap можуть з’являтися повідомлення про записи в swap-файл (page out) або про читання з нього (page in). Чим частіше такі повідомлення з’являються — тим гірше. Дискові операції вже дуже повільні… Ну і, звичайно, треба стежити за кількістю вільної пам’яті і завантаження процесора. Втім, якщо ви зумієте домогтися ситуації, коли swap-файл не буде використовуватися, то, швидше за все, все інше швидко прийде в норму…