Базы данных
Реляционная модель: спроектировать и мигрировать multi-tenant схему
Читать про дизайн схемы — не то же, что жить со схемой два года. Спроектируйте реальную multi-tenant схему маркетплейса, обоснуйте каждый выбор key и constraint, изолируйте арендаторов через RLS, затем докажите, что можете менять её под нагрузкой, не сломав ни одного потребителя — это и есть цикл, который вы реально запускаете на продакшн-базе.
Превратите юнит в один инженерный артефакт: схему Postgres, где каждое решение по key, типу, constraint и хранению обосновано паттерном запроса, арендаторы изолированы вглубь, а ломающее изменение выкатывается без простоя — каждое утверждение подкреплено миграцией, которую можно запустить.
Спроектируйте multi-tenant B2B-схему маркетплейса в Postgres (workspaces, users, products, orders, order_items, tags), где каждый key, тип и constraint обоснован паттерном запроса; обеспечьте изоляцию арендаторов через Row-Level Security; затем выполните ломающее изменение без простоя через expand-then-contract — всё как запускаемые файлы миграций.
- schema.sql плюс упорядоченные файлы миграций, которые чисто применяются на пустом Postgres и воссоздают полную схему, включая все constraints, индексы, RLS-политику и миграцию expand-then-contract.
- Одностраничное обоснование дизайна, связывающее каждое неочевидное решение (каждый surrogate key, каждый выбор FK ON DELETE, выбор JSONB-vs-side-таблица для тегов, снимок unit price) с паттерном запроса или режимом отказа, который оно защищает.
- Вывод EXPLAIN ANALYZE для трёх горячих запросов, демонстрирующий index scan на FK- и tag-колонках при ~1M строк — снятый, а не предполагаемый — плюс демонстрация, что RLS возвращает только строки текущего workspace при заданном app.current_workspace_id.
- Доказательство, что миграция expand-then-contract выполняется на заполненной таблице с parity-запросом, возвращающим ноль расхождений, и без поломки читателя во время transition (старая колонка читаема до шага contract).
- Добавьте guard переходов состояния через BEFORE UPDATE триггер, отклоняющий нелегальные смены status заказа (например, completed не может вернуться в pending), и тест, что триггер отвергает плохой переход.
- Добавьте сохранённый агрегат: колонку workspace_revenue_cents, поддерживаемую триггером на order_items, с reconciliation-запросом, который пере-выводит её из исходных строк и доказывает нулевой дрифт.
- Добавьте DEFERRABLE INITIALLY DEFERRED foreign key для поддержки настоящего циркулярного или многошагового insert (например, самоссылающееся дерево категорий) и задокументируйте, почему immediate-проверка заблокировала бы его.
- Сбенчмаркайте BIGSERIAL vs UUIDv4 vs UUIDv7 как primary key на крупнейшей таблице при ~10M строк и сообщите размер индекса и throughput вставок, подтвердив историю про locality из урока про heap и TOAST.
Это цикл, который вы запускаете на каждой реальной схеме: проектируйте под запросы и жизненный цикл, а не под первый insert; кладите identity в неизменяемый surrogate key и охраняйте business keys через UNIQUE NOT NULL; нормализуйте до 3NF и денормализуйте только с явным согласованием; выбирайте JSONB, типизированную колонку или side-таблицу по тому, как запрашиваете значение; делайте referential integrity и изоляцию арендаторов работой движка, а не приложения; и докажите, что можете менять схему под нагрузкой через expand-then-contract. Сделав это однажды на схеме маркетплейса, вы превращаете продакшн-версию в суждение, а не в угадывание.