Выбор хранилища: полиглот медиа-сервис
Практический проект: собери бэкенд обмена медиа, что кладёт каждую нагрузку на правильное хранилище — реляционные метаданные, байты в объектном хранилище через presigned-ссылки, поисковый индекс — и осознанно решает двойную запись между базой и индексом.
Прочитать, что надо отделить метаданные от байтов и вывести поисковый индекс из коммита БД — не то же, что собрать систему, которая правда это делает и остаётся согласованной, когда процесс падает между двумя записями. Собери небольшой бэкенд обмена медиа, что кладёт каждую нагрузку на правильное хранилище, отдаёт байты через presigned-ссылки, и затем докажи, что твой поисковый индекс не может тихо разойтись с базой, даже когда ты убиваешь релей посреди записи.
Этот проект делает весь раздел операционным: ты разместишь каждую нагрузку на хранилище, чьи гарантии ей подходят, удержишь крупные байты вне базы, а затем встретишь проблему двойной записи лоб в лоб, выведя поисковый индекс из коммита базы и доказав — осознанно уронив процесс — что он сходится.
Собери небольшой бэкенд обмена медиа (любой язык), что хранит метаданные файлов в реляционной базе, байты файлов в объектном хранилище с отдачей через presigned-ссылки и полнотекстовый поисковый индекс по названиям/подписям — затем сделай поисковый индекс производной, в-итоге-согласованной проекцией базы, что доказуемо не может тихо с ней разойтись.
- Реляционная схема, держащая только метаданные (без байтов файлов), с обоснованием каждой колонки и колонкой object_key, указывающей на байты в объектном хранилище.
- Рабочий поток загрузки, где клиент PUT-ит байты прямо в объектное хранилище через presigned-ссылку, приложение пишет метаданные pending→complete, а уборщик сверяет провальные загрузки — продемонстрировано от начала до конца на многомегабайтном файле, что ни разу не стримится через уровень приложения.
- Endpoint полнотекстового поиска на инвертированном индексе с короткой заметкой, показывающей, почему эквивалентный запрос LIKE полностью сканировал бы, а запрос по инвертированному индексу — нет.
- Поисковый индекс, питаемый из коммита БД (транзакционный outbox или CDC), с убранной из обработчиков запросов двойной записью — нигде нет best-effort «проиндексировать и игнорировать провалы».
- Воспроизводимый тест согласованности, что роняет релей/процесс посреди записи и показывает, что индекс сходится к базе после перезапуска, плюс абзац объяснения, почему он не может тихо разойтись.
- Короткий разбор, сопоставляющий каждую нагрузку её хранилищу и модели согласованности (ACID-метаданные, в-итоге-согласованный производный поиск), изложенный на высоте композиции.
- Добавь lifecycle-тиринг: политику (реальную или симулированную), что двигает объекты в класс нечастого доступа/архив по возрасту, и измерь изменение стоимости хранения против держания всего горячим.
- Добавь обработку долговечность-против-доступности: настрой (или симулируй) межрегиональную репликацию объектного хранилища и опиши путь чтения с failover, что держит байты достижимыми во время сбоя региона.
- Добавь второе производное хранилище: time-series под счётчики просмотров/метрики на элемент, питаемое тем же способом (из журнала коммитов), и покажи даунсэмплинг + retention, ограничивающие его хранение.
- Добавь граф- или рекомендательный запрос («элементы, что также понравились тем, кому понравился этот») и обсуди, какое семейство хранилищ его обслужило бы и почему реляционный self-join не масштабировался бы — оставаясь на высоте композиции.
- 01Почему отделять метаданные от байтов и как поток загрузки держит байты вне уровня приложения?
- 02Как сделать поисковый индекс согласованным с базой без двойной записи и как это доказать?
- 03Какую модель согласованности даёт каждый кусок этой системы и почему это правильная композиция?
Этот проект превращает раздел хранилищ в рабочую систему. Ты размещаешь каждую нагрузку на хранилище, чьи гарантии ей подходят: метаданные в реляционной базе (ACID, запрашиваемо, источник истины о том, что существует и кто владеет), байты в объектном хранилище под ключом (никогда в БД), отдаваемые через presigned-ссылки, чтобы клиент грузил и скачивал напрямую, а stateless-уровень приложения ни разу не проксировал байт — со статусом pending→complete и уборщиком, сверяющим разрыв метаданные↔байты. Поиск работает на производном инвертированном индексе (поисковый движок или tsvector), с подтверждением, что он избегает полного скана, что вынудил бы LIKE с ведущим wildcard. Сердце проекта — проблема двойной записи: ты убираешь best-effort запись индекса из обработчиков запросов и вместо этого выводишь индекс из коммита базы через транзакционный outbox или CDC, затем доказываешь схождение, осознанно уронив релей посреди записи и наблюдая, как индекс догоняет после перезапуска. Результат — система, что компонует сильную согласованность там, где требуется, и итоговую там, где терпимо — полиглот-обмен — и инженер, что собрал её раз, тянется к правильному хранилищу под нагрузку и никогда не доверяет двум независимым записям остаться в синхроне.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.