Skip to content

System Design

System Design

Як підходити до System Design співбесіди [💡22/100]

Summary

SD-співбесіда - це не "накидати коробок", а структурований діалог. Канонічний порядок: уточнити функціональні вимогинефункціональні (масштаб, доступність, latency) → оцінка навантаження (DAU, RPS, read/write ratio) → сутності й модель данихAPIhigh-level дизайнвузькі місця й масштабування. Спочатку уточнюють і рахують, лише потім малюють.

Порядок кроків.

  1. Уточнити функціональні вимоги. Які сценарії входять у задачу, а які ні. Питати замовника, відсікати зайве ("аутентифікацію не проєктуємо"). Краще згаяти хвилину на питання, ніж проєктувати не те.
  2. Нефункціональні вимоги (NFR). Масштаб (DAU), доступність (HA, multi-AZ), latency-ціль (сотні мс? секунди?), співвідношення читань і записів, консистентність, retention та обсяг даних.
  3. Оцінка навантаження (capacity estimation). Прикинути порядок, бо від нього залежить архітектура (формули нижче). 100k DAU і 100M DAU - різні дизайни.
  4. Core entities і модель даних. Які сутності, які поля, де і як зберігаються.
  5. API. Ендпоінти, метод, що в запиті/відповіді (request id для трасування).
  6. High-level дизайн. Блоки і data flow: gateway → сервіси → сховища. Спершу задовольнити функціональні вимоги, перформанс - окремим проходом.
  7. Вузькі місця й масштабування. Де впреться, як горизонтально масштабувати, що зробити stateless за load balancer, де autoscaling, де реплікація.
  8. Оцінка строків. Декомпозиція на команди/задачі, груба оцінка зверху.

Рівень абстракції: модель C4. Корисно явно проговорити, на якому рівні деталізації йде розмова, щоб не змішувати загальну картину з деталями реалізації. Модель C4 (Simon Brown) описує чотири рівні:

  • C1 - Context. Система як одна коробка серед зовнішніх акторів і суміжних систем (хто користується, з чим інтегрується).
  • C2 - Container. Розгортувані одиниці всередині системи: застосунки, бази даних, брокери, черги - і протоколи між ними.
  • C3 - Component. Внутрішня будова окремого контейнера: модулі/сервіси й їхні обов'язки.
  • C4 - Code. Класи, функції - рідко малюють, зазвичай генерують з коду.

Типова помилка на співбесіді - почати з C1, але одразу провалитися в C3 (вибір конкретного брокера, схема таблиць), не закінчивши верхній рівень. Спершу завершують картину на одному рівні, потім спускаються нижче.

Оцінка навантаження (формули).

  • Пік. Реальний трафік нерівномірний - беруть пік-коефіцієнт (×2-×10) поверх середнього: peak = avg × peak_factor.
  • Read/write ratio. Визначає вибір сховища: система збору подій (відстеження)
  • майже 100% запис; стрічка соцмережі - читання-важка. Запис-важкі й читання-важкі профілі проєктують по-різному.
  • RPS. RPS ≈ запитів_за_день / 86400 (86400 = 24×3600 с), де запитів за день = DAU × запитів_на_користувача. Для піку домножують на пік-коефіцієнт.

Евристики вибору сховища під масштаб.

  • Запис-важкий збір подій (мільйони на потоці): розподілене wide-column сховище з лінійним масштабуванням і вбудованими реплікацією та шардуванням (Cassandra) - щоб не шардувати руками.
  • Аналітика по зібраних даних (воронки, зрізи): колонкове OLAP-сховище (ClickHouse), не time-series і не OLTP. Див. infrastructure/database.md розділ "OLTP та OLAP".
  • Конвеєр збагачення сирих подій: брокер з можливістю повтору (Kafka - можна "відмотати" offset і переграти події після фіксу бага в enrichment) → enrichment-сервіс → пакетна вставка в OLAP.
  • Stateless-сервіси за load balancer з autoscaling-групою, репліки в різних availability-зонах для HA.

Проблеми та рішення, з якими стикаємось при розвитку додатку [💡10/100]

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

MVP - стадія прототипу

  • Достатньо сервера з додатками (фронтенд та бекенд в якості API), бази даних.
  • Для зберігання файлів, картинок потрібно користуватись стороннім сервісом Amazon S3 або аналогічним.
  • Можна масштабуватись тільки за рахунок вертикального масштабування - більше місця, більше оперативної пам'яті, кращий процесор.
  • Індекси на потрібні колонки.

Бекапи - Backups

  • Щоб не втратити дані - потрібно робити бекапи. Або обрати зовнішнє рішення, яке буде це робити самостійно.
  • Потрібно перевірити процес відновлення з бекапів, оскільки вони можуть бути не робочі. Для цього періодично можна тестувати відновлення системи після інциденту.

Доступність, Відказостійкість - Availability, Fault Tolerance - Load Balancer

  • Коли збільшується трафік, одного сервера може почати не вистачати. Тоді необхідно додати один або кілька інших, та налаштувати розподілення трафіку між ними.
  • Для розподілення трафіку потрібно скористатись Load balancer (Nginx). Рівні балансування (L4 проти L7), алгоритми розподілу (round-robin, weighted, least-connections, hash) і відмінність reverse proxy від load balancer - у microservices.md.
  • Доступність синхронного ланцюга - добуток доступностей. Якщо запит послідовно проходить N обов'язкових компонентів (відмова будь-якого = відмова запиту), за незалежних відмов загальна доступність дорівнює A₁ × A₂ × ... × Aₙ: три ланки по 99.9% дають уже 0.999³ ≈ 99.7%. Звідси правило - менше синхронних залежностей у критичному шляху, вища доступність. Винесення некритичної роботи в асинхронне повідомлення через брокер прибирає компонент із цього добутку (запит завершується, навіть якщо споживач тимчасово недоступний), але сам брокер стає спільною залежністю й має бути кластеризованим. Деталі слабкого зв'язування - у infrastructure/mq.md.

Час відповіді - Performance - Cache, Database Replica, CDN

  • Якщо вузьким місцем стає база даних, потрібно налаштувати кешування найбільш часто запитуваних даних.
  • Якщо кешування недостатньо - потрібно налаштувати репліку, та перевести на неї read запити. Також потрібно налаштувати механізм master-slave, якщо одна з них вийде з ладу.
  • Якщо клієнти додатку розкидані географічно (користувачі із Сінгапуру незадоволені, що хост зі Штатів довго відповідає), можна скоротити час очікування відповіді, якщо перенести сервери ближче до кінцевих споживачів.
  • Також можна налаштувати кешування, CDN (Cloudflare).
  • Стратегії оновлення кешу. Cache-aside (lazy) - застосунок сам читає з кешу, а при промаху дістає з БД і докладає в кеш; найпоширеніший, але дані застарівають (керують через TTL). Write-through - запис іде синхронно і в кеш, і в БД: читання завжди свіже ціною повільніших записів. Write-behind (write-back) - запис спершу в кеш, а в БД асинхронно: швидкі записи з ризиком втрати даних при збої. Refresh-ahead - кеш сам оновлює популярні ключі перед протуханням, ховаючи затримку від користувача.
  • CDN - роздача статики з edge-вузлів ближче до користувача; стратегії наповнення (push/pull), архітектура й маршрутизація - у секції CDN нижче.

Аналітика - Analytics

  • Аналітичні запити зазвичай дістають велику кількість даних, мають кілька JOIN запитів, тож завжди повинні виконуватись на репліці. Оскільки ці великі запити вимагають великої кількості CPU, що може заблокувати основний додаток, а також вони забивають кеш, який база даних використовували для обслуговування додатку.
  • Якщо аналітичних даних багато, можливо потрібна денормалізація або NoSQL база даних.

База даних - Database

  • Для оптимізації роботи бази даних, якщо вже є кешування та репліки, можна використати
    • Денормалізація
    • Партиціонування
    • Шардування

Мікросервіси - Microservices

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

Пікові навантаження

  • Якщо ми очікуємо маркетингову сесію, рекламу, свята - тобто піковий онлайн може бути 10х від звичайного, потрібно налаштувати політики автоскейлінгу.
  • Перед піком можна прогріти сервіси, тобто запустити додаткові сервіси так, щоб навантаження було близько 10% від максимуму. Це допоможе краще справитись з ростом навантаження.
  • Автоскейлінг не миттєвий. Підняти інстанс (виділити ресурс, задеплоїти код, завести в балансер) займає одиниці хвилин, та й реакція на ріст не миттєва - спершу витримують паузу, щоб відрізнити стійкий тренд від короткого сплеску. Тому на різкий короткочасний наплив автоскейлінг не встигає: latency просідає, поки піднімаються інстанси. Звідси прогрів наперед і буферизація сплеску через чергу замість покладання тільки на реактивне масштабування.

Перформанс-мантри (методологія Брендана Грегга). Коли впираємось у вузьке місце, оптимізації перебирають у такому порядку - від найдешевшої до найдорожчої:

  • Не роби цього - найшвидша операція та, якої немає (прибрати зайвий запит/виклик).
  • Роби, але один раз - кешуй / мемоізуй результат.
  • Роби це рідше - групування викликів (batching), debounce, більший TTL.
  • Зроби це пізніше - винеси в асинхронну чергу, відклади до фактичної потреби (lazy).
  • Зроби, поки користувач не бачить - фонові задачі, прогрів кешу, прекомп'ют.
  • Зроби це паралельно - розпаралель між ядрами / воркерами / шардами.
  • Зроби це дешевше - дешевший алгоритм / інстанс / сховище.

Stateless-застосунок і горизонтальне масштабування

Протоколи на основі HTTP не вимагають постійного з'єднання й містять усю потрібну інформацію в кожному пакеті, тож застосунок легко масштабувати горизонтально - але лише якщо він написаний відповідно. Щоб перевірити, чи правильно спроєктований веб-застосунок (чи телеграм-бот), варто поставити три питання:

  • чи коректно працюватиме він, якщо після будь-якої обробленої події станеться перезапуск?
  • чи коректно, якщо одночасно прийде кілька подій?
  • чи коректно, якщо запущено кілька процесів і події приходять то в один, то в інший?

Перезапуски трапляються будь-коли - і при помилках, і при штатному оновленні. Запуск кількох копій потрібен для задіяння додаткових ресурсів і зростання продуктивності. Тому сам процес роблять stateless, а дані, які треба зберегти між подіями, тримають у спеціально спроєктованій зовнішній системі (фактично в БД).

Типові проблемні місця:

  • Донастройка правил вибору обробника в процесі роботи (роути, фільтри диспатчера). При перезапуску ці налаштування скинуться. Натомість правила налаштовують один раз при старті так, щоб вони враховували змінюваний стан, який вантажать зі сховища.
  • Зберігання даних у глобальній змінній. При кількох подіях вони переплутаються, скинуться при рестарті, а кілька процесів не зможуть їх розділяти. Для бізнес-даних використовують БД або тимчасове сховище (сесія, FSM). Виняток - дані самого процесу: лічильники для моніторингу, кеші.
  • Запуск задач, не пов'язаних з обробкою запитів, у межах процесу веб-сервісу. Якщо задачу запускають з обробника, вона скинеться при рестарті; якщо при старті - буде кілька копій при кількох процесах. У першому випадку використовують чергу чи окремий планувальник, у другому - окремий процес із власними правилами масштабування.

Окремі оптимізації (in-memory кеш, проміжний буфер перед записом) припустимі за припущення, що перезапуск нечастий, але їх впровадження має бути обґрунтованим. Винесення фонової роботи в чергу - у infrastructure/mq.md.

Робота в конкурентному середовищі [💡23/100]

Щоб забезпечити цілісність даних (Data Consistency) у розподілених системах потрібно контролювати конкурентність у системі. У більшості вебсистем конкурентність реально контролюється саме на рівні бази даних, бо саме БД гарантує атомарність, ізоляцію та цілісність даних, тоді як аплікейшен лише правильно користується цими механізмами.

Основні підходи до контролю конкурентного доступу — песимістичне та оптимістичне блокування, кожне з яких має свої trade-off’и.

Вибір між песимістичним і оптимістичним підходом завжди залежить від:

  • частоти конфліктів
  • вимог до latency
  • допустимості повторних спроб
  • характеру бізнес-операцій

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

Песимістичне блокування - коли блокуються певні рядки, й інші користувачі, які намагаються щось зробити з ними, пропускаються. Такий вид блокування може не підходити у випадку великої кількості запитів до одного ресурсу, оскільки може створювати затримки. Песимістичне блокування гарантує, що жоден інший процес не змінить рядок, поки він заблокований. Це підходить для сценаріїв із високою критичністю даних (наприклад, фінансові транзакції), але може створювати затримки при великій кількості запитів. Також потрібно використовувати коли конкуренція за запис настільки велика, що оптимістичний підхід призведе до постійних retry циклів.

Оптимістичне блокування - блокування за допомогою версіонування, таймстемпів, логічних годинників. Базується на припущенні, що конфлікти трапляються рідко. Ми дозволяємо паралельні операції, але перевіряємо версію або таймстемп перед записом. Якщо версія змінилася — транзакція відхиляється, і користувач має повторити операцію. Краще використовувати у системах з інтенсивним читанням (наприклад, бронювання квитків, редагування статей, e-commerce каталоги), де ймовірність того, що два користувачі редагують один запис одночасно, є низькою.

Песимістичне блокування (Pessimistic Locking)

  • Блокує рядки або таблиці наперед, ще до зміни даних (рядкове блокування, наприклад, SELECT FOR UPDATE в PostgreSQL). Поки транзакція А тримає блокування, транзакція Б, яка намагається вибрати ті самі рядки через FOR UPDATE, буде чекати завершення транзакції А (або впаде з таймаутом, якщо налаштовано NOWAIT).
  • Ідеально підходить для критичних даних (фінансові транзакції), де не можна допустити "race conditions".
  • Мінімізує race condition, але погіршує масштабованість, знижує пропускну здатність системи (throughput), тримає з'єднання з БД відкритим довше.
  • Може призводити до дедлоків (deadlocks) при складних транзакціях.
  • Погано підходить для систем із великою кількістю паралельних запитів до одного ресурсу.
SELECT * FROM orders
WHERE id = 42
FOR UPDATE;
# Django example with pessimistic locking
with transaction.atomic():
    order = Order.objects.select_for_update().get(id=42)
    order.status = "paid"
    order.save()

Оптимістичне блокування (Optimistic Locking)

  • Не блокує рядки наперед, а перевіряє, чи не були дані змінені кимось іншим перед збереженням.
  • Реалізується через version, updated_at, timestamp або логічні лічильники. Транзакція читає дані без блокувань, а при оновленні додається умова WHERE, перевіряючи, чи версія в базі збігається з тією, яку прочитали (якщо версія не співпадає — конфлікт, транзакція скасовується).
  • Дає кращу пропускну здатність під навантаженням - висока швидкодія при читанні (read-heavy systems).
  • Потребує обробки конфліктів під час save/update, вимагає реалізації логіки повторних спроб (retry mechanism) на рівні коду застосунку.
  • Найкраще працює, коли конфлікти трапляються рідко - при високій конкуренції (high contention) багато транзакцій будуть скасовуватися і перезапускатися, що витрачає ресурси CPU.
UPDATE orders
SET status = 'paid', version = version + 1
WHERE id = 42 AND version = 3;
# Optimistic locking example
rows_updated = Order.objects.filter(id=42, version=3).update(
    status="paid",
    version=F("version") + 1,
)

if rows_updated == 0:
    raise ConcurrencyError("Order was modified by another transaction")

Реалізація на Postgres

  • Дефолт READ COMMITTED не оптимістичний - конфліктні UPDATE чекають на блокування й перечитують оновлений рядок. Оптимістична поведінка з serialization_failure (SQLSTATE 40001) з'являється лише на REPEATABLE READ (помилка на самому UPDATE/DELETE) або SERIALIZABLE (SSI - може спрацювати на читанні, записі або коміті). У SQLAlchemy це OperationalError з orig.pgcode == "40001". Перехоплення лише OperationalError без перевірки pgcode охоплює також deadlock (40P01) і збої з'єднання, тому фільтрація за кодом обов'язкова.
  • Песимістичне в Postgres - SELECT ... FOR UPDATE (X-lock) або FOR SHARE (S-lock); деталі синтаксису та інших варіантів (FOR NO KEY UPDATE, NOWAIT, SKIP LOCKED) - див. infrastructure/sql.md.
  • Retry для оптимістичного підходу робиться з exponential backoff + jitter - очікування подвоюють при кожній спробі плюс додають випадкове відхилення, щоб конкуруючі клієнти не зверталися до БД синхронно.
from sqlalchemy.exc import OperationalError
import random, time

# Without REPEATABLE READ / SERIALIZABLE this retry is a no-op:
# READ COMMITTED (default) never raises 40001.
session = Session(engine.execution_options(isolation_level="REPEATABLE READ"))

def update_with_retry(session, order_id, max_retries=5):
    for attempt in range(max_retries):
        try:
            order = session.get(Order, order_id)
            order.status = "processing"
            session.commit()
            return
        except OperationalError as e:
            session.rollback()
            # Retry only on serialization_failure; deadlock / disconnect propagate up.
            if getattr(e.orig, "pgcode", None) != "40001" or attempt == max_retries - 1:
                raise
            time.sleep((2 ** attempt) * 0.1 + random.uniform(0, 0.1))

Links

Data Consistency (Консистентність у мікросервісній архітектурі) [💡15/100]

Консистентність у мікросервісній архітектурі означає, що всі сервіси узгоджено відображають стан системи, навіть якщо відбуваються збої чи часткові відмови. У мікросервісній архітектурі неможливо покладатися на класичні ACID-транзакції між сервісами, тому консистентність досягається через eventual consistency, контроль побічних ефектів і правильні механізми відновлення після збоїв.

Тримати систему в консистентному стані в мікросервісній архітектурі можна кількома підходами, що враховують розподілену природу сервісів і можливі збої мережі.

  • Ретраї (повторні спроби)
  • Try-Confirm/Cancel (TCC)
  • Saga
  • Двохфазний коміт (2PC)

Кожен із цих підходів має свої плюси та мінуси і обирається залежно від характеру навантаження та критичності бізнес-процесів. Наприклад, для систем із високою частотою записів і відмовостійкістю більше підходять Saga і ідемпотентні ретраї, а для критичних транзакцій з низьким трафіком — 2PC або TCC.

Ретраї (Retries)

Ретраї (Retries) - повторні запити - найпростіший спосіб боротьби з тимчасовими збоями мережі або недоступністю сервісів. Ретраї допомагають виправити тимчасові помилки, але вони мають бути ідемпотентними. Це гарантує безпечне відновлення без дублювання дій.

Ідемпотентність - це властивість операції, яка гарантує, що повторне виконання запиту дає той самий результат, що й однократне виконання, без зміни результату або зайвих ефектів. Ідемпотентність зазвичай реалізується через idempotency key або перевірку унікального бізнес-ідентифікатора операції. Наприклад, клієнт генерує унікальний Idempotency-Key (UUID) і передає його в заголовку запиту. Сервер зберігає цей ключ (наприклад, у Redis) разом із результатом першого успішного виконання. Якщо приходить запит з тим самим ключем, сервер не виконує логіку знову, а повертає збережений результат.

Try-Confirm/Cancel (TCC) Pattern

TCC - розбиває операцію на три фази: спроба резервування (Try), підтвердження (Confirm) або скасування (Cancel). Якщо в фазі Confirm виникає помилка або збій, виконується Cancel, який відкочує зміни.

Приклад реалізації

  • Try: Резервуємо ресурси (наприклад, позначаємо товар як "pending" або заморожуємо суму на карті). Це не фінальна зміна, а лише резерв.
  • Confirm: Якщо всі "Try" пройшли успішно, переводимо статус у "Sold" або списуємо гроші. Ця операція має бути ідемпотентною і не може зафейлитись (в теорії).
  • Cancel: Якщо десь помилка, скасовуємо резерви (розморожуємо гроші).

Недоліки TCC

  • Складна реалізація на рівні кожного сервісу
  • Потрібно тримати стан резервацій
  • Збільшує кількість запитів до сервісів (мінімум 2x)
  • Потрібна логіка для звільнення "застряглих" резервацій

Saga Pattern

Послідовність локальних транзакцій із компенсуючими відкатами; підходить для асинхронних багатокрокових бізнес-процесів із послідовними залежностями. Повний розбір (хореографія проти оркестрації, типи кроків, forward-recovery, обробка збою компенсації, реалізація) - у architecture_patterns.md.

Двофазний коміт (Two-Phase Commit)

Двохфазний коміт (2PC) — класичний протокол, який координує коміти у всіх сервісах через централізований координатор. Гарантує атомарність змін, але може викликати блокування і має складність у масштабуванні, тому краще для систем з низьким навантаженням і критичною консистентністю. У сучасних високонавантажених системах майже не використовується через низьку пропускну здатність та тісний зв'язок (coupling).

Фази

  • Фаза 1 (Prepare): Координатор запитує всі бази даних: "Чи готові ви зробити коміт?". Ресурси блокуються.
  • Фаза 2 (Commit): Якщо всі відповіли "Так", координатор каже "Коміт". Якщо хоч один "Ні" — "Rollback".

Недоліки 2PC

  • Блокуючий протокол — ресурси заблоковані під час всієї транзакції
  • Single point of failure — якщо координатор впаде, ресурси залишаться заблокованими.
  • Погана масштабованість — не підходить для high-load систем
  • Висока латентність — потрібні синхронні виклики до всіх учасників

Порівняння підходів та рекомендації

  • Ретраї + Ідемпотентність
    • Найпростіший підхід, використовується завжди як базовий рівень захисту
    • Підходить для більшості сценаріїв з eventual consistency
    • Мінімальна складність коду
  • TCC
    • Використовується для бізнес-критичних операцій з резервуванням ресурсів
    • Приклади: бронювання готелів, резервування товарів у кошику
    • Середня складність реалізації
  • Saga
    • Найкращий вибір для складних багатокрокових бізнес-процесів
    • Приклади: оформлення замовлення, onboarding користувачів
    • Висока складність, але хороша масштабованість
  • 2PC
    • Використовується тільки якщо дійсно потрібна сильна консистентність
    • Приклади: фінансові транзакції між рахунками
    • Не підходить для high-load систем

CDN (Content Delivery Network)

Summary

CDN - географічно розподілена мережа вузлів, що кешують контент ближче до користувача і віддають його з найближчої точки. Зменшує латентність, розвантажує origin-сервер і згладжує сплески трафіку. Origin лишається джерелом істини; edge-вузли обслуговують користувачів, а DNS/Anycast направляють запит до найближчого вузла.

Призначення. Якщо origin у Каліфорнії, а користувач у Києві, кожен байт проходить десятки маршрутизаторів і міжконтинентальних каналів - затримки, втрати пакетів, повільне завантаження, особливо під навантаженням. CDN тримає копії статики (а інколи й динаміки) на вузлах поблизу й віддає їх звідти, а origin не помічає сплесків трафіку.

Архітектура.

  • Origin - джерело істини (бекенд або сховище). Не спілкується з користувачами напряму (захист від DDoS і дублюючих запитів); його смикають лише при першому запиті об'єкта або після спливу TTL.
  • PoP (Point of Presence) - регіональний вузол із десятками серверів (DNS, TLS-термінація, балансувальники). Що більше PoP, то коротший шлях до кешу - ключовий критерій вибору CDN.
  • Edge-сервери - усередині PoP, безпосередньо обслуговують користувача: є об'єкт у кеші -> віддають одразу; промах -> ідуть вище.
  • Mid-tier кеш - проміжний шар: при промаху edge тягне з регіонального кеша, а не з origin (tiered caching), знижуючи навантаження на origin і міжрегіональний трафік.

Маршрутизація до найближчого вузла.

  • GeoDNS - CDN як авторитативний DNS віддає різні IP залежно від регіону запиту.
  • Anycast - один IP анонсується з багатьох майданчиків; запит іде туди, куди "ближче" за маршрутизацією інтернету.
  • EDNS Client Subnet - передає підмережу клієнта резолверу, щоб публічний DNS (8.8.8.8) направляв за місцем користувача, а не резолвера.

Кешування і TTL. Час життя об'єкта задають через Cache-Control: max-age. Статика (зображення, JS, CSS) - довгий TTL; HTML чи персоналізований API - короткий. Витіснення при переповненні - LRU (за давністю звернень) або TLRU (враховує і строк придатності). Примусова інвалідація (purge за URL) скидає застарілу версію з усіх вузлів.

Типи контенту. Статика - ідеальна для кешу. Динаміка (персоналізований HTML) - частковий кеш, Edge Side Includes, кеш блоків за географією чи cookie. Стрім (HLS/DASH) - кешування сегментів. Real-time (WebSocket, push) - кешувати нема чого, важливі низька латентність і Anycast. Великі файли - Range-запити й багатопотокове завантаження.

Протоколи й безпека. Сучасні CDN працюють на HTTP/2 (мультиплексування багатьох запитів в одному TCP-з'єднанні) і HTTP/3 (QUIC поверх UDP - без head-of-line blocking, 0-RTT для повторних з'єднань). TLS термінують на edge (ближче до клієнта -> коротше рукостискання), keep-alive прискорює повторні запити. На межі також ставлять WAF (Web Application Firewall) для фільтрації SQL-ін'єкцій, XSS і сплесків запитів, а приватний контент захищають перевіркою підпису, географії чи ACL.

Push проти pull. Pull CDN підтягує контент з origin при першому запиті й кешує за TTL - менше клопоту й сховища, добре для високого трафіку (перші запити повільніші, можливі зайві підтягування). Push CDN - контент завантажують на CDN самостійно при зміні - доречно для рідко змінюваного вмісту й невисокого трафіку (ручне керування, більше сховища).

Links