Знакомство с Brew
Привет. Ты только что вышел бэкенд-разработчиком в Brew — сеть кофеен с собственным приложением. Касса принимает заказы, сайт показывает меню, маркетинг крутит промо, а в конце месяца аналитик просит отчёт по выручке. За всем этим стоит один Postgres, и теперь он твоя забота. Не администрировать его — это работа DBA. Твоя работа — писать код, который в него пишет и из него читает: запросы, схему, транзакции, индексы.
Этот юнит — не про SQL. Это карта. Что такое Brew, что мы построим за курс, чем всё закончится и какие темы пройдём по дороге. Дальше каждый юнит будет открываться конкретной болью бизнеса и закрывать её одним приёмом Postgres — но сначала стоит увидеть маршрут целиком, чтобы не чувствовать, что тебя бросили в код с первой строки.
Что такое Brew
Brew начинался как одна кофейня, а к началу курса это уже сеть: точка в Москве (BREW-CENTRAL), точка в Питере (BREW-NORTH), общее меню, общая база клиентов. Дальше он будет расти — и это не просто фон. Чем больше Brew, тем дороже ошибка: то, что на одной кассе было незаметно, на сети оборачивается потерянными заказами и кассой, которая встала в обед. Поэтому боль в курсе нарастает от модуля к модулю вместе с самим Brew.
Данные, с которыми ты будешь жить весь курс, простые и узнаваемые: меню напитков (drinks), клиенты (customers), заказы (orders) и их позиции (order_items), кофейни (shops), остатки по точкам (inventory), блог (articles) и outbox — таблица, через которую события заказа уходят во внешний мир. Один и тот же набор таблиц проходит через весь курс: от первого подключения до финального капстона.
Чтобы абстракция «заказ» не оставалась абстракцией, у нас есть якорь — заказ #1. Его оформила Алиса Иванова: две позиции (Капучино + Колд брю), 15 января, статус paid. К этой строке курс будет возвращаться снова и снова — на ней мы посмотрим, как работают JOIN'ы, что происходит при конкурентном обновлении, как читать план запроса. Когда дальше встретишь слово «заказ» и оно покажется слишком общим — вспоминай заказ #1 Алисы.
Что построим и чем закончим
Маршрут курса — это и маршрут твоего роста в Brew. В начале ты новичок, который склеивает SQL строками (и открывает дыру для инъекции) и роняет пул соединений под первой же нагрузкой. В конце ты инженер, который защищает инварианты прямо в схеме, читает EXPLAIN вместо догадок и пишет ретрай-петлю на сериализационный конфликт.
Финал курса — конкретный. Ты сам откроешь PUBLICATION и передашь поток изменений Brew в соседний курс kafka-cookbook: Postgres отдаёт эстафету, Kafka принимает. Две кофейные истории — один мир, одна модель данных. Поэтому базовые таблицы Brew в этом курсе байт-в-байт совпадают со схемой соседнего: переименуешь колонку — и эстафета сломается. Об этом будет отдельный разговор в капстоне, а пока просто запомни, что у курса есть выход наружу, а не только внутренняя кухня.
Карта курса
Одиннадцать модулей, выстроенных по нарастанию сложности. Грубо — снизу вверх:
- Подключение и ориентация (этот модуль) — клиент и сервер, песочница,
psql, подключение из Go, конвейерsqlc, жизнь соединения и пул. - Типы данных — какой тип выбрать и почему:
numericпротивfloatдля денег,timestamptzдля времени,uuidиuuidv7, enum/массивы и введение вjsonb. - Схема и ограничения — ключи,
NOT NULL, внешние ключи,UNIQUE/CHECK, генерируемые столбцы и мышление миграций: какойALTERмгновенный, а какой кладёт кассу. - CRUD-беглость —
INSERT … RETURNING, keyset-пагинация, безопасныеUPDATE/DELETE, upsert черезON CONFLICTи трезвая семантикаNULL. - Запросы по таблицам — JOIN'ы, агрегация,
DISTINCT ON, подзапросы и CTE: здесь данные превращаются в ответы на вопросы бизнеса. - Транзакции, MVCC и конкурентность — ACID, ментальная модель MVCC, блокировки строк, уровни изоляции, ретрай на
40001, дедлоки. - Индексы и EXPLAIN — производительность через чтение планов: B-tree и порядок столбцов, когда индекс не помогает, GIN,
CREATE INDEX CONCURRENTLY. - JSONB, массивы и поиск — гибкие данные и поиск внутри БД: containment, SQL/JSON path, полнотекстовый поиск и нечёткий через
pg_trgm. - Аналитика, оконные функции и LATERAL — running total, ранжирование, top-N на группу, рекурсивные CTE, LATERAL как убийца N+1.
- Запись, события и серверная логика —
MERGE/COPY, очередь задач наSKIP LOCKED, transactional outbox,LISTEN/NOTIFY, триггеры. - Use cases — сквозные капстоны с integration-тестами, которые связывают весь курс в работающие приложения, и тот самый CDC-шов наружу.
Чего в курсе нет: репликации, бэкапов, тюнинга сервера, мониторинга парка, ролей и расширений. Это работа того, кто держит базу, а не того, кто пишет к ней запросы. Где урок упрощает то, что в проде делают иначе, он честно говорит об этом в секции «Заборчик».
Как устроен курс
Весь курс работает против одной песочницы — контейнер Postgres 18 плюс Adminer, поднимается одной командой из корня репозитория. База одна, brew, на весь курс: каждый юнит докатывает свою схему поверх общей через make db-reset. Отдельный контейнер на юнит не нужен.
Каждый юнит оставляет наблюдаемый след: изменённые строки, план, который можно прочитать, публикацию, которую можно стримить. SQL мы пишем руками — он не растворяется в построителе запросов; для большинства юнитов работает конвейер «query.sql руками → sqlc generate → типизированный pgx-код», и это позвоночник курса (подробно разберём его через несколько юнитов, когда дойдём до типизированных запросов). А когда уроку нужна интерактивность, системные колонки или EXPLAIN, мы откладываем sqlc и пишем psql-скрипты или сырой pgx — выбираем возможность, а не инструмент.
Важная деталь, которая будет всплывать постоянно: вывод демо в README — настоящий и детерминированный. Никаких now(), uuid-значений или случайных чисел в stdout. Если число напечатано, оно одинаково на любой машине и при любом числе прогонов — поэтому ему можно верить и сверять байт-в-байт. Это не педантизм ради педантизма: воспроизводимость — то, что отличает «у меня на машине работает» от «работает у всех».
Как устроен каждый юнит
Юниты собраны по одному шаблону, и узнать его стоит сейчас — дальше он повторяется шесть десятков раз. Урок открывается болью бизнеса Brew (пропал напиток из меню, отчёт не сошёлся на копейки, касса встала под нагрузкой), потом концепт собирается в прозе по стадиям — почему, прежде чем как. Затем идёт раздел ## Запуск с настоящим выводом демо, который мы тут же читаем обратно по фактам. А в самом конце каждого юнита — две секции, которые стоит объяснить отдельно, потому что они повторяются везде.
«Заборчик» — это граница упрощений. Любой учебный пример что-то срезает ради ясности: одна база на весь курс, sslmode=disable, пароль brew/brew прямо в строке подключения. «Заборчик» — место, где урок честно показывает, где именно он упростил и как то же самое делают в проде (часто — словами «а твой DBA сделал бы так»). Название буквальное: за этим забором заканчивается учебная песочница и начинается продакшен. Читай его, чтобы не унести упрощение из урока в боевой код — это та разница, на которой потом горят на ревью.
«Что забрать с собой» — три-четыре буллета с выжимкой урока: то, что должно остаться в голове, когда детали забудутся через месяц. Если просматриваешь юнит по диагонали или возвращаешься к нему как к шпаргалке — читай хотя бы это.
Что показывает наш код
Первый контакт. Подключаемся к песочнице, спрашиваем у сервера его версию (убедиться, что на том конце именно Postgres 18) и делаем перепись мира Brew — сколько строк лежит в каждой из 9 таблиц канона после seed. Никакого SQL-героизма, просто «оглянись по сторонам, прежде чем браться за дело».
query.sql — два запроса, написанных руками:
-- name: ServerVersion :one
SELECT version();
-- name: BrewWorld :many
-- Перепись мира Brew: сколько строк в каждой таблице канона.
SELECT entity, n FROM (
SELECT 1 AS ord, 'customers'::text AS entity, count(*) AS n FROM customers
UNION ALL SELECT 2, 'drinks', count(*) FROM drinks
UNION ALL SELECT 3, 'articles', count(*) FROM articles
UNION ALL SELECT 4, 'orders', count(*) FROM orders
UNION ALL SELECT 5, 'outbox', count(*) FROM outbox
UNION ALL SELECT 6, 'processed_outbox_ids', count(*) FROM processed_outbox_ids
UNION ALL SELECT 7, 'shops', count(*) FROM shops
UNION ALL SELECT 8, 'order_items', count(*) FROM order_items
UNION ALL SELECT 9, 'inventory', count(*) FROM inventory
) w
ORDER BY ord;main.go остаётся тонким — подключиться, выполнить два типизированных запроса, напечатать результат:
pool, err := pg.NewPool(ctx) // пул соединений к песочнице
queries := db.New(pool) // типизированная обёртка из sqlc
version, err := queries.ServerVersion(ctx)
world, err := queries.BrewWorld(ctx) // перепись: таблица → строкЗапуск
Подними песочницу (из корня репозитория) и накати канон Brew:
docker compose up -d
make lecture L=00-getting-connected/00-01-meet-brew T=db-resetЗапусти демо:
make lecture L=00-getting-connected/00-01-meet-brew(T=run — значение по умолчанию, поэтому без T=... сразу запускается демо.)
Вывод:
Сервер: PostgreSQL 18.4 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
Мир Brew — 9 таблиц канона. Что лежит в них после seed:
ТАБЛИЦА СТРОК
customers 3
drinks 5
articles 2
orders 3
outbox 0
processed_outbox_ids 0
shops 2
order_items 4
inventory 5
Итого 24 строки — на этих данных поедет весь курс.Читаем вывод обратно. Во-первых, версия начинается с PostgreSQL 18 — на том конце сокета именно та версия, на которую рассчитан курс (хвост строки про архитектуру и gcc у тебя может отличаться, это зависит от машины, на которой собран образ postgres:18-alpine). Во-вторых, в каноне 9 таблиц, и часть из них пока пустые: outbox и processed_outbox_ids ждут модуля про события — их мы наполним сами. В-третьих, заказов ровно три, и среди них тот самый заказ #1 Алисы; пять напитков в меню и две кофейни — это и есть мир, в котором мы проживём следующие десять модулей.
Заборчик
- Курс — для тех, кто пишет приложение, а не администрирует сервер. Репликация, бэкапы, тюнинг,
max_connections, мониторинг — зона DBA, и почти каждый юнит будет явно отмечать эту границу. Ты на том конце сокета клиент, а не оператор сервера. - Песочница — это
docker composeна твоём ноуте.sslmode=disable, парольbrew/brewв открытой строке подключения, одна общая база — всё это годится только локально. В проде соединение шифруют, пароль приходит из секрет-менеджера, а окружения разведены по разным базам. - Одна база
brewна весь курс — учебное упрощение. На реальном проектеmake db-reset(внутри —TRUNCATE) по живым данным не запускают никогда.
Что забрать с собой
- Brew — сеть кофеен; ты её бэкенд-разработчик. Весь курс — это рост от новичка, который роняет пул, до инженера, который открывает
PUBLICATIONи отдаёт поток вkafka-cookbook. - Данные одни на весь курс: меню, клиенты, заказы, остатки, outbox. Осязаемый якорь — заказ #1 Алисы, к нему будем возвращаться.
- Работаем против одной песочницы; вывод демо детерминированный и сверяется байт-в-байт, поэтому числам можно верить.
Дальше — юнит 00-02 «Клиент, сервер и песочница»: первый технический шаг. Что вообще находится на том конце сокета, чем сервер отличается от клиента — и как поднять локальную копию, которую не страшно сломать.