Тайна общей тарелки или System Design дачного шашлыка на 20 гостей

от автора

Дисклеймер: это не stand-up и не пародия. Это полноценное упражнение в системном дизайне на необычной предметной области: TL;DR, требования, парные сравнения альтернатив, failure modes, реальные измерения, references на DDIA / Release It! / Use The Index Luke. Все числа реальные, основаны на 12+ годах личного шашлычного опыта.

Идея статьи проста: дисциплина системного дизайна применима к любой системе, не только к hi-load backend. После прочтения возьми любой свой текущий сервис и пройди те же шаги: workload analysis, paired comparisons, failure modes, реальные измерения. Структура та же, инструменты разные.


TL;DR

  • Что строим: шашлык-pipeline для дачного праздника на 20 гостей. Один мангальщик (папа, дядя или муж сестры), один участок, 4-часовое окно.

  • Целевая нагрузка: 20 гостей × ~2.2 шампура в среднем = ~45 шампуров готовых (~9-11 кг мяса). Распределение неравномерное (от 0 до 5-6 шампуров на гостя; кто-то вообще не ест мясо, но таких 1-2 человека). Bursty profile: первый пик в первый час, когда все голодные, потом устойчивая подкладка.

  • Архитектура: producer-consumer pattern с общей тарелкой как bounded buffer. Шеф готовит партиями, снимает мясо с шампура в общую тарелку, шампура возвращаются в пул, гости берут из блюда сами.

  • Измерено: на даче, +28°C, штиль. Шеф sustained rate 12-15 шампуров/час на мангал (8-10 параллельных шампуров, ~13 мин на batch), скорость опустошения блюда 0.8-1.2 порции/мин в первый час. 0 burnt шампуров.

  • Когда ломается: 50+ гостей (свадьба, корпоратив: переход на hybrid — ordering layer поверх producer-consumer, 3+ мангалов, несколько су-шефов), дождь без тента, вегетарианцев > 30% (cross-contamination на блюде), внезапные +5 гостей без предупреждения (переход на кейтеринг).


1. Проблема и требования

1.1. Сценарий

Шашлык-конвейер на одном дачном участке (single-host) с одним мангальщиком. Возможные варианты:

  • День рождения: 15-25 родственников и друзей, одно 4-часовое окно.

  • Семейные выходные: 8-15 человек, классическая дачная сборка с детьми и собаками.

  • Соседский шашлык-день: 3-6 семей (15-25 человек), distributed potluck-style ingest (каждый приносит своё мясо).

  • Корпоратив backend-отдела (mini): 10-20 человек, два кластера интересов: «мясо» и «vegetables-only».

  • Встреча со школьными друзьями: 12-20 знакомых, durability = how-many-old-stories × time.

Источник мяса: рынок (push-модель: «возьмите свежее, режу при вас») или сетевой супермаркет (pull-модель: «берёшь то, что есть на полке»). Главное что НЕ входит: multi-tenant кейтеринг на несколько праздников одновременно, real-time WebSocket для опаздывающих, multi-region доставка по адресам.

Это не промежуточный вариант на пути к свадьбе на 100 гостей. Это рабочая конфигурация для тысяч дач, большая часть из них никогда не дорастает до кейтеринговой компании, и это нормально.

1.2. Функциональные требования

  • Принимать заказы (тип мяса: свинина / курица / рыба / vegetables)

  • Хранить мясо надёжно (холодильник: даже если повар отвлёкся, блюдо не пропадает)

  • Запросы: «сколько готового сейчас», «что ещё в очереди», «дайте мне ещё»

  • Health check: огонь не тух, маринад не кончился, шеф жив

  • Минимальный admin-интерфейс: лист ожидания + текущий статус мангала (визуальный)

1.3. Нефункциональные требования

Dimension

Target

Producer throughput

12-15 шампуров/час устойчиво (шеф + 1 мангал)

Latency блюда p99

<5 мин от снятия с мангала до тарелки гостя (если блюдо не пусто)

Wait time, когда блюдо пусто

<8 мин до следующей партии (1 цикл готовки)

Durability

Zero burn loss + zero food poisoning (TTL мяса на блюде < 30 мин)

Idempotency

Не повторять batch N (шеф tracks: какой batch на каком мангале)

Operational complexity

1 host + 1 опциональный су-шеф, без on-call после 23:00

1.4. Что не требуется (явные границы)

  • Multi-tenant: один host, один праздник. Если у соседа параллельно свой шашлык, не наша проблема.

  • Multi-source ingestion: мясо из одного места. Смешивать 5 разных магазинов с разной маринадной schema = chaos.

  • Real-time fan-out в общий чат для опаздывающих: пусть приходят на запах, pull-модель.

  • Multi-region: всё в одном дворе. Доставка к соседу через забор вне границ.

  • Sub-5-минут latency: это ресторанный tier, дача не претендует.

  • HA / failover: если шеф устал, делаем перерыв 20 минут. Гости поймут.

Каждое «не нужно» — это сэкономленное усилие. Premature scaling шашлыка («давайте сразу как у профессионалов») приводит к тому, что принимающая сторона выгорает к третьему часу и праздник разваливается.

1.5. Что вне scope этой статьи (другие сервисы дачного праздника)

Шашлык-pipeline — это только один сервис в реальной дачной архитектуре. Параллельно с ним работают другие, независимые:

Service

Pattern

SLA

Backend analog

Напитки (вода, горячительные, чай)

Self-service от стола, prep до праздника

«Всегда есть холодная вода»

Independent service, separate DB

Салаты / закуски (оливье, селёдка)

Pre-event batch, лежат в мисках, read-only после 18:00

Topped up редко

CDN static content (many readers, no writers)

Хлеб / лаваш

Pre-staged, self-service

«Не кончается»

Static asset, простой replenishment

Десерты / чай

Late-stage service, после 22:00

Запускается, когда шашлык wind-down

Async job triggered by upstream event

Эти сервисы независимы от шашлык-pipeline (разные продюсеры, разные хранилища, разные SLA). В реальной архитектуре это были бы отдельные microservices с собственными требованиями. Шашлык — самый тяжёлый и интересный (real-time producer-consumer), поэтому в фокусе этой статьи. Полный кейтеринг для праздников — отдельная задача с другими trade-offs (которую не разбираем).

Урок для backend: при проектировании системы явно определяй границы сервисов. Шашлык-pipeline самостоятелен; напитки и салаты — другие сервисы. Не пытайся перегрузить одну архитектуру под все use cases.


2. Откуда взялись эти числа

Считаем общее потребление снизу вверх: от распределения аппетита к сколько мяса заказать и сколько шампуров реально приготовить.

2.1. Реальное распределение аппетита (20 гостей)

Pareto distribution аппетита

Pareto distribution аппетита

Распределение не равномерное, а парето-образное:

Категория

Шампуров/чел

Кол-во гостей

Итого шампуров

Non-meat (вегетарианец, диета, поел плотно дома)

0

1-2

0

Light (дети, поел до этого, скромный аппетит)

0.5-1

5-6

~5

Average (средний аппетит, большинство гостей)

2-3

9-10

~22

Heavy (любители мяса)

4

3

12

Hot key (один — Дядя Петя)

5-6

1

~5-6

Total consumed (avg ~2.2/чел)

20

~44

+ Резерв / делятся с детьми / упало

~50

+ «гостям с собой»

~55

Total: ~50-55 шампуров готовых = ~10-11 кг мяса (200 г на шампур).

2.2. Один шампур (production primitive)

Параметр

Значение

Вес мяса на шампуре

200-250 г (6-8 кусочков)

Время прожарки

12-15 минут (с поворотами каждые 3-4 мин)

Параллельных шампуров на одном мангале

8-10 (определяет batch size)

Шеф работает не с одним шампуром, а с партией на мангале. Когда партия готова целиком, мясо снимается в общую тарелку, а шампура возвращаются в пул для следующей партии.

2.3. Producer rate (шеф + 1 мангал)

  • 1 batch = 8-10 шампуров на мангале одновременно

  • 1 batch cycle = ~13 минут (cook + снять на блюдо + перезагрузить мангал)

  • ~4.6 batches/час = ~38-46 шампуров/час theoretical max

Это верхняя граница production’а. Realistic sustained 12-15 шампуров/час: шеф отдыхает между batches, пьёт чай, разговаривает с гостями. Это нормально, потому что гости не едят 46/час: они едят максимум 25-30 в первый час, потом сильно меньше. Блюдо балансирует.

2.4. Single-host aggregate scenarios

Scenario

Гостей

Total шампуров

Active grilling time

Семейный ужин (мама-папа-дети-бабушка)

4-6

10-15

1-1.5 ч

Friendly BBQ (компания друзей)

8-12

18-30

1.5-2.5 ч

Дачный праздник на 20 (наша верхняя граница)

20

45-55

3-4 ч

Свадьба / большой корпоратив

50+

100+

6+ ч (кейтеринг-уровень)

Tier 0 верхняя граница = ~50 шампуров за праздник на 1 мангал + папа/дядя как шеф + опциональный су-шеф. Укладывается в 3-4 часа активной готовки с расслабленным темпом.

После 50+ гостей нужен переход на ticket-based ordering + 3+ мангалов (общая тарелка перестаёт работать на масштабе: слишком долго на ней мясо стоит, food safety нарушается).


3. Анализ нагрузки

Архитектура начинается с понимания нагрузки: что приходит в систему, как часто, каким распределением, какими пиками. Только после этого выбираем инструменты. Если делать наоборот, проектируем наугад. Разберём 4 характеристики нашей нагрузки, которые определяют архитектурные решения в §5.

3.1. Retry rate vs effective production

Возвраты в модели общего блюда работают иначе, чем в ticket-модели:

  • «Холодное на блюде»: кусок остыл, гость кладёт на край мангала, чтобы погреть (~5%)

  • «Один кусок недожарился»: гость возвращает на мангал на 2 мин (~3%)

  • Резерв (остатки): несъеденное мясо к концу праздника уходит «гостям с собой» (упаковали в контейнеры, увезли). Это не потеря, это feature (~10-15% от общего объёма)

В отличие от ticket-модели (заказ → шампур → гость) здесь нет per-guest retry. Если мясо плохое, оно просто не съедено и попадает в остатки. Метрика: plate clearance rate (какой % приготовленного съели за праздник).

При нормальных условиях clearance ~85-90%. Если <70%, что-то с маринадом или прожаркой (но не «retry storm»: гости просто перестают подходить).

3.2. Backpressure — центральная характеристика буфера

Backpressure feedback loop

Backpressure feedback loop

Блюдо — это bounded buffer. Capacity ~10-15 порций готового мяса. Шеф не может класть бесконечно:

  • Блюдо наполовину опустошается (гости разбирают) → шеф готовит следующую партию параллельно, на опережение

  • Блюдо полное → шеф приостанавливает (нет смысла готовить, мясо станет холодным)

  • Steady state = шеф попадает в темп: к моменту опустошения очередная партия уже готова

  • Если блюдо опустело до того как партия готова → wait time spike (хорошая backpressure-настройка такого избегает)

Так и работает backpressure в чистой форме. В backend терминах: producer замедляется, когда buffer почти полный, и работает с anticipation, когда buffer drain’ится.

3.3. Hot keys — даже при низкой нагрузке

«Hot key» — это когда непропорционально много запросов идёт на один ресурс. На общей тарелке это гораздо опаснее, чем в ticket-модели.

Дядя Петя ест 5-6 шампуров (~12-15% от ~44 шампуров общего потребления). При ticket-модели ему пришлось бы делать 6 отдельных заказов в очередь, и все стояли бы вместе с ним. При общей тарелке дядя Петя просто подходит и берёт без очереди: нет механизма справедливого распределения. Если он подошёл первым после batch’а, половина блюда ушла ему, остальные приходят к пустому блюду.

Что это означает:

  • No queue fairness: first-come-first-served в чистом виде

  • Burst-protection отсутствует: дядя Петя в один заход может опустошить блюдо

  • Hot key effect amplified: особенно, когда блюдо мелкое

Решения (в порядке social complexity):

  1. Social rate limiting: дяде Пете говорят «не больше двух за раз». Дёшево, но зависит от social pressure

  2. Несколько блюд: раздельные блюда для разных типов мяса, дядя Петя берёт с одного, остальные с другого

  3. Manual portioning: папа выкладывает по тарелкам индивидуально (но это ticket-модель, теряется async-преимущество)

На 20 гостях обычно (1) достаточно: каждый знает дядю Петю и подколы про его аппетит. На 50+ гостях среди незнакомых людей нет social pressure, нужна (2) или переход на hybrid с ordering layer (см. §10).

Tech analog: шумный сосед в multi-tenant без изоляции. Решается per-tenant rate limiting (token bucket): у нас social token bucket через шуточки.

3.4. Producer/consumer rate balance

Producer/Consumer rate balance во времени

Producer/Consumer rate balance во времени

В модели общего блюда нет «reads vs writes» в классическом смысле. Вместо этого: producer rate (шеф кладёт на блюдо) vs consumer rate (гости берут с блюда):

  • Producer (шеф): 8-10 шампуров каждые 13 минут = ~40-46/час theoretical max, ~12-15/час sustained

  • Consumer (20 гостей): пик до 25-30/час в первый час, потом 5-15/час стабильно, снижаясь к концу праздника

Состояние блюда во времени:

Время

Шеф rate

Guest rate

Блюдо depth

Status

19:00-19:15 (warmup)

0

0

0

пусто (шеф разогревает мангал)

19:15-19:30 (1st batch)

35/час

0

filling

блюдо начинает наполняться

19:30-20:00 (peak hour)

40/час

30/час

стабильно ~5-8 порций

balanced

20:00-21:30 (sustained)

15/час

12/час

стабильно ~3-5 порций

low load

21:30-22:30 (late)

5/час

3/час

shrinking

wind-down

22:30+ (остатки)

0

0

небольшой остаток

гостям с собой

Key insight: шеф и гости decoupled через блюдо. Гость напрямую не ждёт шефа, шеф не реагирует на каждый конкретный заказ. Это backbone async event-driven архитектуры.

Architectural implications:

  • Блюдо = bounded buffer (Kafka topic с retention TTL): capacity 10-15 порций, TTL ~30 мин (после food safety)

  • Backpressure: шеф видит, что блюдо полное, делает паузу (см. §3.2). В backend это producer отслеживает consumer lag и замедляется.

  • Async decoupling: шеф и гости работают параллельно независимо. В backend это producer service и consumer service, соединённые через очередь.

  • Eventual consistency: гость пришёл через 10 мин после batch’а, ест, что осталось. Не «exactly the skewer ordered», а «what’s available».

3.5. Burst vs sustained

Шеф production rate почти никогда не работает на пике постоянно. Реальный профиль вечера:

  • 18:00-19:00 (prep): 0 шампуров готово, маринад в холодильнике, мангал холодный

  • 19:00-19:30 (warmup + 1st batch): 0-10 шампуров, warm-up phase, длинный TTFB

  • 19:30-20:30 (peak hour): шеф производит 25-30 шампуров → блюдо наполняется → гости разбирают → блюдо в балансе

  • 20:30-22:00 (sustained): шеф производит 12-15/час → гости разбирают ~10/час → блюдо опустошается, остатки накапливаются

  • 22:00+ (wind-down): шеф останавливается, остатки упаковывают «гостям с собой»

Math sanity check: 30 (peak hour) + 25 + 5 (sustained 1.5h) ≈ 50-55 шампуров всего за праздник. Совпадает с TL;DR (~50 готовых, ~10-11 кг мяса).

Архитектура под импульсную нагрузку отличается от устойчивой:

  • Pre-warm мангала: шеф разогревает до прибытия гостей (mitigate cold start)

  • Burst absorption: блюдо ёмкостью 10-15 порций накапливает запас на первую волну

  • Резерв угля 50%: запас для вечернего пика

  • Cooking surface рассчитан на peak: 1× large либо 2× standard parallel (см. §5.3)

  • Pre-loaded холодильник: мясо замариновано и готово до прибытия гостей

  • Отдых между партиями: в период устойчивой нагрузки шеф не выгорает к 22:00

На устойчивых 40/час за 8+ часов история другая (свадьба, корпоратив с пограничными требованиями): пром-мангал, электрический резерв, отдельная поставка дров. Но это уже не дача.

Глубже: дисциплина анализа нагрузки
  • Designing Data-Intensive Applications (Kleppmann), Ch1 «Reliability, Scalability, Maintainability»: applies к любой системе, включая шашлык

  • Systems Performance (Brendan Gregg), Ch2 «Methodology»: USE method (Utilization, Saturation, Errors), мангал utilization, очередь saturation, burnt errors

  • «Capacity Planning for Internet Services» (Allspaw): про bursts vs sustained, sized для peak


4. Архитектура

Архитектура шашлычной системы

Архитектура шашлычной системы

Два пути доставки:

  • Default (95-99%): шеф → блюдо → гость (async через буфер)

  • Direct VIP (1-5%): шеф → шампур → connoisseur (bypass блюдо, sync handoff)

Большинство гостей идёт через блюдо: natural async pull. Connoisseur (1-2 человека на 20 гостей) получает direct delivery (special-case bypass). Это classic default path + premium bypass pattern. Подробнее в §5.1.

Один шеф (папа / дядя), 1× large или 2× standard мангала, 1 холодильник, общая тарелка как буфер. Producer-consumer pattern с bounded buffer: backbone async event-driven архитектуры, переведённый на дачный язык.

Дальше разбираем стек (§4.1) и ключевые решения (§5).

4.1. Stack

Компонент

Выбор

Что даёт

Источник тепла

Уголь (берёзовый) + стартер для углей

I/O-bound (теплопередача), разогрев за 15 мин

Cooking surface

На 20 гостей: 1× large (90+ см, 12-15 шамп параллельно) ИЛИ 2× standard (60×30 каждый, parallel) — см. §5.3

Зрелая экосистема, работает с любыми шампурами

Skewer pool (resource pool)

15-20 шампуров всего (8-10 в мангале + 5-10 в резерве)

Многоразовый ресурс, чисто возвращается после снятия мяса

Блюдо / общая тарелка (bounded buffer)

Глубокое сервировочное блюдо, ёмкость 10-15 порций

Async decoupling шеф ↔ гости, обработка backpressure

Хранилище

Бытовой холодильник 280L

Durable, выдерживает 4 часа постоянного открывания/закрывания

Marinade pipeline

Готовый накануне (8h ферментация) + аварийная партия

Pre-staged async pipeline

Раздача тарелок

Стол с тарелками для гостей

Self-service, гости сами берут

4.2. Containers: тарелки + шампуры, без дорогой сервировки

Дорогая сервировка даёт «бесплатно»: визуальную привлекательность, нарядную подачу, фото для соцсетей.

На дачном шашлыке это operational tax без выгоды:

Что даёт сервировка

Что есть в нашей задаче

Visual appeal

Гости голодные, им важна еда, а не картинка из Pinterest

Fancy presentation

Через час разговоров никто не помнит

Фото для соцсетей

Делают 2 человека, остальные едят

Fine dining ratio

1:1 тарелка/гость наша, full-service 1:3 (overkill)

Тарелки + шампуры = stage 1 default. Fine dining появится только когда праздник для food critic (не наш случай).

Глубже: архитектурные основы до события
  • «Twelve-Factor Шашлык» (наш аналог 12factor.net): III. Закупки накануне, IV. Backing services (соседский электричество и вода), IX. Disposability (можно прервать без потери данных)

  • «Building Microservices» применённый к кейтерингу, Ch3 «When NOT to use professional catering»: must-read для дач до 100 человек

  • «Choose Boring Technology» (Dan McKinley): методология «innovation tokens». Команда организаторов может позволить ~3 эксперимента (новый маринад, новый соус, новая мясная позиция). Каждый — это investment.


5. Key design decisions

Разберём девять решений, которые меняют систему. Cosmetic-уровень (свинина vs баранина, кетчуп vs аджика, лимонад vs морс — все взаимозаменяемы в этой задаче) живёт в README репозитория.

Важная рамка перед чтением раздела. Все решения ниже оптимизированы под scope из §1: один мангальщик, один праздник, без требований профессионального кейтеринга. Решения — это функция требований, не функция количества гостей. При других требованиях (например, «корпоративный комплаенс на халяльное мясо» или «гости-веганы > 50%») тот же стек на тех же 50 гостях может потребовать broker (кейтеринговую компанию), отдельную analytics БД (food-cost tracking), кэш, даже без роста по нагрузке.

5.1. Async через блюдо vs sync ticket-per-guest

Async через блюдо vs Sync ticket-per-guest

Async через блюдо vs Sync ticket-per-guest

Решение: async producer-consumer через общую тарелку как bounded buffer. Шеф готовит batch’ами, мясо снимается с шампура на блюдо, гости берут с блюда сами.

Почему в нашей задаче (20 гостей):

Sync model (ticket-per-guest)

Async model (наш — блюдо)

Гость заказывает «2 куриных» → шеф готовит на отдельных шампурах для него

Шеф готовит batch’ами для всех → снимает на блюдо

Прямая coupling шеф ↔ гость

Decoupling через блюдо

Нужны tickets для idempotency (per-guest)

Idempotency на batch level (не повторять batch N)

Гость ждёт свой заказ (queue)

Гость ест что есть на блюде (eventual consistency)

Latency: order → cook → deliver = 13-22 мин

Latency: пришёл к блюду → ест сразу = 0 мин (или 0-13, если пусто)

Хорошо для формального праздника (свадьба)

Хорошо для дачного формата

Почему блюдо выигрывает на дачном масштабе:

  • Decoupling: шеф не отвлекается на каждый запрос «когда мой?», работает batch’ами в своём ритме

  • Burst absorption: блюдо capacity 10-15 порций позволяет накопить запас до прибытия гостей

  • Naturally backpressure’d: шеф видит блюдо полное, делает паузу; видит пустое, запускает batch

  • Меньше ожидания для большинства гостей: пришёл к блюду, мясо уже есть, 0 ожидания. При sync все ждут свою очередь параллельно.

  • Соответствует культурному паттерну дачи и семейного праздника (никто не делает tickets на бабушкин ужин)

Когда добавлять ordering layer (hybrid с tickets):

Trigger

Reason

50+ гостей

Блюдо не масштабируется: слишком долго мясо стоит, food safety нарушается

Vegetarians / халяль / детское меню

Cross-contamination в общем блюде; нужны отдельные блюда, проще tickets

Формальный праздник (свадьба, корпоратив)

Гости ожидают «их заказ», а не «что осталось»

Multi-source ingestion (соседский potluck из 5 источников мяса)

Нужен ordering layer: чьё мясо когда жарится

Saga / Outbox / CDC patterns в требованиях

Это broker territory (Kafka/RabbitMQ analog), общая тарелка не enough

Tech analog: блюдо — это classic producer-consumer bounded buffer (Kafka topic с retention TTL, Redis Stream). Большинство реальных backend-систем используют именно эту модель: чистый sync HTTP без буфера встречается реже, чем кажется. Если ты строишь backend и не делал event-driven через очередь, ты делал чистый sync request-response (sync ticket-per-guest здесь) — и он быстро упирается в потолок. На масштабе добавляется ordering layer (tickets) поверх async, не вместо.

Edge case: direct-skewer delivery (VIP path)

В реальной дачной практике 95-99% мяса идёт через блюдо. Но 1-5% — это special-case bypass: гость говорит «дай прямо с шампура» (мясо горячее, не остыло на блюде, текстура другая, сок прямо в куске). Это архетипичный VIP / direct path pattern.

Path

Coverage

Latency

Use case

Default async (через блюдо)

95-99% гостей

0-13 мин wait

Большинство, не привередливы к прожарке

Direct sync (прямо с шампура)

1-5% connoisseurs

<1 мин (шеф передаёт напрямую)

Heavy enthusiast или special request

Architecture implications direct-path’а:

  • Шампур temporarily out of pool: пока гость держит и ест, шампур не возвращается в reserve. Если 3-4 connoisseur’а одновременно, effective pool size сокращается, меньшие batches.

  • Шеф interrupted from batch flow: отдать шампур = микропауза в основном цикле, batch counter может сбиться.

  • Не масштабируется свыше 5%: больше direct requests блокирует основной pipeline. На 20 гостях 1-2 connoisseur’а — это норма; на 50 гостях нужен отдельный су-шеф для direct-path или убрать совсем.

Tech analog:

  • Stripe: большинство charges через standard processing, enterprise merchants получают direct queue (priority + bypass)

  • Twitter: большинство tweets через стандартную fanout pipeline, celebrity tweets имеют специализированный handler из-за high subscriber count

  • Cloudflare: большинство requests через CDN edge, специфические routes идут direct-to-origin минуя cache

Pattern везде одинаковый: default path для большинства трафика + bypass для небольшого premium-сегмента со специальными требованиями. В backend это часто реализуется через priority queues или dedicated worker pools. На даче через шефа, который запоминает «Тиму всегда даёт прямо с шампура».

Честный trade-off: добавляет mental complexity (шеф вручную помнит про special requests). На дачном масштабе нормально, потому что connoisseur’ов 1-2 максимум; complexity tax negligible. На 50+ гостях среди незнакомых людей direct-path расплывается, лучше унифицировать через блюдо для всех.

5.2. Уголь, не газ / электричество / костёр

Решение: берёзовый уголь + стартер для углей.

Альтернатива

Почему отбросил

Газовый гриль

Зрелый рынок, мгновенный запуск, предсказуемый. Но: нет дымного аромата (гурманы отвергают), зависимость от газового баллона (кончится в самый неподходящий момент), высокая стоимость покупки ($300-800).

Электрогриль

Стабильная температура, легко контролировать. Но: пиковая мощность 2-3 кВт не вытянет 2 параллельных мангала на дачной проводке (выбьет пробки в момент пика), нет дымного аромата, привязан к розетке (риск удлинителя).

Костёр на земле

Классика, бесплатно, аутентично. Но: нестабильная температура (high variance p99), пепел в шашлыке (food safety violation), PM2.5 emissions (cancer-causing при длительной устойчивой нагрузке), пожар-риск на сухой траве.

Дрова в мангале

Экологично, дымный аромат отличный. Но: 30+ минут на разгорание (no warm pool, slow start), нестабильная теплоотдача (variance 100°C peak-to-peak).

Угольные таблетки (для шашлычницы)

Компактно, удобно для пикника. Но: химический привкус, быстро выгорают (перезарядка каждые 20 минут — это operational overhead).

Решение: берёзовый уголь + стартер для углей (металлическая труба для разжигания за 15 минут).

Берёзовый уголь даёт стабильный 220-260°C на поверхности мангала, держит температуру 1.5-2 часа без перезарядки, smoky flavor устраивает большинство гостей.

5.3. Vertical vs horizontal: один большой мангал или два стандартных

Vertical vs Horizontal scaling: 1× large vs 2× standard parallel

Vertical vs Horizontal scaling: 1× large vs 2× standard parallel

Решение: для 20 гостей один стандартный мангал 60×30 — undersized. Realistic baseline: либо 1 большой мангал (90+ см, 12-15 шампуров параллельно), либо 2 стандартных параллельно (60×30 каждый, 8-10 шампуров на каждом, 16-20 параллельно всего).

Один стандартный мангал 60×30 справляется до 6-12 гостей (семейный ужин, дружеский BBQ). На 20 гостях он становится узким местом: шеф упирается в потолок реального темпа (12-15/час устойчиво недостижимо, если шеф отдыхает между партиями как нормальный человек).

Почему это classic vertical-vs-horizontal scaling:

1× large (90+ см)

2× standard parallel (60×30)

Параллельных шампуров

12-15

16-20 (8-10 на каждом)

Координация

Один шеф, один heat zone

Нужен су-шеф для второго мангала

Уголь / праздник

1-2 пакета (3-6 кг)

2-3 пакета (×1.5-2 расход)

Temperature control

Сложнее (большая зона нагрева)

Проще (independent зоны)

Bulkhead (изоляция типов мяса)

Нет — всё на одном heat zone

Да — курица на одном, свинина на другом

Failure mode «прогорел уголь»

Single point of failure: 20 мин пауза

Один продолжает, второй разогревается заново

Operational complexity

Простая, 1 worker

Distributed coordination, 2 workers

Когда какой выбрать:

  • 1× large — если соло работа без су-шефа, гости едят однотипное мясо, нет vegetarian/halal segment, операционная простота важнее redundancy

  • 2× standard parallel — если су-шеф есть, нужен bulkhead (vegetarians > 5, или allergens), требуется fault tolerance (важный праздник, нельзя себе позволить 20-минутную паузу)

Tech analog:

  • 1 large = vertical scaling (один bigger DB instance, scale-up)

  • 2 standard = horizontal scaling (две replicas, scale-out)

  • Bulkhead = изоляция по мангалу — критично для food safety, когда есть сегменты, которые нельзя смешивать

Честный trade-off: 2× мангала + су-шеф = выше операционная сложность (координация процесса между двумя исполнителями, разделённое внимание папы), но дают fault tolerance + изоляцию по типам мяса. На дачном уровне 20 гостей с однородной кухней 1× large обычно достаточно. С сегментом вегетарианцев > 5 человек 2× становится обязательным (cross-contamination в одном мангале неприемлем, см. §10).

5.4. Идемпотентность на уровне партии

Решение: на 20 гостях (4-7 партий за вечер) формальная идемпотентность — overkill. Mental tracking + физический skewer pool как natural counter (8 шампуров на мангале vs 5-10 в reserve) хватает; финал — когда мясо в холодильнике закончилось.

Pattern важен на scale: при 50+ гостях / multi-day events / нескольких шефах формальный batch ID и persistent state становятся критичны. В §5.5 ниже — schema design как «scale-form» идемпотентности (то, что нужно когда мангал перестаёт быть мысленно-trackable).

Почему именно batch-level, не per-guest:

  • В модели общего блюда гость не идентифицируется на пути от мангала до тарелки. «Кто это съел» выявляется постфактум через социальное наблюдение («дядя Петя ест уже четвёртый»)

  • Per-guest ticket был бы дублирующим tracking’ом: шампур → блюдо → гость (где гость не идентифицируется)

  • Партия — natural unit для шефа: «одна партия = одна загрузка мангала = ~13 минут работы»

Tech analog: Kafka producer idempotency через acks=all + sequence numbers — producer не отправляет дубликат, если уже acked. Шеф на масштабе делает то же самое через формальный batch ID; на 20 гостях — через физический skewer pool.

5.5. Schema design — batches + состояние блюда

shashlyk_batches:                       -- producer side (шеф's mental log)  id          BIGSERIAL PRIMARY KEY,  batch_id    TEXT UNIQUE,              -- 'batch-1', 'batch-2' (шеф-generated, idempotency key)  mangal_id   INT,                      -- 1 или 2 (если есть 2-й)  loaded_at   TIMESTAMPTZ,              -- когда загружен  ready_at   TIMESTAMPTZ,               -- когда снят с мангала (NULL пока готовится)  skewers_count INT,                    -- 6-10 шампуров в batch'е  meat_types  JSONB,                    -- {chicken: 5, pork: 3} per batch  notes       TEXT                      -- 'без лука', 'больше маринада'UNIQUE INDEX idx_batch ON shashlyk_batches (batch_id);INDEX idx_loaded_ts ON shashlyk_batches USING BRIN (loaded_at);platter_state:                          -- consumer side (volatile, не persistent)  current_count   INT,                  -- сколько порций на блюде прямо сейчас  oldest_added_at TIMESTAMPTZ,          -- TTL tracking (food safety)  current_types   JSONB,                -- {chicken: 3, pork: 2} на блюде сейчасskewer_pool:                            -- resource tracking  total           INT = 15-20,          -- весь pool  in_mangal       INT,                  -- 8-10 active (если batch на мангале)  cooling         INT,                  -- 2-3 returned, ещё горячие  available       INT                   -- ready для следующего batch'а

Решения, которые требуют объяснения:

  • batch_id UNIQUE: idempotency key — гарантия в schema от повторной загрузки партии. На масштабе, где физический skewer pool уже не отслеживается в голове, это критично.

  • shashlyk_batches не имеет guest_id: в модели общего блюда гость отвязан от tracking’а. Не отслеживаем «кто что ел», это вне scope.

  • platter_state volatile: не персистится в long-term time-series, это текущее состояние. Полезен для backpressure-решений (полный/пустой checks).

  • skewer_pool как resource counter: аналог connection pool. Если available = 0 и cooling > 0 → ждём, пока cooling закончится. Если available = 0 и cooling = 0 — у нас утечка (потеряли шампур).

  • BRIN на loaded_at: партии приходят в хронологическом порядке, идеально для BRIN на исторический анализ после праздника.

  • meat_types JSONB на партию служит audit trail для food safety forensics: если кому-то стало плохо, можно отследить «что было в партии N в 20:15». Без этого incident investigation невозможен.

Глубже: bbq event schema design
  • «PostgreSQL Internals» (Egor Rogov): JSONB indexing strategies, применимо даже к заметкам на бумажке

  • «Use The Index, Luke!» (Markus Winand): partial indexes для WHERE status != 'served' (active queue queries)

  • «Database Internals» (Petrov), Ch3 «File Formats»: почему BRIN компактнее для time-series

5.6. Connection pooling — параллельность мангалов

Skewer pool — resource partitioning

Skewer pool — resource partitioning

Решение: 15-20 шампуров всего в пуле, из которых 8-10 активны в мангале одновременно, остальные остывают / доступны для следующей партии. Шампура — это многоразовый ресурс, не одноразовый на партию.

Почему размер пула = 15-20:

  • Пул < 12 (только на 1 партию): нет резерва для следующей партии, пока текущая остывает, шеф простаивает

  • Пул 15-20: текущая партия на мангале (8-10) + резерв для следующей (5-10), zero-wait turnover

  • Пул > 25: дополнительные шампура занимают пространство в резерве, прироста throughput не даёт

At any moment:  in_mangal     = 8-10  (active batch cooking)  cooling       = 0-2   (сняли с мангала, ещё горячие, мыть нельзя)  available     = 5-10  (clean, ready для next batch)  total pool    = 15-20

Теоретический максимум throughput: 10 шампуров × (60 мин / 13 мин на цикл) = ~46 шампуров/час на 1 мангал. Реалистично устойчиво 12-15/час, потому что гости не едят 46/час и блюдо балансирует шефа через backpressure.

Это буквальный connection pool в backend:

  • in_mangal = активные connections (заняты обработкой requests)

  • cooling = recently-released, ещё в TIME_WAIT

  • available = idle pool, готов к следующему request

  • Если всего = 0 → resource starvation → wait, как при pool exhaustion в DB

Настройки таймаутов:

  • Max время на партии = 18 минут (после этого риск burnt, прерываем, выбрасываем). Backend analog: statement_timeout для PG queries.

  • Idle batch warning = 5 минут без поворота (мясо подгорает с одной стороны). Backend analog: idle_in_transaction_timeout, утечка transaction warning.

Эти таймауты обязательны независимо от количества гостей: это failure mode уровня кода, не уровня нагрузки.

После 50+ гостей или сегмента вегетарианцев добавляется дополнительный мангал + су-шеф + второе блюдо. Это horizontal scaling пула: больше worker’ов, не больше ёмкости на каждый. Backend analog: pgbouncer + read replicas вместо более крупного single instance.

5.7. Caching: маринад как pre-warm cache

Cache hierarchy шашлычной системы

Cache hierarchy шашлычной системы

Решение: маринование за 8 часов до праздника — это pre-warm cache, который снимает 8 часов с critical path во время самого праздника.

Cache hierarchy в нашей задаче:

Слой

Что хранит

Время до подачи

Ёмкость

Cold storage (холодильник, сырое)

Сырое мясо

8h+ (нужен маринад на critical path)

~10 кг

L2: pre-marinated (8h ферментация готова)

Готовое к шампуру мясо

~13 мин (только готовка)

~10 кг

L1: край мангала

Снятый шампур, ещё горячий

0 мин (direct handoff)

2-3 шампура

Shared cache (блюдо)

Готовое мясо для всех

0 мин (самообслуживание)

10-15 порций

Почему pre-warm обязателен:

  • Без маринада мясо требует 8h ферментации на critical path: первая партия появляется в 03:00 ночи.

  • Pre-marinated = чистый shift left: затраты оплачены накануне (off-peak), горячий путь — только готовка.

Cache stampede на первой партии. В 19:30 первая партия готова, 20 голодных гостей одновременно подходят к блюду: classic dogpile. Решение: первая партия размером больше обычного (8-10 шампуров вместо 6), даже если все 20 подойдут, каждый получит свою порцию без 13-минутного ожидания следующего цикла. В backend это request coalescing или увеличенный TTL для hot keys на старте.

Cache invalidation (TTL-based):

  • Маринад TTL 8-12h: перемаринованное мясо деградирует структурно (см. §9 anti-pattern #4)

  • Готовое мясо на блюде TTL 30 мин: food safety threshold (см. §6.2)

  • Eviction policy: не LRU (мясо берут не по «когда положили»), а TTL + visual inspection шефом

Tech analog:

  • Маринад = pre-warming cache (Redis warmup на старте сервиса, prerendered pages в CDN)

  • Край мангала = L1 in-process cache (process-local memory, application-level cache)

  • Блюдо = shared cache (Redis, доступный всем readers)

  • Stampede mitigation = request coalescing (singleflight pattern)

Этот подход — work shifted left: 8 часов вложений накануне снимают 8 часов с critical path во время праздника. На backend это эквивалент prerendering, CDN warmup, DB query cache prepopulation. Без cache layer любой read-heavy сервис захлёбывается; на даче точно так же.

5.8. Шеф runtime: «папа», не нанятый профессионал

Решение: шеф — это папа / дядя / муж сестры, который уже 15+ раз делал шашлык на этой даче. Знание накапливается через повторение, не через формальное обучение.

Почему в нашей задаче: мы I/O-bound, теплопередача и timing доминируют, raw skill критичен. Папа с 15-летним BBQ-опытом знает, какой производитель угля быстрее разгорается, как правильно его рассыпать, по каким признакам понять, что пора снимать. На 20 гостях интуиция «сейчас этот шампур готов» работает быстрее любого таймера. Плюс никаких mockup’ов от нанятого человека: атмосфера семейного праздника зависит от того, кто стоит у мангала.

Подробное сравнение grill runtimes: папа/дядя vs amateur / professional / robot

Альтернатива

Почему отбросил для tier 0

Любитель (первый раз кто-то из гостей решил попробовать)

Дёшево, но: burnt rate >20%, длинные latency p99, гости уйдут в магазин за пельменями. На семейном празднике никто не доверит.

Профессиональный шеф (нанятый)

Качество высокое, но: 15-30 тыс. рублей за праздник, overkill для дачи на 20 гостей. И атмосферу убивает (это уже не домашний праздник).

Робот-шашлычница (BBQ автомат)

Стабильное качество, нет человеческого фактора. Но: 100-200 тыс. рублей капитальных затрат, нет дымного аромата (электрический нагреватель), отсутствует «душа повара» (мама обидится).

AI-driven grill (с computer vision определением прожарки)

Современный, hype. Но: технология на ранней стадии, требует Wi-Fi на даче (LOL), failure mode «AI считает, что готово» труднодиагностируется.

Когда я бы перешёл на профессионального шефа:

  • 50+ гостей (свадьба, корпоратив: долгая нагрузка выматывает даже опытного папу)

  • Hard SLA на качество (food critic в гостях, важный корпоратив)

  • Несколько кухонь параллельно (steak + sushi + кебаб одновременно)

  • Operational pod density: папа тянет 1 мангал, профессиональный — 2-3 мангала параллельно

До этих триггеров папа/дядя справляется. И гораздо приятнее по атмосфере.

5.9. Су-шеф framework: жена / друг / соседка

Решение: один опциональный су-шеф для часов пик (peak hour 19:00-20:00).

Почему в нашей задаче: structure для роста (жена приносит готовое мясо к мангалу, шеф жарит, DI / separation of concerns) без cost на старте. На 50 гостях координация между шефом и су-шефом экономит больше времени, чем стоит, и плавно масштабируется до кейтеринг-уровня: DI container уже есть, добавить второго су-шефа занимает 30 секунд инструкций.

Подробное сравнение су-шеф frameworks: wife/friend/neighbor/no-helper

Альтернатива

Почему отбросил для tier 0

Без помощника (соло)

Минимум coordination overhead, но: пик выматывает шефа, длинные queue waits после первого часа

Случайный гость (в последний момент)

Бесплатно, но: нет контекста, нужно объяснять каждое действие, больше времени тратится на onboarding, чем на помощь

Профессиональный официант (кейтеринг)

Оптимальная координация, но: $50-100/час за праздник, overkill

AI-помощник (Алиса / Маруся для таймеров)

Современный, но: помогает только с таймингом, не с физическим переносом мяса

В нашем сценарии (если хостинг праздников станет регулярным) будет рост в multi-event tier, и структура «host + су-шеф» оправдана с первого дня.


6. Durability, потеря данных, безопасность

Вопрос, который часто упускают на дачных праздниках: что мы реально гарантируем, и где риски потери.

6.1. Что архитектура гарантирует по умолчанию

Угольный мангал + блюдо дают несколько durability primitives бесплатно:

  • Synchronous heat transfer. Мясо в контакте с углём, передача энергии мгновенная. Если шампур положен, процесс идёт.

  • Atomicity на уровне партии. Партия обрабатывается целиком: либо вся снята с мангала и положена на блюдо, либо ещё на мангале. Половина партии на блюде, половина на мангале — это не баг, это transient state между циклами.

  • Buffer durability: мясо на блюде остаётся там, пока кто-то не возьмёт. Шеф не теряет «уже приготовленное», как могло бы быть в чистом sync chain.

  • TTL via cooling: мясо холоднеет при долгом лежании. Это explicit retention policy: после ~30 мин мясо технически съедобное, но холодное; >2 часов — это food safety violation. Блюдо само сигнализирует «забери меня».

Итого: producer и consumer decoupled через durable buffer. Как Kafka topic: producer положил, топик хранит, consumer берёт, когда готов. Даже если гость отвлёкся на разговор и пришёл через 15 минут, мясо ещё на блюде (если TTL не нарушен).

6.2. Failure modes — что мы теряем и от чего спасает что

Failure modes matrix — 9 mode'ов отсортированы по severity

Failure modes matrix — 9 mode’ов отсортированы по severity

Сбой

Что происходит

От чего спасает

От чего НЕ спасает

Блюдо пустое, гости ждут

Скачок времени ожидания (1 batch cycle = ~13 мин)

Pre-warm мангала, достаточно большая первая партия

Если шеф ушёл за углем, длинная пауза

Блюдо переполнено, мясо стоит >30 мин

Food safety violation (cold meat → bacteria growth)

Backpressure (шеф приостанавливает, когда блюдо полное)

Если шеф не следит за блюдом, риск

Шеф потерял счётчик партий (через 2 часа + отвлечения)

Дубль партии или пропуск

Маркер на бумажке + skewer pool counter

Полное истощение → нужен су-шеф

Шеф забыл повернуть партию

Сгоревшая партия (потеря 8-10 шампуров)

Таймер на телефоне 4 мин

Если уже пережарено, прерываем

Уголь кончился (storage exhaustion)

20-минутная пауза, блюдо опустошается

50% резерва

Если резерв тоже кончился, празднику конец

Дождь без тента

Мангал затоплен, партии теряются

Ничего без тента или резерва в помещении

Durability-гарантии бесполезны

Соседская собака украла мясо с блюда

Частичная потеря с блюда

Высокий стол + присмотр

Мясо уже в собаке

Внезапные +5 гостей без предупреждения

Шеф отвлекается, счётчик партий сбивается, capacity exceeded

Backup host (су-шеф принимает координацию)

Деградация по времени до конца праздника

Маринад испортился (food poisoning)

Катастрофично

Свежий маринад с уксусом каждые 8 часов

Если уже подано, все гости в больнице

Что критично заметить: durability primitives мангала (heat, ACID шампура) защищают от process failures, но не от storage failures (дождь, собака, незваные гости). Single host без backup’а — это single point of failure.

Именно это «ощущение высокого риска потери» чувствуется при чтении §3-§5. Оно правильное: без backup’ов и/или су-шефа disk failure (дождь) стирает праздник. Закрываем в §6.3-§6.4.

6.3. Backup strategy — обязательно с момента приглашения гостей

Не «ну как-нибудь обойдётся». Это не обсуждается:

  • Pre-event capacity planning: посчитать гостей × средний аппетит + 30% резерва. Заказать мясо за 1-2 дня до праздника.

  • Тент / навес: восстановление после дождя за 5 минут (vs 30 минут спешной перебежки в дом)

  • Резервная кухня в помещении: если уголь промок, можно дожарить на сковороде

  • Стоимость: ~5-8 тыс. рублей за тент + 30% дополнительного мяса.

6.4. Replication — рекомендация, не обязательно на tier 0

Су-шеф как replica — это следующий шаг durability:

Сценарий

Су-шеф?

Семейный ужин на 4-6 человек

Не нужен, соло OK, папа справляется один

Дачный праздник на 12-20 (наша задача)

Желателен на пиковый час, иначе папа не успеет за час до 20:00

Свадьба / 50-летие / 50+ гостей

Обязательно + standby второй су-шеф + дополнительные мангалы

Корпоратив с food critic в гостях

Обязательно + профессиональный шеф как primary

После выхода на кейтеринг-уровень су-шеф становится обязательным + automated failover (когда основной шеф устал, су-шеф принимает работу незаметно для гостей).

6.5. Что про вкус и food safety

Вкус и food safety — это универсальная проблема, не зависящая от tier. Стандартная практика одна и та же на 5 гостях, 50 и 500: маринад с уксусом или лимоном (убивает бактерии), прогрев до 70°C внутри куска (USDA standard для свинины/курицы), сырое мясо не на сервировочных тарелках. Стандартные практики не различаются между tier’ами, поэтому подробное обсуждение пропускаю.

Единственный tier-specific нюанс — два смысла rate limiting’а.

  • Per-guest fair-share (защита одного гостя от другого, чтобы дядя Петя не съел всё): на дачном масштабе не нужен, папа знает гостей лично, социального давления достаточно. На корпоративе с незнакомыми гостями нужен.

  • Per-mangal safety throttle (защита мангала от перегрузки 30 шампурами): нужен независимо. Это safety net на любом масштабе.

6.6. Минимальный production checklist

Помимо архитектурных решений выше:

  • [ ] Тент или резервная кухня в помещении организованы

  • [ ] Запас мяса 30% сверх расчётного (20 гостей × 5 шампуров × 250г = 25кг, берём 32-35кг с резервом на heavy eaters типа дяди Пети)

  • [ ] Маринад готов накануне (8h ферментация) + аварийная партия (2h)

  • [ ] Alert на угольный запас < 1 пакета (rule of thumb: пакет ≈ 2 часа непрерывной готовки)

  • [ ] Alert на 19:00-20:00 пик (су-шеф on standby)

  • [ ] Per-mangal safety throttle (см. §6.5): не больше 10 шампуров параллельно на один мангал

  • [ ] Один-два рукописных post-mortem’а после крупных праздников («что пошло не так», «что улучшить») для не-очевидных решений (берёзовый уголь vs дубовый, секрет маринада)

Глубже: durability и event recovery
  • «Crash-Only Software» (Candea & Fox), applied to grilling: «прервать → перезапустить» лучше, чем «осторожно завершать»

  • Aphyr Jepsen analyses: distributed system consistency, применимо когда 2 мангала с разной скоростью прожарки, нужно упорядочивание событий

  • «Site Reliability Engineering» (Google), Ch26 «Data Integrity»: что значит «доверять backup’у», как тестировать, что reserve мяса не испорчен


7. Что мерить с первого шампура

Базовые метрики, которые отвечают на вопрос «у нас всё OK?»:

Метрика

Что показывает

Backend analog

Глубина блюда (количество + типы на блюде прямо сейчас)

Buffer occupancy: полное=backpressure, пустое=starvation

Kafka topic depth, Redis queue length

Время на блюде p99 (сколько времени самый старый кусок лежит)

TTL nearing: мясо стынет, food safety risk

Message age in queue

Темп партий шефа (партий/час) vs скорость опустошения блюда (порций/мин)

Producer/consumer balance: равны ли темпы?

Producer throughput vs consumer rate

Skewer pool utilization (in_mangal / cooling / available)

Resource pool exhaustion early warning

Connection pool stats (active/idle/total)

Запас угля

Storage exhaustion: мангал остынет, если ниже threshold

Disk usage / available capacity

Счётчик сгоревших партий

Quality degradation: шеф устал или мангал перегрет

Error rate by category

prom-client для шашлыка — это доска с пометками возле мангала: счётчик (приготовлено / возвращено), таймер последнего поворота, оценка запаса угля. 30 минут на настройку. На 5 гостях избыточно? Нет, это база для улучшения на следующих праздниках: «в прошлый раз возвращено было 12%, почему? Маринад? Уголь? Шеф устал?».

Формализуем SLO. Метрики без thresholds бесполезны: alert «блюдо пусто 2 минуты» (не требует реакции) и «блюдо пусто 10 минут» (шеф ушёл, гости расходятся) — это два разных события. Базовый SLO для дачного шашлыка: wait time p95 < 3 мин (нормальный customer experience), burnt batches < 2% (quality threshold), alert, если 3 партии подряд > 5 мин ожидания (вероятен шеф burnout или storage exhaustion). На backend без определения SLO метрики — это просто dashboards без смысла.

Глубже: observability foundations для праздников
  • «Effective Monitoring and Alerting» (Slawek Ligus): alerting hygiene applied к bbq events

  • Brendan Gregg: USE Method: для каждого ресурса (мангал, шеф, су-шеф, уголь) проверь Utilization, Saturation, Errors


8. Measured: настоящий шашлык на даче

8.1. Setup

Honesty disclaimer: числа ниже — это оценки по реальному дачному опыту, не формальные benchmarks с секундомером. Разброс ±20-30% от праздника к празднику в зависимости от погоды, состава гостей, настроения шефа. На большом празднике (50+ гостей) degradation factor ещё больше (см. §8.4).

  • Hardware: Дача 6 соток, 2× мангала 60×30 см параллельно (horizontal scaling из §5.3 — bulkhead для курицы и свинины), 1 холодильник 280L, 1 стартер для углей.

  • Stack: папа как шеф + я как су-шеф (2 человека, соответствует §1.3), 8h-маринованная свинина + курица, настройки по умолчанию из «Книги о вкусной и здоровой пище» 1952 г.

  • Источник нагрузки: 20 гостей реальные (родственники + друзья), 4-часовое окно активной готовки.

8.2. Результаты

Время

Партий/час шефа

Глубина блюда

Время ожидания гостей

19:00-19:15 (разогрев)

0 (мангал разгорается)

пусто

19:15-19:30 (1-я партия)

4 (1 партия за 13 мин)

заполняется до 8 порций

0-13 мин (cold start)

19:30-20:30 (пиковый час)

4 партии/час (~32 шамп)

стабильно 5-10 порций

<2 мин (всегда есть готовое)

20:30-22:00 (устойчиво)

1-2 партии/час (~12 шамп)

стабильно 3-7 порций

<3 мин

22:00-22:30 (wind-down)

0-1 партия

опустошается, остатки

0 (для тех, кто хочет ещё)

Всего за праздник: ~50 шампуров (10-11 кг мяса), 0 сгоревших партий, 1 возвращённый кусок (сыровато), ~5-8 шампуров остаток «гостям с собой».

Заполненность блюда по времени (визуально):

Заполненность блюда по времени

Заполненность блюда по времени

8.3. Что это говорит

  1. Балансировка блюда работает: глубина колеблется 3-10 порций весь праздник, никогда не пусто в пиковый час, никогда overflow > TTL threshold.

  2. Wait time <3 мин для большинства гостей: гостям не приходится ждать (в этом ценность модели общего блюда).

  3. Темп шефа адаптивный: 4 партии/час в пике, 1-2 в устойчивом режиме. Backpressure через визуальную проверку блюда.

  4. Cold start: первые 15 минут гости ждут разогрева мангала + 1-й партии. Это неизбежно.

  5. Остатки ~10-15%: feature, не bug. Никто не ушёл голодный, все довольны.

8.4. Honest disclaimer: synthetic шашлык ≠ production event

Это dyno test на семейном празднике. На большом празднике 50+ (synthetic ≠ prod):

  • Гости менее предсказуемы ближе к концу праздника: всплески потребления непредсказуемые (одни уже сыты, другие неожиданно голодны)

  • Сосед как concurrent admin user («ну кто так жарит, дай покажу») в реальном времени добавляет ~15% overhead

  • Дети как chaos engineering (могут опрокинуть мангал, унести шампуры)

  • Главный гость (юбиляр / именинник / важное лицо) как critical SLA: если не вкусно — disaster

  • Длительность 8+ часов (vs наши 4 часа), выгорание шефа накапливается

Realistic degradation factor: 30-50% от дачной synthetic’и на «реальном» празднике.

Наш «40/час @ p99 18 мин» в production conditions:

Сценарий

Реалистично

Семейный ужин на даче (наш базовый случай)

40/час @ p99 18 мин

Соседи зашли на запах (+5 человек)

30/час @ p99 25 мин

Встреча бывших одноклассников

25/час @ p99 30 мин (все спорят про прожарку)

Любой праздник после 22:00 (после танцпола)

15/час @ p99 45 мин (chaos mode)

Это покрывает design point «20 гостей, 4 часа» в production conditions для семейного ужина (с запасом). Если папа не выспался / болеет / отвлекается быстрее, чем готовит — нужна ротация су-шефа, иначе latency degradation.


9. Anti-patterns на этом уровне

7 anti-pattern'ов дачного праздника

7 anti-pattern’ов дачного праздника

7 ошибок, которые регулярно встречаются на дачных праздниках и убивают праздник быстрее любых scaling-проблем:

1. Уголь «на будущее» (cold start procrastination). «Разожгу, когда гости приедут», и первые 30 минут общая тарелка пустая, гости голодные. Уголь надо стартовать за 30 минут до первого гостя, чтобы 1-я партия была готова, когда тарелка нужна.

2. Готовить, пока тарелка не переполнится. Шеф игнорирует полную общую тарелку, кладёт ещё партию, тарелка переполнена, мясо стоит >30 мин, food poisoning risk. Backpressure через визуальную проверку: если тарелка полная — пауза, разговаривай с гостями вместо новой партии.

3. Микросервис-гриль (один мангал на каждый тип мяса). Мангал для курицы + мангал для свинины + мангал для рыбы. На 20 гостях это только усложняет: координация добавляет overhead, три пула скевер’ов, distributed tracing «а где сейчас рыба?». Монолит-мангал (общий с разделением температурных зон) — скучный, надёжный, быстрый вариант по умолчанию.

4. Агрессивное партионное маринование без TTL. «Замариную всё мясо сразу на 8 кг за 24 часа до праздника». Через 24 часа: 4 кг перемариновано (мясо «варится» в кислоте, теряет структуру), 4 кг технически OK. Маринад имеет TTL 8-12 часов, после этого мясо деградирует, а не улучшается. Лучше поэтапная подготовка: первая партия за 8 часов, вторая за 4 часа.

5. Skewer pool leak. Шеф отдал шампур гостю «возьми с собой к мангалу», не вернул в пул, через час available = 0, следующая партия ждёт, пока кто-то заметит пропажу. Шампура — это многоразовый ресурс, не одноразовый. Отслеживай возврат, не отдавай гостям.

6. No social rate limiting на дядю Петю. «Дядя Петя пусть берёт, сколько хочет, с тарелки». Дядя Петя берёт половину после каждой партии, остальные приходят к пустой тарелке, время ожидания скачком вырастает, люди расходятся к холодильнику за пельменями. Минимум social pressure: «дядя, не больше двух за раз».

7. Strong consistency там, где не нужна. «Подождём, пока ВСЕ шампуры всех гостей готовы, и подадим одновременно» — на read-heavy празднике (20 гостей хотят есть сейчас) eventual consistency через тарелку (раздавать партиями, как готовится) достаточно и значительно быстрее. Понимай, где strong consistency нужна (общий тост — все вместе), а где overhead (ужин: каждому когда готово).

Глубже: stability patterns для гриль-master’ов
  • Release It! 2nd ed (Michael Nygard), Part II «Stability Patterns»: timeouts (max prep time), circuit breakers (су-шеф takes over), bulkheads (мясо не на одном мангале), fail fast (если сыро, сразу обратно). Работают с первого дачного праздника независимо от количества гостей.

  • «Why programmers should avoid microservices» (DHH), applied to grilling: один мангал на 5 типов мяса лучше 5 специализированных.


10. Когда эта архитектура ломается

5 триггеров перехода на кейтеринг tier

5 триггеров перехода на кейтеринг tier

Триггер

Что меняется

→ Кейтеринг-уровень (вне scope этой статьи)

50+ гостей (свадьба, юбилей)

Выгорание единственного шефа, queue overflow, переход с модели общего блюда на tickets, нужно 3+ мангалов и несколько су-шефов

2-3 шефа с ротацией или кейтеринг

Multi-event hosting (несколько праздников параллельно)

Resource contention, tenant isolation

Multi-tenant кейтеринг schema, ticket isolation

8+ часов длительности (свадьба, корпоратив)

Накапливающееся выгорание, маринад выдыхается

Ротация шефа + свежий маринад каждые 4 часа

Вегетарианцев > 30%

Разделение мангалов (нельзя мясо и овощи на одном)

Dedicated veg-pipeline, отдельный мангал

Непредсказуемая погода

Требуется disaster recovery testing

Резервная кухня в помещении обязательна

Если ни один из триггеров не сработал, не лезь в кейтеринг. Папа + cooking surface из §5.3 (1× large или 2× standard parallel) + опциональный су-шеф на этом уровне — это не промежуточный вариант, а рабочая конфигурация для тысяч дачных праздников.


11. Воспроизводимо

# Repository: ваша дачаcd ~/dacha && git checkout 20-guestsdocker compose up -d  # (запустить мангал, опц. 2-й standby)# Подготовкаnode scripts/marinade.mjs 4kg-pork 2kg-chicken 1kg-fish --hours 8# Run eventnode scripts/grill-event.mjs --guests 20 --duration 4h --weather sunny --chef papa

Те же hardware (мангалы + холодильник), тот же stack (уголь + маринад), те же data (мясо + гости), те же numbers ± 15% (разброс от погоды, состава компании, наличия комаров).


12. Почему этот фреймворк работает на любой системе

Shashlyk ↔ Backend mapping паттернов

Shashlyk ↔ Backend mapping паттернов

Если ты дочитал до этого места, мы только что прошли через полный цикл системного дизайна. Те же шаги применяются к любой backend-системе независимо от масштаба:

Дисциплина / Компонент

Шашлык на 20 гостей

Backend на любом tier’е

Анализ нагрузки

распределение аппетита, hot keys, producer/consumer balance, burst

то же (workload analysis)

Service boundaries

Шашлык / напитки / салаты — раздельные сервисы с разными SLA

Microservices / bounded contexts (DDD)

Парные сравнения

газ vs уголь vs дрова

MySQL vs PG vs MongoDB

Блюдо (bounded buffer)

Общая тарелка с TTL и backpressure

Kafka topic с retention / Redis Stream

Skewer pool

15-20 шампуров, многоразовых

Connection pool (DB connections, HTTP keep-alive)

Vertical vs horizontal

1× large 90 см vs 2× standard parallel

Scale-up vs scale-out (bigger DB instance vs replicas)

Bulkhead

Курица на одном мангале, свинина на другом

Failure isolation (отдельные thread pools / queues)

Cache hierarchy

Маринад (8h pre-warm) → край мангала → блюдо

L1/L2 cache + CDN warmup

Batch idempotency

Маркер «batch-3 готов» на бумажке

Producer idempotency (Kafka acks=all + sequence numbers)

Schema design

batch_id UNIQUE, JSONB на типы мяса, BRIN на time-series

DB constraints + indexing strategy

Backpressure

Блюдо полное → шеф приостанавливает

Producer замедляется, когда consumer lag растёт

Hot key (дядя Петя)

Половина блюда за один заход

Noisy neighbor в multi-tenant

Audit trail

meat_types JSONB на партию

Forensics log для incident investigation

Replication

Су-шеф как backup шефа

Read replicas / hot standby

SLO

wait p95 < 3 мин, burnt < 2%

Error budget + alert thresholds

Failure modes table

блюдо overflow, batch counter loss, дождь

queue overflow, idempotency loss, disk failure

Honest disclaimer

дачный замер ≠ большой праздник

localhost dyno ≠ production

Anti-patterns

7 ошибок на bbq

7 ошибок на backend

Итог: дисциплина системного дизайна применима к любой системе, будь то распределённая база на 1M TPS или дачный мангал на 20 гостей. Принципы те же:

  1. Анализ нагрузки до архитектурных решений

  2. Парные сравнения вместо слепого выбора инструментов

  3. Честная failure modes table вместо «всё будет ОК»

  4. Реальные измерения вместо «по ощущениям норм»

  5. Anti-patterns как явные предупреждения

Инструменты меняются (мангал vs PostgreSQL), паттерны остаются (idempotency, ACID, time-series indexing, retry budget).

Если ты узнал в шашлыке свой backend — фреймворк сработал. Принципы те же, инструменты разные. Архитектуру определяют паттерны, а не масштаб.


Reading day budget — что прочитать перед постройкой production-системы

Per-§ reading lists под спойлерами «Глубже» дают depth по конкретным темам. Если есть один день на foundational подготовку (применимо к любой системе: backend, BBQ, distributed cluster):

  1. DDIA chapters 1-3 (Kleppmann, та самая «книга с кабанчиком»), 3-4 часа — foundations reliability / scalability / maintainability. Тот самый frame через который смотришь на любую систему после прочтения.

  2. Release It! Part II «Stability Patterns» (Nygard), 2 часа — pattern catalog в чистом виде: timeouts, circuit breakers, bulkheads, fail fast. Большая часть §5-§6 этой статьи опирается на этот раздел.

  3. Use The Index, Luke! chapters 1-3 (Winand), 1 час — indexing intuition без theory overhead. После прочтения BRIN, partial indexes, composite keys перестают быть mystery.

  4. Twelve-Factor App, 30 минут — operational hygiene checklist. Прочитать за обед, применять следующие 5 лет.


P.S.

Всё что выше — про то как думать, а не про то как жить.

Шашлык на даче работает не из-за producer-consumer pattern и не из-за batch idempotency. Он работает, потому что папа у мангала, дядя травит шутки, дети бегают по двору, а соседский кот терроризирует собаку. System design тут ни при чём.

Когда дача, лето и собрались близкие — лучшая дисциплина — это отложить ноут, налить чаю и просто быть. Архитектура подождёт до понедельника.

Хороших шашлыков и хорошей компании.

ссылка на оригинал статьи https://habr.com/ru/articles/1034968/