Модуль 14

Rate limiting та контроль навантаження

Ось твій урок у стилі CS50! 🔥


🎓 CS50: Rate Limiting та Контроль Навантаження

(Або як не дати вашому серверу «лягти» в Чорну п’ятницю)

Привіт, світе! 👋 Радий бачити вас.

Сьогодні ми торкнемося теми, яка відрізняє «програму, що працює на моєму ноутбуці» від «високонавантаженої системи, яка обслуговує мільйони». Ми поговоримо про Rate Limiting (обмеження швидкості запитів).


1. 🔥 Вступ: Проблема та мотивація

Уявіть, що ви відкрили найкрутішу кав'ярню в місті. У вас є один бариста — супер-професіонал. Він робить ідеальну каву за 1 хвилину.

А тепер уявіть ранок понеділка. До кав’ярні заходить 100 людей одночасно і всі разом кричать свої замовлення. Що станеться з баристою? 1. Він запанікує. 2. Він почне плутати замовлення. 3. Зрештою, він просто зависне (або звільниться).

Риторичне запитання: Чи справедливо, що одна людина, яка кричить найголосніше, забирає весь час баристи, поки інші чемно чекають?

У світі вебу: * Бариста — це ваш сервер (API). * Клієнти — це користувачі або боти. * Крик — це HTTP-запити.

Якщо ми не поставимо «турнікет» або адміністратора на вході, який скаже: "Ей, друже, не більше 5 замовлень за хвилину!", наш сервер просто впаде під DDoS-атакою або навіть від звичайного напливу користувачів.

Навіщо нам Rate Limiting? 1. Стабільність: Щоб сервер не «помер» від перевантаження. 2. Безпека: Захист від Brute-force атак (підбір паролів). 3. Справедливість (Fairness): Щоб один користувач не з'їв усі ресурси, залишивши інших ні з чим. 4. Гроші: Багато API платні. Ми не хочемо обслуговувати безкоштовно більше, ніж домовлено.


2. 🧠 Теоретична база (без нудних лекцій)

Що ж таке Rate Limiting "під капотом"?

Це просто лічильник із таймером. Коли приходить запит, ми ставимо собі два питання: 1. Хто це? (IP-адреса, токен користувача, API-ключ). 2. Скільки разів ми його бачили за останні N секунд?

Якщо число перевищує ліміт — ми кажемо: "STOP".

🔑 Ключові поняття (запам'ятайте це):

  • Window (Вікно): Період часу (наприклад, 1 хвилина), протягом якого ми рахуємо запити.
  • Limit (Ліміт): Максимальна кількість дозволених дій у вікні (наприклад, 60 запитів).
  • Throttling: Процес уповільнення або відхилення запитів.
  • HTTP 429: Це код відповіді сервера, який означає "Too Many Requests" (Забагато запитів). Це ввічливе "Приходь пізніше".

Як це працює? (Алгоритми на пальцях)

Уявіть "Відро з жетонами" (Token Bucket). Це найпопулярніша аналогія: 1. У вас є відро. 2. Кожну секунду у відро падає 1 жетон (це поповнення ліміту). 3. Коли юзер хоче зробити запит, він повинен взяти жетон з відра. 4. Є жетон? Запит проходить. 5. Відро порожнє? Запит відхиляється.

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


3. 🧪 Приклади (від простого до реального)

Давайте напишемо код. Не лякайтеся, це буде Python, і ми зробимо це разом.

Сценарій 1: Наївний підхід

Чого ви очікуєте? Ми просто будемо рахувати запити в словнику.

import time

# Наша "база даних" у пам'яті
request_counts = {} 
LIMIT = 5  # 5 запитів
WINDOW = 60 # за 60 секунд

def check_limit(user_ip):
    current_time = time.time()

    if user_ip not in request_counts:
        # Перший раз бачимо юзера: [кількість, час_початку]
        request_counts[user_ip] = [1, current_time]
        return True # Дозволено
    else:
        count, start_time = request_counts[user_ip]

        if current_time - start_time > WINDOW:
            # Час вікна вийшов, скидаємо лічильник
            request_counts[user_ip] = [1, current_time]
            return True
        else:
            if count < LIMIT:
                # Час іде, але ліміт ще не вичерпано
                request_counts[user_ip][0] += 1
                return True
            else:
                # Ліміт вичерпано!
                return False

# Тестуємо!
user = "192.168.1.1"
print(check_limit(user)) # True (1-й)
# ... уявіть ще 4 виклики ...
print(check_limit(user)) # False (6-й запит заблоковано)

У чому проблема? Це "Fixed Window" (фіксоване вікно). Якщо користувач зробить 5 запитів о 11:59:59 і ще 5 запитів о 12:00:01 — сервер отримає 10 запитів за 2 секунди, хоча ми хотіли 5 за хвилину. Це називається проблема граничних умов.


Сценарій 2: Реальний світ (Token Bucket)

Чого ми очікуємо? Більш плавного контролю.

Уявіть, що ми використовуємо бібліотеку, яка реалізує логіку "відра".

from flask import Flask, jsonify
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)

# Магія CS50: ініціалізуємо Лімітер
# get_remote_address — визначає юзера по IP
limiter = Limiter(
    get_remote_address,
    app=app,
    default_limits=["200 per day", "50 per hour"]
)

@app.route("/slow")
@limiter.limit("1 per second") # Жорсткий ліміт для цього маршруту
def slow_route():
    return jsonify({"message": "Я відповідаю не частіше 1 разу на секунду!"})

@app.route("/fast")
def fast_route():
    return jsonify({"message": "Тут ліміти загальні (50 на годину)"})

# Якщо запустити цей сервер і спамити на /slow, 
# дуже швидко отримаємо помилку 429.

Чому це краще? Ми не вигадуємо велосипед. Ми використовуємо перевірені алгоритми, які коректно обробляють час і "вибухи" трафіку.


4. 🛠 Практична частина

Час забруднити руки! 👐

Завдання 1: Ручний таймер Візьміть перший приклад коду ("Наївний підхід"). Змініть його так, щоб він повертав не False, а кількість секунд, яку користувачеві треба почекати до розблокування.

Завдання 2: VIP-клуб Модифікуйте функцію перевірки. Якщо user_ip починається з "10.0..." (внутрішня мережа адмінів), ліміт має бути не 5, а 100 запитів.

Завдання 3: Headers (Це важливо!) Коли ви повертаєте помилку (блокуєте юзера), гарним тоном є додати HTTP-заголовки. Напишіть псевдокод, який разом із відповіддю "429 Too Many Requests" додає: * X-RateLimit-Limit: 60 * X-RateLimit-Remaining: 0 * Retry-After: 30 (секунд)

Завдання 4: Міні-кейс Ви розробляєте API для відправки SMS. SMS коштують грошей. * Придумайте стратегію лімітів. * Чи має сенс ставити ліміт "1000 запитів на секунду"? Або краще "10 запитів на хвилину"? Чому?


5. 💡 Мислення як у розробника

Ось де ми переходимо від "кодера" до "інженера".

Помилка новачка: Зберігати ліміти у пам'яті самого процесу (Python dict), як у нашому першому прикладі. Чому це погано? Коли ваш стартап виросте, ви запустите 5 серверів одночасно. Кожен сервер матиме свою пам'ять. Користувач зробить 5 запитів на сервер А, а потім 5 запитів на сервер Б. Ліміт порушено, але ніхто цього не помітив.

Як думає профі: "Мені потрібне спільне сховище для лічильників". Зазвичай для цього використовують Redis (супершвидка база даних типу ключ-значення). Всі сервери ходять в один Redis перевіряти ліміти. Це стандарт індустрії.

Ще одна порада: Ніколи не блокуйте мовчки. Завжди повертайте заголовок Retry-After. Ваш клієнт (мобільний додаток) повинен знати, коли можна спробувати знову, щоб не «довбатися» у зачинені двері.


6. 🧩 Підсумок

Отже, що ми сьогодні розібрали?

  1. Rate Limiting — це фейс-контроль для вашого додатку. Без нього — хаос і падіння.
  2. Ми дізналися про вікна (windows), ліміти та помилку 429.
  3. Ми зрозуміли, що зберігати стан краще в Redis, а не в локальній змінній, якщо у вас більше одного сервера.

Тепер ви вмієте захищати свої API від ботів і надто активних користувачів. Ваша система стала надійною, передбачуваною і "дорослою".

🔍 У наступній серії: Ми захистили сервер від напливу запитів, але що робити, якщо запити важкі й серверу треба багато часу на їх обробку? Наступного разу ми поговоримо про Кешування (Caching) — мистецтво запам'ятовувати відповіді, щоб не працювати двічі.

Це був CS50. Побачимось! 👋