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

Выбор хранилища: чтение кода и конфигов

Читай реальную схему, конфиг и код, затем прими решение: анти-паттерн блоба-в-БД, запрос LIKE против инвертированного индекса, best-effort двойная запись и путь загрузки через presigned-ссылку. Выбери изменение, под которым подпишется senior.

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

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

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

Сниппет 1 — схема

CREATE TABLE attachments (
  id          uuid PRIMARY KEY,
  owner_id    uuid NOT NULL,
  filename    text NOT NULL,
  content     bytea NOT NULL,   -- сырые байты файла, до 25 МБ
  created_at  timestamptz NOT NULL DEFAULT now()
);
Викторина

Эта таблица хранит файлы по 25 МБ в колонке `content` BYTEA. Какова senior-поправка?

Сниппет 2 — поисковый запрос

-- поиск товаров, 3 000 000 строк, B-tree индекс на title
SELECT * FROM products
WHERE title LIKE '%' || :term || '%'
ORDER BY created_at DESC;
Викторина

Этот запрос отваливается по таймауту под нагрузкой на 3 млн строк несмотря на B-tree индекс на `title`. Почему и какова починка?

Сниппет 3 — путь записи

async function publishPost(post) {
  await db.insert("posts", post);          // источник истины
  await search.index("posts", post);       // сделать ищущимся
  return post;                              // нет общей транзакции
}
Викторина

Какой латентный баг тут и какова надёжная починка?

Сниппет 4 — endpoint загрузки

// обработчик загрузки на stateless-уровне приложения
app.post("/upload", async (req, res) => {
  const buf = await readEntireBody(req);     // буферизует весь файл в памяти приложения
  await objectStore.put(key(req), buf);      // приложение проксирует каждый байт в S3
  res.json({ ok: true });
});
Викторина

Загрузки крупных файлов OOM-ят серверы приложения и насыщают их egress. Какова починка, что держит уровень приложения stateless и без пропускной способности?

Вспомните перед уходом
  1. 01
    Два хранилища должны остаться в синхроне после записи. Почему await обоих вызовов по порядку не делает их согласованными и что делает?
  2. 02
    Почему загрузка через presigned-ссылку — починка для уровня приложения, что OOM-ит и насыщает egress на крупных загрузках?
Итог

Каждое решение хранения в этом разделе читаемо прямо со схемы и кода. Колонка BYTEA/BLOB под крупные файлы — анти-паттерн блоба-в-БД — убери её, храни байты в объектном хранилище под ключом, держи только маленькие метаданные. LIKE с ведущим wildcard не может использовать B-tree и полностью сканирует таблицу; починка — инвертированный индекс (поисковый движок или Postgres tsvector/GIN). Две записи в две системы без общей транзакции тихо расходятся при любом падении или провале — выведи второе хранилище из коммита БД через транзакционный outbox или CDC, никогда best-effort. А обработчик загрузки, что буферизует или проксирует байты, OOM-ит и насыщает уровень приложения — убери приложение с пути байтов presigned-ссылкой, чтобы клиент грузил прямо в хранилище. Senior-привычка — найти, где байты и гарантии реально живут, и выбрать изменение, что уважает свойства хранилища, а не заклеивает цену.

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

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

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

Trademarks belong to their respective owners. Editorial reference only.