HTTP-кешування API: де ламається і що втрачає замовник
HTTP-кешування — це шар, який стоїть між клієнтом і бекендом і вирішує, чи можна віддати вже готову відповідь, не піднімаючи застосунок.
Коли його немає, кожен запит мобільного клієнта, кожне перемальовування інтерфейсу і кожен повторний виклик з інтеграції йдуть у базу й перераховують один і той самий результат.
На середньому API це перетворюється на зростання затримок API: час відповіді зростає не лінійно, а ступенями, бо черга запитів починає чекати на звільнення з'єднань з базою.
Далі вмикається ефект доміно. Навантаження на бекенд зростає разом із кількістю клієнтів, а не разом з обсягом реально нових даних — типова картина, коли 80% трафіку припадає на ті самі довідники, каталоги та списки.
За пікового навантаження запас за ресурсами закінчується раніше, ніж надходить основний трафік, і починаються відмови під час піків: спочатку поодинокі таймаути, потім масові помилки на найбільш «гарячих» ендпоінтах, які якраз і викликаються найчастіше.
Кешування API знімає це навантаження на рівні протоколу, а не за рахунок додавання серверів.
Заголовки ETag і Last-Modified дозволяють клієнту запитати «чи змінилося» і отримати коротку відповідь замість повного тіла, Vary розводить кеш за потрібними ознаками запиту, а коректні директиви свіжості визначають, скільки відповідь живе без звернення до застосунку. У результаті повторні запити закриваються на межі, а бекенд займається лише новими даними.
Розбираємо, де саме ламається HTTP-кешування в реальних API і як налаштувати його так, щоб затримки та відмови перестали бути вузьким місцем.
Що дає кероване кешування: вимірювані ефекти
HTTP-кешування перестає бути «налаштуванням на око», коли кожна відповідь має валідатор: ETag або Last-Modified.
Клієнт надсилає If-None-Match / If-Modified-Since, сервер відповідає 304 Not Modified без тіла — час відповіді API на повторних запитах падає в рази, бо мережею їде лише заголовок, а не корисне навантаження.
Бекенд при цьому не перезбирає відповідь: розвантаження йде не за рахунок кешу на боці клієнта, а за рахунок того, що важка логіка взагалі не запускається.
Заголовок Vary вирішує другу половину задачі — коректний поділ кешу за Accept-Encoding, Authorization та мовою. Без нього проксі віддає одному клієнту відповідь, зібрану для іншого: чужі права, неправильну локаль, стиснення не під той формат.
Це і є окремий клас відмов — не падіння сервісу, а тиха видача неправильних даних, яку ловлять уже в проді. Кероване кешування прибирає його на рівні контракту, а не латками в коді.
Стабільність під навантаженням — наслідок. Коли частка повторних запитів, закритих валідацією, зростає, пікове навантаження на бекенд перестає масштабуватися разом із кількістю клієнтів: ті самі сервери тримають більше трафіку, а час відповіді залишається передбачуваним. Розбираємо, як це налаштовується на практиці — від вибору валідатора до політики для проксі та CDN.
Підходи до кешування: порівняння режимів і обмежень
Кешування — це не «увімкнути й забути», а вибір режиму під профіль трафіку. Нижче — реальні альтернативи, між якими доводиться обирати під час проєктування API, з їхньою застосовністю та обмеженнями. Окремо варто дивитися на те, хто керує політикою: застосунок, зворотний проксі чи клієнт.
| Варіант | Коли підходить | Обмеження |
|---|---|---|
| Без кешування | Персональні дані, платіжні операції, відповіді, що залежать від прав користувача | Кожен запит іде до джерела, зростає навантаження й затримка |
Cache-Control: no-store |
Чутливі відповіді, які не можна залишати на диску | Не дає виграшу за швидкістю, лише захищає дані |
max-age + public |
Публічні довідники, каталоги, контент, що рідко змінюється | Застарівання даних у межах TTL, потрібне скидання під час оновлення |
ETag + If-None-Match |
Ресурси, які змінюються нечасто, але мають бути свіжими | Зайвий round-trip на перевірку, відповідь 304 замість тіла |
Last-Modified + If-Modified-Since |
Файли, статика, документи з датою зміни | Точність до секунди, хибні спрацювання під час перезбереження |
Vary за заголовками |
Контент залежить від мови, авторизації, типу пристрою | Дробить кеш на багато варіантів, зростає обсяг сховища |
| Керовані політики на проксі | Єдині правила для десятків сервісів | Вимагають узгодження із застосунком, інакше конфлікт заголовків |
Комбінувати режими доводиться майже завжди: ETag для динаміки, max-age для статики, Vary там, де відповідь залежить від контексту запиту. Ключовий ризик — неузгодженість заголовків між застосунком і проксі: клієнт отримує застарілу відповідь або, навпаки, кеш не працює взагалі.
Вибір підходу фіксується в керованих політиках на рівні шлюзу, а не в коді кожного сервісу. Так правила залишаються передбачуваними, а обмеження кешування видно до релізу, а не після інциденту.
Як ми впроваджуємо кешування: етапи інженерної роботи
Кешування API — це не «увімкнути плагін», а послідовність рішень: де стоїть кеш, за яким ключем він розрізняє відповіді та в який момент дані вважаються застарілими. Нижче — як ми ведемо таку роботу, щоб після запуску поведінка була передбачуваною, а не «наче працює».
- Аудит заголовків. Знімаємо реальні відповіді API і дивимося, що віддає сервер зараз: чи є
Cache-Controlз осмисленимmax-age, чи проставляєтьсяETagабоLast-Modified, чи не губитьсяVaryна заголовках авторизації та мови. Часто з'ясовується, що частина відповідей кешується там, де не повинна, а частина — навпаки, не кешується взагалі. - Проєктування політик. Для кожного типу ресурсу визначаємо режим: публічні довідники — довгий
max-ageзіstale-while-revalidate, персональні дані —privateбез спільного кешу, змінювані сутності — валідація черезETagі відповідь304. Окремо фіксуємо ключ кешу та набір полів уVary. - Налаштування кешу. Прописуємо заголовки на рівні застосунку та проміжного шару, синхронізуємо їх між собою, щоб проксі не переписував політику застосунку. Налаштовуємо коректну роботу з умовними запитами
If-None-MatchіIf-Modified-Since. - Перевірка інвалідації. Проганяємо сценарії зміни даних: оновлення запису, зміна прав доступу, публікація нової версії. Переконуємося, що після запису клієнт і проміжний кеш отримують свіжі дані, а не віддають застарілі.
- Навантажувальна перевірка. Дивимося частку відповідей
304і влучань у кеш, час відгуку до і після. Мета — зростання частки валідованих відповідей і зниження навантаження на джерело. - Передача документації. Віддаємо карту політик за ендпоінтами, правила інвалідації та інструкцію для розробників: які заголовки змінювати можна, а які зламають кеш.
- Супровід. Після запуску відстежуємо метрики влучань і коригуємо
max-ageтам, де дані змінюються частіше, ніж передбачалося.
Опишіть вашу задачу — повернемося з оцінкою та планом робіт.
Що замовник отримує на руки: конфігурації та регламенти
Робота з HTTP-кешуванням закінчується не «увімкнули заголовки й забули», а набором артефактів, які залишаються в репозиторії та живуть довше за підрядника. Кожен пункт нижче — те, що можна відкрити, перевірити й передати новому інженеру без усних пояснень.
- Конфігурації кешу — робочі файли для зворотного проксі та застосунку: правила щодо
Cache-Control(max-age,s-maxage,stale-while-revalidate), генераціяETagіLast-Modified, налаштуванняVaryпідAccept-Encodingта авторизацію. З коментарями, чому обрано саме такий набір директив для кожного типу відповіді. - Схеми потоків — діаграми шляху запиту: клієнт → CDN → зворотний проксі → застосунок → сховище. На схемі позначено, на якому шарі кешується відповідь, де відбувається ревалідація і який шар відповідає за інвалідацію.
- Політики інвалідації — регламент скидання: за тегами ресурсів, за префіксами URL, за подією зміни даних. Із зазначенням, що чиститься примусово, а що живе до спливу
max-age, і як це співвідноситься з бізнес-логікою. - Документація API — опис ендпоінтів із зазначенням кешованості: які відповіді публічні, які приватні, де обов'язкова авторизація і чому така відповідь не повинна потрапляти до спільного кешу.
- Регламенти перевірки — чек-ліст приймання: контроль заголовків відповіді, поведінка при
304 Not Modified, коректністьVaryза різнихAccept-Encoding, відсутність витоку приватних даних через спільний кеш. - Моніторинг і алерти — метрики частки влучань у кеш, часу відповіді на промаху та ревалідації. Пороги, за яких інженер отримує повідомлення, а не дізнається про проблему від користувачів.
- Інструкція з відкату — порядок дій, якщо нова політика кешування ламає видачу: як швидко скинути кеш на всіх шарах і повернути попередню конфігурацію.
Пакет самодостатній: за ним можна провести аудит поточного кешування, відтворити налаштування в новому середовищі та передати супровід іншій команді. Опишіть задачу — повернемося з оцінкою та планом робіт.
Кейс: як кероване кешування прибрало сплески відмов
Проєкт — маркетплейс із каталогом на кілька сотень тисяч позицій і мобільним застосунком, який ходив в API за кожним екраном. Кеш-заголовків бекенд не віддавав: Cache-Control не виставлявся, ETag не рахувався, відповіді йшли з no-store за замовчуванням. Кожен запуск застосунку і кожен перехід по каталогу били в базу напряму.
Що було до впровадження
На піках — ранковий трафік і розпродажі — час відповіді каталогу сягав понад секунду, частка відповідей із помилкою сервера трималася в районі кількох відсотків, а база впиралася в стелю з'єднань.
Відмови йшли хвилями: перевантаження викликало таймаути, клієнти повторювали запити, і навантаження подвоювалося на рівному місці. Чергова зміна гасила це ручним розширенням пулу і перезапусками.
Що змінили
Налаштували кероване кешування на рівні API-шлюзу і застосунку. Для довідників і карток товарів увімкнули ETag і Last-Modified — повторний запит без змін повертає 304 Not Modified і не доходить до бекенду.
Для списків, що залежать від фільтрів, ролей і мови, прописали Vary за потрібними заголовками, щоб не віддавати чужу версію відповіді.
Публічні дані отримали Cache-Control з роздільними директивами max-age і stale-while-revalidate: клієнт віддає застарілу копію, поки фоном підтягується свіжа. Інвалідацію прив'язали до подій зміни товару, а не до таймера.
Що стало
Частка відповідей 304 на повторних запитах каталогу перевищила половину — ці звернення перестали доходити до бази. Час відповіді на піках скоротився в рази, частка помилок сервера впала до часток відсотка, а навантаження на бекенд у години пік знизилося приблизно вдвічі. Хвилеподібні відмови припинилися: система тримає трафік розпродажу без ручного втручання чергового.
Практика впровадження показала головне: виграш дає не сам заголовок, а узгоджена політика — що кешується, на який термін, за якими ознаками розрізняється і коли скидається.
Такий підхід ми застосовуємо і в інших проєктах на FastAPI і подібних стеках, де важлива передбачуваність під навантаженням.
Чим відрізняється ETag від Last-Modified і коли потрібен Vary?
ETag і Last-Modified вирішують одне завдання — дати клієнту дешевий спосіб перевірити, чи змінився ресурс, не передаючи його тіло повністю.
Last-Modified спирається на час останньої правки і працює з точністю до секунди: якщо дві зміни вклалися в одну секунду або файл перезаписано тим самим вмістом, мітка бреше.
ETag — це відбиток конкретної версії представлення, тому він ловить зміни, яких час не бачить, і коректно працює при віддачі з CDN та при збиранні відповіді з кількох джерел.
На практиці їх не протиставляють, а ставлять разом: ETag дає точну перевірку, Last-Modified залишається запасним валідатором для посередників, які не вміють працювати з відбитком.
Клієнт надсилає умовний запит з If-None-Match і If-Modified-Since, сервер порівнює і повертає або тіло, або коротку відповідь «не змінилося» — економія трафіку й часу відповіді на порядок, а не на відсотки.
Слабкий ETag відрізняється від сильного: перший підтверджує смислову еквівалентність, другий — побайтову. Для JSON API майже завжди достатньо слабкого, для статики й файлів віддачі — сильного.
Помилка новачка — генерувати ETag від часу старту процесу: після перезапуску або переїзду на інший вузол мітка змінюється, і весь кеш клієнтів інвалідується разом.
Vary відповідає на інше питання — не «та сама це версія», а «той самий це клієнт».
Якщо відповідь залежить від заголовка Accept, Accept-Encoding, Accept-Language або авторизації, без Vary проміжний кеш віддасть чужу версію: стиснуту відповідь — клієнту без підтримки стиснення, англійську — тому, хто просив російську.
Vary: Accept-Encoding і Vary: Accept-Language закривають це, але кожен доданий заголовок дробить кеш на більшу кількість варіантів, тому перелічувати потрібно лише те, що реально впливає на тіло відповіді.
Окремо варто Vary: Authorization і приватні відповіді: їх або позначають Cache-Control: private, або забороняють кешування взагалі, інакше персональні дані осідають у спільному кеші.
Перевіряти зв'язку зручно за журналом: частка відповідей «не змінилося» на умовні запити показує, чи працює валідація, а частка влучань у кеш за кожним варіантом Vary — чи не розростається сховище.
Часті технічні сумніви при налаштуванні кешування
Кеш найчастіше вже розгорнуто — на CDN, reverse-proxy або в самому застосунку, і питання не «вмикати чи ні», а «як не зламати те, що працює». Починаємо з інвентаризації: які маршрути кешуються, за якими ключами, хто виставляє заголовки — проксі чи бекенд.
Далі змінюємо точково: спочатку відповіді без персоналізації, потім усе інше, з перевіркою на кожному кроці.
Авторизація та кеш конфліктують там, де відповідь залежить від користувача, а ключ кешу — лише від URL. Рішення — або явний Cache-Control: private для таких маршрутів, або Vary: Authorization, або розділення на публічний і приватний контури.
Ми не покладаємося на «за замовчуванням не закешується»: перевіряємо кожен маршрут, де у відповіді є дані користувача.
Перевірка інвалідації — окремий етап, а не формальність. Дивимося, що відбувається при оновленні ресурсу: чи змінюється ETag, чи приходить 304 на умовний запит, чи йде стара відповідь з проксі після PURGE. Якщо інвалідація не спрацьовує, клієнт отримує застарілі дані й не розуміє чому.
У нас уже розгорнуто кеш — доведеться все перебудовувати?
Ні. Спочатку фіксуємо поточну поведінку: які заголовки віддає бекенд, що перезаписує проксі, де розходяться ключі. Перебудова потрібна лише там, де правила конфліктують — зазвичай це 2-3 маршрути з десятків.
Як не зламати авторизацію при вмиканні кешу?
Розділяємо контури: публічні відповіді кешуються за URL, приватні — позначаються private або отримують Vary: Authorization. Після вмикання прогоняємо сценарії з різними ролями й перевіряємо, що користувач не отримує чужу відповідь.
Як переконатися, що інвалідація працює?
Перевіряємо ланцюжок: оновили ресурс — чи змінився ETag або Last-Modified, чи повернув сервер 304 на умовний запит, чи пішла стара відповідь з проксі. Якщо хоча б один крок не спрацьовує, кеш віддає застаріле.
Ці сумніви виринають майже на кожному проєкті — і майже завжди вирішуються точковим налаштуванням, а не переписуванням. Опишіть вашу конфігурацію — повернемося з оцінкою та планом перевірки.
Обговоримо ваше завдання щодо кешування API
Щоб повернутися з оцінкою та планом впровадження, нам потрібен контекст щодо вашого API: де саме кешування не працює або працює не так, як очікується. Опишіть завдання у вільній формі — цього достатньо, щоб наш інженер зрозумів картину та запропонував реалістичний план.
Що корисно додати до опису:
- поточні заголовки відповідей API —
Cache-Control,ETag,Last-Modified,Vary: видно, що вже віддається клієнтам і де логіка розсипається; - архітектуру: моноліт чи кілька сервісів, чи є CDN або зворотний проксі перед застосунком;
- які методи та ендпоінти кешуються, а які мають залишатися строго свіжими (платежі, баланси, статуси замовлень);
- як влаштована авторизація — токени в заголовку, cookie, сесії: від цього залежить коректне налаштування
Varyта приватності кешу; - скарги від користувачів або метрики: застарілі дані, зайві запити, зростання навантаження на базу;
- обмеження: регламенти, вимоги до безпеки, строки, які не можна зсувати.
За цими ввідними ми повернемося з оцінкою робіт і планом впровадження: які заголовки та правила виставляємо, як налаштовуємо розділення публічного та приватного кешу, як перевіряємо коректність через умовні запити та відкат.
Якщо під час розбору з'являться розвилки — обговоримо їх з вашим інженером, щоб рішення лягло в наявну інфраструктуру, а не потребувало її перебудови.
Опишіть завдання — повернемося з оцінкою та планом.







