open atlas
↑ К треку
Основы System Design SD · 08 · 08

Строительные блоки: чтение кода и конфигов

Читай реальный код лимитера, конфиг ID, вызов размера bloom-фильтра и запрос geohash, затем выбери ответ, на который подписался бы senior: гонка потерянного обновления, фрагментирующий индекс ключ, насыщенный фильтр и пропущенный граничный сосед.

SD Senior ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

Баги строительных блоков прячутся в коде, не в прозе: read-then-write, что не атомарен, первичный ключ, фрагментирующий индекс, фильтр, размеренный под неверный N, запрос, забывший соседние ячейки. Читай каждый сниппет, найди дефект и выбери починку, на которую подписался бы senior.

Практикуй цикл ревью, что ты гоняешь на реальном коде блоков: найди операцию, проверь, атомарна ли она / упорядочена по времени / верно размерена / учитывает границу, и выбери изменение, которого механизм действительно требует.

Сниппет 1 — инкремент лимитера

def allow(key, limit):
    n = redis.get(key) or 0          # чтение
    if int(n) < limit:
        redis.set(key, int(n) + 1)   # modify-write (отдельная операция)
        return True
    return False
Викторина

Этот лимитер работает на многих серверах к одному Redis. Под конкурентностью какой баг вводит read-then-write и какова починка?

Сниппет 2 — первичный ключ

-- высоконагруженная таблица событий, InnoDB (кластеризованный PK)
CREATE TABLE events (
  id   UUID DEFAULT gen_random_uuid()  PRIMARY KEY,  -- случайный UUIDv4
  body JSONB,
  ts   TIMESTAMPTZ
);
Викторина

При миллионах вставок/час этот кластеризованный случайно-UUID первичный ключ вредит. В чём проблема и минимальное изменение?

Сниппет 3 — размер bloom-фильтра

# размерен один раз при старте
bf = BloomFilter(expected_items=1_000, fp_rate=0.01)
# ...позже, в проде, он принимает каждый ключ события
for key in stream:        # миллионы ключей со временем
    bf.add(key)
Викторина

Фильтр размерен под 1 000 элементов, но принимает миллионы. Что происходит и что остаётся гарантированным?

Сниппет 4 — запрос близости

def nearby(lat, lng):
    cell = geohash_encode(lat, lng, precision=6)   # ячейка ~1 км
    return db.query(
        "SELECT * FROM drivers WHERE geohash LIKE %s", cell + '%'
    )   # лишь собственная ячейка пассажира
Викторина

Этот возвращает ноль водителей, даже когда один припаркован через улицу. Что не так и какова починка?

Вспомните перед уходом
  1. 01
    Как заметить и починить гонку распределённого лимитера в коде?
  2. 02
    Почему случайно-UUID кластеризованный первичный ключ вредит пропускной способности вставок и минимальная починка?
  3. 03
    Что происходит, когда фильтр Блума размерен под слишком мало элементов, и что сохраняется?
Итог

Каждое решение по блоку в этом юните читается прямо с кода. Лимитер, делающий отдельные чтение и запись на флоте, имеет гонку потерянного обновления — схлопни в атомарный INCR или Lua-скрипт. Случайно-UUID кластеризованный первичный ключ фрагментирует B-дерево на высоких темпах вставки — перейди на упорядоченный по времени UUIDv7 ради вставок у правого края. Фильтр Блума, размеренный под слишком мало элементов, насыщается (доля ложноположительных → 100%, но никогда ложноотрицательных) — перестрой под истинное N. А запрос близости geohash, сканирующий лишь ячейку пассажира, попадает в проблему границы — включи 8 соседних ячеек, затем ранжируй по точному расстоянию. Senior-привычка одна и та же каждый раз: найди операцию, назови инвариант, что она должна держать (атомарность / упорядоченность по времени / верный размер / учёт границы), и выбери починку, его восстанавливающую.

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.