Пошук уроків, статей та іншого контенту
Опануєте структуру відповіді на System Design Interview: від вимог і оцінок до архітектури, компромісів та висновків.
System Design Interview перевіряє не здатність намалювати «ідеальну» архітектуру, а вміння:
уточнювати нечіткі вимоги;
оцінювати масштаб системи;
розкладати систему на компоненти;
пояснювати потоки даних;
знаходити вузькі місця;
аргументувати компроміси;
адаптувати рішення до заданого масштабу;
комунікувати послідовно й зрозуміло.
Інтерв’юер зазвичай оцінює не конкретний набір технологій, а якість інженерного мислення. Для однієї й тієї самої задачі може існувати кілька правильних архітектур.
Зручно будувати відповідь у такому порядку:
Уточнити функціональні вимоги.
Уточнити нефункціональні вимоги.
Зафіксувати припущення та оцінити масштаб.
Запропонувати API і модель даних.
Описати архітектуру верхнього рівня.
Пройти основні сценарії end-to-end.
Зробити deep dive у найскладніші частини.
Обговорити відмовостійкість, узгодженість і масштабування.
Підсумувати рішення та назвати наступні кроки.
Не варто починати з переліку баз даних, черг і балансувальників. Спочатку потрібно зрозуміти, що саме будується і які властивості система повинна мати.
Спочатку визначте основні дії користувача або зовнішніх систем.
Наприклад, для сервісу скорочення URL:
користувач створює коротке посилання;
користувач переходить за коротким посиланням;
система перенаправляє його на оригінальний URL;
система може збирати статистику переходів.
Потрібно відокремити обов’язкові можливості від другорядних. На інтерв’ю достатньо сказати:
Спочатку спроєктую створення короткого URL і перенаправлення. Аналітику та керування посиланнями розгляну, якщо залишиться час.
Це показує вміння контролювати межі задачі.
Корисно поставити кілька цілеспрямованих запитань:
Які основні сценарії використання?
Чи потрібна автентифікація?
Чи може користувач задавати власний короткий ідентифікатор?
Чи має коротке посилання бути постійним?
Який максимальний розмір вхідних даних?
Чи потрібна статистика переходів?
Чи є вимоги до географічного розподілу?
Яка допустима затримка?
Чи можна втрачати частину другорядних даних?
Яка очікувана кількість користувачів і запитів?
Не потрібно ставити десятки запитань. Оберіть ті, що впливають на архітектуру.
Нефункціональні вимоги описують властивості системи:
доступність;
затримку;
пропускну здатність;
довговічність даних;
узгодженість;
масштабованість;
безпеку;
вартість;
вимоги до відновлення після аварій.
Наприклад:
Припустімо, що перенаправлення має вкладатися в 100 мс для більшості запитів, система повинна обробляти десятки тисяч запитів на секунду, а вже створене коротке посилання не повинно зникати.
Таке припущення допомагає обґрунтувати подальший вибір компонентів.
Оцінки не повинні бути точними до одиниці. Їхнє завдання — зрозуміти порядок величин.
Зазвичай достатньо оцінити:
кількість активних користувачів;
кількість операцій запису;
кількість операцій читання;
піковий трафік;
обсяг збережених даних;
пропускну здатність мережі;
приблизний розмір кешу.
Припустімо:
100 мільйонів нових коротких посилань на місяць;
10 мільярдів переходів на місяць;
30 днів у місяці;
пік у 5 разів більший за середнє навантаження.
Середня кількість записів за секунду:
100 000 000 / (30 × 24 × 60 × 60) ≈ 39 записів/сСередня кількість читань:
10 000 000 000 / (30 × 24 × 60 × 60) ≈ 3 858 читань/сПікове навантаження:
записи: 39 × 5 ≈ 195 записів/с
читання: 3 858 × 5 ≈ 19 290 читань/сВисновок: система переважно read-heavy. Це впливає на кешування, реплікацію та вибір сховища.
Невеликий скрипт допомагає уникати арифметичних помилок під час підготовки або інтерв’ю:
const secondsPerMonth = 30 * 24 * 60 * 60;
const monthlyWrites = 100_000_000;
const monthlyReads = 10_000_000_000;
const peakMultiplier = 5;
const averageWritesPerSecond = monthlyWrites / secondsPerMonth;
const averageReadsPerSecond = monthlyReads / secondsPerMonth;
const peakWritesPerSecond = averageWritesPerSecond * peakMultiplier;
const peakReadsPerSecond = averageReadsPerSecond * peakMultiplier;
console.log({
averageWritesPerSecond: Math.round(averageWritesPerSecond),
averageReadsPerSecond: Math.round(averageReadsPerSecond),
peakWritesPerSecond: Math.round(peakWritesPerSecond),
peakReadsPerSecond: Math.round(peakReadsPerSecond),
});У реальному інтерв’ю важливіше чітко назвати припущення та порядок величини, ніж писати такий код.
Якщо один запис займає приблизно 500 байт, а створюється 100 мільйонів записів на місяць:
100 000 000 × 500 байт = 50 000 000 000 байт ≈ 50 ГБ на місяцьЗа рік без урахування індексів, реплік і службових даних:
50 ГБ × 12 ≈ 600 ГБДалі потрібно додати запас на:
індекси;
реплікацію;
журнали;
резервні копії;
метадані;
зростання трафіку.
Не потрібно одразу називати конкретну кількість серверів. Спочатку визначте навантаження, а потім поясніть, які компоненти його обробляють.
API допомагає зробити дизайн конкретним. Не потрібно описувати кожен endpoint — достатньо ключових операцій.
Для сервісу скорочення URL:
POST /v1/links
Request:
{
"url": "https://example.com/articles/system-design",
"customAlias": "optional-alias",
"expiresAt": "2027-01-01T00:00:00Z"
}
Response:
{
"id": "abc123",
"shortUrl": "https://short.example/abc123",
"url": "https://example.com/articles/system-design",
"expiresAt": "2027-01-01T00:00:00Z"
}Перенаправлення:
GET /{code}
Response:
302 Location: https://example.com/articles/system-designВажливі питання API:
Чи безпечна повторна відправка запиту?
Як система повідомляє про конфлікт alias?
Які коди помилок повертаються?
Чи потрібно використовувати idempotency key?
Чи є обмеження частоти запитів?
Чи повинно API повертати актуальні або кешовані дані?
Для створення ресурсу можна підтримати Idempotency-Key. Якщо клієнт повторить запит через мережевий тайм-аут, система не створить два різних коротких посилання.
Для основного запису можна використати таку модель:
Link
- code: string, primary key
- originalUrl: string
- ownerId: string | null
- createdAt: timestamp
- expiresAt: timestamp | null
- status: active | disabledОсновний доступ під час перенаправлення:
code -> originalUrlТому code має бути ключем або мати ефективний індекс.
Якщо потрібна статистика:
ClickEvent
- eventId: string
- code: string
- occurredAt: timestamp
- country: string | null
- userAgent: string | nullПодії переходів не обов’язково зберігати в тій самій транзакційній базі, що й основні посилання. Для перенаправлення важливий швидкий пошук URL, а аналітика може бути асинхронною.
Для описаного сервісу можна запропонувати таку схему:
Клієнт
|
v
Load Balancer
|
v
Link Service
|------------------> Cache
|
|------------------> Primary Database
|
|------------------> Read Replicas
|
`------------------> Event Queue -> Analytics Workers -> Analytics StorageLoad Balancer
розподіляє запити між екземплярами сервісу;
перевіряє стан інстансів;
може завершувати TLS-з’єднання.
Link Service
перевіряє вхідні дані;
створює короткий код;
читає та записує посилання;
застосовує rate limiting і правила доступу.
Cache
зберігає часто запитувані пари code -> originalUrl;
зменшує навантаження на базу;
прискорює перенаправлення.
Primary Database
приймає записи;
зберігає джерело істини;
гарантує унікальність короткого коду.
Read Replicas
обслуговують читання;
дозволяють масштабувати read-heavy трафік;
можуть мати невелику затримку реплікації.
Event Queue
відокремлює перенаправлення від запису аналітики;
дозволяє пережити тимчасове падіння споживачів;
буферизує сплески подій.
Клієнт надсилає POST /v1/links.
Load balancer передає запит одному з екземплярів сервісу.
Сервіс перевіряє URL, права доступу та ліміти.
Генерується короткий код.
Запис зберігається в primary database.
Система повертає коротке посилання.
За потреби запис додається до кешу.
Унікальність коду повинна гарантуватися на рівні сховища. Перевірка «чи вільний код» у застосунку без унікального обмеження є небезпечною через race condition.
Якщо два запити одночасно отримають один код, база даних повинна відхилити конфлікт хоча б для одного з них. Сервіс може повторити генерацію та спробувати запис ще раз.
Клієнт надсилає GET /abc123.
Сервіс перевіряє кеш.
Якщо значення знайдено, повертається 302.
Якщо значення відсутнє, сервіс читає його зі сховища.
Результат додається до кешу.
Користувачу повертається перенаправлення.
Подія переходу асинхронно надсилається до черги.
Критичний шлях не повинен чекати запису аналітики. Інакше тимчасова проблема з чергою або сховищем статистики зламає основну функцію сервісу.
Один із простих підходів — cache-aside:
Спочатку читаємо кеш.
Якщо значення є, повертаємо його.
Якщо значення немає, читаємо базу.
Записуємо результат у кеш із TTL.
Повертаємо результат клієнту.
Після зміни або деактивації посилання потрібно інвалідовувати кеш. Інакше користувачі можуть отримувати застарілий URL протягом TTL.
Є кілька підходів.
Система отримує числовий ID і кодує його, наприклад, у base62.
Переваги:
короткий код;
проста перевірка унікальності;
ефективний індекс.
Недоліки:
можна приблизно оцінити кількість створених посилань;
послідовні значення можуть спрощувати перебір;
глобальний генератор ID може стати вузьким місцем.
Генерується випадковий рядок заданої довжини.
Переваги:
складніше вгадати;
немає очевидної послідовності.
Недоліки:
можливі колізії;
потрібні повторні спроби;
необхідно контролювати якість генератора випадкових чисел.
Якщо використовується алфавіт із 62 символів, кількість комбінацій для довжини 7:
62^7 ≈ 3,5 трильйонаЦього достатньо для великої кількості посилань, але реальний вибір залежить від політики безпеки, вимог до читабельності та майбутнього масштабу.
Оскільки переходів значно більше, ніж створення посилань, кеш є природною оптимізацією.
Кешується відповідність:
link:{code} -> originalUrlМожна також кешувати негативний результат для неіснуючих кодів на короткий час. Це захищає базу від повторних запитів до випадкових або шкідливих кодів.
Застарілі дані.
Якщо посилання вимкнули, старе значення може залишитися в кеші.
Cache stampede.
Коли популярний запис одночасно закінчує TTL, багато запитів звертаються до бази.
Можливі рішення:
розподілений lock для заповнення кешу;
випадковий додаток до TTL;
попереднє оновлення популярних записів;
обмеження кількості одночасних запитів до бази.
Нерівномірна популярність.
Невелика кількість посилань може генерувати більшість переходів. Потрібно контролювати пам’ять кешу та політику витіснення.
Для перенаправлення зазвичай достатньо eventual consistency між репліками, якщо коротке посилання не змінюється після створення.
Але є сценарії, де потрібна сильніша гарантія:
створення коду не повинно породжувати дублікати;
деактивація посилання може вимагати швидкої видимості;
зміна власника або прав доступу не повинна довго залишатися невідомою.
Важливо сформулювати політику явно:
Для створення та унікальності коду використовую сильну узгодженість primary database. Для звичайних читань допускаю невелику затримку реплікації. Аналітика є eventually consistent.
Це краще, ніж загально сказати «система буде consistent».
Сервіс перенаправлень має бути stateless. Стан потрібно зберігати в зовнішніх компонентах:
базі даних;
кеші;
черзі;
сервісі конфігурації.
Тоді можна додавати екземпляри за навантаженням без перенесення локального стану.
Початково може бути достатньо primary database із read replicas.
Якщо одного вузла стає недостатньо:
дані можна розділити на шарди за хешем code;
записи можна розподілити за діапазонами ID;
окремо масштабувати сховище основних записів і аналітики.
Вибір ключа шардингу має забезпечувати рівномірний розподіл. Шардинг за часом може створити гарячий поточний шард, якщо більшість записів надходить у нього.
Для глобальної аудиторії можна розмістити точки входу ближче до користувачів. Однак потрібно вирішити:
де знаходиться джерело істини;
як синхронізуються регіони;
чи допускається регіональна eventual consistency;
що відбувається при розриві зв’язку між регіонами.
Не варто додавати multi-region active-active без вимоги, яка цього потребує. Така архітектура суттєво ускладнює конфлікти, маршрутизацію та відновлення.
Потрібно розглянути відмову кожного критичного компонента.
Кеш не повинен бути єдиним джерелом істини. Якщо він недоступний, система може читати з бази, хоча із більшою затримкою.
Сервіс повинен переключити читання на іншу репліку або primary, якщо це допустимо за навантаженням.
Якщо аналітика не є критичною для відповіді, перенаправлення може продовжувати працювати. Події можна:
тимчасово буферизувати;
повторно надсилати;
записувати в локальний журнал із контрольованим розміром;
відкидати за чітко визначеною політикою.
Потрібні:
репліка або standby-вузол;
автоматичний або ручний failover;
резервні копії;
перевірена процедура відновлення.
Під час інтерв’ю важливо згадати не лише backup, а й час відновлення та допустиму втрату даних:
RPO — скільки даних можна втратити;
RTO — за який час потрібно відновити роботу.
Навіть у задачі, яка здається простою, варто назвати основні ризики:
перевірка схеми та довжини URL;
блокування небезпечних або заборонених доменів;
rate limiting для створення посилань;
захист від перебору коротких кодів;
контроль доступу до приватних посилань;
аудит адміністративних операцій;
коректна обробка персональних даних у статистиці.
Важливо не перетворювати відповідь на окреме security interview. Назвіть загрози, що безпосередньо змінюють дизайн.
Сильна відповідь пояснює не тільки «що обрано», а й «чому не інший варіант».
Якщо кожне читання повинно бачити найновіший стан, можна читати з primary, але це обмежує масштабування та доступність.
Якщо допускається коротка затримка, читання можна розподілити між репліками.
Синхронний запис події:
простіший для гарантування доставки;
збільшує latency основного запиту;
робить аналітичне сховище частиною критичного шляху.
Асинхронний запис:
швидше повертає відповідь користувачу;
краще переживає сплески;
вимагає повторних спроб і контролю втрати подій.
Реляційна база зручна, якщо потрібні:
унікальні обмеження;
транзакції;
чітка схема;
зв’язки між сутностями.
Key-value сховище добре відповідає простому доступу:
code -> originalUrlВибір повинен випливати з патернів доступу, вимог до узгодженості та операційного середовища, а не з популярності технології.
TTL зменшує обсяг даних, але може видалити посилання, яке ще очікує користувач. Постійне зберігання збільшує вартість і вимоги до життєвого циклу даних.
Корисно регулярно перевіряти спільне розуміння:
На цьому етапі маємо read-heavy систему з вимогою низької затримки перенаправлення. Тому пропоную кеш перед базою. Чи достатньо такого рівня деталізації, чи розглянути інший сценарій?
Коли інтерв’юер ставить нову умову, не перебудовуйте всю систему мовчки. Скажіть:
яка частина вимог змінилася;
який компонент це зачіпає;
яке рішення ви коригуєте;
який новий компроміс виникає.
Наприклад:
Якщо користувачі повинні змінювати цільовий URL, кеш більше не можна інвалідовувати лише під час створення. Потрібна атомарна зміна в primary і надійна інвалідація кешу. Це збільшує складність, але зберігає швидке читання.
Фраза «використаємо Kafka, Redis і Cassandra» не є архітектурою. Спочатку поясніть навантаження, доступи та вимоги.
Без вимог неможливо обґрунтувати ні сховище, ні кеш, ні рівень узгодженості.
Без приблизного QPS незрозуміло, чи потрібні репліки, шардинг або взагалі розподілена система.
Середній трафік приховує сплески. Назвіть коефіцієнт піку або опишіть сезонність.
Аналітика, email, індексація та інші другорядні операції часто не повинні блокувати основну відповідь.
Перевірка унікальності лише в коді застосунку не захищає від одночасних запитів. Гарантія повинна бути на рівні сховища або іншого атомарного механізму.
Діаграма має пояснювати, як запит проходить через систему, де читаються дані та де виникають помилки.
Не потрібно одразу проєктувати внутрішню реалізацію кешу або формат кожного повідомлення. Спочатку побудуйте правильну структуру, а потім заглиблюйтеся в найризикованіше місце.
Будь-яке рішення має ціну. Якщо ви додаєте репліки, згадайте затримку реплікації. Якщо додаєте кеш, згадайте stale data та cache stampede.
Кеш, черга й репліки також можуть відмовляти. Потрібно пояснити деградацію системи та відновлення.
Під час інтерв’ю можна використовувати такий короткий план:
1. Scope
- Основні сценарії
- Що не входить у першу версію
2. Requirements
- Latency
- Availability
- Consistency
- Data retention
3. Estimates
- Users
- Read/write QPS
- Peak multiplier
- Storage
4. API and data model
- Основні endpoints
- Ключі та індекси
5. High-level design
- Клієнти
- Load balancer
- Stateless services
- Cache
- Database
- Queue
6. Critical flows
- Write path
- Read path
- Async path
7. Deep dive
- Найскладніший компонент
- Масштабування
- Відмови
8. Trade-offs
- Що обрано
- Які обмеження
- Коли потрібна інша архітектура
9. Summary
- Ключові рішення
- Потенційні наступні покращенняДля успішного System Design Interview:
починайте з вимог, а не з технологій;
чітко фіксуйте припущення;
оцінюйте порядок величини трафіку та даних;
проєктуйте API і модель даних до вибору інфраструктури;
описуйте критичні потоки від клієнта до сховища;
відокремлюйте синхронний критичний шлях від асинхронної роботи;
пояснюйте кешування, реплікацію та масштабування;
розглядайте відмови й відновлення;
називайте компроміси кожного важливого рішення;
завершуйте відповідь коротким підсумком і відкритими питаннями.
Найкраща відповідь — це не найдовша діаграма, а послідовне рішення, в якому кожен компонент випливає з конкретної вимоги або оцінки.