Знакомство с 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 — два запроса, написанных руками:

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 остаётся тонким — подключиться, выполнить два типизированных запроса, напечатать результат:

go
pool, err := pg.NewPool(ctx)            // пул соединений к песочнице
queries := db.New(pool)                 // типизированная обёртка из sqlc
version, err := queries.ServerVersion(ctx)
world, err := queries.BrewWorld(ctx)    // перепись: таблица → строк

Запуск

Подними песочницу (из корня репозитория) и накати канон Brew:

sh
docker compose up -d
make lecture L=00-getting-connected/00-01-meet-brew T=db-reset

Запусти демо:

sh
make lecture L=00-getting-connected/00-01-meet-brew

(T=run — значение по умолчанию, поэтому без T=... сразу запускается демо.)

Вывод:

plaintext
Сервер: 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 «Клиент, сервер и песочница»: первый технический шаг. Что вообще находится на том конце сокета, чем сервер отличается от клиента — и как поднять локальную копию, которую не страшно сломать.

·Модуль 01

Этот урок ещё впереди

Курс изучается по порядку — чтобы открыть этот шаг, сначала завершите предыдущие. Так контекст накапливается без пропусков.

/ вы пытались открыть
Подключение и ориентация / Знакомство с Brew