Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni
🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!Пости з тегом #Оптимізація
11.02.2025
Причина уповільнення вебзастосунка майже завжди в базі
Можу згадати тільки один випадок, де повільні запити до вебзастосунка були спричинені процесорним часом самого застосунку. Це коли я оптимізував css_parser. А в типових ситуаціях завжди потрібно зрозуміти, що робиться з базою.
Або це якийсь один жирний запит — тоді доведеться робити EXPLAIN
, шукати вірний індекс, може, навіть переосмислити схему даних. Але спочатку обовʼязково зрозуміти, який саме запит повільний: без конкретних цифр краще не продовжувати.
Або це багато запитів в циклі — це вже хиба логіки застосунку, та потрібно шукати, як ті запити обʼєднати. Робити запити у циклі майже завжди погана ідея — але зазвичай це не так очевидно, бо процес прихований в шарах логіки. Тут, як і в першому випадку, допоможе логування запитів. (А ще — інструмент на кшталт New Relic, Sentry, Scout тощо, який автоматично збиратиме статистику по запитах.)
В будь-якому разі, що там сам застосунок робить - JSON кодує, HTML малює — буде незрівнянно простіше. Така вона, модель класичного вебзастосунка — важку роботу він перекладає на базу. Помітне навантаження на процесор річ зазвичай передбачувана, бо потребує якоїсь алгоритмічної компоненти, а не тільки пересипання даних з місця на місце.
Отже, будь-яка робота з оптимізації у вебі починається з розуміння та налагодження бази даних.
12.02.2025
Оптимізація: треба йти глибше!
Далі, коли шукаєш причини уповільнення, мусиш ставити під сумнів кожний свій висновок та знаходити для нього обʼєктивні обґрунтування.
Вчора хотів написати пост “чому OpenSearch повільний, коли у вас надто багато шардів?” (Шард — це неподільний блок даних, як партиція; індекс — тобто колекція — складається з фіксованого набору шардів.) Всі радять робити шарди розміром у 10-50 Гб… але чи значить це, що робити їх меншими обовʼязково погано?
Виявилося, не дуже; дійсно, кожен шард коштує трохи памʼяті, але поки памʼять не вичерпана, додаткові шарди не суттєво впливають на швидкість операцій. Негативно — ні, але позитивно — теж ні, навіть попри те, що шард також є одиницею паралелізації. Бо щоб пришвидшити щось паралелізацією, потрібно більше ядер процесора, а не потоків.
Але це я знаю після того, як перевірив. А міг би залишитись із такою гадкою, витратити час на переформування індексів, та… нічого не отримати на заміну. Та це не перша та не остання ланка в розслідуванні! Перевіряти потрібно кожну.
Легко вказати на неприємну частину проєкту та сказати: проблема тут! (Мені, наприклад, доводилося витрачати час на алгоритмічну оптимізацію “некрасивого коду” там, де можна було обійтися як максимум кешем.) Але взагалі шукати причини — це ціле мистецтво. Треба знати, що важливо, що не важливо, та параноїдально собі не довіряти (що, до речі, називається науковим методом.)
Ото було колись — шукав, чому API повільний. Локально наче все було чудово, але більшість часу API проводив у серіалізації, тому я довго обирав оптимальний формат. А потім виявилося, що в продакшні є ще рукостискання TLS, та вся проблема зовсім не в серіалізації, а у встановленні підключення. Так що завжди перевіряти!
13.02.2025
Історія однієї пакетної оптимізації
В тій історії про повільний API є нюанс. Це був внутрішній API між двома тісно повʼязаними сервісами — фактично, RPC, віддалений виклик процедури, бо один з сервісів був витягнутий з іншого (та переписаний на Go.)
Після цього в коді залишилися ще тисячі звернень до функцій, які стали тепер зовнішніми. Функції стосувалися обчислення метрик та раніше були суворо закешовані. Але нові вимоги додавали більше фільтрів та закешувати всі комбінації стало неможливим. Переписування на Go, те ще й зі станом в памʼяті робили обчислення швидкими, втім API накладав вагому ціну.
Перше та критичне покращення в таких обставинах — це впровадити стале підключення замість щоразового. Головне, що це уникає вище загаданого рукостискання TLS. Коли у вас швидкий API, то це реально може бути найдорожчою частиною. (Взагалі цікаво, що головні витрати на шифрування в інтернеті відбуваються в момент підключення.)
Друге — як не крути, тисяча запитів у 10 мс це вже 10 секунд. Звісно, з боку Go я міг робити їх паралельно, тому впровадив пакетні запити та переробив декілька місць. Це досить легко, поки запити відбуваються в циклі — наприклад, будується якийсь графік. Але ж як я казав, застосунок писався із вільним доступом до тих функцій…
Особливо важко було позбавитись серійних запитів у звітах. Там були величезні багатосторінкові шаблони, які зверталися до метрик як у себе вдома. Відокремити доступ до даних від коду звіту було непіднімною задачею.
Тоді я зробив хитрість. Спочатку запускав звіт із фальшивим джерелом даних, яке повертало нульові результати та запамʼятовувало кожний виклик. Потім — викликав справжній сервіс із пакетом викликів, які назбирав. Та нарешті — другий раз запускав той самий звіт, але тепер із підготованим набором даних. Звіт став швидким, а головне — я не перетворив розробку звітів у складну інженерну задачу. It just works!