<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Senior Go Interview Prep</title><link>https://go.vbloher.org/</link><description>Recent content on Senior Go Interview Prep</description><generator>Hugo</generator><language>ru</language><atom:link href="https://go.vbloher.org/index.xml" rel="self" type="application/rss+xml"/><item><title>Analytics Pipeline</title><link>https://go.vbloher.org/docs/10-system-design/analytics-pipeline/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/analytics-pipeline/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Аналитический пайплайн принимает миллиарды событий в сутки от продуктовых SDK/бэкендов, буферизует их в Kafka, обрабатывает потоково (Flink/Spark Structured Streaming) и пакетно, складывает в колоночное OLAP-хранилище (ClickHouse/Druid/BigQuery) и обслуживает дашборды и ad-hoc аналитику. Ключевые оси решений: stream vs batch (latency vs полнота/точность), lambda vs kappa (две кодовые базы vs reprocess из лога), exactly-once vs at-least-once + дедупликация, raw vs pre-aggregated (rollups). Главная единица параллелизма — партиции Kafka; главные узкие места — skew/hot partition, конкуренция query vs ingest в OLAP и стоимость хранения/retention.&lt;/p&gt;</description></item><item><title>CAP теорема</title><link>https://go.vbloher.org/docs/08-distributed-systems/cap-theorem/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/cap-theorem/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CAP — это &lt;strong&gt;не&lt;/strong&gt; «выбери 2 из 3». Сетевые партиции (P) случаются вне твоего контроля, поэтому реальный выбор делается &lt;strong&gt;только во время партиции&lt;/strong&gt;: либо Consistency, либо Availability. В отсутствие партиции можно иметь и C, и A одновременно.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C&lt;/strong&gt; в CAP — это &lt;strong&gt;linearizability&lt;/strong&gt; (строгая согласованность по чтению, recency guarantee), а &lt;strong&gt;не&lt;/strong&gt; буква C из ACID (consistency = соблюдение инвариантов/констрейнтов). Это разные понятия, которые путают чаще всего.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A&lt;/strong&gt; в CAP — это «каждый запрос к работающему узлу получает (не-error) ответ». Это очень жёсткое определение: latency не учитывается, ответ должен прийти от любой ноды, не упавшей физически.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P (partition tolerance)&lt;/strong&gt; — система продолжает работать при потере произвольного числа сообщений между узлами. Для распределённой системы это не опция, а данность.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CP&lt;/strong&gt;: при партиции жертвуем доступностью (некоторые узлы отвечают ошибкой/таймаутом ради согласованности). Примеры: etcd, ZooKeeper, Consul, HBase, Spanner, традиционные RDBMS с синхронной репликацией.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AP&lt;/strong&gt;: при партиции жертвуем линеаризуемостью (узлы отвечают возможно устаревшими данными). Примеры: Cassandra, DynamoDB, Riak, Voldemort.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PACELC&lt;/strong&gt; дополняет CAP: при &lt;strong&gt;P&lt;/strong&gt;artition выбираем между &lt;strong&gt;A&lt;/strong&gt;/&lt;strong&gt;C&lt;/strong&gt;, иначе (&lt;strong&gt;E&lt;/strong&gt;lse) — между &lt;strong&gt;L&lt;/strong&gt;atency/&lt;strong&gt;C&lt;/strong&gt;onsistency. Это честнее описывает реальные системы.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="формулировка-и-её-точный-смысл"&gt;Формулировка и её точный смысл&lt;a class="anchor" href="#%d1%84%d0%be%d1%80%d0%bc%d1%83%d0%bb%d0%b8%d1%80%d0%be%d0%b2%d0%ba%d0%b0-%d0%b8-%d0%b5%d1%91-%d1%82%d0%be%d1%87%d0%bd%d1%8b%d0%b9-%d1%81%d0%bc%d1%8b%d1%81%d0%bb"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Теорему сформулировал Эрик Брюер (2000, conjecture), формально доказали Gilbert &amp;amp; Lynch (2002). Точная формулировка доказанной версии:&lt;/p&gt;</description></item><item><title>CI/CD: пайплайны, стадии, стратегии деплоя</title><link>https://go.vbloher.org/docs/11-devops/cicd/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/11-devops/cicd/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: DevOps · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CI (Continuous Integration)&lt;/strong&gt; — на каждый push/PR автоматически прогоняются lint, тесты, сборка. Цель: ловить интеграционные проблемы рано, держать &lt;code&gt;main&lt;/code&gt; всегда зелёным.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CD&lt;/strong&gt; — двусмысленно: &lt;em&gt;Continuous Delivery&lt;/em&gt; (автоматически готовим релиз, кнопка деплоя ручная) vs &lt;em&gt;Continuous Deployment&lt;/em&gt; (каждый зелёный коммит в &lt;code&gt;main&lt;/code&gt; автоматически едет в prod). На senior-собеседовании важно разделять эти два понятия.&lt;/li&gt;
&lt;li&gt;Типичные стадии Go-пайплайна: &lt;code&gt;lint&lt;/code&gt; → &lt;code&gt;test&lt;/code&gt; (unit + race + coverage) → &lt;code&gt;build&lt;/code&gt; (бинарь/Docker-образ) → &lt;code&gt;scan&lt;/code&gt; (уязвимости) → &lt;code&gt;publish&lt;/code&gt; (push в registry) → &lt;code&gt;deploy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Артефакт&lt;/strong&gt; — иммутабельный результат сборки (Docker-образ по digest, бинарь, helm chart). Билдим один раз, продвигаем по окружениям (build once, deploy many). Не пересобираем для каждого env.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Версионирование&lt;/strong&gt;: SemVer для библиотек/API, git SHA или &lt;code&gt;git describe&lt;/code&gt; для сервисов. Образы тегируем и неизменяемым (&lt;code&gt;sha-abc123&lt;/code&gt;), и плавающим (&lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;v1.2&lt;/code&gt;) тегом.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Стратегии деплоя&lt;/strong&gt;: rolling (по умолчанию в k8s), blue-green (мгновенное переключение + быстрый откат), canary (постепенный rollout на % трафика с метриками).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="pipeline-as-code"&gt;Pipeline as Code&lt;a class="anchor" href="#pipeline-as-code"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Пайплайн описывается в репозитории (&lt;code&gt;.github/workflows/*.yml&lt;/code&gt;, &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;, &lt;code&gt;Jenkinsfile&lt;/code&gt;). Преимущества: версионируется вместе с кодом, review через PR, воспроизводимость. Конфиг в UI — антипаттерн (нет истории, нет review).&lt;/p&gt;</description></item><item><title>Grafana</title><link>https://go.vbloher.org/docs/09-observability/grafana/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/09-observability/grafana/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Observability · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Grafana — слой визуализации и алертинга поверх множества источников данных (Prometheus, Loki, Tempo, и др.). Дашборд = набор &lt;strong&gt;panels&lt;/strong&gt;, каждая панель выполняет запрос к data source и рендерит результат (time series, stat, gauge, table, heatmap). Сила Grafana — &lt;strong&gt;корреляция трёх сигналов&lt;/strong&gt;: из метрики (Prometheus) по exemplar прыгнуть в трейс (Tempo), из трейса в логи (Loki) через derived fields. &lt;strong&gt;Templating&lt;/strong&gt; (&lt;code&gt;$variable&lt;/code&gt;) делает дашборды переиспользуемыми между сервисами/инстансами. Grafana &lt;strong&gt;Unified Alerting&lt;/strong&gt; — отдельная подсистема (alert rules → notification policies → contact points), не путать с панельным «алертом». Senior-практика: &lt;strong&gt;дашборды как код&lt;/strong&gt; (provisioning/Terraform/Grafonnet, не клики), иерархия overview → service → instance, аннотации деплоев, и осторожность с &lt;code&gt;$__rate_interval&lt;/code&gt;, downsampling и вводящими в заблуждение average-панелями.&lt;/p&gt;</description></item><item><title>sync/atomic</title><link>https://go.vbloher.org/docs/02-concurrency/atomic/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/atomic/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sync/atomic&lt;/code&gt; даёт атомарные операции над одиночными словами (load/store/add/swap/CAS), реализованные аппаратными инструкциями (LOCK-префикс на x86, LL/SC или CASAL на ARM) без блокировок и парковки горутин. Go 1.19 ввёл типобезопасные обёртки &lt;code&gt;atomic.Int64&lt;/code&gt;, &lt;code&gt;atomic.Pointer[T]&lt;/code&gt;, &lt;code&gt;atomic.Bool&lt;/code&gt; и др., которые нельзя случайно скопировать и которые гарантируют выравнивание. Atomic — для простых счётчиков/флагов/указателей и lock-free структур; для составных инвариантов (несколько связанных полей) нужен мьютекс.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="операции"&gt;Операции&lt;a class="anchor" href="#%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%86%d0%b8%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Load / Store&lt;/strong&gt; — атомарное чтение/запись слова целиком (без «разорванной» записи).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add&lt;/strong&gt; — атомарный инкремент/декремент, возвращает новое значение.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swap&lt;/strong&gt; — атомарно записать новое и вернуть старое.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CompareAndSwap (CAS)&lt;/strong&gt; — если текущее значение == old, записать new, вернуть успех. Фундамент lock-free алгоритмов.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Int64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// CAS-цикл (классический lock-free паттерн)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareAndSwap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// успех: никто не вмешался между Load и CAS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// кто-то изменил n — повторяем&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="типобезопасные-обёртки-go-119"&gt;Типобезопасные обёртки (Go 1.19+)&lt;a class="anchor" href="#%d1%82%d0%b8%d0%bf%d0%be%d0%b1%d0%b5%d0%b7%d0%be%d0%bf%d0%b0%d1%81%d0%bd%d1%8b%d0%b5-%d0%be%d0%b1%d1%91%d1%80%d1%82%d0%ba%d0%b8-go-119"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// для произвольного типа, тип фиксируется первым Store&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Преимущества над старыми функциями &lt;code&gt;atomic.AddInt64(&amp;amp;x, 1)&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>testify, assert/require и golden files</title><link>https://go.vbloher.org/docs/04-testing/assertions-testify/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/04-testing/assertions-testify/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Тестирование · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;testify&lt;/code&gt; — самая популярная вспомогательная библиотека: &lt;code&gt;assert&lt;/code&gt; (помечает fail и &lt;strong&gt;продолжает&lt;/strong&gt; тест) и &lt;code&gt;require&lt;/code&gt; (помечает fail и &lt;strong&gt;немедленно останавливает&lt;/strong&gt; через &lt;code&gt;t.FailNow&lt;/code&gt;). Используйте &lt;code&gt;require&lt;/code&gt; для предусловий, без которых дальнейшие проверки бессмысленны/опасны (nil-разыменование), и &lt;code&gt;assert&lt;/code&gt; для независимых проверок, где хочется увидеть все падения сразу. Стандартной библиотеки (&lt;code&gt;if got != want { t.Errorf(...) }&lt;/code&gt;) часто достаточно — для простых сравнений она читаема и без зависимостей; testify окупается на множестве проверок и понятных дифф-сообщениях. Для больших/структурных выходов — &lt;strong&gt;golden files&lt;/strong&gt;: эталон в &lt;code&gt;testdata/*.golden&lt;/code&gt;, обновляемый флагом &lt;code&gt;-update&lt;/code&gt;, сравнение через &lt;code&gt;go-cmp&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Аутентификация и авторизация: AuthN/AuthZ, сессии vs токены, RBAC/ABAC, API keys, mTLS, секреты</title><link>https://go.vbloher.org/docs/05-backend/auth-authz/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/auth-authz/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Аутентификация (AuthN)&lt;/strong&gt; — «кто ты?» (проверка личности). &lt;strong&gt;Авторизация (AuthZ)&lt;/strong&gt; — «что тебе можно?» (проверка прав). Это два независимых шага: сначала AuthN, потом AuthZ.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Сессии (stateful)&lt;/strong&gt;: server-side store + opaque cookie. Легко отозвать, но требует общего стора и липкости/репликации. &lt;strong&gt;JWT (stateless)&lt;/strong&gt;: self-contained подпись, масштабируется горизонтально, но &lt;strong&gt;невозможно мгновенно отозвать&lt;/strong&gt; без денилиста. Не путайте «stateless токен» с «нет состояния вообще».&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cookie&lt;/strong&gt;: всегда &lt;code&gt;HttpOnly&lt;/code&gt;, &lt;code&gt;Secure&lt;/code&gt;, &lt;code&gt;SameSite&lt;/code&gt;. Защита от XSS (HttpOnly) и CSRF (SameSite/anti-CSRF token) — это разные угрозы, нужны оба слоя.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Пароли&lt;/strong&gt;: только &lt;code&gt;argon2id&lt;/code&gt; (предпочтительно) / &lt;code&gt;bcrypt&lt;/code&gt; / &lt;code&gt;scrypt&lt;/code&gt; с уникальной солью. Никогда plaintext/MD5/SHA-256 без KDF. Сравнение — constant-time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RBAC&lt;/strong&gt; (роли) для простых иерархий; &lt;strong&gt;ABAC&lt;/strong&gt; (атрибуты + политики) для контекстных решений; &lt;strong&gt;ReBAC&lt;/strong&gt; (Zanzibar/SpiceDB) для графов отношений («владелец документа», sharing).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API keys&lt;/strong&gt; — для machine-to-machine: хранить только хэш, скоупить, ротировать. &lt;strong&gt;mTLS&lt;/strong&gt; — взаимная аутентификация по сертификатам, типично в service mesh.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Секреты&lt;/strong&gt;: не в git, не хардкодить. Env vars — минимум; Vault/Secrets Manager + ротация + encryption at rest — для прода. Принцип наименьших привилегий + defense in depth везде.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-аутентификация-authn-vs-авторизация-authz"&gt;1. Аутентификация (AuthN) vs Авторизация (AuthZ)&lt;a class="anchor" href="#1-%d0%b0%d1%83%d1%82%d0%b5%d0%bd%d1%82%d0%b8%d1%84%d0%b8%d0%ba%d0%b0%d1%86%d0%b8%d1%8f-authn-vs-%d0%b0%d0%b2%d1%82%d0%be%d1%80%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d1%8f-authz"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Это самая базовая и самая часто путаемая пара понятий.&lt;/p&gt;</description></item><item><title>Конфликты, разногласия и работа со стейкхолдерами</title><link>https://go.vbloher.org/docs/13-behavioral/conflicts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/13-behavioral/conflicts/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Behavioral · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Senior не «избегает конфликтов» — он переводит их из эмоциональной плоскости в плоскость данных, рисков и trade-off&amp;rsquo;ов.&lt;/li&gt;
&lt;li&gt;Технические споры решаются критериями, а не громкостью голоса: SLA, стоимость, сроки, поддерживаемость, обратимость решения.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disagree and commit&lt;/strong&gt; — ключевой навык: ты можешь быть против, но после принятия решения исполняешь его как своё и не саботируешь.&lt;/li&gt;
&lt;li&gt;С легаси работаешь инкрементально и с измеримой ценностью, а не «давайте всё перепишем».&lt;/li&gt;
&lt;li&gt;Со стейкхолдерами говоришь на их языке (деньги, риски, сроки, клиент), а не на языке стектрейсов.&lt;/li&gt;
&lt;li&gt;Пушбэк на нереалистичные сроки — это не «нет», а «вот варианты scope/quality/time, выбирайте».&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория--подход"&gt;Теория / Подход&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f--%d0%bf%d0%be%d0%b4%d1%85%d0%be%d0%b4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="природа-конфликта-на-senior-уровне"&gt;Природа конфликта на senior-уровне&lt;a class="anchor" href="#%d0%bf%d1%80%d0%b8%d1%80%d0%be%d0%b4%d0%b0-%d0%ba%d0%be%d0%bd%d1%84%d0%bb%d0%b8%d0%ba%d1%82%d0%b0-%d0%bd%d0%b0-senior-%d1%83%d1%80%d0%be%d0%b2%d0%bd%d0%b5"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Интервьюер проверяет не «бесконфликтность» (это red flag — значит человек либо избегает ответственности, либо врёт), а &lt;strong&gt;зрелость&lt;/strong&gt;: умеешь ли ты не доводить разногласие до личного, отделять позицию человека от проблемы, и доводить ситуацию до решения, с которым команда может жить.&lt;/p&gt;</description></item><item><title>Механика defer в Go</title><link>https://go.vbloher.org/docs/01-core-go/defer/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/defer/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; откладывает вызов функции до момента выхода из окружающей функции (return, паника или нормальное завершение). Аргументы отложенного вызова вычисляются &lt;strong&gt;в момент выполнения оператора &lt;code&gt;defer&lt;/code&gt;&lt;/strong&gt;, а сам вызов — позже, в порядке &lt;strong&gt;LIFO&lt;/strong&gt;. Отложенные функции могут читать и модифицировать &lt;strong&gt;именованные возвращаемые значения&lt;/strong&gt;, что лежит в основе паттерна &lt;code&gt;recover&lt;/code&gt;. С Go 1.14 для статически известного и небольшого (≤8) числа defer без циклов компилятор применяет &lt;strong&gt;open-coded defer&lt;/strong&gt; — почти нулевые накладные расходы; иначе используется &lt;code&gt;_defer&lt;/code&gt;-запись в runtime.&lt;/p&gt;</description></item><item><title>Паттерны аллокаций и снижение давления на GC</title><link>https://go.vbloher.org/docs/03-runtime-memory/allocation-patterns/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/allocation-patterns/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Большинство «горячих» проблем производительности в Go — это не CPU, а аллокации: каждая аллокация в куче нагружает аллокатор и увеличивает работу GC (assist, mark, scan). Senior-разработчик умеет считать аллокации через &lt;code&gt;-benchmem&lt;/code&gt;, предаллоцировать слайсы/мапы с учётом amortized growth, переиспользовать буферы (&lt;code&gt;strings.Builder&lt;/code&gt;, &lt;code&gt;bytes.Buffer&lt;/code&gt;, &lt;code&gt;sync.Pool&lt;/code&gt;) и избегать неявного boxing в интерфейсах. Главное — измерять (&lt;code&gt;pprof&lt;/code&gt;, &lt;code&gt;-benchmem&lt;/code&gt;, &lt;code&gt;escape analysis&lt;/code&gt;), а не угадывать.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="стоимость-аллокации-и-связь-с-gc"&gt;Стоимость аллокации и связь с GC&lt;a class="anchor" href="#%d1%81%d1%82%d0%be%d0%b8%d0%bc%d0%be%d1%81%d1%82%d1%8c-%d0%b0%d0%bb%d0%bb%d0%be%d0%ba%d0%b0%d1%86%d0%b8%d0%b8-%d0%b8-%d1%81%d0%b2%d1%8f%d0%b7%d1%8c-%d1%81-gc"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В Go стоимость одной аллокации складывается из нескольких частей:&lt;/p&gt;</description></item><item><title>Пул соединений к PostgreSQL в Go: database/sql, pgx, pgxpool, PgBouncer</title><link>https://go.vbloher.org/docs/07-databases/connection-pooling-pgx/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/connection-pooling-pgx/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Установка TCP+TLS соединения и форк backend-процесса в PostgreSQL — дорогая операция (десятки мс, мегабайты RAM на backend). Пул переиспользует физические соединения, амортизируя стоимость.&lt;/li&gt;
&lt;li&gt;PostgreSQL — process-per-connection: каждое соединение это отдельный ОС-процесс. &lt;code&gt;max_connections&lt;/code&gt; ограничен сверху, и реальная пропускная способность падает задолго до лимита из-за конкуренции за CPU, lock manager и shared buffers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database/sql&lt;/code&gt; даёт встроенный пул: &lt;code&gt;SetMaxOpenConns&lt;/code&gt; (жёсткий потолок), &lt;code&gt;SetMaxIdleConns&lt;/code&gt; (сколько держать свободными), &lt;code&gt;SetConnMaxLifetime&lt;/code&gt; (ротация), &lt;code&gt;SetConnMaxIdleTime&lt;/code&gt; (закрытие простаивающих). Метрики — &lt;code&gt;db.Stats()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pgx&lt;/code&gt; имеет два режима: native API (&lt;code&gt;pgx.Conn&lt;/code&gt;, &lt;code&gt;pgxpool.Pool&lt;/code&gt;) с extended-протоколом и кэшем prepared statements, и совместимый &lt;code&gt;database/sql&lt;/code&gt; драйвер (&lt;code&gt;stdlib&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pgxpool&lt;/code&gt; — production-grade пул pgx: &lt;code&gt;MaxConns&lt;/code&gt;, &lt;code&gt;MinConns&lt;/code&gt;, &lt;code&gt;MaxConnLifetime&lt;/code&gt;, &lt;code&gt;MaxConnIdleTime&lt;/code&gt;, &lt;code&gt;HealthCheckPeriod&lt;/code&gt;, lifecycle-хуки.&lt;/li&gt;
&lt;li&gt;PgBouncer — внешний пулер. В &lt;code&gt;transaction&lt;/code&gt; mode ломаются: серверные prepared statements, session-level advisory locks, &lt;code&gt;SET&lt;/code&gt;, &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;, temp tables, &lt;code&gt;WITH HOLD&lt;/code&gt; cursors. Лечится &lt;code&gt;DefaultQueryExecMode&lt;/code&gt; = simple protocol или &lt;code&gt;statement_cache_capacity=0&lt;/code&gt; (на новых PgBouncer &amp;gt;= 1.21 есть серверный prepared statement support).&lt;/li&gt;
&lt;li&gt;Размер пула считается, а не «ставится побольше». Базовая формула: &lt;code&gt;connections ≈ ((core_count * 2) + effective_spindle_count)&lt;/code&gt;. Больше соединений почти всегда хуже из-за context switching.&lt;/li&gt;
&lt;li&gt;Утечки соединений — главный production-инцидент: незакрытые &lt;code&gt;Rows&lt;/code&gt;, забытый &lt;code&gt;Close()&lt;/code&gt; у транзакции, отсутствие &lt;code&gt;context&lt;/code&gt; с таймаутом. Пул исчерпывается, запросы виснут на ожидании свободного соединения.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-вообще-пул-цена-соединения-в-postgresql"&gt;Зачем вообще пул: цена соединения в PostgreSQL&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%b2%d0%be%d0%be%d0%b1%d1%89%d0%b5-%d0%bf%d1%83%d0%bb-%d1%86%d0%b5%d0%bd%d0%b0-%d1%81%d0%be%d0%b5%d0%b4%d0%b8%d0%bd%d0%b5%d0%bd%d0%b8%d1%8f-%d0%b2-postgresql"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PostgreSQL использует модель &lt;strong&gt;process-per-connection&lt;/strong&gt;. Каждое новое клиентское соединение приводит к тому, что главный процесс postmaster делает &lt;code&gt;fork()&lt;/code&gt;, создавая отдельный backend-процесс. Это влечёт:&lt;/p&gt;</description></item><item><title>Пулы соединений: http.Transport, БД, утечки</title><link>https://go.vbloher.org/docs/06-networking/connection-pooling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/06-networking/connection-pooling/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Сети и протоколы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Пул соединений переиспользует установленные TCP(+TLS) соединения, экономя дорогой handshake (RTT + TLS + slow start) и избегая исчерпания эфемерных портов / TIME_WAIT.&lt;/li&gt;
&lt;li&gt;В Go HTTP-пул живёт в &lt;code&gt;http.Transport&lt;/code&gt;: ключевые ручки — &lt;code&gt;MaxIdleConns&lt;/code&gt;, &lt;code&gt;MaxIdleConnsPerHost&lt;/code&gt;, &lt;code&gt;MaxConnsPerHost&lt;/code&gt;, &lt;code&gt;IdleConnTimeout&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Главный антипаттерн&lt;/strong&gt;: создавать новый &lt;code&gt;http.Client&lt;/code&gt;/&lt;code&gt;Transport&lt;/code&gt; на каждый запрос — пул не переиспользуется, соединения не шарятся. Клиент должен быть один и переиспользуемый.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Утечки соединений&lt;/strong&gt;: незакрытое &lt;code&gt;resp.Body&lt;/code&gt; → соединение не возвращается в пул → рост FD, новые коннекты, деградация. То же для БД: незакрытые &lt;code&gt;*sql.Rows&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Для БД (&lt;code&gt;database/sql&lt;/code&gt;): &lt;code&gt;SetMaxOpenConns&lt;/code&gt;, &lt;code&gt;SetMaxIdleConns&lt;/code&gt;, &lt;code&gt;SetConnMaxLifetime&lt;/code&gt;, &lt;code&gt;SetConnMaxIdleTime&lt;/code&gt;. Lifetime критичен для DNS-failover и балансировки.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-пул"&gt;Зачем пул&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%bf%d1%83%d0%bb"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Установка соединения дорогая: TCP handshake (1 RTT) + TLS handshake (1-2 RTT) + TCP slow start. На горячем пути это десятки-сотни мс. Пул держит idle-соединения готовыми к переиспользованию (keep-alive), убирая эти затраты и снижая TIME_WAIT/исчерпание портов.&lt;/p&gt;</description></item><item><title>Типовые алгоритмические задачи и паттерны</title><link>https://go.vbloher.org/docs/12-algorithms/common-problems/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/12-algorithms/common-problems/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Алгоритмы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;На senior live-coding дают задачи &lt;strong&gt;средней сложности&lt;/strong&gt;: two pointers, sliding window, binary search, BFS/DFS, базовый DP, сортировки.&lt;/li&gt;
&lt;li&gt;Ценят не «знание трюка», а &lt;strong&gt;распознавание паттерна&lt;/strong&gt; по условию, корректные edge cases, чистый код и проговаривание сложности.&lt;/li&gt;
&lt;li&gt;Большинство паттернов сводят O(n²) перебор к O(n) / O(n log n) за счёт инварианта (указатели, окно, отсортированность).&lt;/li&gt;
&lt;li&gt;Всегда: уточнить вход (пустой? отсортирован? дубликаты? отрицательные?), назвать сложность, проверить границы.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="паттерн-1-two-pointers-два-указателя"&gt;Паттерн 1: Two Pointers (два указателя)&lt;a class="anchor" href="#%d0%bf%d0%b0%d1%82%d1%82%d0%b5%d1%80%d0%bd-1-two-pointers-%d0%b4%d0%b2%d0%b0-%d1%83%d0%ba%d0%b0%d0%b7%d0%b0%d1%82%d0%b5%d0%bb%d1%8f"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Два индекса, движущихся навстречу или в одном направлении. Часто требует &lt;strong&gt;отсортированного&lt;/strong&gt; входа. Заменяет O(n²) на O(n).&lt;/p&gt;</description></item><item><title>Chat System</title><link>https://go.vbloher.org/docs/10-system-design/chat/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/chat/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Чат-система — это набор stateful WebSocket-коннектов поверх stateless-логики доставки. Главная боль: коннекты живут на конкретных серверах, а сообщение приходит «не туда». Решается через &lt;strong&gt;session registry&lt;/strong&gt; (Redis: &lt;code&gt;user_id → ws_node&lt;/code&gt;) и брокер для маршрутизации между нодами. Доставка — &lt;strong&gt;at-least-once + idempotency key + ACK&lt;/strong&gt;, порядок — &lt;strong&gt;монотонный seq per channel&lt;/strong&gt; (а не глобальный). Хранение горячего потока — &lt;strong&gt;wide-column (Cassandra/ScyllaDB)&lt;/strong&gt; с ключом &lt;code&gt;(channel_id, bucket) / seq&lt;/code&gt;. Offline — fallback на &lt;strong&gt;push (APNs/FCM)&lt;/strong&gt; + полная синхронизация по seq при reconnect. Узкое место senior-уровня — не throughput сообщений, а &lt;strong&gt;миллионы одновременных stateful-коннектов&lt;/strong&gt;, presence-стадо и hot groups.&lt;/p&gt;</description></item><item><title>Circuit Breaker</title><link>https://go.vbloher.org/docs/08-distributed-systems/circuit-breaker/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/circuit-breaker/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Circuit breaker&lt;/strong&gt; («предохранитель») оборачивает вызов нестабильной зависимости и при росте ошибок &lt;strong&gt;размыкает цепь&lt;/strong&gt; — перестаёт слать запросы, мгновенно возвращая ошибку (&lt;strong&gt;fail fast&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Три состояния: &lt;strong&gt;Closed&lt;/strong&gt; (запросы идут, считаем ошибки) → при превышении порога → &lt;strong&gt;Open&lt;/strong&gt; (запросы блокируются, fail fast) → по таймауту → &lt;strong&gt;Half-Open&lt;/strong&gt; (пропускаем пробные запросы) → успех → Closed, провал → снова Open.&lt;/li&gt;
&lt;li&gt;Зачем: &lt;strong&gt;fail fast&lt;/strong&gt; (не ждать таймаутов на мёртвый сервис), &lt;strong&gt;защита downstream&lt;/strong&gt; от добивания нагрузкой во время деградации, &lt;strong&gt;дать сервису время восстановиться&lt;/strong&gt;, предотвратить &lt;strong&gt;каскадные сбои&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bulkhead&lt;/strong&gt; — изоляция ресурсов (отдельные пулы соединений/семафоры на каждую зависимость), чтобы один тормозящий downstream не выел все воркеры/горутины.&lt;/li&gt;
&lt;li&gt;В Go стандарт де-факто — &lt;strong&gt;&lt;code&gt;github.com/sony/gobreaker&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;Settings{ReadyToTrip, OnStateChange, Interval, Timeout, MaxRequests}&lt;/code&gt;, &lt;code&gt;cb.Execute(fn)&lt;/code&gt;, &lt;code&gt;Counts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fallback&lt;/strong&gt;: вернуть кэш, дефолт или деградированный ответ вместо ошибки.&lt;/li&gt;
&lt;li&gt;Circuit breaker ≠ retry. Retry борется с &lt;em&gt;транзиентными&lt;/em&gt; сбоями повтором; breaker борется с &lt;em&gt;устойчивыми&lt;/em&gt; сбоями прекращением запросов. &lt;strong&gt;Комбинация retry+breaker опасна&lt;/strong&gt; — ретраи накручивают счётчик ошибок и раздувают нагрузку; порядок и настройка критичны.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="проблема-каскадный-сбой"&gt;Проблема: каскадный сбой&lt;a class="anchor" href="#%d0%bf%d1%80%d0%be%d0%b1%d0%bb%d0%b5%d0%bc%d0%b0-%d0%ba%d0%b0%d1%81%d0%ba%d0%b0%d0%b4%d0%bd%d1%8b%d0%b9-%d1%81%d0%b1%d0%be%d0%b9"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Сервис A зависит от B. B начинает тормозить (latency 5s вместо 50ms). Без предохранителя:&lt;/p&gt;</description></item><item><title>DNS: записи, резолвинг, кэширование, DNS в Go</title><link>https://go.vbloher.org/docs/06-networking/dns/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/06-networking/dns/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Сети и протоколы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DNS — распределённая иерархическая система имён; резолвер рекурсивно идёт от root → TLD → authoritative, отдавая записи с TTL.&lt;/li&gt;
&lt;li&gt;Типы записей: A/AAAA (IP), CNAME (алиас), MX (почта), TXT (метаданные/SPF), NS, SOA, SRV (сервис+порт), PTR (обратный), CAA.&lt;/li&gt;
&lt;li&gt;Кэширование на каждом уровне по TTL — отсюда главные продовые боли: устаревшие записи, &amp;ldquo;залипший&amp;rdquo; резолв при деплое/failover.&lt;/li&gt;
&lt;li&gt;В Go два резолвера: &lt;strong&gt;cgo (системный)&lt;/strong&gt; и &lt;strong&gt;чистый Go&lt;/strong&gt;; выбор влияет на поведение &lt;code&gt;/etc/hosts&lt;/code&gt;, &lt;code&gt;/etc/resolv.conf&lt;/code&gt;, кэширование (которого в Go-нет по умолчанию).&lt;/li&gt;
&lt;li&gt;Прод-грабли: отсутствие кэша в Go → DNS на каждый запрос, негативное кэширование, TTL vs балансировка, DNS-таймауты как причина &amp;ldquo;медленных&amp;rdquo; сервисов.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="иерархия-и-резолвинг"&gt;Иерархия и резолвинг&lt;a class="anchor" href="#%d0%b8%d0%b5%d1%80%d0%b0%d1%80%d1%85%d0%b8%d1%8f-%d0%b8-%d1%80%d0%b5%d0%b7%d0%be%d0%bb%d0%b2%d0%b8%d0%bd%d0%b3"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; root (.)
 |
 TLD (.com, .org, .ru)
 |
 authoritative (example.com NS)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Recursive resolver&lt;/strong&gt; (обычно у провайдера / 8.8.8.8 / внутренний) делает работу за клиента: спрашивает root → получает NS для TLD → спрашивает TLD → получает authoritative NS → получает финальный ответ.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stub resolver&lt;/strong&gt; — это сам клиент (&lt;code&gt;getaddrinfo&lt;/code&gt;/Go), который шлёт запрос рекурсивному резолверу.&lt;/li&gt;
&lt;li&gt;Транспорт: UDP/53 (дефолт), TCP/53 при truncation (флаг TC) или zone transfer. Современное: DoT (DNS over TLS, 853), DoH (DNS over HTTPS, 443).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="типы-записей"&gt;Типы записей&lt;a class="anchor" href="#%d1%82%d0%b8%d0%bf%d1%8b-%d0%b7%d0%b0%d0%bf%d0%b8%d1%81%d0%b5%d0%b9"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Тип&lt;/th&gt;
					&lt;th&gt;Назначение&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;hostname → IPv4&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;AAAA&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;hostname → IPv6&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;CNAME&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;алиас на другое имя (нельзя на apex-домене вместе с другими записями)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;MX&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;почтовые серверы (+приоритет)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;TXT&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;произвольный текст: SPF, DKIM, верификации&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;NS&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;делегирование зоны на name servers&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;SOA&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;параметры зоны (serial, refresh, TTL негатива)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;SRV&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;_service._proto&lt;/code&gt; → host+port (service discovery)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;PTR&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;обратный резолв IP → имя&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;CAA&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;какие CA могут выдавать сертификаты для домена&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="ttl-и-кэширование"&gt;TTL и кэширование&lt;a class="anchor" href="#ttl-%d0%b8-%d0%ba%d1%8d%d1%88%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Каждая запись несёт &lt;strong&gt;TTL&lt;/strong&gt; — сколько секунд её можно кэшировать.&lt;/li&gt;
&lt;li&gt;Кэш на всех уровнях: stub, рекурсивный резолвер, ОС, иногда приложение.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Negative caching&lt;/strong&gt;: NXDOMAIN тоже кэшируется (TTL берётся из SOA minimum) — поэтому только что созданная запись может &amp;ldquo;не находиться&amp;rdquo; какое-то время.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trade-off TTL&lt;/strong&gt;: низкий TTL → быстрый failover/смена IP, но больше нагрузки и латентности; высокий TTL → меньше запросов, но медленная пропагация изменений. Перед миграцией TTL заранее снижают.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cname-нюансы"&gt;CNAME-нюансы&lt;a class="anchor" href="#cname-%d0%bd%d1%8e%d0%b0%d0%bd%d1%81%d1%8b"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CNAME — алиас, требует доп. резолва на целевое имя.&lt;/li&gt;
&lt;li&gt;На &lt;strong&gt;apex&lt;/strong&gt; (корне зоны, &lt;code&gt;example.com&lt;/code&gt;) CNAME запрещён стандартом (конфликт с SOA/NS) → провайдеры предлагают ALIAS/ANAME/CNAME flattening.&lt;/li&gt;
&lt;li&gt;Цепочки CNAME увеличивают латентность резолва.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dns-в-go"&gt;DNS в Go&lt;a class="anchor" href="#dns-%d0%b2-go"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Go имеет &lt;strong&gt;два резолвера&lt;/strong&gt;:&lt;/p&gt;</description></item><item><title>Escape Analysis: когда переменная убегает в кучу</title><link>https://go.vbloher.org/docs/03-runtime-memory/escape-analysis/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/escape-analysis/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Escape analysis — это анализ компилятора, определяющий, переживёт ли значение функцию, в которой оно создано. Если переживёт (или компилятор не может доказать обратное), значение размещается в куче; иначе — на стеке. Анализ консервативен: при неопределённости выбирается куча. Смотреть решения можно через &lt;code&gt;go build -gcflags=&amp;quot;-m&amp;quot;&lt;/code&gt; (или &lt;code&gt;-m -m&lt;/code&gt; для подробностей). Типовые причины escape: возврат указателя, попадание значения в интерфейс, замыкания, захватывающие переменную по ссылке, слишком большой объект, отправка указателя в канал, передача в &lt;code&gt;fmt.Println&lt;/code&gt; (через &lt;code&gt;any&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Graceful Shutdown HTTP/gRPC сервера в Go</title><link>https://go.vbloher.org/docs/05-backend/graceful-shutdown/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/graceful-shutdown/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Graceful shutdown&lt;/strong&gt; — это управляемая остановка сервиса: перестать принимать новые соединения, дождаться завершения уже идущих запросов (в пределах таймаута), корректно закрыть ресурсы (БД, брокеры, пулы), и только потом завершить процесс. Цель — &lt;strong&gt;нулевой даунтайм при деплое&lt;/strong&gt; и отсутствие оборванных запросов / потерянных данных.&lt;/li&gt;
&lt;li&gt;В Go ловим сигналы через &lt;code&gt;signal.NotifyContext&lt;/code&gt; (Go 1.16+): он отменяет &lt;code&gt;context.Context&lt;/code&gt; при &lt;code&gt;SIGTERM&lt;/code&gt;/&lt;code&gt;SIGINT&lt;/code&gt;. &lt;strong&gt;&lt;code&gt;SIGKILL&lt;/code&gt; (kill -9) и &lt;code&gt;SIGSTOP&lt;/code&gt; перехватить нельзя&lt;/strong&gt; — ОС убивает процесс мгновенно.&lt;/li&gt;
&lt;li&gt;HTTP: &lt;code&gt;srv.Shutdown(ctx)&lt;/code&gt; — мягко (дренаж активных запросов с таймаутом контекста), &lt;code&gt;srv.Close()&lt;/code&gt; — жёстко (рвёт всё). &lt;code&gt;ListenAndServe&lt;/code&gt; возвращает &lt;code&gt;http.ErrServerClosed&lt;/code&gt; при штатной остановке.&lt;/li&gt;
&lt;li&gt;gRPC: &lt;code&gt;GracefulStop()&lt;/code&gt; — ждёт завершения RPC и дренирует стримы; &lt;code&gt;Stop()&lt;/code&gt; — рвёт немедленно. На практике &lt;code&gt;GracefulStop()&lt;/code&gt; оборачивают в таймаут с fallback на &lt;code&gt;Stop()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Kubernetes: при удалении пода kubelet шлёт &lt;code&gt;SIGTERM&lt;/code&gt;, ждёт &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt;, затем &lt;code&gt;SIGKILL&lt;/code&gt;. Главная проблема — &lt;strong&gt;race между удалением пода из Endpoints/EndpointSlice и остановкой сервера&lt;/strong&gt;: трафик ещё долетает после SIGTERM. Решение — &lt;code&gt;preStop&lt;/code&gt; hook со &lt;code&gt;sleep&lt;/code&gt; + завал &lt;code&gt;readinessProbe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Правильный порядок: &lt;strong&gt;fail readiness → подождать, пока LB/kube-proxy уберут под из ротации → shutdown HTTP/gRPC → закрыть зависимости (БД, очереди)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-нужен-graceful-shutdown"&gt;Зачем нужен graceful shutdown&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%bd%d1%83%d0%b6%d0%b5%d0%bd-graceful-shutdown"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Когда процесс просто падает (&lt;code&gt;os.Exit&lt;/code&gt;, паника, &lt;code&gt;kill -9&lt;/code&gt;), все открытые TCP-соединения резко рвутся (RST), активные запросы недоисполняются, транзакции откатываются на стороне БД по таймауту, in-flight сообщения брокера могут потеряться или дублироваться. Для клиента это &lt;code&gt;connection reset&lt;/code&gt; / &lt;code&gt;502&lt;/code&gt; / &lt;code&gt;EOF&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Асимптотическая сложность (Big-O)</title><link>https://go.vbloher.org/docs/12-algorithms/complexity/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/12-algorithms/complexity/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Алгоритмы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Big-O&lt;/strong&gt; описывает асимптотический рост ресурсов (время/память) при росте входа &lt;code&gt;n&lt;/code&gt;, отбрасывая константы и младшие члены.&lt;/li&gt;
&lt;li&gt;Оцениваем &lt;strong&gt;отдельно время и память&lt;/strong&gt; — оптимизация одного часто ухудшает другое (trade-off).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Амортизированная сложность&lt;/strong&gt; — средняя стоимость операции на длинной серии (классика: &lt;code&gt;append&lt;/code&gt; к слайсу = O(1) амортизированно).&lt;/li&gt;
&lt;li&gt;В Go важно знать: &lt;code&gt;append&lt;/code&gt; может реаллоцировать и копировать (амортизированно O(1)), доступ к map — O(1) в среднем, сортировка &lt;code&gt;sort.Slice&lt;/code&gt; — O(n log n).&lt;/li&gt;
&lt;li&gt;На senior ждут не «знаю определение», а умения &lt;strong&gt;вывести сложность по коду&lt;/strong&gt;, заметить скрытые копирования и аллокации, обсудить worst/average/best case.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-такое-big-o-формально"&gt;Что такое Big-O формально&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-big-o-%d1%84%d0%be%d1%80%d0%bc%d0%b0%d0%bb%d1%8c%d0%bd%d0%be"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;O(f(n)) — множество функций, ограниченных сверху &lt;code&gt;c·f(n)&lt;/code&gt; начиная с некоторого &lt;code&gt;n₀&lt;/code&gt;. На практике: «как растёт ресурс при стремлении n к бесконечности».&lt;/p&gt;</description></item><item><title>Бенчмарки в Go</title><link>https://go.vbloher.org/docs/04-testing/benchmarks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/04-testing/benchmarks/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Тестирование · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Бенчмарк — функция &lt;code&gt;BenchmarkXxx(b *testing.B)&lt;/code&gt;, в которой измеряемый код крутится &lt;code&gt;b.N&lt;/code&gt; раз; рантайм сам подбирает &lt;code&gt;b.N&lt;/code&gt;, увеличивая его, пока время прогона не станет статистически осмысленным (по умолчанию ~1s, регулируется &lt;code&gt;-benchtime&lt;/code&gt;). &lt;code&gt;b.ResetTimer()&lt;/code&gt; отсекает дорогой setup, &lt;code&gt;b.ReportAllocs()&lt;/code&gt; (или флаг &lt;code&gt;-benchmem&lt;/code&gt;) добавляет аллокации/op и байты/op. Результаты нестабильны от прогона к прогону — сравнивать релизы нужно через &lt;code&gt;benchstat&lt;/code&gt; по нескольким прогонам (&lt;code&gt;-count&lt;/code&gt;), а не по одному числу. Главные ошибки измерений: мёртвый код, который выкидывает компилятор (нужен sink), setup внутри измеряемого цикла, шумная машина, и интерпретация одного прогона как истины.&lt;/p&gt;</description></item><item><title>Буферизованные vs небуферизованные каналы</title><link>https://go.vbloher.org/docs/02-concurrency/buffered-unbuffered/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/buffered-unbuffered/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Небуферизованный канал (&lt;code&gt;make(chan T)&lt;/code&gt;) — это rendezvous: отправка и приём происходят одновременно, давая строгую синхронизацию (happens-before). Буферизованный (&lt;code&gt;make(chan T, n)&lt;/code&gt;) развязывает отправителя и получателя на &lt;code&gt;n&lt;/code&gt; элементов, давая пропускную способность и backpressure при заполнении. Размер буфера — это про производительность и поток, не про корректность.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="семантика-синхронизации"&gt;Семантика синхронизации&lt;a class="anchor" href="#%d1%81%d0%b5%d0%bc%d0%b0%d0%bd%d1%82%d0%b8%d0%ba%d0%b0-%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Небуферизованный&lt;/strong&gt;: &lt;code&gt;ch &amp;lt;- v&lt;/code&gt; завершается только когда другая горутина выполнила &lt;code&gt;&amp;lt;-ch&lt;/code&gt;. Это точка синхронизации — гарантия, что приём начался. Memory model: запись отправителя до &lt;code&gt;ch&amp;lt;-v&lt;/code&gt; видна получателю после &lt;code&gt;&amp;lt;-ch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Буферизованный&lt;/strong&gt;: &lt;code&gt;ch &amp;lt;- v&lt;/code&gt; завершается, как только значение положено в буфер (если есть место), не дожидаясь получателя. Happens-before связывает k-ю отправку с (k+cap)-м приёмом (классическое правило из Go Memory Model для ограниченной семафорной семантики).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// unbuffered = сигнал «готово»&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;work&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// дождётся, пока main прочитает → синхронизация&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="гарантии-go-memory-model-дословно-по-смыслу"&gt;Гарантии Go Memory Model (дословно по смыслу)&lt;a class="anchor" href="#%d0%b3%d0%b0%d1%80%d0%b0%d0%bd%d1%82%d0%b8%d0%b8-go-memory-model-%d0%b4%d0%be%d1%81%d0%bb%d0%be%d0%b2%d0%bd%d0%be-%d0%bf%d0%be-%d1%81%d0%bc%d1%8b%d1%81%d0%bb%d1%83"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Отправка в канал happens-before завершения соответствующего приёма.&lt;/li&gt;
&lt;li&gt;Закрытие канала happens-before приёма, вернувшего нулевое значение из-за закрытия.&lt;/li&gt;
&lt;li&gt;Для &lt;strong&gt;небуферизованного&lt;/strong&gt;: приём happens-before завершения &lt;strong&gt;отправки&lt;/strong&gt; (приём «начинается раньше»).&lt;/li&gt;
&lt;li&gt;k-я отправка в канал ёмкости C happens-before завершения (k+C)-го приёма.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Правило 3 уникально для небуферизованного — поэтому он сильнее как примитив синхронизации.&lt;/p&gt;</description></item><item><title>Взаимоблокировки (Deadlocks) в PostgreSQL</title><link>https://go.vbloher.org/docs/07-databases/deadlocks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/deadlocks/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deadlock&lt;/strong&gt; — циклическое ожидание блокировок: транзакция A держит ресурс 1 и ждёт ресурс 2, транзакция B держит ресурс 2 и ждёт ресурс 1. Никто не может продвинуться.&lt;/li&gt;
&lt;li&gt;PostgreSQL &lt;strong&gt;автоматически обнаруживает&lt;/strong&gt; deadlock через &lt;code&gt;deadlock detector&lt;/code&gt;, который запускается, если транзакция ждёт блокировку дольше &lt;code&gt;deadlock_timeout&lt;/code&gt; (по умолчанию &lt;strong&gt;1 секунда&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Детектор выбирает одну транзакцию-&lt;strong&gt;жертву&lt;/strong&gt; (victim), откатывает её и возвращает ошибку &lt;strong&gt;SQLSTATE &lt;code&gt;40P01&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;deadlock_detected&lt;/code&gt;). Остальные продолжают работу.&lt;/li&gt;
&lt;li&gt;Главная причина — &lt;strong&gt;разный порядок захвата блокировок&lt;/strong&gt; в разных транзакциях. Главная профилактика — &lt;strong&gt;единый порядок&lt;/strong&gt; (например, всегда блокировать строки &lt;code&gt;ORDER BY id&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Инструменты: единый порядок блокировок, короткие транзакции, &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; с &lt;code&gt;ORDER BY&lt;/code&gt;, &lt;code&gt;lock_timeout&lt;/code&gt;, &lt;code&gt;NOWAIT&lt;/code&gt;, &lt;code&gt;SKIP LOCKED&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Диагностика: &lt;code&gt;pg_locks&lt;/code&gt; (кто что держит и кто чего ждёт), &lt;code&gt;pg_stat_activity&lt;/code&gt; (что выполняет бэкенд, &lt;code&gt;wait_event_type = 'Lock'&lt;/code&gt;), а также сообщение в логе PostgreSQL с деталями цикла.&lt;/li&gt;
&lt;li&gt;В приложении на Go deadlock — &lt;strong&gt;ожидаемая ситуация при конкуренции&lt;/strong&gt;; правильная реакция — детектировать &lt;code&gt;40P01&lt;/code&gt; и &lt;strong&gt;повторить транзакцию&lt;/strong&gt; (retry с backoff), а не падать.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-такое-deadlock-и-почему-он-возникает"&gt;Что такое deadlock и почему он возникает&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-deadlock-%d0%b8-%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-%d0%be%d0%bd-%d0%b2%d0%be%d0%b7%d0%bd%d0%b8%d0%ba%d0%b0%d0%b5%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Deadlock (взаимоблокировка) — ситуация, когда две или более транзакций образуют &lt;strong&gt;цикл ожидания&lt;/strong&gt;: каждая держит блокировку, которая нужна другой, и ни одна не может завершиться. Это фундаментальное свойство любой системы с блокировками, а не баг PostgreSQL.&lt;/p&gt;</description></item><item><title>Встраивание структур и интерфейсов (Embedding)</title><link>https://go.vbloher.org/docs/01-core-go/embedding/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/embedding/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Встраивание (embedding) — это включение анонимного поля типа (структуры, интерфейса, указателя на структуру или любого именованного типа) в другую структуру. Go продвигает (promote) поля и методы встроенного типа во внешний, давая синтаксический сахар, но это &lt;strong&gt;композиция, а не наследование&lt;/strong&gt;: внешний тип не является подтипом внутреннего, динамической диспетчеризации между ними нет, а конфликты имён разрешаются по глубине вложенности. Понимание того, что под капотом это просто доступ к вложенному полю по сгенерированному компилятором пути, снимает почти все вопросы о method promotion, shadowing и неоднозначности.&lt;/p&gt;</description></item><item><title>Как проходит senior-интервью: этапы, оценка, оффер</title><link>https://go.vbloher.org/docs/13-behavioral/interview-flow/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/13-behavioral/interview-flow/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Behavioral · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Типичная воронка: рекрутерский скрининг → техническое (coding) → system design → behavioral/leadership → team fit / hiring manager → оффер.&lt;/li&gt;
&lt;li&gt;На senior оценивают не «знаешь ли синтаксис», а &lt;strong&gt;глубину, trade-off-мышление, влияние на команду и продукт&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;System design и behavioral на senior+ часто весят больше, чем алгоритмическая секция.&lt;/li&gt;
&lt;li&gt;Вопросы кандидата работодателю — это &lt;strong&gt;часть оценки&lt;/strong&gt;: senior проверяет компанию так же, как компания проверяет его.&lt;/li&gt;
&lt;li&gt;Переговоры об оффере — нормальная и ожидаемая часть; не называй цифру первым, опирайся на рынок и весь компенсационный пакет.&lt;/li&gt;
&lt;li&gt;Red flags бывают с обеих сторон — умей их читать.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория--подход"&gt;Теория / Подход&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f--%d0%bf%d0%be%d0%b4%d1%85%d0%be%d0%b4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="общая-структура-воронки"&gt;Общая структура воронки&lt;a class="anchor" href="#%d0%be%d0%b1%d1%89%d0%b0%d1%8f-%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%82%d1%83%d1%80%d0%b0-%d0%b2%d0%be%d1%80%d0%be%d0%bd%d0%ba%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Конкретика зависит от компании, но скелет почти всегда такой:&lt;/p&gt;</description></item><item><title>Метрики: RED, USE, Golden Signals</title><link>https://go.vbloher.org/docs/09-observability/metrics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/09-observability/metrics/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Observability · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Три методологии «что мерить»: &lt;strong&gt;RED&lt;/strong&gt; (Rate / Errors / Duration — для request-driven сервисов, Tom Wilkie), &lt;strong&gt;USE&lt;/strong&gt; (Utilization / Saturation / Errors — для ресурсов: CPU, память, диск, пулы, очереди; Brendan Gregg) и &lt;strong&gt;Four Golden Signals&lt;/strong&gt; (Latency / Traffic / Errors / Saturation — Google SRE). RED отвечает «как сервису», USE — «как ресурсу». Для латенси &lt;strong&gt;нельзя использовать average&lt;/strong&gt;: среднее скрывает хвост, чувствительно к выбросам и не композируется. Нужны &lt;strong&gt;перцентили&lt;/strong&gt; (p50/p99/p99.9) и понимание tail amplification (при fan-out на 100 бэкендов p99 каждого задевает почти каждый запрос). Перцентили считают через &lt;strong&gt;histogram&lt;/strong&gt; (&lt;code&gt;histogram_quantile&lt;/code&gt; по агрегированным бакетам), потому что готовые квантили &lt;strong&gt;нельзя усреднять между инстансами&lt;/strong&gt;. И сквозная тема — &lt;strong&gt;labels/cardinality&lt;/strong&gt;: лейблы только bounded, иначе взрыв рядов.&lt;/p&gt;</description></item><item><title>Облака (AWS / GCP) для бэкендера</title><link>https://go.vbloher.org/docs/11-devops/cloud-aws-gcp/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/11-devops/cloud-aws-gcp/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: DevOps · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compute&lt;/strong&gt; — спектр от «управляй сам» до «не думай о серверах»: VM (EC2 / Compute Engine) → контейнеры-оркестратор (ECS, EKS / GKE) → serverless-контейнеры (Fargate, Cloud Run) → FaaS (Lambda / Cloud Functions). Чем правее, тем меньше операционки и тем дороже за единицу вычислений, но дешевле при низкой/рваной нагрузке.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage&lt;/strong&gt; — объектное хранилище S3 / GCS (для файлов, бэкапов, статики, data lake), блочное (EBS / Persistent Disk) для дисков VM. Объектное — не файловая система: нет частичной перезаписи, eventual/strong consistency-нюансы, оплата за объём + запросы + трафик.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Managed DB&lt;/strong&gt; — RDS / Aurora (AWS), Cloud SQL / AlloyDB / Spanner (GCP). Облако берёт на себя бэкапы, патчи, реплики, failover. Вы платите за удобство и теряете часть контроля (версии, расширения, суперюзер).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Очереди/события&lt;/strong&gt; — SQS (очередь) + SNS (pub/sub fan-out) в AWS; Pub/Sub в GCP (и то, и другое в одном). Для бэкендера это основа асинхронной обработки, развязки сервисов, ретраев и DLQ.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IAM&lt;/strong&gt; — кто (principal) может делать что (action) с чем (resource) при каких условиях. Принцип наименьших привилегий. Для сервисов — роли/service accounts, а не статичные ключи; короткоживущие креды через assume-role / Workload Identity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="спектр-compute"&gt;Спектр compute&lt;a class="anchor" href="#%d1%81%d0%bf%d0%b5%d0%ba%d1%82%d1%80-compute"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Уровень&lt;/th&gt;
					&lt;th&gt;AWS&lt;/th&gt;
					&lt;th&gt;GCP&lt;/th&gt;
					&lt;th&gt;Операционка&lt;/th&gt;
					&lt;th&gt;Когда&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;VM&lt;/td&gt;
					&lt;td&gt;EC2&lt;/td&gt;
					&lt;td&gt;Compute Engine&lt;/td&gt;
					&lt;td&gt;вы патчите ОС, масштабируете&lt;/td&gt;
					&lt;td&gt;legacy, специфичные требования, GPU&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Контейнеры + оркестратор&lt;/td&gt;
					&lt;td&gt;EKS, ECS&lt;/td&gt;
					&lt;td&gt;GKE&lt;/td&gt;
					&lt;td&gt;управляете кластером/нодами&lt;/td&gt;
					&lt;td&gt;микросервисы, сложная маршрутизация&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Serverless-контейнеры&lt;/td&gt;
					&lt;td&gt;Fargate, App Runner&lt;/td&gt;
					&lt;td&gt;Cloud Run&lt;/td&gt;
					&lt;td&gt;только образ, без нод&lt;/td&gt;
					&lt;td&gt;HTTP-сервисы, рваная нагрузка&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;FaaS&lt;/td&gt;
					&lt;td&gt;Lambda&lt;/td&gt;
					&lt;td&gt;Cloud Functions&lt;/td&gt;
					&lt;td&gt;только функция&lt;/td&gt;
					&lt;td&gt;события, glue-логика, cron&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Для Go-бэкендера типичный выбор сегодня — &lt;strong&gt;Cloud Run&lt;/strong&gt; (GCP) или &lt;strong&gt;ECS/Fargate / EKS&lt;/strong&gt; (AWS): пушишь Docker-образ, платформа крутит и масштабирует.&lt;/p&gt;</description></item><item><title>Docker для Go-разработчика</title><link>https://go.vbloher.org/docs/11-devops/docker/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/11-devops/docker/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: DevOps · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker-образ — это набор &lt;strong&gt;read-only слоёв&lt;/strong&gt; (union filesystem, OverlayFS) + JSON-манифест. Каждая инструкция &lt;code&gt;RUN&lt;/code&gt;/&lt;code&gt;COPY&lt;/code&gt;/&lt;code&gt;ADD&lt;/code&gt; создаёт новый слой; &lt;code&gt;ENV&lt;/code&gt;/&lt;code&gt;WORKDIR&lt;/code&gt;/&lt;code&gt;CMD&lt;/code&gt; создают слои-метаданные нулевого размера.&lt;/li&gt;
&lt;li&gt;Для Go используйте &lt;strong&gt;multistage build&lt;/strong&gt;: stage сборки на &lt;code&gt;golang:1.x&lt;/code&gt;, финальный образ — &lt;code&gt;scratch&lt;/code&gt; или &lt;code&gt;gcr.io/distroless/static&lt;/code&gt;. Итог: 5–15 МБ вместо 800+ МБ.&lt;/li&gt;
&lt;li&gt;Статическая линковка: &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; даёт полностью статический бинарь, который работает в &lt;code&gt;scratch&lt;/code&gt;. С CGO нужен glibc/musl в рантайме.&lt;/li&gt;
&lt;li&gt;Кэш слоёв ломается на первом изменившемся слое и всех последующих. Поэтому &lt;code&gt;COPY go.mod go.sum&lt;/code&gt; и &lt;code&gt;go mod download&lt;/code&gt; идут ДО &lt;code&gt;COPY . .&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dockerignore&lt;/code&gt; обязателен: исключает &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, локальные бинарники из build context (ускоряет билд и уменьшает риск утечки секретов).&lt;/li&gt;
&lt;li&gt;Безопасность: non-root &lt;code&gt;USER&lt;/code&gt;, минимальный базовый образ, никаких секретов в слоях, multi-stage чтобы не тащить toolchain.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="образы-и-слои"&gt;Образы и слои&lt;a class="anchor" href="#%d0%be%d0%b1%d1%80%d0%b0%d0%b7%d1%8b-%d0%b8-%d1%81%d0%bb%d0%be%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Образ состоит из:&lt;/p&gt;</description></item><item><title>gRPC: типы RPC, интерсепторы, контекст, метаданные, error model</title><link>https://go.vbloher.org/docs/05-backend/grpc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/grpc/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;gRPC&lt;/strong&gt; — RPC-фреймворк поверх &lt;strong&gt;HTTP/2&lt;/strong&gt;, использующий &lt;strong&gt;Protocol Buffers&lt;/strong&gt; как IDL и формат сериализации (бинарный, schema-first). Один TCP-коннект мультиплексирует множество параллельных стримов (нет head-of-line blocking на уровне приложения).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 типа RPC&lt;/strong&gt;: unary (1→1), server streaming (1→N), client streaming (N→1), bidirectional streaming (N→M, полнодуплексный).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Интерсепторы&lt;/strong&gt; = middleware. Отдельные типы для unary/stream и для client/server. Цепочки строятся через &lt;code&gt;grpc.ChainUnaryInterceptor&lt;/code&gt; / &lt;code&gt;ChainStreamInterceptor&lt;/code&gt;. Типичные: logging, auth, recovery (panic→&lt;code&gt;codes.Internal&lt;/code&gt;), metrics, tracing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Контекст и дедлайны&lt;/strong&gt;: &lt;code&gt;context.WithTimeout&lt;/code&gt; на клиенте → дедлайн сериализуется в заголовок &lt;code&gt;grpc-timeout&lt;/code&gt; → сервер получает его в &lt;code&gt;ctx&lt;/code&gt; → &lt;strong&gt;дедлайн распространяется по цепочке&lt;/strong&gt; при дальнейших вызовах. Отмена клиента доходит до сервера (&lt;code&gt;ctx.Done()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Метаданные&lt;/strong&gt; (&lt;code&gt;metadata.MD&lt;/code&gt;) — пары ключ/значение (≈HTTP-заголовки). &lt;code&gt;FromIncomingContext&lt;/code&gt; (чтение на сервере), &lt;code&gt;NewOutgoingContext&lt;/code&gt; (отправка с клиента). Различают &lt;strong&gt;headers&lt;/strong&gt; (до тела) и &lt;strong&gt;trailers&lt;/strong&gt; (после тела, для стримов критично).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error model&lt;/strong&gt;: ошибки = &lt;code&gt;status.Status&lt;/code&gt; с &lt;code&gt;codes.Code&lt;/code&gt; + message + опциональные &lt;code&gt;details&lt;/code&gt; (&lt;code&gt;google.rpc.*&lt;/code&gt;, напр. &lt;code&gt;ErrorInfo&lt;/code&gt;, &lt;code&gt;BadRequest&lt;/code&gt;). &lt;code&gt;status.Errorf(codes.NotFound, ...)&lt;/code&gt;. Есть стандартный маппинг codes ↔ HTTP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC vs REST&lt;/strong&gt;: gRPC — для внутренних сервис-сервис вызовов (производительность, строгий контракт, streaming); REST/JSON — для публичных/браузерных API. Браузер не умеет gRPC напрямую → gRPC-Web / gRPC-Gateway.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-что-такое-grpc"&gt;1. Что такое gRPC&lt;a class="anchor" href="#1-%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-grpc"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;gRPC — это контрактно-ориентированный (schema-first) RPC-фреймворк. Ключевые составляющие:&lt;/p&gt;</description></item><item><title>OpenTelemetry</title><link>https://go.vbloher.org/docs/09-observability/opentelemetry/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/09-observability/opentelemetry/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Observability · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenTelemetry (OTel) — это вендор-нейтральный стандарт и набор SDK для сбора телеметрии: &lt;strong&gt;traces, metrics, logs&lt;/strong&gt; (три сигнала + появляющиеся profiles). Ключевая идея — разделить &lt;strong&gt;API&lt;/strong&gt; (стабильный контракт, который вызывает код приложения и библиотеки) и &lt;strong&gt;SDK&lt;/strong&gt; (реализация, которая семплирует, батчит и экспортирует). Данные текут через &lt;strong&gt;pipeline&lt;/strong&gt;: &lt;code&gt;Instrumentation → Provider → Processor → Exporter → Collector → Backend&lt;/code&gt;. &lt;strong&gt;Collector&lt;/strong&gt; — отдельный процесс/демон, который принимает (receivers), обрабатывает (processors) и отправляет (exporters) телеметрию, отвязывая приложение от конкретного бэкенда. Context propagation через &lt;code&gt;context.Context&lt;/code&gt; + W3C &lt;code&gt;traceparent&lt;/code&gt; связывает спаны между сервисами. В Go главные грабли: не забыть &lt;code&gt;Shutdown()&lt;/code&gt; (иначе теряются батчи), не плодить cardinality в метриках, прокидывать &lt;code&gt;ctx&lt;/code&gt; сквозь весь стек.&lt;/p&gt;</description></item><item><title>Версии HTTP: 1.1, 2, 3</title><link>https://go.vbloher.org/docs/06-networking/http-versions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/06-networking/http-versions/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Сети и протоколы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP/1.1&lt;/strong&gt;: текстовый, одно соединение = один запрос в момент времени; keep-alive переиспользует соединение; pipelining почти не работает (HoL blocking); параллелизм достигается множеством TCP-соединений (браузеры ~6 на хост).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2&lt;/strong&gt;: бинарный, мультиплексирование множества потоков по одному TCP, сжатие заголовков (HPACK), приоритеты, server push (по факту устарел). Решает HoL на уровне приложения, но НЕ на уровне TCP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/3&lt;/strong&gt;: HTTP поверх QUIC (UDP+TLS1.3). Убирает HoL на транспорте, быстрый handshake, миграция соединения.&lt;/li&gt;
&lt;li&gt;Бэкендеру: gRPC требует HTTP/2; Go &lt;code&gt;net/http&lt;/code&gt; сервер и клиент поддерживают h2 автоматически по TLS+ALPN; HTTP/3 — через &lt;code&gt;quic-go&lt;/code&gt;/&lt;code&gt;http3&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="http11"&gt;HTTP/1.1&lt;a class="anchor" href="#http11"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Текстовый протокол&lt;/strong&gt;, человекочитаемый, парсится построчно.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent connections (keep-alive)&lt;/strong&gt; — дефолт в 1.1: соединение не закрывается после ответа, переиспользуется. Управляется &lt;code&gt;Connection: keep-alive&lt;/code&gt;/&lt;code&gt;close&lt;/code&gt; и таймаутами.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipelining&lt;/strong&gt;: можно отправить несколько запросов, не дожидаясь ответов, НО ответы должны прийти строго по порядку → &lt;strong&gt;head-of-line blocking&lt;/strong&gt;: медленный первый ответ блокирует остальные. На практике pipelining отключён почти везде (баги прокси).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Параллелизм&lt;/strong&gt;: реальная конкурентность достигается открытием нескольких TCP-соединений (браузеры ~6 на origin). Отсюда хаки вроде domain sharding.&lt;/li&gt;
&lt;li&gt;Один запрос/ответ в момент времени на соединение → накладные расходы и затраты на handshake.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="http2"&gt;HTTP/2&lt;a class="anchor" href="#http2"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Бинарный протокол поверх одного TCP-соединения (обычно поверх TLS, ALPN &lt;code&gt;h2&lt;/code&gt;):&lt;/p&gt;</description></item><item><title>Индексы в PostgreSQL</title><link>https://go.vbloher.org/docs/07-databases/indexes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/indexes/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;B-tree&lt;/strong&gt; — дефолтный индекс, отсортированное сбалансированное дерево. Работает на &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;BETWEEN&lt;/code&gt;, &lt;code&gt;IN&lt;/code&gt;, &lt;code&gt;IS NULL&lt;/code&gt;, &lt;code&gt;LIKE 'prefix%'&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt;. Поддерживает уникальность. 90% случаев — это он.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hash&lt;/strong&gt; — только &lt;code&gt;=&lt;/code&gt;. С PG 10 WAL-логируется и crash-safe. Чуть компактнее B-tree на равенстве, но без диапазонов и сортировки — почти всегда B-tree предпочтительнее.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GIN&lt;/strong&gt; — для составных значений: &lt;code&gt;jsonb&lt;/code&gt;, массивы (&lt;code&gt;@&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;), full-text (&lt;code&gt;tsvector @@ tsquery&lt;/code&gt;), &lt;code&gt;pg_trgm&lt;/code&gt; (LIKE/ILIKE/похожесть). Хранит «значение → список TID». Медленный на запись, очень быстрый на поиск; есть &lt;code&gt;fastupdate&lt;/code&gt; (pending list).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GiST&lt;/strong&gt; — обобщённое дерево для «перекрытий»: геоданные (PostGIS), диапазоны (&lt;code&gt;tsrange &amp;amp;&amp;amp;&lt;/code&gt;), kNN (&lt;code&gt;ORDER BY point &amp;lt;-&amp;gt; point&lt;/code&gt;), exclusion constraints. Lossy по природе.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SP-GiST&lt;/strong&gt; — несбалансированные структуры (quadtree, radix), для непересекающихся данных (IP, точки).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BRIN&lt;/strong&gt; — block range index. Крошечный, хранит min/max по блокам. Только для больших таблиц с физической корреляцией (timestamp append-only). Не точечный — даёт кандидатов на recheck.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Составной индекс&lt;/strong&gt;: порядок колонок решает всё. Leftmost prefix rule. Equality-колонки слева, range — справа, по последней range-колонке возможен &lt;code&gt;ORDER BY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partial&lt;/strong&gt; (&lt;code&gt;WHERE&lt;/code&gt;), &lt;strong&gt;covering/INCLUDE&lt;/strong&gt; (index-only scan + visibility map), &lt;strong&gt;expression index&lt;/strong&gt; (индекс по &lt;code&gt;lower(email)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Индекс &lt;strong&gt;не используется&lt;/strong&gt; при низкой селективности, функции/каста над колонкой, type mismatch, &lt;code&gt;OR&lt;/code&gt; (иногда), &lt;code&gt;LIKE '%x'&lt;/code&gt;, устаревшей статистике.&lt;/li&gt;
&lt;li&gt;Каждый индекс — налог на каждый &lt;code&gt;INSERT/UPDATE/DELETE&lt;/code&gt;. HOT-update спасает только если меняемые колонки не индексированы. Настраивай &lt;code&gt;fillfactor&lt;/code&gt;, следи за bloat.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-индекс-и-общая-модель"&gt;Зачем индекс и общая модель&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%b8%d0%bd%d0%b4%d0%b5%d0%ba%d1%81-%d0%b8-%d0%be%d0%b1%d1%89%d0%b0%d1%8f-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d1%8c"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Индекс — это вспомогательная структура данных, которая позволяет находить строки без полного сканирования таблицы (heap). В PostgreSQL индексы &lt;strong&gt;отделены от таблицы&lt;/strong&gt;: индекс хранит ключ + указатель &lt;code&gt;TID&lt;/code&gt; (ctid = &lt;code&gt;(block, offset)&lt;/code&gt;), указывающий на физическое расположение версии строки в heap.&lt;/p&gt;</description></item><item><title>Канал vs Mutex: когда что выбрать</title><link>https://go.vbloher.org/docs/02-concurrency/channel-vs-mutex/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/channel-vs-mutex/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;«Share memory by communicating» — это рекомендация, а не догма. Каналы хороши, когда вы &lt;strong&gt;передаёте владение данными&lt;/strong&gt; между горутинами или координируете поток событий/жизненный цикл; мьютекс/atomic хороши, когда вы &lt;strong&gt;защищаете разделяемое состояние&lt;/strong&gt; при коротких операциях. Канал внутри — это структура с мьютексом и очередью, так что он не «бесплатнее» мьютекса. Критерий простой: владение/передача/оркестрация → канал; разделяемое состояние/кэш/счётчик → мьютекс/atomic.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="два-девиза-go"&gt;Два девиза Go&lt;a class="anchor" href="#%d0%b4%d0%b2%d0%b0-%d0%b4%d0%b5%d0%b2%d0%b8%d0%b7%d0%b0-go"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;«Don&amp;rsquo;t communicate by sharing memory; share memory by communicating.»&lt;/strong&gt; — каналы как основной способ координации.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Контр-цитата команды Go:&lt;/strong&gt; &lt;em&gt;«Use whichever is most expressive and/or most simple.»&lt;/em&gt; (Go wiki). Каналы — не самоцель.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="что-такое-канал-на-самом-деле"&gt;Что такое канал на самом деле&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-%d0%ba%d0%b0%d0%bd%d0%b0%d0%bb-%d0%bd%d0%b0-%d1%81%d0%b0%d0%bc%d0%be%d0%bc-%d0%b4%d0%b5%d0%bb%d0%b5"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Канал (&lt;code&gt;hchan&lt;/code&gt;) — это &lt;strong&gt;структура с мьютексом&lt;/strong&gt; (&lt;code&gt;lock&lt;/code&gt;), кольцевым буфером, очередями &lt;code&gt;sendq&lt;/code&gt;/&lt;code&gt;recvq&lt;/code&gt; парковки горутин. То есть канал реализован поверх блокировок и семафоров рантайма. Поэтому:&lt;/p&gt;</description></item><item><title>Консенсус и Raft: репликация состояния в присутствии отказов</title><link>https://go.vbloher.org/docs/08-distributed-systems/consensus-raft/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/consensus-raft/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Консенсус&lt;/strong&gt; — задача договориться о едином значении/порядке между несколькими узлами так, чтобы решение пережило отказы части узлов. Лежит в основе &lt;strong&gt;replicated state machine (RSM)&lt;/strong&gt;: одинаковый детерминированный лог команд -&amp;gt; одинаковое состояние на всех репликах.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FLP impossibility&lt;/strong&gt;: в &lt;em&gt;асинхронной&lt;/em&gt; сети (нет границ на задержки) невозможно гарантировать консенсус при наличии даже одного отказавшего узла, который нельзя отличить от медленного. Практика обходит это через &lt;strong&gt;частичную синхронность&lt;/strong&gt; и &lt;strong&gt;таймауты&lt;/strong&gt; (рандомизированные) — жертвуем гарантированной liveness, сохраняя safety.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raft&lt;/strong&gt; — алгоритм консенсуса, спроектированный как &lt;em&gt;understandable&lt;/em&gt; альтернатива Paxos. Декомпозиция на три подзадачи: &lt;strong&gt;leader election&lt;/strong&gt;, &lt;strong&gt;log replication&lt;/strong&gt;, &lt;strong&gt;safety&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Роли: &lt;strong&gt;leader / follower / candidate&lt;/strong&gt;. Время делится на &lt;strong&gt;terms&lt;/strong&gt; (логические эпохи). В каждом term не более одного лидера; все записи идут только через лидера (&lt;strong&gt;leader-only writes&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Запись коммитится, когда её реплицировало &lt;strong&gt;большинство (кворум N/2+1)&lt;/strong&gt;. Поэтому нужно &lt;strong&gt;нечётное&lt;/strong&gt; число нод — кворум тот же, а fault tolerance не теряется при чётности.&lt;/li&gt;
&lt;li&gt;Применяется в &lt;strong&gt;etcd&lt;/strong&gt;, &lt;strong&gt;Consul&lt;/strong&gt;, &lt;strong&gt;CockroachDB&lt;/strong&gt; (Raft per range), &lt;strong&gt;TiKV&lt;/strong&gt;. &lt;strong&gt;ZooKeeper&lt;/strong&gt; использует ZAB — близкий по духу leader-based atomic broadcast.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-нужен-консенсус"&gt;Зачем нужен консенсус&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%bd%d1%83%d0%b6%d0%b5%d0%bd-%d0%ba%d0%be%d0%bd%d1%81%d0%b5%d0%bd%d1%81%d1%83%d1%81"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Распределённая система хочет вести себя как один надёжный узел, несмотря на отказы отдельных машин. Для этого данные/решения реплицируются. Возникает вопрос: как несколько реплик договариваются об &lt;strong&gt;одном и том же значении и одном и том же порядке&lt;/strong&gt; операций?&lt;/p&gt;</description></item><item><title>Лидерство и менторство</title><link>https://go.vbloher.org/docs/13-behavioral/leadership-mentoring/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/13-behavioral/leadership-mentoring/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Behavioral · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Senior лидирует &lt;strong&gt;без формальной власти&lt;/strong&gt;: через техническую репутацию, ясность аргументации и помощь другим, а не через приказы.&lt;/li&gt;
&lt;li&gt;Менторство — это не «давать ответы», а &lt;strong&gt;поднимать самостоятельность&lt;/strong&gt;: задавать вопросы, давать рамку, отпускать ответственность.&lt;/li&gt;
&lt;li&gt;Code review — главный масштабируемый инструмент влияния. Тон, фокус на важном и обучающие комментарии важнее, чем количество найденных проблем.&lt;/li&gt;
&lt;li&gt;Архитектурные решения senior принимает &lt;strong&gt;с обоснованием и обратимостью&lt;/strong&gt;: ADR, прототип, понятный trade-off, путь отката.&lt;/li&gt;
&lt;li&gt;Техническое влияние измеряется не «я был прав», а «решение приняли и оно сработало», и тем, что &lt;strong&gt;другие выросли&lt;/strong&gt; рядом с тобой.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория--подход"&gt;Теория / Подход&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f--%d0%bf%d0%be%d0%b4%d1%85%d0%be%d0%b4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="лидерство-без-формальной-власти"&gt;Лидерство без формальной власти&lt;a class="anchor" href="#%d0%bb%d0%b8%d0%b4%d0%b5%d1%80%d1%81%d1%82%d0%b2%d0%be-%d0%b1%d0%b5%d0%b7-%d1%84%d0%be%d1%80%d0%bc%d0%b0%d0%bb%d1%8c%d0%bd%d0%be%d0%b9-%d0%b2%d0%bb%d0%b0%d1%81%d1%82%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;На senior у тебя обычно нет людей в подчинении, но есть ожидание, что ты двигаешь команду. Источники влияния:&lt;/p&gt;</description></item><item><title>Ошибки в Go: error, wrapping, errors.Is/As/Join</title><link>https://go.vbloher.org/docs/01-core-go/errors/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/errors/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;В Go ошибка — это обычное значение, реализующее интерфейс &lt;code&gt;error&lt;/code&gt; с единственным методом &lt;code&gt;Error() string&lt;/code&gt;. Ошибки конструируются (&lt;code&gt;errors.New&lt;/code&gt;, &lt;code&gt;fmt.Errorf&lt;/code&gt;), оборачиваются с сохранением причины через &lt;code&gt;%w&lt;/code&gt;, образуя цепочку (или дерево при &lt;code&gt;errors.Join&lt;/code&gt;/нескольких &lt;code&gt;%w&lt;/code&gt;), и инспектируются через &lt;code&gt;errors.Is&lt;/code&gt; (сравнение по идентичности/семантике) и &lt;code&gt;errors.As&lt;/code&gt; (извлечение конкретного типа). &lt;code&gt;panic&lt;/code&gt; предназначен для программных багов и неинвариантных состояний, а не для ожидаемых ошибок. Стандартные ошибки намеренно не несут stacktrace — это сознательный компромисс между стоимостью и информативностью.&lt;/p&gt;</description></item><item><title>Покрытие, -race и флаки-тесты</title><link>https://go.vbloher.org/docs/04-testing/coverage-race/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/04-testing/coverage-race/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Тестирование · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;go test -cover&lt;/code&gt; показывает процент покрытых &lt;strong&gt;строк&lt;/strong&gt; (точнее — операторов/блоков), &lt;code&gt;-coverprofile=cover.out&lt;/code&gt; пишет профиль для &lt;code&gt;go tool cover -html&lt;/code&gt;/&lt;code&gt;-func&lt;/code&gt;. Покрытие говорит «этот код &lt;strong&gt;исполнялся&lt;/strong&gt; в тестах», но НЕ «он протестирован»: высокий процент при отсутствии ассертов, непокрытые ветки ошибок, неучтённые конкурентные пути — всё это даёт ложную уверенность. &lt;code&gt;go test -race&lt;/code&gt; включает детектор гонок (ThreadSanitizer): инструментирует доступы к памяти и ловит data race в рантайме на тех путях, что реально исполнились — обязателен в CI для конкурентного кода. Флаки-тесты (недетерминированно падающие) — следствие гонок, зависимости от времени/порядка/сети, общего состояния; их находят повторными прогонами (&lt;code&gt;-count&lt;/code&gt;, &lt;code&gt;-race&lt;/code&gt;, &lt;code&gt;-shuffle&lt;/code&gt;) и устраняют, а не ретраят вслепую.&lt;/p&gt;</description></item><item><title>Сборщик мусора Go: concurrent tri-color mark-sweep</title><link>https://go.vbloher.org/docs/03-runtime-memory/gc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/gc/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;GC в Go — это конкурентный, неперемещающий (non-moving), tri-color mark-sweep сборщик с очень короткими STW-паузами (обычно sub-millisecond). Конкурентность корректна благодаря &lt;strong&gt;hybrid write barrier&lt;/strong&gt; (Dijkstra insertion + Yuasa deletion), появившемуся в Go 1.8, который позволил убрать повторное STW-сканирование стеков. Темп сборки регулирует &lt;strong&gt;GC pacer&lt;/strong&gt;, балансируя момент старта цикла так, чтобы heap не превысил цель &lt;code&gt;GOGC&lt;/code&gt; (или мягкий лимит &lt;code&gt;GOMEMLIMIT&lt;/code&gt; с 1.19); при отставании mark-фазы горутины-мутаторы платят &lt;strong&gt;mark assist&lt;/strong&gt;. Аллокация идёт через многоуровневую иерархию &lt;code&gt;mcache → mcentral → mheap&lt;/code&gt; с разбиением по size classes.&lt;/p&gt;</description></item><item><title>Структуры данных в Go</title><link>https://go.vbloher.org/docs/12-algorithms/data-structures/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/12-algorithms/data-structures/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Алгоритмы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Go аскетичен: из коробок — слайсы и map. Остальное (&lt;code&gt;container/list&lt;/code&gt;, &lt;code&gt;container/ring&lt;/code&gt;, &lt;code&gt;container/heap&lt;/code&gt;) — в &lt;code&gt;container/*&lt;/code&gt;, причём &lt;code&gt;heap&lt;/code&gt; это &lt;strong&gt;интерфейс над вашим слайсом&lt;/strong&gt;, а не готовый тип.&lt;/li&gt;
&lt;li&gt;На live-coding 90% задач решаются &lt;strong&gt;слайсом + map&lt;/strong&gt;. Stack/queue делаются на слайсе руками. Heap — через &lt;code&gt;container/heap&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;linked list&lt;/code&gt;, &lt;code&gt;tree&lt;/code&gt;, &lt;code&gt;trie&lt;/code&gt;, &lt;code&gt;graph&lt;/code&gt; пишут вручную через структуры с указателями. Знать обходы и инварианты.&lt;/li&gt;
&lt;li&gt;В Go 1.21+ есть &lt;code&gt;slices&lt;/code&gt; и &lt;code&gt;maps&lt;/code&gt; пакеты (generics) — &lt;code&gt;slices.Sort&lt;/code&gt;, &lt;code&gt;slices.Contains&lt;/code&gt;, &lt;code&gt;maps.Keys&lt;/code&gt; и т.д.&lt;/li&gt;
&lt;li&gt;Senior должен знать &lt;strong&gt;сложности всех операций&lt;/strong&gt;, когда какая структура нужна, и подводные камни Go (shared backing array, nil map, random iteration).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="массивы-и-слайсы"&gt;Массивы и слайсы&lt;a class="anchor" href="#%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2%d1%8b-%d0%b8-%d1%81%d0%bb%d0%b0%d0%b9%d1%81%d1%8b"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Массив&lt;/strong&gt; &lt;code&gt;[N]T&lt;/code&gt; — фиксированный размер, value-тип (копируется при передаче). Редко используется напрямую.
&lt;strong&gt;Слайс&lt;/strong&gt; &lt;code&gt;[]T&lt;/code&gt; — header &lt;code&gt;{ptr, len, cap}&lt;/code&gt; поверх массива; основная рабочая структура.&lt;/p&gt;</description></item><item><title>Фреймворк System Design интервью</title><link>https://go.vbloher.org/docs/10-system-design/framework/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/framework/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;System design интервью оценивает не «знание решения», а &lt;strong&gt;способ мышления&lt;/strong&gt;: как ты переводишь размытые требования в конкретные числа, как обосновываешь выбор компонентов и где честно проговариваешь trade-offs. Senior-кандидата отличает не количество названных технологий, а умение управлять временем, задавать уточняющие вопросы и связывать решение с бизнес-ограничениями.&lt;/p&gt;
&lt;p&gt;Работающий фреймворк на 45 минут:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;requirements → estimations → API → data model → high-level → deep dive → scale → trade-offs
 (5&amp;#39;) (5&amp;#39;) (3&amp;#39;) (3&amp;#39;) (8&amp;#39;) (12&amp;#39;) (5&amp;#39;) (4&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Главные провалы кандидатов: сразу рисуют боксы без требований, не считают цифры (QPS/storage), не делают deep dive, перечисляют технологии без обоснования и молчат про trade-offs. Senior всегда говорит «зависит от&amp;hellip;» и явно называет, от чего именно.&lt;/p&gt;</description></item><item><title>GitHub Actions и GitLab CI</title><link>https://go.vbloher.org/docs/11-devops/github-gitlab-ci/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/11-devops/github-gitlab-ci/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: DevOps · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;: единица — &lt;em&gt;workflow&lt;/em&gt; (&lt;code&gt;.github/workflows/*.yml&lt;/code&gt;), внутри &lt;em&gt;jobs&lt;/em&gt;, внутри &lt;em&gt;steps&lt;/em&gt;. Шаги — это либо shell-команды (&lt;code&gt;run&lt;/code&gt;), либо переиспользуемые &lt;em&gt;actions&lt;/em&gt; (&lt;code&gt;uses&lt;/code&gt;). Jobs по умолчанию параллельны, изолированы (разные раннеры), связываются через &lt;code&gt;needs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitLab CI&lt;/strong&gt;: единица — &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;, внутри &lt;em&gt;jobs&lt;/em&gt;, сгруппированные по &lt;em&gt;stages&lt;/em&gt;. Stages выполняются последовательно, jobs внутри одного stage — параллельно. Артефакты передаются между stages через &lt;code&gt;artifacts&lt;/code&gt;/&lt;code&gt;dependencies&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Кэширование&lt;/strong&gt; обязательно для Go: кэшируем &lt;code&gt;$GOMODCACHE&lt;/code&gt; (&lt;code&gt;~/go/pkg/mod&lt;/code&gt;) и build cache (&lt;code&gt;~/.cache/go-build&lt;/code&gt;). В GH Actions — &lt;code&gt;actions/cache&lt;/code&gt; или &lt;code&gt;setup-go&lt;/code&gt; с &lt;code&gt;cache: true&lt;/code&gt;; в GitLab — &lt;code&gt;cache:&lt;/code&gt; с ключом по &lt;code&gt;go.sum&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Матрицы&lt;/strong&gt; (&lt;code&gt;strategy.matrix&lt;/code&gt; / &lt;code&gt;parallel.matrix&lt;/code&gt;) гоняют один job по комбинациям (версии Go, ОС, arch).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Секреты&lt;/strong&gt;: GH — Secrets (repo/org/environment) + OIDC для облаков без статичных ключей; GitLab — CI/CD variables (masked, protected, file). Никогда не печатать в лог.&lt;/li&gt;
&lt;li&gt;Концептуально оба про одно: декларативный pipeline-as-code, изолированные раннеры, кэш, артефакты, секреты, матрицы.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="github-actions-модель"&gt;GitHub Actions: модель&lt;a class="anchor" href="#github-actions-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d1%8c"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# .github/workflows/ci.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# отмена устаревших запусков на тот же ref&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# принцип наименьших привилегий для GITHUB_TOKEN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-go@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;go-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1.22&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# кэширует mod cache + build cache по go.sum&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;go vet ./...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;go test -race -coverprofile=cover.out ./...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/upload-artifact@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;coverage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cover.out&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;on&lt;/code&gt; — триггеры (push, pull_request, schedule (cron), workflow_dispatch (ручной), tags).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runs-on&lt;/code&gt; — тип раннера (GitHub-hosted или self-hosted).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jobs.&amp;lt;id&amp;gt;.needs&lt;/code&gt; — зависимости между job (DAG).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uses&lt;/code&gt; — переиспользуемый action (всегда пинить версию: &lt;code&gt;@v4&lt;/code&gt; или, безопаснее, по SHA коммита).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; — автогенерируемый токен; ограничивайте через &lt;code&gt;permissions&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="github-actions-stages-эмулируются-через-needs"&gt;GitHub Actions: stages эмулируются через &lt;code&gt;needs&lt;/code&gt;&lt;a class="anchor" href="#github-actions-stages-%d1%8d%d0%bc%d1%83%d0%bb%d0%b8%d1%80%d1%83%d1%8e%d1%82%d1%81%d1%8f-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-needs"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В GH нет явных stages. Последовательность задаётся графом &lt;code&gt;needs&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>JWT (JSON Web Token)</title><link>https://go.vbloher.org/docs/05-backend/jwt/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/jwt/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JWT — это НЕ шифрование, а подпись.&lt;/strong&gt; Формат &lt;code&gt;header.payload.signature&lt;/code&gt;, каждая часть — &lt;code&gt;base64url&lt;/code&gt;. Payload читается кем угодно (&lt;code&gt;base64url -d&lt;/code&gt;). Никогда не кладите в payload секреты (пароли, PAN-карты, токены доступа к другим системам).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Подпись&lt;/strong&gt; гарантирует целостность и аутентичность, а не конфиденциальность. Для конфиденциальности нужен JWE (encrypted), а не JWS (signed) — и в 99% случаев это не то, что вам нужно.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HS256&lt;/strong&gt; (HMAC) — симметричный, один секрет для подписи и проверки. &lt;strong&gt;RS256/ES256&lt;/strong&gt; (RSA/ECDSA) — асимметричный: приватным подписывают (auth-server), публичным проверяют (resource-servers). Микросервисы → почти всегда асимметрика + JWKS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Валидация обязана&lt;/strong&gt;: проверять подпись, &lt;code&gt;exp&lt;/code&gt;/&lt;code&gt;nbf&lt;/code&gt;/&lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;aud&lt;/code&gt;, и &lt;strong&gt;whitelisting алгоритма&lt;/strong&gt; (защита от &lt;code&gt;alg=none&lt;/code&gt; и algorithm confusion).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT stateless ⇒ отозвать нельзя&lt;/strong&gt; без внешнего состояния. Решения: короткий TTL access-токена + refresh-токен с ротацией и reuse-detection, либо denylist (Redis) / token version в БД.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Хранение на клиенте&lt;/strong&gt;: &lt;code&gt;httpOnly&lt;/code&gt;+&lt;code&gt;Secure&lt;/code&gt;+&lt;code&gt;SameSite&lt;/code&gt; cookie, а не &lt;code&gt;localStorage&lt;/code&gt; (XSS читает localStorage тривиально).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-структура"&gt;1. Структура&lt;a class="anchor" href="#1-%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%82%d1%83%d1%80%d0%b0"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JWT (а точнее — самая частая его форма, JWS Compact Serialization) состоит из трёх частей, разделённых точками:&lt;/p&gt;</description></item><item><title>Notification Service</title><link>https://go.vbloher.org/docs/10-system-design/notification-service/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/notification-service/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Notification Service — это асинхронный шлюз доставки сообщений пользователям по разным каналам (push, email, SMS, in-app, webhook). Ключевая идея: &lt;strong&gt;разделить приём запроса (быстрый, синхронный) и доставку (медленную, ненадёжную, зависящую от внешних провайдеров)&lt;/strong&gt; через очереди. Senior-сложность сосредоточена не в «отправить письмо», а в:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;идемпотентности&lt;/strong&gt; (один логический event не должен породить два письма даже при ретраях и at-least-once семантике брокера);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ретраях с exponential backoff + jitter&lt;/strong&gt; и DLQ для poison-сообщений;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rate limiting на провайдера&lt;/strong&gt; (APNs/SES/Twilio имеют жёсткие квоты, превышение = throttling/бан);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fan-out&lt;/strong&gt; (один event → миллионы получателей: топик, маркетинговая рассылка);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;user preferences / opt-out&lt;/strong&gt; (нельзя слать туда, где пользователь отписался; compliance — CAN-SPAM, GDPR, TCPA);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;per-channel изоляции&lt;/strong&gt; (деградация SMS-провайдера не должна останавливать push).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Базовый принцип: &lt;em&gt;очередь на канал + пул воркеров + circuit breaker на провайдера + дедупликация в Redis/БД&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Prometheus</title><link>https://go.vbloher.org/docs/09-observability/prometheus/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/09-observability/prometheus/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Observability · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Prometheus — это TSDB + язык запросов PromQL + &lt;strong&gt;pull-модель&lt;/strong&gt; сбора метрик: сервер сам ходит (scrape) по HTTP-эндпоинтам &lt;code&gt;/metrics&lt;/code&gt; целей, обнаруженных через service discovery, и складывает числовые временные ряды. Метрика — это &lt;code&gt;name{label=&amp;quot;value&amp;quot;}&lt;/code&gt; → значение во времени; уникальная комбинация имени и набора лейблов = один &lt;strong&gt;временной ряд (series)&lt;/strong&gt;. Четыре типа: &lt;strong&gt;counter&lt;/strong&gt; (монотонно растёт, мерим через &lt;code&gt;rate()&lt;/code&gt;), &lt;strong&gt;gauge&lt;/strong&gt; (произвольно колеблется), &lt;strong&gt;histogram&lt;/strong&gt; (бакеты &lt;code&gt;_bucket{le}&lt;/code&gt; + &lt;code&gt;_sum&lt;/code&gt; + &lt;code&gt;_count&lt;/code&gt;, перцентили считаются на сервере через &lt;code&gt;histogram_quantile&lt;/code&gt;, агрегируется между инстансами), &lt;strong&gt;summary&lt;/strong&gt; (квантили считает клиент, НЕ агрегируется). Главный враг senior&amp;rsquo;а — &lt;strong&gt;cardinality explosion&lt;/strong&gt;: лейбл с неограниченным множеством значений (user_id, request_id, URL с ID) умножает число рядов и убивает память/диск. В Go используем &lt;code&gt;prometheus/client_golang&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>TCP/IP: модель, транспорт и что важно бэкендеру</title><link>https://go.vbloher.org/docs/06-networking/tcp-ip/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/06-networking/tcp-ip/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Сети и протоколы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TCP/IP — это 4-уровневая модель (Link / Internet / Transport / Application), на практике вместо OSI обсуждают именно её.&lt;/li&gt;
&lt;li&gt;TCP даёт надёжную, упорядоченную, байт-ориентированную доставку поверх ненадёжного IP за счёт sequence/ack-номеров, ретрансмиссий и контрольных сумм.&lt;/li&gt;
&lt;li&gt;Установка — 3-way handshake (SYN / SYN-ACK / ACK), закрытие — 4-way (FIN/ACK обе стороны), активный закрыватель уходит в &lt;code&gt;TIME_WAIT&lt;/code&gt; на 2×MSL.&lt;/li&gt;
&lt;li&gt;Flow control = защита получателя (sliding window), congestion control = защита сети (slow start, congestion avoidance, fast recovery, CUBIC/BBR).&lt;/li&gt;
&lt;li&gt;Бэкендеру критичны: &lt;code&gt;TIME_WAIT&lt;/code&gt;-исчерпание портов, Nagle vs delayed ACK (латентность), TCP keep-alive vs HTTP keep-alive (это разные вещи), backlog очередей accept.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="модель-tcpip-vs-osi"&gt;Модель TCP/IP vs OSI&lt;a class="anchor" href="#%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d1%8c-tcpip-vs-osi"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;TCP/IP&lt;/th&gt;
					&lt;th&gt;Аналог OSI&lt;/th&gt;
					&lt;th&gt;Примеры&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Application&lt;/td&gt;
					&lt;td&gt;L5-L7&lt;/td&gt;
					&lt;td&gt;HTTP, gRPC, DNS, TLS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Transport&lt;/td&gt;
					&lt;td&gt;L4&lt;/td&gt;
					&lt;td&gt;TCP, UDP, QUIC(поверх UDP)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Internet&lt;/td&gt;
					&lt;td&gt;L3&lt;/td&gt;
					&lt;td&gt;IP, ICMP, маршрутизация&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Link&lt;/td&gt;
					&lt;td&gt;L1-L2&lt;/td&gt;
					&lt;td&gt;Ethernet, ARP, MAC&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Senior-нюанс: TLS формально живёт между L4 и L7, поэтому в собеседовании TLS обычно относят к &amp;ldquo;L6-ish&amp;rdquo; в OSI, но в TCP/IP-модели это просто часть Application.&lt;/p&gt;</description></item><item><title>Дженерики в Go (1.18+)</title><link>https://go.vbloher.org/docs/01-core-go/generics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/generics/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Дженерики появились в Go 1.18 и позволяют параметризовать функции и типы по типам (type parameters) с ограничениями (constraints), которые задаются интерфейсами, описывающими &lt;strong&gt;множество типов&lt;/strong&gt; (type set), а не множество методов. Под капотом Go использует не чистую мономорфизацию (как C++/Rust) и не чисто словари (как Java erasure), а гибрид — &lt;strong&gt;GC Shape Stenciling&lt;/strong&gt;: компилятор генерирует по одной инстанцированной копии кода на каждую уникальную «GC-форму» (gcshape) аргумента, а различия в пределах одной формы разрешаются через скрытый словарь (dictionary). Это даёт компактный бинарник, но и неочевидные накладные расходы: дженерики далеко не всегда быстрее интерфейсов и часто теряют девиртуализацию и инлайнинг.&lt;/p&gt;</description></item><item><title>Каналы: устройство hchan</title><link>https://go.vbloher.org/docs/02-concurrency/channels/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/channels/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Канал — типизированная очередь с синхронизацией, представленная структурой &lt;code&gt;runtime.hchan&lt;/code&gt;. Отправка/приём блокируют горутины через парковку (&lt;code&gt;gopark&lt;/code&gt;); рантайм поддерживает очереди ожидающих отправителей/получателей и кольцевой буфер. Закрытый канал отдаёт оставшиеся значения, затем нулевые с &lt;code&gt;ok=false&lt;/code&gt;; отправка в закрытый — паника; операции с &lt;code&gt;nil&lt;/code&gt;-каналом блокируются навсегда.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="структура-hchan"&gt;Структура hchan&lt;a class="anchor" href="#%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%82%d1%83%d1%80%d0%b0-hchan"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// src/runtime/chan.go (упрощённо)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hchan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// элементов в буфере сейчас&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// размер буфера (cap)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// кольцевой буфер (только для буферизованных)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elemsize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elemtype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;_type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// индекс записи в буфер&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recvx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// индекс чтения из буфера&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recvq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;waitq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// очередь ожидающих получателей (sudog)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;waitq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// очередь ожидающих отправителей (sudog)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mutex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// защищает ВСЕ поля&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Каждый блокирующийся актёр оборачивается в &lt;code&gt;sudog&lt;/code&gt; (G + указатель на элемент). &lt;code&gt;lock&lt;/code&gt; — это рантаймовый мьютекс (не &lt;code&gt;sync.Mutex&lt;/code&gt;), под ним выполняются все операции с каналом.&lt;/p&gt;</description></item><item><title>Модели согласованности</title><link>https://go.vbloher.org/docs/08-distributed-systems/consistency/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/consistency/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistency model&lt;/strong&gt; — это контракт между хранилищем и приложением: какие чтения допустимы при заданной истории операций. Сильнее модель ⇒ проще рассуждать ⇒ дороже по latency/availability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linearizability (strong/atomic consistency)&lt;/strong&gt; — recency-гарантия над &lt;strong&gt;одним объектом&lt;/strong&gt;: как только запись завершилась, все последующие чтения видят её (или более новое значение). Есть единый total order, согласованный с реальным временем.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serializability&lt;/strong&gt; — isolation-гарантия над &lt;strong&gt;транзакциями&lt;/strong&gt; (мульти-объектными): результат конкурентного выполнения транзакций эквивалентен &lt;strong&gt;какому-то&lt;/strong&gt; последовательному порядку. Не обязана уважать реальное время.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strict serializability = serializability + linearizability&lt;/strong&gt; — эквивалент последовательному порядку, который к тому же уважает реальное время (Spanner: external consistency).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eventual consistency&lt;/strong&gt; — слабейшая полезная модель: при прекращении записей реплики сходятся. Ничего не говорит про порядок «во время».&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Causal consistency&lt;/strong&gt; — сохраняет порядок причинно-связанных операций (happens-before); самая сильная модель, достижимая при сохранении доступности во время партиции.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client-centric / session guarantees&lt;/strong&gt;: read-your-writes, monotonic reads, monotonic writes, writes-follow-reads — гарантии в рамках сессии одного клиента, удобный компромисс.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-нужны-модели-согласованности"&gt;Зачем нужны модели согласованности&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%bd%d1%83%d0%b6%d0%bd%d1%8b-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8-%d1%81%d0%be%d0%b3%d0%bb%d0%b0%d1%81%d0%be%d0%b2%d0%b0%d0%bd%d0%bd%d0%be%d1%81%d1%82%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В одной машине память «просто работает»: записал — прочитал. В распределённой системе с репликами и кэшами «последнее значение» становится неопределённым: чтение с другой реплики может вернуть старое. Модель согласованности формально фиксирует, какие исходы чтений система &lt;strong&gt;обещает не допускать&lt;/strong&gt;. Это нужно, чтобы разработчик мог рассуждать о корректности, не зная деталей репликации.&lt;/p&gt;</description></item><item><title>Нативный fuzzing в Go (1.18+)</title><link>https://go.vbloher.org/docs/04-testing/fuzzing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/04-testing/fuzzing/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Тестирование · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Fuzzing — автоматическая генерация входных данных, нацеленная на расширение покрытия кода (coverage-guided), чтобы найти входы, на которых код паникует, виснет или нарушает инварианты. С Go 1.18 fuzzing встроен в &lt;code&gt;testing&lt;/code&gt;: функция &lt;code&gt;FuzzXxx(f *testing.F)&lt;/code&gt;, seed corpus через &lt;code&gt;f.Add&lt;/code&gt;, тело — &lt;code&gt;f.Fuzz(func(t *testing.T, ...){...})&lt;/code&gt;. Движок мутирует входы, отслеживая покрытие; найденный падающий вход автоматически минимизируется и сохраняется в &lt;code&gt;testdata/fuzz/FuzzXxx/&lt;/code&gt; как регрессионный кейс. Лучше всего ловит панику на парсерах/декодерах, нарушения round-trip инвариантов (encode→decode) и расхождения с reference-реализацией (differential). Запуск: &lt;code&gt;go test -fuzz=FuzzXxx&lt;/code&gt;; без &lt;code&gt;-fuzz&lt;/code&gt; fuzz-тест прогоняет только seed corpus как обычный тест.&lt;/p&gt;</description></item><item><title>Специфика live-coding на Go</title><link>https://go.vbloher.org/docs/12-algorithms/go-specifics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/12-algorithms/go-specifics/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Алгоритмы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;На Go-интервью ценят &lt;strong&gt;идиоматичность&lt;/strong&gt;: &lt;code&gt;slices&lt;/code&gt;/&lt;code&gt;maps&lt;/code&gt;/&lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;map[T]struct{}&lt;/code&gt; как set, multiple return + &lt;code&gt;comma-ok&lt;/code&gt;, ранние возвраты.&lt;/li&gt;
&lt;li&gt;Главные ловушки: &lt;strong&gt;range копирует значение&lt;/strong&gt;, &lt;strong&gt;shared backing array&lt;/strong&gt; при slicing, &lt;strong&gt;байты vs руны&lt;/strong&gt; в строках, &lt;strong&gt;nil map panic&lt;/strong&gt;, &lt;strong&gt;захват переменной цикла в горутине/замыкании&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Знать stdlib: &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;slices&lt;/code&gt; (1.21+), &lt;code&gt;maps&lt;/code&gt;, &lt;code&gt;strings&lt;/code&gt;, &lt;code&gt;strconv&lt;/code&gt;, &lt;code&gt;container/heap&lt;/code&gt;. Не изобретать велосипед.&lt;/li&gt;
&lt;li&gt;Senior отличают: чистый код, обработка edge cases, понимание аллокаций/escape, написанные тесты (table-driven), проговаривание сложности.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="идиоматичные-приёмы-для-алгоритмов"&gt;Идиоматичные приёмы для алгоритмов&lt;a class="anchor" href="#%d0%b8%d0%b4%d0%b8%d0%be%d0%bc%d0%b0%d1%82%d0%b8%d1%87%d0%bd%d1%8b%d0%b5-%d0%bf%d1%80%d0%b8%d1%91%d0%bc%d1%8b-%d0%b4%d0%bb%d1%8f-%d0%b0%d0%bb%d0%b3%d0%be%d1%80%d0%b8%d1%82%d0%bc%d0%be%d0%b2"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// comma-ok для map и type assertion:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// multiple return — норма, не &amp;#34;костыль&amp;#34;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set через map[T]struct{} (0 байт на значение):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// swap без temp:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ранний возврат вместо вложенности:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="слайсы-то-что-спросят"&gt;Слайсы: то, что спросят&lt;a class="anchor" href="#%d1%81%d0%bb%d0%b0%d0%b9%d1%81%d1%8b-%d1%82%d0%be-%d1%87%d1%82%d0%be-%d1%81%d0%bf%d1%80%d0%be%d1%81%d1%8f%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// pre-alloc cap — меньше реаллокаций&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vals&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// распаковка слайса&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// независимая копия (Go 1.21+)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// удаление с сохранением порядка&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// поиск, -1 если нет&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;mx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 2D-слайс — правильная инициализация (каждый ряд отдельно!):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([][]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="map-то-что-спросят"&gt;Map: то, что спросят&lt;a class="anchor" href="#map-%d1%82%d0%be-%d1%87%d1%82%d0%be-%d1%81%d0%bf%d1%80%d0%be%d1%81%d1%8f%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// hint = ожидаемый размер&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// работает даже если k нет (zero value 0)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;absent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// zero value, без panic (для ЧТЕНИЯ)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// детерминированный обход (map рандомизирован!):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// maps пакет (1.21+):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;maps&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// iterator (1.23+)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="строки-и-руны-utf-8"&gt;Строки и руны (UTF-8!)&lt;a class="anchor" href="#%d1%81%d1%82%d1%80%d0%be%d0%ba%d0%b8-%d0%b8-%d1%80%d1%83%d0%bd%d1%8b-utf-8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Строка в Go — неизменяемая последовательность &lt;strong&gt;байтов&lt;/strong&gt; (UTF-8). Индексация &lt;code&gt;s[i]&lt;/code&gt; даёт &lt;strong&gt;байт&lt;/strong&gt;, &lt;code&gt;range&lt;/code&gt; даёт &lt;strong&gt;руны&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>Типовые поведенческие вопросы для Senior</title><link>https://go.vbloher.org/docs/13-behavioral/senior-questions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/13-behavioral/senior-questions/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Behavioral · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Поведенческое интервью проверяет, как ты реально действовал в прошлом, а не как «считаешь правильным». Прошлое поведение — лучший предиктор будущего.&lt;/li&gt;
&lt;li&gt;Используй метод STAR: &lt;strong&gt;S&lt;/strong&gt;ituation → &lt;strong&gt;T&lt;/strong&gt;ask → &lt;strong&gt;R&lt;/strong&gt;esult, через &lt;strong&gt;A&lt;/strong&gt;ction. 70% времени ответа — на Action и Result.&lt;/li&gt;
&lt;li&gt;На senior уровне важны не сами факты, а демонстрация &lt;strong&gt;владения (ownership), влияния (impact) и рефлексии&lt;/strong&gt;: что ты решил, почему, чему научился, что измерил.&lt;/li&gt;
&lt;li&gt;Готовь заранее 6–8 «историй» из опыта и переиспользуй их под разные вопросы. Одна сильная история закрывает 3–4 темы.&lt;/li&gt;
&lt;li&gt;Цифры и trade-off&amp;rsquo;ы продают senior&amp;rsquo;а. «Снизили p99 latency с 1.2s до 180ms» сильнее, чем «улучшили производительность».&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория--подход"&gt;Теория / Подход&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f--%d0%bf%d0%be%d0%b4%d1%85%d0%be%d0%b4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-на-самом-деле-оценивают"&gt;Что на самом деле оценивают&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d0%bd%d0%b0-%d1%81%d0%b0%d0%bc%d0%be%d0%bc-%d0%b4%d0%b5%d0%bb%d0%b5-%d0%be%d1%86%d0%b5%d0%bd%d0%b8%d0%b2%d0%b0%d1%8e%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Поведенческая секция — это не «проверка на адекватность». Интервьюер собирает сигналы по компетенциям, которые у компании прописаны в гайде. Для senior это обычно:&lt;/p&gt;</description></item><item><title>Тюнинг GC: GOGC и GOMEMLIMIT</title><link>https://go.vbloher.org/docs/03-runtime-memory/gogc-gomemlimit/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/gogc-gomemlimit/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;GOGC&lt;/code&gt; (по умолчанию 100) управляет частотой сборки мусора через целевой размер кучи: &lt;code&gt;heap_target = live_heap * (1 + GOGC/100)&lt;/code&gt; — то есть при 100 GC запускается, когда куча удваивается относительно живых данных после предыдущей сборки. &lt;code&gt;GOMEMLIMIT&lt;/code&gt; (Go 1.19+) задаёт мягкий лимит на общую память рантайма и заставляет GC работать чаще по мере приближения к нему, страхуя от OOM. Канонический паттерн для контейнеров — &lt;code&gt;GOGC=off&lt;/code&gt; (или высокий) + &lt;code&gt;GOMEMLIMIT&lt;/code&gt;, установленный на ~90-95% от лимита пода, что даёт минимум GC при нормальной нагрузке и автоматическое уплотнение при пиках. Главная опасность — «death spiral», когда лимит занижен и GC начинает крутиться непрерывно, выжигая CPU.&lt;/p&gt;</description></item><item><title>Уровни изоляции транзакций в PostgreSQL</title><link>https://go.vbloher.org/docs/07-databases/isolation-levels/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/isolation-levels/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Стандарт SQL определяет 4 уровня изоляции: &lt;strong&gt;Read Uncommitted&lt;/strong&gt;, &lt;strong&gt;Read Committed&lt;/strong&gt;, &lt;strong&gt;Repeatable Read&lt;/strong&gt;, &lt;strong&gt;Serializable&lt;/strong&gt; — и описывает их через 3 запрещённые аномалии (dirty read, non-repeatable read, phantom read).&lt;/li&gt;
&lt;li&gt;Стандарт определяет уровни через аномалии, а не через механизм. PostgreSQL реализует их через &lt;strong&gt;MVCC (snapshot isolation)&lt;/strong&gt;, поэтому реальная защита от аномалий &lt;em&gt;строже&lt;/em&gt;, чем минимально требует стандарт.&lt;/li&gt;
&lt;li&gt;В PostgreSQL фактически &lt;strong&gt;три&lt;/strong&gt; уровня: &lt;code&gt;READ UNCOMMITTED&lt;/code&gt; ведёт себя как &lt;code&gt;READ COMMITTED&lt;/code&gt; (грязного чтения не существует никогда — MVCC отдаёт только закоммиченные версии строк).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read Committed&lt;/strong&gt; (дефолт) — снимок берётся на каждый отдельный statement. Защищает от dirty read, но допускает non-repeatable read, phantom read и lost update между statements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeatable Read&lt;/strong&gt; в PG — это полноценный &lt;strong&gt;snapshot isolation&lt;/strong&gt;: один снимок на всю транзакцию. Предотвращает dirty/non-repeatable/phantom read (в отличие от стандарта, где RR допускает phantom). Но допускает &lt;strong&gt;write skew&lt;/strong&gt; и при конфликте на запись бросает &lt;code&gt;40001&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serializable&lt;/strong&gt; в PG реализован через &lt;strong&gt;SSI (Serializable Snapshot Isolation)&lt;/strong&gt;: отслеживает зависимости чтения/записи через predicate locks (SIReadLock) и откатывает транзакции с &lt;code&gt;serialization_failure&lt;/code&gt; (SQLSTATE &lt;code&gt;40001&lt;/code&gt;), нарушающие сериализуемость, включая write skew.&lt;/li&gt;
&lt;li&gt;На уровнях RR и Serializable код &lt;strong&gt;обязан&lt;/strong&gt; иметь retry-цикл на &lt;code&gt;40001&lt;/code&gt; (и &lt;code&gt;40P01&lt;/code&gt; deadlock). Без него приложение будет случайно падать под нагрузкой.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="стандарт-sql-уровни-через-аномалии"&gt;Стандарт SQL: уровни через аномалии&lt;a class="anchor" href="#%d1%81%d1%82%d0%b0%d0%bd%d0%b4%d0%b0%d1%80%d1%82-sql-%d1%83%d1%80%d0%be%d0%b2%d0%bd%d0%b8-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-%d0%b0%d0%bd%d0%be%d0%bc%d0%b0%d0%bb%d0%b8%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Стандарт ANSI/ISO SQL не описывает &lt;em&gt;как&lt;/em&gt; СУБД должна изолировать транзакции, а только &lt;em&gt;какие аномалии запрещены&lt;/em&gt; на каждом уровне. Аномалии:&lt;/p&gt;</description></item><item><title>GOMAXPROCS: параллелизм планировщика и проблема контейнеров</title><link>https://go.vbloher.org/docs/03-runtime-memory/gomaxprocs/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/gomaxprocs/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;GOMAXPROCS&lt;/code&gt; задаёт число &lt;code&gt;P&lt;/code&gt; (logical processors) в планировщике Go — максимальное количество горутин, выполняющихся на CPU параллельно. Исторически дефолт равнялся числа CPU хоста (&lt;code&gt;runtime.NumCPU()&lt;/code&gt;), что в контейнерах с cgroup-лимитом приводило к oversubscription: Go видит, скажем, 64 ядра хоста, а квота — 2 ядра, отсюда лишние context switches, раздутый GC и хвостовая латентность. Решали это библиотекой &lt;code&gt;automaxprocs&lt;/code&gt; от Uber; в Go 1.25 runtime стал cgroup-aware и читает CPU-квоту автоматически, периодически её обновляя.&lt;/p&gt;</description></item><item><title>Kubernetes для Go-разработчика</title><link>https://go.vbloher.org/docs/11-devops/kubernetes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/11-devops/kubernetes/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: DevOps · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pod&lt;/strong&gt; — минимальная единица деплоя (1+ контейнеров с общими network namespace и volumes). Поды эфемерны и заменяемы.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment&lt;/strong&gt; управляет ReplicaSet&amp;rsquo;ами, обеспечивает rolling update и self-healing. &lt;strong&gt;Service&lt;/strong&gt; даёт стабильный виртуальный IP/DNS и L4-балансировку поверх меняющихся подов. &lt;strong&gt;Ingress&lt;/strong&gt; — L7-роутинг (host/path) снаружи.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ConfigMap&lt;/strong&gt; — некритичная конфигурация, &lt;strong&gt;Secret&lt;/strong&gt; — чувствительные данные (base64, не шифрование по умолчанию). Монтируются как env или файлы.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probes&lt;/strong&gt;: liveness (перезапуск зависшего), readiness (убрать из балансировки, не убивая), startup (для медленного старта, отключает остальные пока не пройдёт).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;requests&lt;/strong&gt; — гарантированный минимум для планировщика, &lt;strong&gt;limits&lt;/strong&gt; — потолок. CPU limit троттлит, memory limit → OOMKill.&lt;/li&gt;
&lt;li&gt;Главная Go-проблема: &lt;strong&gt;GOMAXPROCS не видит cgroup-лимит CPU&lt;/strong&gt; — по умолчанию берёт число ядер ноды, что вызывает throttling. Решение: &lt;code&gt;automaxprocs&lt;/code&gt; или Go 1.25+ (cgroup-aware runtime). Аналогично &lt;code&gt;GOMEMLIMIT&lt;/code&gt; под memory limit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graceful shutdown&lt;/strong&gt;: SIGTERM → дренаж readiness → дослужить запросы → exit до истечения &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt;. &lt;code&gt;preStop&lt;/code&gt; хук для синхронизации с балансировщиком.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="базовые-объекты"&gt;Базовые объекты&lt;a class="anchor" href="#%d0%b1%d0%b0%d0%b7%d0%be%d0%b2%d1%8b%d0%b5-%d0%be%d0%b1%d1%8a%d0%b5%d0%ba%d1%82%d1%8b"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Pod&lt;/strong&gt; — один или несколько контейнеров, разделяющих сетевой namespace (один IP, общий localhost) и volumes. В norm-практике 1 контейнер на под + sidecar&amp;rsquo;ы (логи, прокси). Под не переживает перезапуск ноды — его пересоздаёт контроллер.&lt;/p&gt;</description></item><item><title>Middleware-паттерн в Go</title><link>https://go.vbloher.org/docs/05-backend/middleware/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/middleware/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Middleware&lt;/strong&gt; в идиоматическом Go — это функция &lt;code&gt;func(http.Handler) http.Handler&lt;/code&gt;: она принимает следующий хендлер, оборачивает его и возвращает новый &lt;code&gt;http.Handler&lt;/code&gt;. Это декоратор поверх интерфейса &lt;code&gt;http.Handler&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Цепочка&lt;/strong&gt; строится композицией: &lt;code&gt;Chain(m1, m2, m3)(final)&lt;/code&gt;. Внешний middleware оборачивает внутренний. На входе порядок прямой (m1 → m2 → m3 → handler), на выходе — &lt;strong&gt;LIFO&lt;/strong&gt; (handler → m3 → m2 → m1), потому что код после &lt;code&gt;next.ServeHTTP&lt;/code&gt; выполняется при разворачивании стека.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Захват status code&lt;/strong&gt; требует обёртки &lt;code&gt;http.ResponseWriter&lt;/code&gt;, т.к. интерфейс не отдаёт код наружу. Наивная обёртка &lt;strong&gt;теряет&lt;/strong&gt; &lt;code&gt;http.Flusher&lt;/code&gt;, &lt;code&gt;http.Hijacker&lt;/code&gt;, &lt;code&gt;http.Pusher&lt;/code&gt; — это классический баг.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Порядок критичен&lt;/strong&gt;: &lt;code&gt;recovery&lt;/code&gt; — самым снаружи (ловит панику из всех остальных), &lt;code&gt;CORS&lt;/code&gt; — рано (чтобы preflight отработал до auth), &lt;code&gt;auth&lt;/code&gt; — до бизнес-логики, &lt;code&gt;rate limit&lt;/code&gt; — обычно до тяжёлой работы.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best practices&lt;/strong&gt;: middleware должны быть дешёвыми, не блокировать, идемпотентными по эффекту, передавать данные через &lt;code&gt;context.Context&lt;/code&gt; (не через мутацию структур), не хардкодить зависимости (внедрять через замыкание/конструктор).&lt;/li&gt;
&lt;li&gt;В &lt;code&gt;net/http&lt;/code&gt; 1.22+ роутер &lt;code&gt;http.ServeMux&lt;/code&gt; умеет методы и path-параметры, но &lt;strong&gt;не&lt;/strong&gt; имеет встроенной поддержки middleware-цепочек — оборачивать &lt;code&gt;mux&lt;/code&gt; целиком или использовать &lt;code&gt;chi&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-каноническое-определение"&gt;1. Каноническое определение&lt;a class="anchor" href="#1-%d0%ba%d0%b0%d0%bd%d0%be%d0%bd%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%be%d0%b5-%d0%be%d0%bf%d1%80%d0%b5%d0%b4%d0%b5%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Middleware — это декоратор над &lt;code&gt;http.Handler&lt;/code&gt;. Базовая сигнатура:&lt;/p&gt;</description></item><item><title>MVCC в PostgreSQL: версии строк, видимость, VACUUM и bloat</title><link>https://go.vbloher.org/docs/07-databases/mvcc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/mvcc/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL использует &lt;strong&gt;MVCC&lt;/strong&gt; (Multi-Version Concurrency Control): каждое изменение строки создаёт &lt;strong&gt;новую физическую версию&lt;/strong&gt; (tuple), а старая помечается «мёртвой», но физически остаётся на странице до очистки.&lt;/li&gt;
&lt;li&gt;Видимость версий определяется &lt;strong&gt;системными полями&lt;/strong&gt; &lt;code&gt;xmin&lt;/code&gt; (транзакция-создатель), &lt;code&gt;xmax&lt;/code&gt; (транзакция-удалитель), &lt;code&gt;cmin/cmax&lt;/code&gt; (command id внутри транзакции) и &lt;strong&gt;снапшотом&lt;/strong&gt; транзакции (&lt;code&gt;xmin&lt;/code&gt;, &lt;code&gt;xmax&lt;/code&gt;, список активных &lt;code&gt;xip&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Главное следствие: &lt;strong&gt;читатели не блокируют писателей, а писатели не блокируют читателей&lt;/strong&gt; — в отличие от блокировочников (2PL, MySQL MyISAM с table-level locks).&lt;/li&gt;
&lt;li&gt;Цена MVCC — &lt;strong&gt;dead tuples&lt;/strong&gt; и &lt;strong&gt;bloat&lt;/strong&gt; (раздувание таблиц/индексов). Их убирает &lt;strong&gt;VACUUM&lt;/strong&gt; (помечает место как переиспользуемое + freezing против wraparound) и &lt;strong&gt;autovacuum&lt;/strong&gt; (автоматически).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VACUUM&lt;/code&gt; обычный не отдаёт место ОС и не блокирует; &lt;code&gt;VACUUM FULL&lt;/code&gt; переписывает таблицу (&lt;code&gt;ACCESS EXCLUSIVE&lt;/code&gt; lock), отдаёт место, но кладёт нагрузку. Для онлайн-дефрагментации — &lt;strong&gt;pg_repack&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;32-битный &lt;code&gt;xid&lt;/code&gt; (&lt;code&gt;2^31&lt;/code&gt; «прошлых» транзакций) требует &lt;strong&gt;freezing&lt;/strong&gt;, иначе &lt;strong&gt;transaction id wraparound&lt;/strong&gt; = катастрофа. &lt;strong&gt;HOT updates&lt;/strong&gt; снижают bloat индексов.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-нужен-mvcc-блокировочники-vs-многоверсионники"&gt;Зачем нужен MVCC: блокировочники vs многоверсионники&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%bd%d1%83%d0%b6%d0%b5%d0%bd-mvcc-%d0%b1%d0%bb%d0%be%d0%ba%d0%b8%d1%80%d0%be%d0%b2%d0%be%d1%87%d0%bd%d0%b8%d0%ba%d0%b8-vs-%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d0%b2%d0%b5%d1%80%d1%81%d0%b8%d0%be%d0%bd%d0%bd%d0%b8%d0%ba%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В классических блокировочных СУБД (двухфазная блокировка, 2PL) согласованность чтения обеспечивается &lt;strong&gt;блокировками&lt;/strong&gt;: транзакция, читающая строку, ставит shared-lock, что мешает писателям; писатель ставит exclusive-lock, что мешает читателям. Крайний случай — &lt;strong&gt;MySQL MyISAM&lt;/strong&gt;, где блокировки уровня &lt;strong&gt;таблицы&lt;/strong&gt;: один писатель останавливает всех.&lt;/p&gt;</description></item><item><title>Order Service</title><link>https://go.vbloher.org/docs/10-system-design/order-service/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/order-service/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Order Service — это «оркестратор истины» о заказе: он владеет состоянием заказа (state machine), координирует резервирование товара (inventory), оплату (payment) и доставку (shipping). Главная сложность — не CRUD, а &lt;strong&gt;согласованность между сервисами без распределённой транзакции 2PC&lt;/strong&gt;. Решение: state machine + saga (orchestration) + outbox + идемпотентность + reserve-with-TTL для inventory. Ключевые враги: oversell на hot SKU, дубли заказов при ретраях, «застрявшие» саги, и UX поверх eventual consistency. На senior+ копают именно границы консистентности: где вы допускаете рассинхрон, как его чините (reconciliation), и почему не выбрали 2PC.&lt;/p&gt;</description></item><item><title>SLI / SLO / SLA</title><link>https://go.vbloher.org/docs/09-observability/slo-sli/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/09-observability/slo-sli/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Observability · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SLI (Service Level Indicator)&lt;/strong&gt; — измеримая метрика поведения сервиса с точки зрения пользователя: доля успешных запросов, доля быстрых запросов, свежесть данных. Формула почти всегда &lt;code&gt;good events / valid events&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SLO (Service Level Objective)&lt;/strong&gt; — целевое значение SLI на окне времени: «99.9% запросов за 30 дней успешны». Внутренняя цель команды.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SLA (Service Level Agreement)&lt;/strong&gt; — юридический/контрактный обязательство перед клиентом с компенсациями (refund, credits). SLA обычно слабее SLO (если SLO=99.9%, SLA=99.5%), чтобы у команды был запас.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error budget&lt;/strong&gt; = &lt;code&gt;1 − SLO&lt;/code&gt;. При SLO 99.9% бюджет ошибок = 0.1% запросов за окно. Бюджет — это разрешённое количество «плохого», которое можно тратить на риск (релизы, эксперименты).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Burn rate&lt;/strong&gt; — скорость расходования бюджета. Алертить надо НЕ на «упал один запрос», а на скорость прожигания бюджета через &lt;strong&gt;multi-window multi-burn-rate&lt;/strong&gt; алерты (быстрый + медленный burn). Это даёт высокий precision и recall и убирает шум.&lt;/li&gt;
&lt;li&gt;SLI выбирают по принципу: метрика должна отражать опыт пользователя, быть измеримой на границе, где пользователь «видит» сервис, и иметь чёткий критерий good/bad.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-вообще-slo-а-не-аптайм-100"&gt;Зачем вообще SLO, а не «аптайм 100%»&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%b2%d0%be%d0%be%d0%b1%d1%89%d0%b5-slo-%d0%b0-%d0%bd%d0%b5-%d0%b0%d0%bf%d1%82%d0%b0%d0%b9%d0%bc-100"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;100% надёжности — недостижимая и вредная цель: каждый «девятка» удорожает систему экспоненциально, а пользователь всё равно ограничен надёжностью своего Wi-Fi, DNS, браузера. SLO — это инженерный компромисс: формальное согласие команды и бизнеса о том, какой уровень ненадёжности приемлем. Из этого согласия рождается &lt;strong&gt;error budget&lt;/strong&gt; — единый язык для конфликта «фичи vs стабильность». Пока бюджет есть — катим релизы; бюджет исчерпан — фриз фич и работа над надёжностью.&lt;/p&gt;</description></item><item><title>TLS: handshake, сертификаты, mTLS, производительность</title><link>https://go.vbloher.org/docs/06-networking/tls/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/06-networking/tls/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Сети и протоколы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TLS даёт конфиденциальность (шифрование), целостность (MAC/AEAD) и аутентификацию (сертификаты) поверх TCP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS 1.3&lt;/strong&gt; — 1-RTT handshake (vs 2-RTT в 1.2), 0-RTT на возобновлении, выкинул небезопасные шифры (RSA key exchange, CBC, RC4), всегда forward secrecy (ECDHE).&lt;/li&gt;
&lt;li&gt;Доверие — через цепочку сертификатов до доверенного корневого CA; сервер шлёт leaf + промежуточные, клиент проверяет до корня.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SNI&lt;/strong&gt; позволяет хостить много доменов на одном IP (выбор сертификата); &lt;strong&gt;ALPN&lt;/strong&gt; согласует протокол (h2/h3/http1.1) в рамках handshake.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mTLS&lt;/strong&gt; — взаимная аутентификация: клиент тоже предъявляет сертификат (zero-trust, service mesh).&lt;/li&gt;
&lt;li&gt;В Go всё в &lt;code&gt;crypto/tls&lt;/code&gt;: &lt;code&gt;tls.Config&lt;/code&gt;, &lt;code&gt;tls.Certificate&lt;/code&gt;, &lt;code&gt;RootCAs&lt;/code&gt;, &lt;code&gt;ClientCAs&lt;/code&gt;, &lt;code&gt;ServerName&lt;/code&gt;, &lt;code&gt;NextProtos&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-даёт-tls"&gt;Что даёт TLS&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d0%b4%d0%b0%d1%91%d1%82-tls"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Конфиденциальность&lt;/strong&gt; — симметричное шифрование сессии (AES-GCM, ChaCha20-Poly1305).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Целостность&lt;/strong&gt; — AEAD (шифрование + аутентификация в одном).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Аутентификация&lt;/strong&gt; — проверка сертификата сервера (и опц. клиента) через PKI.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="handshake-tls-12-vs-13"&gt;Handshake: TLS 1.2 vs 1.3&lt;a class="anchor" href="#handshake-tls-12-vs-13"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;TLS 1.2 (2-RTT):&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Гарантии доставки сообщений: at-most-once / at-least-once / exactly-once</title><link>https://go.vbloher.org/docs/08-distributed-systems/delivery-guarantees/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/delivery-guarantees/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Гарантии доставки определяются тем, &lt;strong&gt;когда отправляется ack&lt;/strong&gt;: до обработки (at-most-once, риск потери) или после (at-least-once, риск дублей).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;at-most-once&lt;/strong&gt; — каждое сообщение доставляется 0 или 1 раз (никогда не дублируется, но может потеряться).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;at-least-once&lt;/strong&gt; — 1+ раз (никогда не теряется, но возможны дубли). Это дефолт большинства брокеров.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;exactly-once delivery&lt;/strong&gt; — миф. Нельзя атомарно «доставить сообщение и зафиксировать ack» через ненадёжную сеть (проблема двух генералов, FLP-невозможность консенсуса в асинхронной модели).&lt;/li&gt;
&lt;li&gt;То, что в индустрии называют exactly-once, на практике есть &lt;strong&gt;effectively-once / exactly-once processing&lt;/strong&gt; = at-least-once delivery + &lt;strong&gt;идемпотентность&lt;/strong&gt; + &lt;strong&gt;дедупликация&lt;/strong&gt; (dedup по message id, idempotent consumer).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka EOS&lt;/strong&gt; реализует это инженерно: idempotent producer (PID + sequence number), транзакции (&lt;code&gt;transactional.id&lt;/code&gt;, atomic read-process-write), &lt;code&gt;isolation.level=read_committed&lt;/code&gt; на консьюмере.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2PC&lt;/strong&gt; даёт атомарность распределённого commit, но это &lt;strong&gt;блокирующий&lt;/strong&gt; протокол: отказ координатора в фазе uncertainty подвешивает участников с захваченными локами. &lt;strong&gt;3PC&lt;/strong&gt; добавляет pre-commit (неблокирующий), но ломается при network partition.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-вообще-гарантируется-и-где"&gt;Что вообще гарантируется и где&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d0%b2%d0%be%d0%be%d0%b1%d1%89%d0%b5-%d0%b3%d0%b0%d1%80%d0%b0%d0%bd%d1%82%d0%b8%d1%80%d1%83%d0%b5%d1%82%d1%81%d1%8f-%d0%b8-%d0%b3%d0%b4%d0%b5"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;«Гарантия доставки» — характеристика контракта между отправителем (producer), брокером/каналом и получателем (consumer). Важно различать три уровня:&lt;/p&gt;</description></item><item><title>Интеграционные тесты, testcontainers-go, TestMain</title><link>https://go.vbloher.org/docs/04-testing/integration-testcontainers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/04-testing/integration-testcontainers/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Тестирование · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Интеграционные тесты проверяют код против &lt;strong&gt;реальных&lt;/strong&gt; зависимостей (БД, брокеры, кэши), а не моков. &lt;code&gt;testcontainers-go&lt;/code&gt; поднимает эти зависимости в Docker-контейнерах на лету: тест получает реальный Postgres/Redis/Kafka с динамическим портом, использует его и уничтожает контейнер по завершении — без ручного docker-compose и без общей «тестовой» БД. &lt;code&gt;TestMain(m *testing.M)&lt;/code&gt; даёт единую точку setup/teardown на весь пакет (один контейнер на пакет — баланс скорости и изоляции). Медленные/требующие Docker тесты отделяют от unit-тестов через &lt;strong&gt;build tags&lt;/strong&gt; (&lt;code&gt;//go:build integration&lt;/code&gt;) или &lt;code&gt;testing.Short()&lt;/code&gt;, чтобы &lt;code&gt;go test ./...&lt;/code&gt; оставался быстрым, а интеграционные шли отдельным шагом CI. Изоляция между тестами — через отдельные схемы/БД/префиксы, транзакции с откатом или truncate.&lt;/p&gt;</description></item><item><title>Интерфейсы в Go</title><link>https://go.vbloher.org/docs/01-core-go/interfaces/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/interfaces/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Интерфейс в Go — это «толстый указатель» из двух слов: дескриптор типа и указатель на данные. Непустой интерфейс представлен структурой &lt;code&gt;iface&lt;/code&gt; (с таблицей методов &lt;code&gt;itab&lt;/code&gt;), пустой — &lt;code&gt;eface&lt;/code&gt;. Главная ловушка senior-уровня: интерфейс с конкретным типом, но nil-значением внутри — НЕ равен nil. Удовлетворение интерфейса проверяется статически и структурно (duck typing).&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="внутреннее-устройство-eface-и-iface"&gt;Внутреннее устройство: eface и iface&lt;a class="anchor" href="#%d0%b2%d0%bd%d1%83%d1%82%d1%80%d0%b5%d0%bd%d0%bd%d0%b5%d0%b5-%d1%83%d1%81%d1%82%d1%80%d0%be%d0%b9%d1%81%d1%82%d0%b2%d0%be-eface-%d0%b8-iface"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Любая переменная интерфейсного типа в рантайме — это структура из двух машинных слов (на amd64 — 16 байт).&lt;/p&gt;</description></item><item><title>Утечки горутин, дедлоки, livelock, starvation</title><link>https://go.vbloher.org/docs/02-concurrency/common-leaks-deadlocks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/common-leaks-deadlocks/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Главные болезни конкурентного Go: &lt;strong&gt;deadlock&lt;/strong&gt; (взаимная блокировка — никто не двигается), &lt;strong&gt;livelock&lt;/strong&gt; (горутины активны, но прогресса нет), &lt;strong&gt;starvation&lt;/strong&gt; (горутина не получает ресурс), и &lt;strong&gt;goroutine leak&lt;/strong&gt; (горутина навсегда заблокирована и не собирается GC). Чаще всего утечки происходят из-за каналов: отправка без получателя, чтение из канала, который никто не закроет, отсутствие ветки &lt;code&gt;ctx.Done()&lt;/code&gt;. Диагностика — &lt;code&gt;runtime.NumGoroutine&lt;/code&gt;, goroutine-профиль pprof, дамп стеков (SIGQUIT), &lt;code&gt;go test -race&lt;/code&gt; и встроенный детектор полного дедлока.&lt;/p&gt;</description></item><item><title>Eventual Consistency</title><link>https://go.vbloher.org/docs/08-distributed-systems/eventual-consistency/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/eventual-consistency/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Eventual consistency — гарантия, что при отсутствии новых записей все реплики &lt;strong&gt;в конце концов&lt;/strong&gt; сойдутся к одному значению. Это слабая модель: нет гарантий, &lt;em&gt;когда&lt;/em&gt; сойдутся и &lt;em&gt;что&lt;/em&gt; прочитает клиент в промежутке. Приемлема там, где временное расхождение не ломает бизнес (лайки, счётчики просмотров, ленты, профили), и &lt;strong&gt;опасна&lt;/strong&gt; там, где нужна линеаризуемость/сильная согласованность (деньги, остатки на складе, уникальность) — там нужны транзакции, кворумы или CRDT с подходящей семантикой. Ключевые механизмы: разрешение конфликтов (&lt;strong&gt;LWW&lt;/strong&gt; — просто, но теряет данные при clock skew; &lt;strong&gt;vector clocks / version vectors&lt;/strong&gt; — детектят causality и concurrent; &lt;strong&gt;CRDT&lt;/strong&gt; — структуры, конфликты которых разрешаются автоматически и детерминированно), и репликация/восстановление (&lt;strong&gt;read repair&lt;/strong&gt;, &lt;strong&gt;anti-entropy&lt;/strong&gt; через &lt;strong&gt;Merkle trees&lt;/strong&gt; и &lt;strong&gt;gossip&lt;/strong&gt;, &lt;strong&gt;hinted handoff&lt;/strong&gt; в стиле Dynamo).&lt;/p&gt;</description></item><item><title>net/http: Server, Handler, ServeMux, таймауты, Client и контекст</title><link>https://go.vbloher.org/docs/05-backend/net-http/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/net-http/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http.Server&lt;/code&gt; — это конфигурация сервера; &lt;code&gt;ListenAndServe&lt;/code&gt; запускает accept-loop, на &lt;strong&gt;каждое соединение создаётся отдельная горутина&lt;/strong&gt; (&lt;code&gt;conn.serve&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Handler&lt;/code&gt; — интерфейс с единственным методом &lt;code&gt;ServeHTTP(w, r)&lt;/code&gt;. &lt;code&gt;HandlerFunc&lt;/code&gt; — адаптер, превращающий обычную функцию в &lt;code&gt;Handler&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;В &lt;strong&gt;Go 1.22&lt;/strong&gt; &lt;code&gt;ServeMux&lt;/code&gt; научился методам и wildcard-паттернам: &lt;code&gt;&amp;quot;GET /items/{id}&amp;quot;&lt;/code&gt;, &lt;code&gt;{path...}&lt;/code&gt;, доступ через &lt;code&gt;r.PathValue(&amp;quot;id&amp;quot;)&lt;/code&gt;, есть детерминированный приоритет специфичности и паника при конфликтах.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Таймауты — обязательны в проде.&lt;/strong&gt; Дефолтный &lt;code&gt;http.Server&lt;/code&gt; без таймаутов уязвим к Slowloris (медленные клиенты держат соединения вечно). Минимум: &lt;code&gt;ReadHeaderTimeout&lt;/code&gt;, &lt;code&gt;ReadTimeout&lt;/code&gt;, &lt;code&gt;WriteTimeout&lt;/code&gt;, &lt;code&gt;IdleTimeout&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http.Client&lt;/code&gt; и &lt;code&gt;http.Transport&lt;/code&gt; потокобезопасны и &lt;strong&gt;переиспользуют&lt;/strong&gt; TCP/TLS-соединения через пул. Создавать клиент на каждый запрос — антипаттерн (утечка соединений и портов). Всегда &lt;code&gt;defer resp.Body.Close()&lt;/code&gt; и вычитывать тело до конца.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r.Context()&lt;/code&gt; отменяется при разрыве клиентского соединения и при истечении server-таймаутов. &lt;code&gt;context.WithValue&lt;/code&gt; — только для request-scoped данных, не для DI и не для опциональных параметров.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="httpserver-поля-и-accept-loop"&gt;http.Server: поля и accept loop&lt;a class="anchor" href="#httpserver-%d0%bf%d0%be%d0%bb%d1%8f-%d0%b8-accept-loop"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;http.ListenAndServe(addr, handler)&lt;/code&gt; — это удобная обёртка, которая создаёт &lt;code&gt;&amp;amp;http.Server{Addr: addr, Handler: handler}&lt;/code&gt; &lt;strong&gt;без единого таймаута&lt;/strong&gt;. В проде так делать нельзя — нужно конфигурировать &lt;code&gt;http.Server&lt;/code&gt; явно.&lt;/p&gt;</description></item><item><title>Payment Service</title><link>https://go.vbloher.org/docs/10-system-design/payment-service/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/payment-service/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Платёжный сервис — это система, где &lt;strong&gt;корректность важнее доступности&lt;/strong&gt;, а потеря или дублирование денег недопустимы. Три кита:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Идемпотентность&lt;/strong&gt; — клиент шлёт &lt;code&gt;Idempotency-Key&lt;/code&gt;, сервер гарантирует, что повтор запроса (ретрай, таймаут, двойной клик) не спишет деньги дважды. Реализуется через уникальный индекс в БД.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Double-entry ledger&lt;/strong&gt; — деньги не «хранятся» как баланс, а выводятся из неизменяемого (append-only) журнала проводок debit/credit, где сумма всех проводок транзакции равна нулю.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transactional outbox + CDC&lt;/strong&gt; — события («платёж проведён») публикуются в брокер строго на основе закоммиченного состояния БД, что даёт &lt;em&gt;exactly-once effect&lt;/em&gt; поверх &lt;em&gt;at-least-once delivery&lt;/em&gt; брокера.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;«Exactly-once деньги» — это не магия брокера, а &lt;strong&gt;at-least-once доставка + идемпотентные обработчики&lt;/strong&gt;. Деньги всегда в целочисленных minor units (копейки/центы), никогда во &lt;code&gt;float&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>sync.Cond</title><link>https://go.vbloher.org/docs/02-concurrency/cond/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/cond/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sync.Cond&lt;/code&gt; — условная переменная: горутина под захваченным мьютексом вызывает &lt;code&gt;Wait&lt;/code&gt;, чтобы заснуть до тех пор, пока другая горутина не вызовет &lt;code&gt;Signal&lt;/code&gt; (разбудить одну) или &lt;code&gt;Broadcast&lt;/code&gt; (разбудить всех). Нужна редко: почти всё решается каналами. Реальная ниша — широковещательное пробуждение многих ожидающих по изменению &lt;strong&gt;разделяемого состояния&lt;/strong&gt;, где каналы неудобны. &lt;code&gt;Wait&lt;/code&gt; &lt;strong&gt;обязан&lt;/strong&gt; вызываться в цикле проверки условия из-за spurious-подобных пробуждений и гонок.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="контракт"&gt;Контракт&lt;a class="anchor" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewCond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Ожидающая сторона&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ВСЕГДА в цикле, не if&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// атомарно: Unlock(L) + park; при пробуждении: Lock(L)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Сигнализирующая сторона&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;changeState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// или c.Broadcast()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Wait()&lt;/code&gt; атомарно отпускает мьютекс и паркует горутину; при пробуждении &lt;strong&gt;снова захватывает&lt;/strong&gt; мьютекс перед возвратом. Поэтому Wait вызывается строго под Lock.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Signal()&lt;/code&gt; будит &lt;strong&gt;одну&lt;/strong&gt; ожидающую горутину (если есть).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Broadcast()&lt;/code&gt; будит &lt;strong&gt;все&lt;/strong&gt; ожидающие горутины.&lt;/li&gt;
&lt;li&gt;Сигнал/Broadcast можно вызывать с захваченным мьютексом или без — но менять защищаемое состояние нужно под мьютексом.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="почему-for-а-не-if"&gt;Почему &lt;code&gt;for&lt;/code&gt;, а не &lt;code&gt;if&lt;/code&gt;&lt;a class="anchor" href="#%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-for-%d0%b0-%d0%bd%d0%b5-if"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;После пробуждения и повторного захвата мьютекса условие &lt;strong&gt;может уже не выполняться&lt;/strong&gt;:&lt;/p&gt;</description></item><item><title>Terraform / Infrastructure as Code</title><link>https://go.vbloher.org/docs/11-devops/terraform/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/11-devops/terraform/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: DevOps · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IaC&lt;/strong&gt; — инфраструктура описана кодом, версионируется в git, применяется детерминированно. Уходим от ручного «кликанья в консоли» (snowflake-серверов) к воспроизводимым, ревьюимым изменениям.&lt;/li&gt;
&lt;li&gt;Terraform &lt;strong&gt;декларативен&lt;/strong&gt;: вы описываете &lt;em&gt;желаемое состояние&lt;/em&gt;, движок сам считает дельту между ним, текущим состоянием (state) и реальностью (refresh) и применяет минимальный набор изменений.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State&lt;/strong&gt; — JSON-файл, сопоставляющий ресурсы в коде с реальными объектами в облаке (по ID). Без него Terraform не знает, что уже создано. Хранить &lt;strong&gt;удалённо&lt;/strong&gt; (S3+DynamoDB, GCS, Terraform Cloud) с &lt;strong&gt;блокировкой&lt;/strong&gt; (lock), иначе два одновременных apply испортят инфраструктуру.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Цикл&lt;/strong&gt;: &lt;code&gt;init&lt;/code&gt; (плагины+бэкенд) → &lt;code&gt;plan&lt;/code&gt; (dry-run, показывает дельту) → &lt;code&gt;apply&lt;/code&gt; (применяет). &lt;code&gt;plan&lt;/code&gt; обязателен в review.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Providers&lt;/strong&gt; — плагины к API (aws, google, kubernetes). &lt;strong&gt;Modules&lt;/strong&gt; — переиспользуемые наборы ресурсов с input/output. &lt;strong&gt;Drift&lt;/strong&gt; — расхождение реальности и state (кто-то поменял руками).&lt;/li&gt;
&lt;li&gt;Best practices: remote state с локом, маленькие изолированные state&amp;rsquo;ы, модули, отсутствие секретов в state/коде в открытом виде, &lt;code&gt;plan&lt;/code&gt; в CI, иммутабельность.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="декларативность-и-граф-зависимостей"&gt;Декларативность и граф зависимостей&lt;a class="anchor" href="#%d0%b4%d0%b5%d0%ba%d0%bb%d0%b0%d1%80%d0%b0%d1%82%d0%b8%d0%b2%d0%bd%d0%be%d1%81%d1%82%d1%8c-%d0%b8-%d0%b3%d1%80%d0%b0%d1%84-%d0%b7%d0%b0%d0%b2%d0%b8%d1%81%d0%b8%d0%bc%d0%be%d1%81%d1%82%d0%b5%d0%b9"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Вы пишете, &lt;em&gt;что должно быть&lt;/em&gt;, а не &lt;em&gt;как это сделать&lt;/em&gt; (в отличие от императивных скриптов). Terraform строит &lt;strong&gt;граф зависимостей&lt;/strong&gt; ресурсов (по ссылкам &lt;code&gt;resource.attr&lt;/code&gt;) и применяет их в правильном порядке, параллеля независимые.&lt;/p&gt;</description></item><item><title>UDP и надёжность поверх UDP</title><link>https://go.vbloher.org/docs/06-networking/udp/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/06-networking/udp/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Сети и протоколы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;UDP — простой датаграммный транспорт: нет соединения, нет гарантий доставки/порядка, нет flow/congestion control. Только порты + контрольная сумма.&lt;/li&gt;
&lt;li&gt;Плюсы: минимальная латентность и накладные расходы, отсутствие head-of-line blocking, broadcast/multicast, контроль над семантикой надёжности на стороне приложения.&lt;/li&gt;
&lt;li&gt;Применять: DNS, DHCP, NTP, VoIP/видео (RTP), realtime-игры, метрики (statsd), service discovery, QUIC.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Надёжность поверх UDP&amp;rdquo; = QUIC (HTTP/3), а также кастомные протоколы (ACK, ретрансмиты, FEC, sequence numbers) — вы переносите логику TCP в userspace.&lt;/li&gt;
&lt;li&gt;В Go: &lt;code&gt;net.UDPConn&lt;/code&gt;, &lt;code&gt;ReadFromUDP/WriteToUDP&lt;/code&gt;, &lt;code&gt;net.ListenPacket&lt;/code&gt;. Один сокет обслуживает множество &amp;ldquo;пиров&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="udp-в-двух-словах"&gt;UDP в двух словах&lt;a class="anchor" href="#udp-%d0%b2-%d0%b4%d0%b2%d1%83%d1%85-%d1%81%d0%bb%d0%be%d0%b2%d0%b0%d1%85"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Заголовок UDP — всего 8 байт: source port, dest port, length, checksum. Никакого состояния. Каждый &lt;code&gt;WriteToUDP&lt;/code&gt; = одна датаграмма = (обычно) один IP-пакет.&lt;/p&gt;</description></item><item><title>Моки, стабы и тестируемость</title><link>https://go.vbloher.org/docs/04-testing/mocks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/04-testing/mocks/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Тестирование · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;В Go тестируемость строится на &lt;strong&gt;интерфейсах&lt;/strong&gt;, объявленных на стороне потребителя (узких, в 1-3 метода), и &lt;strong&gt;dependency injection&lt;/strong&gt; через конструкторы/поля структуры. Терминология: &lt;strong&gt;стаб&lt;/strong&gt; возвращает заранее заданные ответы; &lt;strong&gt;мок&lt;/strong&gt; дополнительно проверяет, как и сколько раз его вызвали (verify expectations); &lt;strong&gt;fake&lt;/strong&gt; — упрощённая рабочая реализация (in-memory БД); &lt;strong&gt;spy&lt;/strong&gt; — записывает вызовы для последующей проверки. Варианты: ручные моки (явные, читаемые, без зависимостей — идиоматичный дефолт для узких интерфейсов), &lt;code&gt;gomock&lt;/code&gt; (кодогенерация, строгие expectations, контроль порядка), &lt;code&gt;testify/mock&lt;/code&gt; (рефлексия, гибко, но рантайм-ошибки и многословно). Принцип Go: «accept interfaces, return structs», интерфейс определяет тот, кто его использует, а не тот, кто реализует.&lt;/p&gt;</description></item><item><title>Обзор NoSQL и Redis</title><link>https://go.vbloher.org/docs/07-databases/nosql-redis/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/nosql-redis/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NoSQL&lt;/strong&gt; — это не «отсутствие схемы», а класс хранилищ с разными моделями данных, оптимизированных под конкретные паттерны доступа: Key-Value (Redis, DynamoDB), Document (MongoDB), Wide-column (Cassandra, HBase), Graph (Neo4j). Выбор движется от паттернов запросов, а не от данных.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL vs NoSQL&lt;/strong&gt; — не «или-или». SQL даёт строгую схему, JOIN-ы, ACID, нормализацию; NoSQL — горизонтальное масштабирование, гибкую схему, денормализацию под чтение. По &lt;strong&gt;CAP&lt;/strong&gt; при сетевом разделении (P неизбежно) выбираешь между C и A; многие NoSQL — это &lt;strong&gt;AP&lt;/strong&gt; с &lt;strong&gt;BASE&lt;/strong&gt;-семантикой (eventual consistency), но это конфигурируемо (Cassandra tunable consistency, DynamoDB strongly consistent reads).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt; — однопоточное (для исполнения команд) in-memory хранилище структур данных. Сила не в «кэше», а в богатых структурах: String, List, Hash, Set, ZSET, Bitmap, HyperLogLog, Stream, geo. Атомарность каждой команды — следствие single-threaded модели.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Персистентность&lt;/strong&gt;: RDB (снапшоты, компактно, риск потери данных между снапшотами), AOF (журнал команд, durability до &lt;code&gt;appendfsync everysec&lt;/code&gt;/&lt;code&gt;always&lt;/code&gt;), гибрид (RDB-преамбула + AOF-хвост) — дефолт в Redis 7.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Кэширование&lt;/strong&gt;: cache-aside (самый частый), write-through/write-back; проблемы — thundering herd / cache stampede (защита: lock на пересчёт, jitter в TTL, early recomputation), stale data / invalidation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distributed lock&lt;/strong&gt;: &lt;code&gt;SET key val NX PX ttl&lt;/code&gt; + unlock через Lua (проверка владельца). Redlock спорен (критика Martin Kleppmann — fencing token нужен всё равно); для строгих гарантий не полагайся только на TTL-lock.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt;: token bucket / sliding window log / sliding window counter на Redis + Lua для атомарности.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Масштабирование&lt;/strong&gt;: репликация (async, replica может отставать), Redis Cluster (16384 хеш-слота, без multi-key операций между слотами без hash tags). Eviction: &lt;code&gt;allkeys-lru&lt;/code&gt;, &lt;code&gt;allkeys-lfu&lt;/code&gt;, &lt;code&gt;volatile-*&lt;/code&gt;, &lt;code&gt;noeviction&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="часть-1-nosql"&gt;Часть 1. NoSQL&lt;a class="anchor" href="#%d1%87%d0%b0%d1%81%d1%82%d1%8c-1-nosql"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id="что-такое-nosql-и-зачем-он-появился"&gt;Что такое NoSQL и зачем он появился&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-nosql-%d0%b8-%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%be%d0%bd-%d0%bf%d0%be%d1%8f%d0%b2%d0%b8%d0%bb%d1%81%d1%8f"&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Реляционные СУБД оптимизированы под нормализованные данные, произвольные запросы (ad-hoc) и строгую консистентность на одном узле. Проблемы возникают при:&lt;/p&gt;</description></item><item><title>Структурированное логирование (slog)</title><link>https://go.vbloher.org/docs/09-observability/structured-logging/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/09-observability/structured-logging/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Observability · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Структурированное логирование = логи как набор &lt;strong&gt;key-value&lt;/strong&gt; (обычно JSON), а не текстовая строка → машинно-парсятся, фильтруются и агрегируются в Loki/ELK по полям. В Go 1.21+ это стандарт &lt;code&gt;log/slog&lt;/code&gt;: &lt;code&gt;Logger&lt;/code&gt; → &lt;code&gt;Handler&lt;/code&gt; (&lt;code&gt;JSONHandler&lt;/code&gt;/&lt;code&gt;TextHandler&lt;/code&gt;/кастомный) → &lt;code&gt;Record&lt;/code&gt; из &lt;code&gt;Attr&lt;/code&gt;. Уровни через &lt;code&gt;slog.LevelVar&lt;/code&gt; можно менять &lt;strong&gt;в рантайме без рестарта&lt;/strong&gt;. Контекстные поля добавляются через &lt;code&gt;With&lt;/code&gt;/&lt;code&gt;WithGroup&lt;/code&gt;. Ключевая senior-практика — &lt;strong&gt;корреляция&lt;/strong&gt;: кастомный Handler достаёт &lt;code&gt;trace_id&lt;/code&gt;/&lt;code&gt;span_id&lt;/code&gt; из &lt;code&gt;context.Context&lt;/code&gt; (OTel SpanContext) и пишет в каждую запись через &lt;code&gt;InfoContext&lt;/code&gt;. &lt;strong&gt;PII не логировать&lt;/strong&gt; (пароли, токены, карты, email) — маскировать через &lt;code&gt;LogValuer&lt;/code&gt; или &lt;code&gt;ReplaceAttr&lt;/code&gt;. При высоком RPS — &lt;strong&gt;sampling/rate-limiting логов&lt;/strong&gt;. Производительность: &lt;code&gt;slog&lt;/code&gt; имеет zero-alloc путь при использовании типизированных &lt;code&gt;Attr&lt;/code&gt; и ленивых &lt;code&gt;LogValuer&lt;/code&gt;, плюс проверку &lt;code&gt;Enabled&lt;/code&gt;, чтобы не вычислять дорогие поля при отключённом уровне.&lt;/p&gt;</description></item><item><title>Устройство map в Go</title><link>https://go.vbloher.org/docs/01-core-go/maps/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/maps/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;map&lt;/code&gt; в Go — это хэш-таблица с разрешением коллизий методом цепочек через «бакеты», где каждый бакет хранит до 8 пар key/value. Таблица растёт инкрементально (эвакуация бакетов происходит порциями при записи/удалении), порядок итерации намеренно рандомизирован, map не потокобезопасна (конкурентная запись приводит к &lt;strong&gt;нерекаверабельному&lt;/strong&gt; &lt;code&gt;fatal error&lt;/code&gt;), и нельзя брать адрес элемента, потому что реаллокация при росте инвалидирует указатели. В Go 1.24 классическая реализация заменена на Swiss Tables.&lt;/p&gt;</description></item><item><title>Утечки горутин (goroutine leaks)</title><link>https://go.vbloher.org/docs/03-runtime-memory/goroutine-leaks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/goroutine-leaks/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Утечка горутины — это горутина, которая никогда не завершается и не освобождается рантаймом, потому что навсегда заблокирована на канале/мьютексе/системном вызове или крутится в бесконечном цикле без условия выхода. Каждая утёкшая горутина удерживает свой стек (минимум 2 КБ, растёт сегментами) плюс всё, что захвачено через замыкание, поэтому утечки проявляются как медленный рост памяти и &lt;code&gt;runtime.NumGoroutine()&lt;/code&gt;. Главные инструменты диагностики — pprof goroutine profile и дамп по &lt;code&gt;SIGQUIT&lt;/code&gt;/&lt;code&gt;debug=2&lt;/code&gt;; главная профилактика — &lt;code&gt;context.Context&lt;/code&gt;, &lt;code&gt;errgroup&lt;/code&gt; и чёткий контракт «кто закрывает канал».&lt;/p&gt;</description></item><item><title>context</title><link>https://go.vbloher.org/docs/02-concurrency/context/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/context/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;context.Context&lt;/code&gt; переносит сигнал отмены, дедлайн и request-scoped значения сквозь границы вызовов и горутин. Корни — &lt;code&gt;Background()&lt;/code&gt;/&lt;code&gt;TODO()&lt;/code&gt;; производные — &lt;code&gt;WithCancel&lt;/code&gt;/&lt;code&gt;WithTimeout&lt;/code&gt;/&lt;code&gt;WithDeadline&lt;/code&gt;/&lt;code&gt;WithValue&lt;/code&gt;. Отмена распространяется по дереву контекстов вниз; всегда вызывайте &lt;code&gt;cancel()&lt;/code&gt; (обычно через &lt;code&gt;defer&lt;/code&gt;), чтобы не текли ресурсы.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="иерархия-и-корни"&gt;Иерархия и корни&lt;a class="anchor" href="#%d0%b8%d0%b5%d1%80%d0%b0%d1%80%d1%85%d0%b8%d1%8f-%d0%b8-%d0%ba%d0%be%d1%80%d0%bd%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;context.Background()&lt;/code&gt; — пустой корень для main, init, тестов; никогда не отменяется.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;context.TODO()&lt;/code&gt; — заглушка, когда непонятно, какой контекст передать; семантически = Background, но сигнализирует «надо доделать».&lt;/li&gt;
&lt;li&gt;Производные образуют &lt;strong&gt;дерево&lt;/strong&gt;: отмена/дедлайн родителя каскадно отменяет детей.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ОБЯЗАТЕЛЬНО: освобождает горутину/таймер и удаляет из дерева&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// без cancel&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="интерфейс"&gt;Интерфейс&lt;a class="anchor" href="#%d0%b8%d0%bd%d1%82%d0%b5%d1%80%d1%84%d0%b5%d0%b9%d1%81"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Deadline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// закрывается при отмене/дедлайне&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// nil | Canceled | DeadlineExceeded&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Done()&lt;/code&gt; возвращает канал, который &lt;strong&gt;закрывается&lt;/strong&gt; (broadcast) при отмене. &lt;code&gt;Err()&lt;/code&gt; после закрытия даёт причину: &lt;code&gt;context.Canceled&lt;/code&gt; или &lt;code&gt;context.DeadlineExceeded&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Distributed Tracing</title><link>https://go.vbloher.org/docs/09-observability/tracing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/09-observability/tracing/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Observability · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Distributed tracing восстанавливает путь &lt;strong&gt;одного запроса&lt;/strong&gt; через множество сервисов как дерево/DAG &lt;strong&gt;спанов&lt;/strong&gt;. Trace = набор спанов с общим &lt;code&gt;trace_id&lt;/code&gt;; span = одна операция (start/end/attributes/events/status/kind), ссылающаяся на родителя. Связь между сервисами держится на &lt;strong&gt;context propagation&lt;/strong&gt;: W3C &lt;code&gt;traceparent&lt;/code&gt; (= &lt;code&gt;version-trace_id-span_id-flags&lt;/code&gt;) инжектится в исходящие заголовки и извлекается на входе. &lt;strong&gt;Sampling&lt;/strong&gt; решает, какие трейсы хранить: &lt;strong&gt;head-based&lt;/strong&gt; (решение на старте, дёшево, но может пропустить редкие ошибки) или &lt;strong&gt;tail-based&lt;/strong&gt; (решение после завершения трейса в коллекторе — можно отобрать по latency/error, но дорого по памяти/буферизации). Чтение трейса = поиск critical path, gaps (сеть/блокировки/GC), fan-out и N+1. Корреляция: &lt;code&gt;trace_id&lt;/code&gt; в логах, exemplars из метрик в трейс. Senior-грабли: разрыв контекста на &lt;code&gt;context.Background()&lt;/code&gt;, потеря спана в горутине без &lt;code&gt;ctx&lt;/code&gt;, clock skew, и &lt;code&gt;RecordError&lt;/code&gt; ≠ &lt;code&gt;SetStatus&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>OAuth2: роли, grant types, OIDC, токены и типовые ошибки</title><link>https://go.vbloher.org/docs/05-backend/oauth2/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/oauth2/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OAuth2 — это про авторизацию (делегированный доступ), а НЕ про аутентификацию.&lt;/strong&gt; Он отвечает на вопрос «можно ли этому клиенту делать X от имени пользователя», а не «кто этот пользователь». Аутентификацию поверх OAuth2 даёт &lt;strong&gt;OIDC&lt;/strong&gt; (OpenID Connect) через &lt;code&gt;id_token&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 роли:&lt;/strong&gt; Resource Owner (пользователь), Client (приложение), Authorization Server (выдаёт токены), Resource Server (API, проверяет токены).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Основной flow сегодня — Authorization Code + PKCE&lt;/strong&gt; для всех типов клиентов (web, SPA, mobile, desktop). &lt;strong&gt;Client Credentials&lt;/strong&gt; — для machine-to-machine без пользователя. &lt;strong&gt;Refresh Token grant&lt;/strong&gt; — для обновления access token без повторного логина.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deprecated:&lt;/strong&gt; Implicit (утечка токена через URL/Referer, отсутствие refresh) и Resource Owner Password Credentials (anti-pattern — клиент видит пароль пользователя).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;access token&lt;/strong&gt; — короткоживущий (минуты), bearer, передаётся в API. &lt;strong&gt;refresh token&lt;/strong&gt; — долгоживущий, хранится максимально защищённо, обменивается на новый access token.&lt;/li&gt;
&lt;li&gt;Безопасность держится на: &lt;code&gt;state&lt;/code&gt; (CSRF), &lt;code&gt;PKCE&lt;/code&gt; (перехват кода), строгая валидация &lt;code&gt;redirect_uri&lt;/code&gt; (open redirect), проверка &lt;code&gt;aud&lt;/code&gt;/&lt;code&gt;iss&lt;/code&gt;/&lt;code&gt;exp&lt;/code&gt;/подписи токена на Resource Server.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-oauth2--аутентификация"&gt;1. OAuth2 ≠ аутентификация&lt;a class="anchor" href="#1-oauth2--%d0%b0%d1%83%d1%82%d0%b5%d0%bd%d1%82%d0%b8%d1%84%d0%b8%d0%ba%d0%b0%d1%86%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Самая частая концептуальная ошибка. OAuth2 (RFC 6749) спроектирован как протокол &lt;strong&gt;делегированной авторизации&lt;/strong&gt;: ресурс-овнер разрешает третьему приложению (клиенту) доступ к своим ресурсам на Resource Server &lt;strong&gt;без передачи пароля&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>panic / recover: механика, раскрутка стека и runtime-паники</title><link>https://go.vbloher.org/docs/01-core-go/panic-recover/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/panic-recover/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;panic&lt;/code&gt; начинает раскрутку стека текущей горутины, по пути выполняя зарегистрированные &lt;code&gt;defer&lt;/code&gt;. &lt;code&gt;recover&lt;/code&gt; останавливает раскрутку и возвращает значение паники, но &lt;strong&gt;работает только при прямом вызове из отложенной функции&lt;/strong&gt; — иначе возвращает &lt;code&gt;nil&lt;/code&gt; и эффекта не имеет. Паника в любой горутине, не пойманная её собственным &lt;code&gt;defer&lt;/code&gt;/&lt;code&gt;recover&lt;/code&gt;, убивает весь процесс — поймать её из другой горутины нельзя. Часть runtime-ошибок (&lt;code&gt;nil deref&lt;/code&gt;, &lt;code&gt;index out of range&lt;/code&gt;) — это обычные паники и их можно восстановить; часть (&lt;code&gt;concurrent map writes&lt;/code&gt;, &lt;code&gt;deadlock&lt;/code&gt;, нехватка памяти) — это fatal errors и они принципиально не recoverable.&lt;/p&gt;</description></item><item><title>Rate Limiter</title><link>https://go.vbloher.org/docs/10-system-design/rate-limiter/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/rate-limiter/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rate Limiter ограничивает число запросов от клиента (по ключу: user_id, API-key, IP) за единицу времени. Базовый ответ при превышении — &lt;code&gt;HTTP 429 Too Many Requests&lt;/code&gt; с &lt;code&gt;Retry-After&lt;/code&gt;. Ключевой выбор — алгоритм (точность vs память) и место размещения (gateway/middleware/sidecar). В распределённой системе состояние обычно держат в Redis с атомарными операциями через Lua-скрипты; для снижения нагрузки на Redis применяют гибрид &lt;strong&gt;local + global&lt;/strong&gt; (локальный токен-бакет на ноде + периодическая синхронизация).&lt;/p&gt;</description></item><item><title>Table-driven тесты, subtests и параллельность</title><link>https://go.vbloher.org/docs/04-testing/table-driven/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/04-testing/table-driven/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Тестирование · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Table-driven тесты — идиоматичный для Go способ покрыть множество входов/выходов одной логикой проверки: срез структур-кейсов + цикл. &lt;code&gt;t.Run&lt;/code&gt; создаёт subtests — изолированные узлы дерева тестов с собственным именем, фильтрацией (&lt;code&gt;-run Foo/case_x&lt;/code&gt;) и независимым fail. &lt;code&gt;t.Parallel()&lt;/code&gt; помечает тест как параллельный: он приостанавливается, пока не завершатся последовательные тесты родителя, затем запускается одновременно с другими параллельными. Главная историческая ловушка — захват переменной цикла замыканием (&lt;code&gt;tc&lt;/code&gt;); в Go &amp;lt; 1.22 это давало все кейсы с последним значением. В 1.22+ переменная цикла per-iteration, ловушка ушла, но на собеседовании про неё всё равно спрашивают.&lt;/p&gt;</description></item><item><title>WebSocket: upgrade, фреймы, масштабирование</title><link>https://go.vbloher.org/docs/06-networking/websocket/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/06-networking/websocket/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Сети и протоколы · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;WebSocket (RFC 6455) — полнодуплексный двунаправленный канал поверх одного TCP, устанавливается HTTP Upgrade-рукопожатием, затем переходит на бинарный фрейм-протокол.&lt;/li&gt;
&lt;li&gt;Использует тот же порт (80/443), проходит прокси/файрволлы как HTTP; после upgrade — это уже не HTTP.&lt;/li&gt;
&lt;li&gt;Фреймы: text/binary/ping/pong/close + fragmentation; клиент обязан &lt;strong&gt;маскировать&lt;/strong&gt; payload.&lt;/li&gt;
&lt;li&gt;Выбирать вместо polling/SSE, когда нужна низкая латентность И двунаправленность (чат, игры, коллаборация). SSE достаточно для server→client односторонних потоков.&lt;/li&gt;
&lt;li&gt;Масштабирование — главная боль: stateful соединения, sticky-сессии или pub/sub-бэкплейн (Redis/NATS/Kafka), лимиты на FD/память.&lt;/li&gt;
&lt;li&gt;В Go: &lt;code&gt;gorilla/websocket&lt;/code&gt; (де-факто, но архивный) и &lt;code&gt;coder/websocket&lt;/code&gt; (бывш. nhooyr.io/websocket, контекст-aware, рекомендуемый).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="установка-http-upgrade"&gt;Установка: HTTP Upgrade&lt;a class="anchor" href="#%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-http-upgrade"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat # опц. субпротоколы

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # SHA1(key+GUID), base64&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;После &lt;code&gt;101&lt;/code&gt; соединение перестаёт быть HTTP — дальше бинарные WS-фреймы.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sec-WebSocket-Accept&lt;/code&gt; = base64(SHA1(client_key + magic_GUID)) — подтверждает, что сервер понял WS (защита от случайного апгрейда кешами).&lt;/li&gt;
&lt;li&gt;Поверх TLS — это &lt;code&gt;wss://&lt;/code&gt; (рекомендуется всегда).&lt;/li&gt;
&lt;li&gt;HTTP/2 имеет своё расширение для WS (RFC 8441, CONNECT), но классика — поверх h1.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="структура-фрейма"&gt;Структура фрейма&lt;a class="anchor" href="#%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%82%d1%83%d1%80%d0%b0-%d1%84%d1%80%d0%b5%d0%b9%d0%bc%d0%b0"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FIN&lt;/strong&gt; бит (последний фрейм сообщения) + &lt;strong&gt;opcode&lt;/strong&gt;: 0x1 text, 0x2 binary, 0x8 close, 0x9 ping, 0xA pong, 0x0 continuation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MASK&lt;/strong&gt; бит + 32-битный masking key: &lt;strong&gt;клиент → сервер фреймы ОБЯЗАНЫ быть замаскированы&lt;/strong&gt; (XOR), сервер → клиент — нет. Это защита от cache-poisoning через прокси.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payload length&lt;/strong&gt;: 7 / 7+16 / 7+64 бит (расширяемая длина).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragmentation&lt;/strong&gt;: большое сообщение можно слать частями (FIN=0 у промежуточных).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="pingpong-keep-alive-на-уровне-ws"&gt;Ping/Pong (keep-alive на уровне WS)&lt;a class="anchor" href="#pingpong-keep-alive-%d0%bd%d0%b0-%d1%83%d1%80%d0%be%d0%b2%d0%bd%d0%b5-ws"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Control-фреймы &lt;code&gt;ping&lt;/code&gt;/&lt;code&gt;pong&lt;/code&gt; для проверки живости и удержания соединения (NAT, прокси-таймауты).&lt;/li&gt;
&lt;li&gt;На &lt;code&gt;ping&lt;/code&gt; peer обязан ответить &lt;code&gt;pong&lt;/code&gt; (с тем же payload).&lt;/li&gt;
&lt;li&gt;Сервер обычно периодически шлёт ping и закрывает соединение, если нет pong в дедлайн → детект мёртвых клиентов. TCP keep-alive здесь недостаточно надёжен/быстр.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="websocket-vs-sse-vs-polling"&gt;WebSocket vs SSE vs Polling&lt;a class="anchor" href="#websocket-vs-sse-vs-polling"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Long Polling&lt;/th&gt;
					&lt;th&gt;SSE&lt;/th&gt;
					&lt;th&gt;WebSocket&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Направление&lt;/td&gt;
					&lt;td&gt;client↔server (через переоткрытие)&lt;/td&gt;
					&lt;td&gt;server→client&lt;/td&gt;
					&lt;td&gt;полный дуплекс&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Транспорт&lt;/td&gt;
					&lt;td&gt;HTTP&lt;/td&gt;
					&lt;td&gt;HTTP (text/event-stream)&lt;/td&gt;
					&lt;td&gt;TCP после upgrade&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Латентность&lt;/td&gt;
					&lt;td&gt;высокая&lt;/td&gt;
					&lt;td&gt;низкая&lt;/td&gt;
					&lt;td&gt;низкая&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Авто-reconnect&lt;/td&gt;
					&lt;td&gt;вручную&lt;/td&gt;
					&lt;td&gt;встроен (Last-Event-ID)&lt;/td&gt;
					&lt;td&gt;вручную&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Бинарные данные&lt;/td&gt;
					&lt;td&gt;да&lt;/td&gt;
					&lt;td&gt;нет (только текст/UTF-8)&lt;/td&gt;
					&lt;td&gt;да&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Через HTTP/2 мультиплекс&lt;/td&gt;
					&lt;td&gt;да&lt;/td&gt;
					&lt;td&gt;да&lt;/td&gt;
					&lt;td&gt;сложнее&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Сложность/инфра&lt;/td&gt;
					&lt;td&gt;низкая&lt;/td&gt;
					&lt;td&gt;низкая&lt;/td&gt;
					&lt;td&gt;высокая (stateful)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Правило: нужен только server→client (нотификации, прогресс, котировки) — бери &lt;strong&gt;SSE&lt;/strong&gt; (проще, авто-reconnect, обычный HTTP). Нужен дуплекс/низкая латентность в обе стороны (чат, игры, совместное редактирование) — &lt;strong&gt;WebSocket&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>Идемпотентность в распределённых системах</title><link>https://go.vbloher.org/docs/08-distributed-systems/idempotency/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/idempotency/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Идемпотентность&lt;/strong&gt; — повторное выполнение операции с теми же параметрами даёт тот же результат и те же побочные эффекты, что и однократное. &lt;code&gt;f(f(x)) == f(x)&lt;/code&gt; на уровне состояния системы.&lt;/li&gt;
&lt;li&gt;Нужна потому, что сеть ненадёжна: клиент не знает, дошёл ли запрос. Любая система с &lt;strong&gt;ретраями&lt;/strong&gt; и доставкой &lt;strong&gt;at-least-once&lt;/strong&gt; обязана быть идемпотентной, иначе дубли (двойные списания, двойные заказы).&lt;/li&gt;
&lt;li&gt;HTTP: &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt; идемпотентны &lt;em&gt;по семантике спецификации&lt;/em&gt;; &lt;code&gt;POST&lt;/code&gt; и &lt;code&gt;PATCH&lt;/code&gt; — нет. Спецификация не гарантирует реализацию.&lt;/li&gt;
&lt;li&gt;Практика для не-идемпотентных операций: &lt;strong&gt;idempotency key&lt;/strong&gt; — клиент генерирует уникальный ключ, сервер хранит &lt;code&gt;(key → результат, статус)&lt;/code&gt; и при повторе отдаёт сохранённый ответ вместо повторного выполнения.&lt;/li&gt;
&lt;li&gt;Самое сложное — &lt;strong&gt;конкурентные запросы с одним ключом&lt;/strong&gt; (in-flight). Решается через unique constraint или distributed lock + конечный автомат статусов &lt;code&gt;processing → completed/failed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;На уровне БД идемпотентность достигается через &lt;code&gt;UPSERT&lt;/code&gt;, &lt;code&gt;INSERT ... ON CONFLICT&lt;/code&gt;, unique constraints, &lt;code&gt;INSERT ... WHERE NOT EXISTS&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-нужна-идемпотентность"&gt;Зачем нужна идемпотентность&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%bd%d1%83%d0%b6%d0%bd%d0%b0-%d0%b8%d0%b4%d0%b5%d0%bc%d0%bf%d0%be%d1%82%d0%b5%d0%bd%d1%82%d0%bd%d0%be%d1%81%d1%82%d1%8c"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В распределённой системе невозможно отличить «запрос не дошёл» от «ответ не дошёл». Это фундаментальная проблема &lt;strong&gt;two generals&lt;/strong&gt;: клиент отправил &lt;code&gt;POST /payments&lt;/code&gt;, получил таймаут. Что произошло?&lt;/p&gt;</description></item><item><title>Партиционирование таблиц в PostgreSQL</title><link>https://go.vbloher.org/docs/07-databases/partitioning/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/partitioning/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Декларативное партиционирование&lt;/strong&gt; (&lt;code&gt;PARTITION BY&lt;/code&gt;, появилось в PG 10, доработано в 11–14) разбивает одну логическую таблицу на физические партиции по ключу. Три стратегии: &lt;strong&gt;RANGE&lt;/strong&gt; (диапазоны, чаще всего для time-series), &lt;strong&gt;LIST&lt;/strong&gt; (явные списки значений), &lt;strong&gt;HASH&lt;/strong&gt; (равномерное распределение по остатку хеша).&lt;/li&gt;
&lt;li&gt;Главная выгода — &lt;strong&gt;управление жизненным циклом данных&lt;/strong&gt;: удаление старых данных через &lt;code&gt;DROP TABLE&lt;/code&gt;/&lt;code&gt;DETACH PARTITION&lt;/code&gt; (метаданные, мгновенно) вместо &lt;code&gt;DELETE&lt;/code&gt; (миллионы строк, dead tuples, нагрузка на vacuum). Плюс &lt;strong&gt;partition pruning&lt;/strong&gt; — планировщик отсекает ненужные партиции и сканирует только релевантные.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partition pruning&lt;/strong&gt; работает только если ключ партиционирования присутствует в &lt;code&gt;WHERE&lt;/code&gt; (или вычислим из условий). Есть pruning на этапе планирования (constraint exclusion / новый механизм) и &lt;strong&gt;runtime pruning&lt;/strong&gt; для параметров prepared statements и nested loop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ключевое ограничение&lt;/strong&gt;: любой &lt;code&gt;PRIMARY KEY&lt;/code&gt; / &lt;code&gt;UNIQUE&lt;/code&gt; обязан включать колонки ключа партиционирования. Глобальных уникальных индексов нет. FK на партиционированную таблицу можно (с PG 12), FK из партиционированной — тоже.&lt;/li&gt;
&lt;li&gt;Партиционирование — &lt;strong&gt;не про &amp;ldquo;ускорить любой запрос&amp;rdquo;&lt;/strong&gt;, а про обслуживаемость больших таблиц, локальность горячих данных и дешёвое удаление. Запросы без ключа партиционирования могут стать &lt;strong&gt;медленнее&lt;/strong&gt; (скан всех партиций + накладные расходы планировщика).&lt;/li&gt;
&lt;li&gt;Старое &lt;strong&gt;наследование (table inheritance) + триггеры/CHECK&lt;/strong&gt; — legacy. Декларативное партиционирование почти всегда предпочтительнее; для автоматизации создания партиций используют &lt;strong&gt;pg_partman&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-такое-партиционирование-и-зачем-оно"&gt;Что такое партиционирование и зачем оно&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-%d0%bf%d0%b0%d1%80%d1%82%d0%b8%d1%86%d0%b8%d0%be%d0%bd%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d0%b8-%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%be%d0%bd%d0%be"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Партиционированная таблица — это «фасад»: у неё нет собственного хранилища, данные физически лежат в &lt;strong&gt;партициях&lt;/strong&gt; (отдельных таблицах). Маршрутизация строки в нужную партицию происходит по &lt;strong&gt;ключу партиционирования&lt;/strong&gt; на стороне сервера автоматически при &lt;code&gt;INSERT&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Утечки памяти в Go (несмотря на GC)</title><link>https://go.vbloher.org/docs/03-runtime-memory/memory-leaks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/memory-leaks/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Garbage collector освобождает только то, на что &lt;strong&gt;нет живых ссылок&lt;/strong&gt; — поэтому утечка в Go — это всегда «случайно удерживаемая ссылка» или незакрытый ресурс, а не ошибка аллокатора. Классика: растущие глобальные мапы, под-слайсы, держащие весь backing array, незакрытые &lt;code&gt;response.Body&lt;/code&gt;/файлы, &lt;code&gt;time.Ticker&lt;/code&gt;/&lt;code&gt;time.After&lt;/code&gt; в циклах, кэши без эвикции, горутины-зомби. Ищут через &lt;code&gt;pprof&lt;/code&gt; heap (&lt;code&gt;inuse_space&lt;/code&gt; vs &lt;code&gt;alloc_space&lt;/code&gt;), diff профилей и &lt;code&gt;runtime.MemStats&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="почему-gc-не-спасает"&gt;Почему GC не спасает&lt;a class="anchor" href="#%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-gc-%d0%bd%d0%b5-%d1%81%d0%bf%d0%b0%d1%81%d0%b0%d0%b5%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Go GC — tracing mark-and-sweep: живо то, что достижимо от корней (стеки горутин, глобальные переменные, регистры). Объект собирается, только когда на него &lt;strong&gt;нет ни одной ссылки&lt;/strong&gt;. «Утечка» — это логически ненужный объект, который остаётся достижимым: его кто-то держит. Дополнительно есть феномен «память освобождена для рантайма, но не возвращена ОС» (RSS остаётся высоким) — это не утечка в строгом смысле, но выглядит похоже.&lt;/p&gt;</description></item><item><title>Apache Kafka</title><link>https://go.vbloher.org/docs/08-distributed-systems/kafka/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/kafka/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Kafka — это распределённый, реплицируемый, persistent &lt;strong&gt;commit log&lt;/strong&gt;, а не классическая очередь. Сообщения не удаляются после чтения; консьюмеры читают по своему оффсету (pull-модель).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Топик → партиции&lt;/strong&gt;. Партиция — единица параллелизма и единственная единица упорядочивания. Глобального порядка между партициями нет.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consumer group&lt;/strong&gt;: одна партиция в конкретный момент обрабатывается ровно одним консьюмером группы. Параллелизм группы ограничен числом партиций.&lt;/li&gt;
&lt;li&gt;Гарантии порядка — только внутри партиции; ключ сообщения определяет партицию (&lt;code&gt;hash(key) % partitions&lt;/code&gt;), поэтому одинаковый ключ → одна партиция → порядок.&lt;/li&gt;
&lt;li&gt;Delivery: по умолчанию &lt;strong&gt;at-least-once&lt;/strong&gt;. &lt;strong&gt;Exactly-once&lt;/strong&gt; (EOS) достигается idempotent producer + transactions + &lt;code&gt;read_committed&lt;/code&gt;. At-most-once — если коммитить оффсет до обработки.&lt;/li&gt;
&lt;li&gt;Надёжность: &lt;code&gt;acks=all&lt;/code&gt; + &lt;code&gt;min.insync.replicas&amp;gt;=2&lt;/code&gt; + &lt;code&gt;replication.factor&amp;gt;=3&lt;/code&gt;. ISR — синхронные реплики.&lt;/li&gt;
&lt;li&gt;Ребалансировка: eager (stop-the-world) vs cooperative/incremental (KIP-429). Ретеншн: по времени/размеру или &lt;strong&gt;log compaction&lt;/strong&gt; (хранит последнее значение на ключ).&lt;/li&gt;
&lt;li&gt;Go-клиенты: &lt;strong&gt;franz-go&lt;/strong&gt; (предпочтительно, нативный, поддержка свежих KIP, EOS) vs &lt;strong&gt;sarama&lt;/strong&gt; (зрелый, но тяжелее и с историей багов).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="лог-как-структура-данных"&gt;Лог как структура данных&lt;a class="anchor" href="#%d0%bb%d0%be%d0%b3-%d0%ba%d0%b0%d0%ba-%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%82%d1%83%d1%80%d0%b0-%d0%b4%d0%b0%d0%bd%d0%bd%d1%8b%d1%85"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Партиция — это append-only лог сегментов на диске. Каждое сообщение получает монотонный &lt;strong&gt;offset&lt;/strong&gt; (int64) — порядковый номер в партиции. Это центральная идея Kafka: брокер не отслеживает, кто что прочитал, он лишь хранит лог и текущие границы.&lt;/p&gt;</description></item><item><title>OpenAPI/Swagger, code generation, contract-first vs code-first, валидация</title><link>https://go.vbloher.org/docs/05-backend/openapi/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/openapi/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenAPI&lt;/strong&gt; — машиночитаемая спецификация (контракт) HTTP/REST API в YAML/JSON. До версии 3.0 называлась &lt;strong&gt;Swagger&lt;/strong&gt; (2.0 == &amp;ldquo;Swagger 2.0&amp;rdquo;). Swagger сейчас — это набор инструментов (Swagger UI, Swagger Editor, Codegen) от SmartBear, а сам формат — OpenAPI Specification (OAS) под управлением OpenAPI Initiative (Linux Foundation).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Версии:&lt;/strong&gt; 2.0 (2014, Swagger) → 3.0 (2017, переработана структура: &lt;code&gt;components&lt;/code&gt;, &lt;code&gt;requestBody&lt;/code&gt;, &lt;code&gt;servers&lt;/code&gt;) → 3.1 (2021, &lt;strong&gt;полная совместимость с JSON Schema Draft 2020-12&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Два подхода:&lt;/strong&gt; &lt;strong&gt;contract-first (design-first)&lt;/strong&gt; — сначала пишем спеку, из неё генерим типы/сервер/клиент (&lt;code&gt;oapi-codegen&lt;/code&gt;); &lt;strong&gt;code-first&lt;/strong&gt; — пишем Go-код с аннотациями, из него генерим спеку (&lt;code&gt;swaggo/swag&lt;/code&gt;). Для контракта между командами/фронтом промышленный стандарт — contract-first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Спека = source of truth.&lt;/strong&gt; Контракт между бэкендом, фронтом, мобайлом, внешними потребителями. Версионируется в git, линтуется в CI (&lt;code&gt;spectral&lt;/code&gt;, &lt;code&gt;vacuum&lt;/code&gt;), на breaking changes проверяется (&lt;code&gt;oasdiff&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Валидация:&lt;/strong&gt; статическая (линт спеки в CI) + рантайм (middleware на базе &lt;code&gt;kin-openapi&lt;/code&gt; валидирует request/response против схемы).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-такое-openapi"&gt;Что такое OpenAPI&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-openapi"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;OpenAPI Specification (OAS) — формальное, языконезависимое описание HTTP API. Описывает: эндпоинты, методы, параметры, тела запросов/ответов, схемы данных, аутентификацию, примеры. Главная ценность — &lt;strong&gt;единый машиночитаемый контракт&lt;/strong&gt;, из которого можно:&lt;/p&gt;</description></item><item><title>URL Shortener</title><link>https://go.vbloher.org/docs/10-system-design/url-shortener/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/10-system-design/url-shortener/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: System Design · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Сервис превращает длинный URL в короткий ключ (&lt;code&gt;tiny.cc/aZ3kP9&lt;/code&gt;) и редиректит обратно. Это классический &lt;strong&gt;read-heavy&lt;/strong&gt; сервис с соотношением чтения к записи ~100:1, где главные проблемы не в редиректе как таковом, а в:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Генерации уникальных коротких ключей&lt;/strong&gt; без коллизий и без single point of contention. Два рабочих подхода: &lt;code&gt;hash(url) + truncate&lt;/code&gt; (просто, но коллизии) и &lt;code&gt;counter + base62&lt;/code&gt; (без коллизий, но нужен распределённый монотонный счётчик).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Редиректе 301 vs 302&lt;/strong&gt; — это не косметика: выбор напрямую ломает или сохраняет аналитику и нагрузку на бэкенд.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Латентности чтения&lt;/strong&gt; — путь редиректа должен быть &lt;code&gt;O(1)&lt;/code&gt; lookup из кэша/CDN, в идеале не доходя до основной БД.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Senior-ответ строится вокруг трёх осей: как раздать диапазоны ключей между инстансами без гонок, почему 302 (а не 301) при наличии аналитики, и как удержать p99 редиректа в единицах миллисекунд при масштабе.&lt;/p&gt;</description></item><item><title>Архитектура PostgreSQL</title><link>https://go.vbloher.org/docs/07-databases/postgresql-architecture/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/postgresql-architecture/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL — это &lt;strong&gt;процессная&lt;/strong&gt; (process-per-connection) СУБД, а не потоковая. Родительский процесс &lt;code&gt;postmaster&lt;/code&gt; форкает по одному backend-процессу на каждое соединение, плюс набор фоновых служебных процессов.&lt;/li&gt;
&lt;li&gt;Все backend&amp;rsquo;ы общаются через &lt;strong&gt;общую память&lt;/strong&gt; (&lt;code&gt;shared memory&lt;/code&gt;), главная часть которой — &lt;strong&gt;shared buffers&lt;/strong&gt; (кэш страниц 8 КБ). Вытеснение страниц — алгоритм &lt;strong&gt;clock-sweep&lt;/strong&gt; (приближение к LRU).&lt;/li&gt;
&lt;li&gt;Перед записью грязной страницы на диск изменения сначала пишутся в &lt;strong&gt;WAL&lt;/strong&gt; (write-ahead log) — это гарантирует durability и crash recovery. Принцип &lt;strong&gt;WAL-before-data&lt;/strong&gt;, позиция в логе — &lt;strong&gt;LSN&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checkpoint&lt;/strong&gt; — точка, до которой все грязные страницы сброшены на диск; ограничивает объём WAL, который нужно проиграть при восстановлении. Регулируется &lt;code&gt;checkpoint_timeout&lt;/code&gt;, &lt;code&gt;max_wal_size&lt;/code&gt;, &lt;code&gt;checkpoint_completion_target&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autovacuum&lt;/strong&gt; убирает dead tuples (следствие MVCC), предотвращает раздувание (bloat) и transaction ID wraparound, обновляет статистику и visibility map.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TOAST&lt;/strong&gt; — механизм хранения значений, не помещающихся в страницу 8 КБ: сжатие + вынос в отдельную таблицу (out-of-line).&lt;/li&gt;
&lt;li&gt;На senior-уровне ждут понимания связи MVCC ↔ vacuum ↔ WAL ↔ checkpoint и умения диагностировать проблемы (I/O-всплески, bloat, wraparound, write amplification).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="процессная-модель"&gt;Процессная модель&lt;a class="anchor" href="#%d0%bf%d1%80%d0%be%d1%86%d0%b5%d1%81%d1%81%d0%bd%d0%b0%d1%8f-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d1%8c"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PostgreSQL запускает дерево процессов. В отличие от MySQL/Oracle (потоки), здесь на каждое клиентское соединение создаётся отдельный процесс ОС. Это упрощает изоляцию сбоев (краш одного backend не валит сервер), но делает соединения дорогими — отсюда необходимость пулеров (PgBouncer, pgcat) на высоконагруженных системах.&lt;/p&gt;</description></item><item><title>Горутины: жизненный цикл, стоимость, стек</title><link>https://go.vbloher.org/docs/02-concurrency/goroutines-lifecycle/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/goroutines-lifecycle/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Горутина — это легковесный поток исполнения, управляемый рантаймом Go, а не ОС. Стартовый стек ~2 КБ, растёт/сжимается копированием. Создаётся через &lt;code&gt;go f()&lt;/code&gt;, планируется на потоки ОС моделью GMP. Завершается при возврате из функции; неперехваченная паника в горутине роняет весь процесс.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="что-такое-горутина-под-капотом"&gt;Что такое горутина под капотом&lt;a class="anchor" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-%d0%b3%d0%be%d1%80%d1%83%d1%82%d0%b8%d0%bd%d0%b0-%d0%bf%d0%be%d0%b4-%d0%ba%d0%b0%d0%bf%d0%be%d1%82%d0%be%d0%bc"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Горутина представлена структурой &lt;code&gt;runtime.g&lt;/code&gt; (файл &lt;code&gt;src/runtime/runtime2.go&lt;/code&gt;). Это не поток ОС: множество горутин мультиплексируется на ограниченное число потоков ОС (&lt;code&gt;M&lt;/code&gt;) через логические процессоры (&lt;code&gt;P&lt;/code&gt;). Ключевые поля &lt;code&gt;g&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Модель памяти Go (Go Memory Model): happens-before и синхронизация</title><link>https://go.vbloher.org/docs/03-runtime-memory/memory-model/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/memory-model/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Go memory model определяет, при каких условиях чтение переменной в одной горутине &lt;strong&gt;гарантированно видит&lt;/strong&gt; значение, записанное в другой. Основа — отношение &lt;strong&gt;happens-before&lt;/strong&gt;: если запись happens-before чтения (и между ними нет других записей в эту переменную), чтение видит эту запись. Гарантии дают &lt;strong&gt;только&lt;/strong&gt; примитивы синхронизации (channels, sync.Mutex, sync.Once, sync/atomic, WaitGroup). &lt;strong&gt;Программа без data race&lt;/strong&gt; ведёт себя как sequential consistency; программа с гонкой имеет неопределённое поведение, и компилятор/CPU вправе переупорядочивать операции. С Go 1.19 модель официально переписана и формализует &lt;code&gt;sync/atomic&lt;/code&gt; как sequentially consistent.&lt;/p&gt;</description></item><item><title>Указатели в Go</title><link>https://go.vbloher.org/docs/01-core-go/pointers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/pointers/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Указатель в Go — это типизированный адрес значения в памяти; Go всегда передаёт аргументы по значению, поэтому указатель — единственный способ позволить функции мутировать чужое значение и избежать копии. Арифметики указателей нет (кроме &lt;code&gt;unsafe.Pointer&lt;/code&gt;), есть GC, и где физически живёт значение (стек или куча) решает escape-анализ, а не наличие &lt;code&gt;&amp;amp;&lt;/code&gt;. Выбор value vs pointer receiver влияет не только на мутацию и стоимость копий, но и на method set, а значит — на то, реализует ли тип интерфейс.&lt;/p&gt;</description></item><item><title>pprof: профилирование CPU, памяти и блокировок в Go</title><link>https://go.vbloher.org/docs/03-runtime-memory/pprof/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/pprof/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pprof&lt;/code&gt; — это система статистического профилирования Go. Большинство профилей (CPU, heap, mutex, block) собираются через сэмплирование, поэтому стоят дёшево и пригодны для продакшена. Профиль можно собрать программно (&lt;code&gt;runtime/pprof&lt;/code&gt;) или через HTTP-эндпоинты (&lt;code&gt;net/http/pprof&lt;/code&gt;), а затем анализировать в &lt;code&gt;go tool pprof&lt;/code&gt; (top/list/web/peek) или в continuous-profiling-системах (Pyroscope, Parca, Grafana Cloud). Ключ к правильному чтению — понимать flat vs cum и разницу между &lt;code&gt;inuse&lt;/code&gt;/&lt;code&gt;alloc&lt;/code&gt; для heap.&lt;/p&gt;</description></item><item><title>Protocol Buffers: схемы, wire format, эволюция и совместимость</title><link>https://go.vbloher.org/docs/05-backend/protobuf/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/protobuf/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protocol Buffers (protobuf)&lt;/strong&gt; — бинарный, схемозависимый формат сериализации от Google. Схема описывается в &lt;code&gt;.proto&lt;/code&gt;, компилируется &lt;code&gt;protoc&lt;/code&gt; в код целевого языка (для Go — пакет &lt;code&gt;google.golang.org/protobuf&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;На проводе (wire format) сообщение — это последовательность пар &lt;code&gt;(tag, value)&lt;/code&gt;, где &lt;code&gt;tag = (field_number &amp;lt;&amp;lt; 3) | wire_type&lt;/code&gt;. &lt;strong&gt;Имена полей на проводе отсутствуют&lt;/strong&gt; — кодируются только номера.&lt;/li&gt;
&lt;li&gt;Есть 4 актуальных wire type: &lt;code&gt;0&lt;/code&gt; VARINT, &lt;code&gt;1&lt;/code&gt; I64 (64-bit), &lt;code&gt;2&lt;/code&gt; LEN (length-delimited), &lt;code&gt;5&lt;/code&gt; I32 (32-bit). Типы 3/4 (start/end group) устарели.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Номера полей 1–15 кодируются в 1 байт&lt;/strong&gt; тега, 16–2047 — в 2 байта. Поэтому горячие/частые поля держим в 1–15.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Совместимость держится на номерах полей, а не на именах&lt;/strong&gt;. Backward (новый код читает старые данные) и forward (старый код читает новые данные) совместимость работают за счёт пропуска/сохранения unknown fields.&lt;/li&gt;
&lt;li&gt;Главные правила эволюции: &lt;strong&gt;никогда не меняй номер существующего поля&lt;/strong&gt;, &lt;strong&gt;никогда не переиспользуй номер удалённого поля&lt;/strong&gt; (&lt;code&gt;reserved&lt;/code&gt;), добавление полей безопасно, переименование безопасно.&lt;/li&gt;
&lt;li&gt;В proto3 нет &lt;code&gt;required&lt;/code&gt;, по умолчанию поля имеют presence &amp;ldquo;implicit&amp;rdquo; (нельзя отличить zero от unset) — для явного presence используют &lt;code&gt;optional&lt;/code&gt;, &lt;code&gt;oneof&lt;/code&gt; или wrapper-типы.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oneof&lt;/code&gt;, well-known types (&lt;code&gt;Timestamp&lt;/code&gt;, &lt;code&gt;Duration&lt;/code&gt;, &lt;code&gt;Any&lt;/code&gt;, &lt;code&gt;Struct&lt;/code&gt;, &lt;code&gt;FieldMask&lt;/code&gt;, wrappers), &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;repeated&lt;/code&gt; + packed encoding — стандартный senior-инструментарий.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-что-такое-protobuf-и-зачем"&gt;1. Что такое protobuf и зачем&lt;a class="anchor" href="#1-%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-protobuf-%d0%b8-%d0%b7%d0%b0%d1%87%d0%b5%d0%bc"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Protobuf — это IDL (interface definition language) + бинарный формат + кодогенерация. По сравнению с JSON:&lt;/p&gt;</description></item><item><title>sync.Mutex и sync.RWMutex</title><link>https://go.vbloher.org/docs/02-concurrency/mutex-rwmutex/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/mutex-rwmutex/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sync.Mutex&lt;/code&gt; — взаимное исключение для защиты разделяемого состояния; устроен на основе атомарного слова состояния + семафора рантайма для парковки горутин. С Go 1.9 у мьютекса есть &lt;strong&gt;starvation mode&lt;/strong&gt;, защищающий «обиженные» горутины от голодания. &lt;code&gt;RWMutex&lt;/code&gt; разрешает много читателей или одного писателя, но из-за дороговизны и риска голодания писателей полезен только при сильном перекосе в сторону чтения и длинных критических секциях. Мьютексы &lt;strong&gt;нельзя копировать&lt;/strong&gt; после первого использования.&lt;/p&gt;</description></item><item><title>Transactional Outbox</title><link>https://go.vbloher.org/docs/08-distributed-systems/outbox/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/outbox/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dual write&lt;/strong&gt; — запись в БД и публикация в брокер выполняются как две независимые операции; между ними возможен сбой → одна сторона зафиксирована, другая нет → потеря или дублирование событий. Распределённой транзакции «БД + Kafka» в общем случае нет (а 2PC дорог и неотказоустойчив).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transactional outbox&lt;/strong&gt; решает это: событие пишется в таблицу &lt;code&gt;outbox&lt;/code&gt; &lt;strong&gt;в той же локальной ACID-транзакции&lt;/strong&gt;, что и бизнес-данные. Атомарность гарантируется БД. Отдельный процесс (relay) асинхронно вычитывает outbox и публикует в брокер.&lt;/li&gt;
&lt;li&gt;Доставка из outbox — &lt;strong&gt;at-least-once&lt;/strong&gt; (relay может упасть после публикации, но до фиксации факта отправки → повторная публикация). Дубликаты неизбежны → консьюмер обязан быть идемпотентным (&lt;strong&gt;inbox pattern&lt;/strong&gt; / dedup-таблица).&lt;/li&gt;
&lt;li&gt;Два способа читать outbox: &lt;strong&gt;polling publisher&lt;/strong&gt; (relay SELECT-ит таблицу) vs &lt;strong&gt;CDC&lt;/strong&gt; (Debezium читает WAL/binlog транзакционного лога — нет нагрузки SELECT-ами, ниже latency).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ordering&lt;/strong&gt; не бесплатен: глобального порядка нет, обычно гарантируют порядок в рамках партиционного ключа (aggregate_id).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="проблема-dual-write"&gt;Проблема dual write&lt;a class="anchor" href="#%d0%bf%d1%80%d0%be%d0%b1%d0%bb%d0%b5%d0%bc%d0%b0-dual-write"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Типичный сценарий: сервис заказов создаёт заказ и должен опубликовать событие &lt;code&gt;OrderCreated&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Планирование и оптимизация запросов в PostgreSQL</title><link>https://go.vbloher.org/docs/07-databases/query-planning/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/query-planning/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EXPLAIN&lt;/code&gt; показывает &lt;strong&gt;план&lt;/strong&gt; запроса с &lt;strong&gt;оценками&lt;/strong&gt; планировщика (estimated rows, cost), не выполняя запрос. &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; &lt;strong&gt;реально выполняет&lt;/strong&gt; запрос и добавляет фактические данные (actual time, actual rows, loops). Поэтому &lt;code&gt;ANALYZE&lt;/code&gt; с &lt;code&gt;INSERT/UPDATE/DELETE&lt;/code&gt; действительно меняет данные — оборачивайте в транзакцию с &lt;code&gt;ROLLBACK&lt;/code&gt; или используйте &lt;code&gt;BEGIN; ... ROLLBACK;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;План читается &lt;strong&gt;снизу вверх и изнутри наружу&lt;/strong&gt;: листья (сканы) внизу, корень (то, что отдаёт результат клиенту) вверху. Главный диагностический приём — сравнить &lt;code&gt;rows=N&lt;/code&gt; (оценка) с &lt;code&gt;actual rows=M&lt;/code&gt; (факт). Расхождение в разы/порядки означает плохую статистику и, как следствие, плохой план.&lt;/li&gt;
&lt;li&gt;Cost — это безразмерная величина в условных единицах &lt;code&gt;seq_page_cost&lt;/code&gt;. Планировщик выбирает план с минимальным суммарным cost, не учитывая реальную нагрузку CPU/IO в миллисекундах.&lt;/li&gt;
&lt;li&gt;Типы сканов по возрастанию избирательности: &lt;code&gt;Seq Scan&lt;/code&gt; (читаем всё), &lt;code&gt;Bitmap Index/Heap Scan&lt;/code&gt; (средняя селективность, много строк по индексу), &lt;code&gt;Index Scan&lt;/code&gt; (мало строк), &lt;code&gt;Index Only Scan&lt;/code&gt; (данные целиком в индексе + visibility map).&lt;/li&gt;
&lt;li&gt;Джойны: &lt;code&gt;Nested Loop&lt;/code&gt; хорош для малых наборов с индексом по внутренней таблице, катастрофичен при недооценке строк; &lt;code&gt;Hash Join&lt;/code&gt; для больших equi-join без сортировки (зависит от &lt;code&gt;work_mem&lt;/code&gt;); &lt;code&gt;Merge Join&lt;/code&gt; для отсортированных/индексированных больших наборов.&lt;/li&gt;
&lt;li&gt;Статистика собирается &lt;code&gt;ANALYZE&lt;/code&gt; в &lt;code&gt;pg_statistic&lt;/code&gt;: &lt;code&gt;n_distinct&lt;/code&gt;, MCV (most common values), гистограммы. По умолчанию &lt;code&gt;default_statistics_target = 100&lt;/code&gt;. Для коррелированных колонок планировщик предполагает независимость и сильно ошибается — лечится &lt;code&gt;CREATE STATISTICS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OFFSET&lt;/code&gt; линейно деградирует (читает и выбрасывает N строк). Используйте keyset pagination (&lt;code&gt;WHERE (created_at, id) &amp;lt; (...)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;N+1 в Go — главный источник деградации latency: решается JOIN, &lt;code&gt;IN (...)&lt;/code&gt;, dataloader-батчингом или &lt;code&gt;pgx.Batch&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="explain-vs-explain-analyze"&gt;EXPLAIN vs EXPLAIN ANALYZE&lt;a class="anchor" href="#explain-vs-explain-analyze"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;EXPLAIN&lt;/code&gt; запрашивает у планировщика выбранный план без выполнения. Вы видите &lt;strong&gt;оценки&lt;/strong&gt;: сколько строк планировщик ожидает (&lt;code&gt;rows&lt;/code&gt;), их средняя ширина в байтах (&lt;code&gt;width&lt;/code&gt;) и стоимость (&lt;code&gt;cost=startup..total&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Рефлексия в Go (reflect)</title><link>https://go.vbloher.org/docs/01-core-go/reflection/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/reflection/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Рефлексия в Go — это механизм, позволяющий программе исследовать и менять собственные значения и типы во время выполнения через пакет &lt;code&gt;reflect&lt;/code&gt;. Она построена поверх внутреннего представления интерфейсов (&lt;code&gt;eface&lt;/code&gt;): &lt;code&gt;reflect.Value&lt;/code&gt; хранит указатель на &lt;code&gt;rtype&lt;/code&gt;, указатель на данные и набор флагов. Рефлексия мощная, но дорогая (аллокации, отсутствие инлайнинга, динамические проверки), поэтому на горячих путях её избегают, а популярные библиотеки (&lt;code&gt;encoding/json&lt;/code&gt;) кэшируют построенные по типу планы кодирования. Ключевые понятия для senior: три закона Роба Пайка, settability (только через указатель + &lt;code&gt;Elem()&lt;/code&gt;), разница &lt;code&gt;Kind&lt;/code&gt; vs &lt;code&gt;Type&lt;/code&gt;, флаги &lt;code&gt;flagIndir/flagAddr/flagRO&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Execution Tracer и runtime/trace: тайминги вместо агрегатов</title><link>https://go.vbloher.org/docs/03-runtime-memory/runtime-tracing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/runtime-tracing/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;runtime/trace&lt;/code&gt; (execution tracer) пишет поток событий планировщика, GC, syscalls и блокировок с наносекундными таймстампами — в отличие от pprof, который даёт агрегированную статистику. Он отвечает на вопрос «почему конкретная горутина простояла 8 мс именно в этот момент», а не «куда в среднем уходит CPU». С Go 1.21 трейсер переписан на низкооверхедный формат (snapshot-free, ~1-2% overhead), а Go 1.25 принёс &lt;code&gt;trace.FlightRecorder&lt;/code&gt; — кольцевой буфер для post-mortem диагностики редких событий. Дополняется неинвазивной диагностикой через &lt;code&gt;GODEBUG&lt;/code&gt; (&lt;code&gt;gctrace&lt;/code&gt;, &lt;code&gt;schedtrace&lt;/code&gt;, &lt;code&gt;inittrace&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>RabbitMQ: AMQP 0-9-1, маршрутизация, надёжность доставки и сравнение с Kafka</title><link>https://go.vbloher.org/docs/08-distributed-systems/rabbitmq/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/rabbitmq/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;RabbitMQ — это &lt;strong&gt;smart broker / dumb consumer&lt;/strong&gt;: брокер хранит логику маршрутизации (exchanges + bindings) и выталкивает (push) сообщения потребителям. Kafka — наоборот: &lt;strong&gt;dumb broker / smart consumer&lt;/strong&gt; (pull, потребитель сам держит offset).&lt;/li&gt;
&lt;li&gt;Базовая модель AMQP 0-9-1: producer публикует в &lt;strong&gt;exchange&lt;/strong&gt;, exchange по &lt;strong&gt;binding&lt;/strong&gt; и &lt;strong&gt;routing key&lt;/strong&gt; раскладывает сообщение по &lt;strong&gt;очередям&lt;/strong&gt;, consumer читает из очереди. Очередь — единственное место, где сообщение реально хранится.&lt;/li&gt;
&lt;li&gt;Типы exchange: &lt;code&gt;direct&lt;/code&gt; (точное совпадение routing key), &lt;code&gt;topic&lt;/code&gt; (паттерны с &lt;code&gt;*&lt;/code&gt; и &lt;code&gt;#&lt;/code&gt;), &lt;code&gt;fanout&lt;/code&gt; (broadcast, ключ игнорируется), &lt;code&gt;headers&lt;/code&gt; (матчинг по заголовкам).&lt;/li&gt;
&lt;li&gt;Надёжность доставки строится на трёх китах: &lt;strong&gt;manual ack/nack&lt;/strong&gt; (подтверждение от consumer), &lt;strong&gt;publisher confirms&lt;/strong&gt; (подтверждение от брокера producer-у), &lt;strong&gt;persistent + durable + quorum queue&lt;/strong&gt; (переживание рестарта/отказа узла).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DLQ&lt;/strong&gt; (dead-letter exchange) ловит сообщения, которые были отвергнуты (reject/nack без requeue), протухли по TTL, или вытеснены по max-length.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prefetch&lt;/strong&gt; (&lt;code&gt;basic.qos&lt;/code&gt;) — главный рычаг производительности: без него один медленный consumer заберёт всю очередь round-robin&amp;rsquo;ом; с prefetch=1 получается fair dispatch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quorum queues&lt;/strong&gt; (на базе Raft) — современный стандарт для надёжности. Classic mirrored queues &lt;strong&gt;deprecated&lt;/strong&gt; и удалены в RabbitMQ 4.0.&lt;/li&gt;
&lt;li&gt;Выбор: RabbitMQ — для &lt;strong&gt;task/job queue&lt;/strong&gt;, сложной маршрутизации, RPC, per-message acknowledgement. Kafka — для &lt;strong&gt;event streaming&lt;/strong&gt;, replay, высокого throughput и ordered log.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="модель-amqp-0-9-1"&gt;Модель AMQP 0-9-1&lt;a class="anchor" href="#%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d1%8c-amqp-0-9-1"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AMQP 0-9-1 (Advanced Message Queuing Protocol) — это &lt;strong&gt;бинарный wire-протокол&lt;/strong&gt;, реализуемый RabbitMQ по умолчанию. Не путать с AMQP 1.0 — это совершенно другой протокол (он тоже поддерживается RabbitMQ через плагин, но дефолтная модель — именно 0-9-1).&lt;/p&gt;</description></item><item><title>REST: принципы, версионирование, идемпотентность, статусы, пагинация, ошибки</title><link>https://go.vbloher.org/docs/05-backend/rest/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/05-backend/rest/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Backend · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST&lt;/strong&gt; — архитектурный стиль (диссертация Роя Филдинга, 2000), а не протокол. Ключевые ограничения: client-server, &lt;strong&gt;stateless&lt;/strong&gt;, cacheable, layered system, &lt;strong&gt;uniform interface&lt;/strong&gt;, опционально code-on-demand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ресурс&lt;/strong&gt; — абстрактная сущность, у неё есть &lt;strong&gt;представления&lt;/strong&gt; (JSON/XML/&amp;hellip;). URI идентифицирует ресурс, метод выражает действие. Не глаголы в URI (&lt;code&gt;/users/42&lt;/code&gt;, а не &lt;code&gt;/getUser?id=42&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Уровни зрелости Ричардсона (RMM)&lt;/strong&gt;: L0 — один эндпоинт (RPC поверх HTTP), L1 — ресурсы, L2 — HTTP-методы + статусы (де-факто отраслевой стандарт), L3 — HATEOAS (гиперссылки).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Семантика методов&lt;/strong&gt;: GET/HEAD/OPTIONS — safe; GET/HEAD/PUT/DELETE/OPTIONS — idempotent; POST/PATCH — нет. Кэшируются по умолчанию GET/HEAD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Идемпотентность&lt;/strong&gt; = N одинаковых запросов дают тот же эффект на сервере, что и один. PUT идемпотентен по дизайну, POST — нет → для платежей нужен &lt;strong&gt;Idempotency-Key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Версионирование&lt;/strong&gt;: URL path (&lt;code&gt;/v1/&lt;/code&gt;) — просто и видно; media-type через &lt;code&gt;Accept&lt;/code&gt; — «правильно» по REST, но сложнее в эксплуатации; query param — компромисс.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Пагинация&lt;/strong&gt;: offset/limit прост, но дрейфует и медленный на больших offset; &lt;strong&gt;cursor/keyset&lt;/strong&gt; — стабилен и быстр (&lt;code&gt;WHERE (created_at, id) &amp;lt; (?, ?)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ошибки&lt;/strong&gt;: единый формат &lt;strong&gt;RFC 7807/9457 Problem Details&lt;/strong&gt; (&lt;code&gt;application/problem+json&lt;/code&gt;), стабильные машинные коды, никаких stack trace и SQL наружу.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-принципы-rest-ограничения-архитектуры"&gt;1. Принципы REST (ограничения архитектуры)&lt;a class="anchor" href="#1-%d0%bf%d1%80%d0%b8%d0%bd%d1%86%d0%b8%d0%bf%d1%8b-rest-%d0%be%d0%b3%d1%80%d0%b0%d0%bd%d0%b8%d1%87%d0%b5%d0%bd%d0%b8%d1%8f-%d0%b0%d1%80%d1%85%d0%b8%d1%82%d0%b5%d0%ba%d1%82%d1%83%d1%80%d1%8b"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;REST задаётся набором &lt;strong&gt;архитектурных ограничений (constraints)&lt;/strong&gt;. Соблюдение даёт масштабируемость, кэшируемость и слабую связанность.&lt;/p&gt;</description></item><item><title>sync.Once</title><link>https://go.vbloher.org/docs/02-concurrency/once/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/once/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sync.Once&lt;/code&gt; гарантирует, что переданная функция выполнится &lt;strong&gt;ровно один раз&lt;/strong&gt; за всё время жизни, даже при конкурентных вызовах из множества горутин — основа для потокобезопасной ленивой инициализации. Внутри — атомарный флаг (быстрый путь без блокировки) плюс мьютекс для медленного пути. Go 1.21 добавил эргономичные обёртки &lt;code&gt;OnceFunc&lt;/code&gt;, &lt;code&gt;OnceValue&lt;/code&gt;, &lt;code&gt;OnceValues&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="контракт"&gt;Контракт&lt;a class="anchor" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Once&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetDB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// выполнится ровно один раз&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Do(f)&lt;/code&gt; вызывает &lt;code&gt;f&lt;/code&gt; только при &lt;strong&gt;первом&lt;/strong&gt; вызове. Все остальные вызовы (включая конкурентные) &lt;strong&gt;блокируются&lt;/strong&gt;, пока первый &lt;code&gt;f&lt;/code&gt; не завершится, и только потом возвращаются.&lt;/li&gt;
&lt;li&gt;Гарантия видимости: записи внутри &lt;code&gt;f&lt;/code&gt; happens-before возврат любого &lt;code&gt;Do&lt;/code&gt; → все горутины видят полностью проинициализированное состояние.&lt;/li&gt;
&lt;li&gt;Если &lt;code&gt;f&lt;/code&gt; паникует, она считается «выполненной» — повторные &lt;code&gt;Do&lt;/code&gt; её не запустят.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="устройство-под-капотом"&gt;Устройство под капотом&lt;a class="anchor" href="#%d1%83%d1%81%d1%82%d1%80%d0%be%d0%b9%d1%81%d1%82%d0%b2%d0%be-%d0%bf%d0%be%d0%b4-%d0%ba%d0%b0%d0%bf%d0%be%d1%82%d0%be%d0%bc"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Once&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0 = не выполнено, 1 = выполнено&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Mutex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Once&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// быстрый путь: атомарное чтение&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doSlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Once&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doSlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// двойная проверка под мьютексом&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ставим флаг ПОСЛЕ f (через defer)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ключевые детали:&lt;/p&gt;</description></item><item><title>Внутреннее устройство слайсов в Go</title><link>https://go.vbloher.org/docs/01-core-go/slices/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/slices/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Слайс — это не контейнер, а трёхсловный дескриптор (header): указатель на бэкинг-массив, длина (len) и ёмкость (cap). Слайс передаётся по значению (header копируется), но указывает на тот же массив, поэтому мутации элементов видны вызывающему, а &lt;code&gt;append&lt;/code&gt; — нет (он может реаллоцировать). Главные источники багов: молчаливый шаринг бэкинг-массива между подслайсами, реаллокация при росте и утечки памяти, когда маленький подслайс удерживает весь большой массив.&lt;/p&gt;</description></item><item><title>Репликация в PostgreSQL</title><link>https://go.vbloher.org/docs/07-databases/replication/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/replication/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Физическая (streaming) репликация&lt;/strong&gt; — побайтовая копия кластера на уровне WAL. Standby проигрывает WAL primary. Всё или ничего: реплицируется весь инстанс, версии PG должны совпадать, архитектура CPU тоже. Основа HA.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Логическая репликация&lt;/strong&gt; — на уровне логических изменений строк (decode WAL → INSERT/UPDATE/DELETE). Выборочно по таблицам (publication/subscription), между разными мажорными версиями, можно писать в таблицы-подписчики. Цена — нет DDL, нужны PK/REPLICA IDENTITY, выше overhead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sync vs async&lt;/strong&gt;: &lt;code&gt;synchronous_commit&lt;/code&gt; управляет, ждать ли подтверждения standby перед возвратом COMMIT. Async = низкая latency, риск потери данных при падении primary. Sync = durability, но latency растёт и primary может «зависнуть», если standby недоступен. &lt;code&gt;quorum&lt;/code&gt;/&lt;code&gt;ANY&lt;/code&gt; — компромисс.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replication lag&lt;/strong&gt; = задержка между primary и standby. Три фазы: write → flush → replay. Мониторинг через &lt;code&gt;pg_stat_replication&lt;/code&gt; (на primary) и &lt;code&gt;pg_stat_wal_receiver&lt;/code&gt; / &lt;code&gt;pg_last_wal_replay_lsn()&lt;/code&gt; (на standby).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Чтение с реплик&lt;/strong&gt; даёт масштабирование read-нагрузки, но порождает stale reads и проблему read-your-writes. Нужен sticky-routing или чтение свежих данных с primary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failover&lt;/strong&gt;: &lt;code&gt;pg_promote()&lt;/code&gt; переводит standby в primary. Опасность split-brain — нужен fencing. В проде это Patroni/repmgr + DCS (etcd/Consul), а не ручной promote.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;: два пула &lt;code&gt;*pgxpool.Pool&lt;/code&gt; / &lt;code&gt;*sql.DB&lt;/code&gt; (writer → primary, reader → реплики), роутинг по типу запроса, с fallback на primary для read-your-writes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-физическая-streaming--wal-репликация"&gt;1. Физическая (streaming / WAL) репликация&lt;a class="anchor" href="#1-%d1%84%d0%b8%d0%b7%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%b0%d1%8f-streaming--wal-%d1%80%d0%b5%d0%bf%d0%bb%d0%b8%d0%ba%d0%b0%d1%86%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PostgreSQL пишет все изменения сначала в WAL (Write-Ahead Log) — последовательность записей, описывающих физические изменения страниц данных (блоков 8 КБ). Физическая репликация — это передача потока WAL с primary на standby и его непрерывное применение.&lt;/p&gt;</description></item><item><title>Паттерны конкурентности</title><link>https://go.vbloher.org/docs/02-concurrency/patterns/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/patterns/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Базовый словарь конкурентного Go: pipeline (этапы через каналы), fan-out/fan-in (распараллелить этап и собрать), worker pool (фиксированный пул на канале задач), семафор (ограничение конкуренции буферизованным каналом), &lt;code&gt;errgroup&lt;/code&gt; (группа горутин с отменой и первой ошибкой), graceful shutdown (через context + WaitGroup), or-done (обёртка для отмены). Общий принцип всех паттернов: &lt;strong&gt;у каждой горутины есть явный путь завершения&lt;/strong&gt; (закрытие входа или &lt;code&gt;ctx.Done()&lt;/code&gt;), иначе — утечка.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="pipeline"&gt;Pipeline&lt;a class="anchor" href="#pipeline"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Этапы, соединённые каналами; каждый этап читает вход, пишет выход, &lt;strong&gt;закрывает свой выход&lt;/strong&gt; при завершении.&lt;/p&gt;</description></item><item><title>Ретраи: backoff, jitter, budgets и идемпотентность</title><link>https://go.vbloher.org/docs/08-distributed-systems/retries/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/retries/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Ретрай — это попытка повторить запрос после сбоя. Наивный «повторить сразу N раз» в распределённой системе создаёт &lt;strong&gt;retry storm&lt;/strong&gt; и усиливает деградацию.&lt;/li&gt;
&lt;li&gt;Базовая формула паузы: &lt;strong&gt;exponential backoff&lt;/strong&gt; (&lt;code&gt;base * 2^attempt&lt;/code&gt;) с &lt;strong&gt;cap&lt;/strong&gt; (потолком) и &lt;strong&gt;jitter&lt;/strong&gt; (рандомизацией), чтобы избежать синхронизации клиентов (thundering herd).&lt;/li&gt;
&lt;li&gt;Лучший дефолт — &lt;strong&gt;decorrelated jitter&lt;/strong&gt; или &lt;strong&gt;full jitter&lt;/strong&gt; (по статье AWS «Exponential Backoff And Jitter»).&lt;/li&gt;
&lt;li&gt;Ретраить можно &lt;strong&gt;только идемпотентные операции&lt;/strong&gt; (или операции, защищённые idempotency key). Иначе риск двойного списания/дублей.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retry budget&lt;/strong&gt; ограничивает долю ретраев от общего трафика (например, не более 10–20%), предотвращая каскадное усиление нагрузки.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deadline propagation&lt;/strong&gt; через &lt;code&gt;context.Context&lt;/code&gt;: общий дедлайн всего вызова пробрасывается вниз, и каждая попытка (включая backoff) обязана в него укладываться.&lt;/li&gt;
&lt;li&gt;Ретраить осмысленно: transient-ошибки (5xx, timeout, connection reset), а не 4xx (кроме 429/503 с Retry-After).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="зачем-вообще-ретраи"&gt;Зачем вообще ретраи&lt;a class="anchor" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%b2%d0%be%d0%be%d0%b1%d1%89%d0%b5-%d1%80%d0%b5%d1%82%d1%80%d0%b0%d0%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В распределённой системе сбои — норма, а не исключение: сетевые таймауты, кратковременная недоступность пода во время rolling update, GC-пауза на сервере, потеря пакета. Многие из них &lt;strong&gt;transient&lt;/strong&gt; — повторная попытка через короткое время с высокой вероятностью пройдёт. Ретрай повышает наблюдаемую надёжность (availability) без изменения самого сервиса.&lt;/p&gt;</description></item><item><title>Стек vs Куча: где живут данные в Go</title><link>https://go.vbloher.org/docs/03-runtime-memory/stack-vs-heap/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/03-runtime-memory/stack-vs-heap/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Runtime и память · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;В Go компилятор сам решает, где разместить значение — на стеке горутины или в куче, — опираясь на escape analysis. Стек дешёв: аллокация это сдвиг указателя &lt;code&gt;SP&lt;/code&gt;, а освобождение — возврат при выходе из функции, без участия GC. Куча дороже: запрос у аллокатора (mcache/mcentral/mheap), последующая работа GC и давление на пропускную способность. Стеки горутин начинаются с 8 КБ (исторически было 2 КБ для системных потоков-&lt;code&gt;g0&lt;/code&gt; и для горутин в разных версиях), растут не сегментами, а целостным копированием (contiguous stacks) через &lt;code&gt;morestack&lt;/code&gt;/&lt;code&gt;copystack&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Строки, руны и байты в Go</title><link>https://go.vbloher.org/docs/01-core-go/strings-runes-bytes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/strings-runes-bytes/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;string&lt;/code&gt; в Go — это неизменяемая (immutable) пара «указатель + длина» (&lt;code&gt;StringHeader{ptr, len}&lt;/code&gt;), указывающая на read-only последовательность байт, без какого-либо требования к кодировке. Текст в исходниках и стандартной библиотеке хранится в UTF-8, поэтому &lt;code&gt;len(s)&lt;/code&gt; возвращает количество &lt;strong&gt;байт&lt;/strong&gt;, &lt;code&gt;s[i]&lt;/code&gt; — отдельный &lt;strong&gt;байт&lt;/strong&gt;, а &lt;code&gt;for i, r := range s&lt;/code&gt; декодирует UTF-8 и отдаёт &lt;strong&gt;руны&lt;/strong&gt; (&lt;code&gt;rune = int32&lt;/code&gt;, Unicode code point) вместе с байтовым смещением. Конвертации &lt;code&gt;string&amp;lt;-&amp;gt;[]byte&lt;/code&gt; и &lt;code&gt;string&amp;lt;-&amp;gt;[]rune&lt;/code&gt; в общем случае копируют память (аллокация в куче), но компилятор умеет несколько важных оптимизаций (ключ map, &lt;code&gt;range []byte(s)&lt;/code&gt;), а &lt;code&gt;strings.Builder&lt;/code&gt; и &lt;code&gt;unsafe.String/Slice&lt;/code&gt; позволяют избегать лишних копий — каждая со своими опасностями.&lt;/p&gt;</description></item><item><title>Шардирование (горизонтальное масштабирование)</title><link>https://go.vbloher.org/docs/07-databases/sharding/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/sharding/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Шардирование&lt;/strong&gt; — разбиение данных на независимые узлы (шарды), каждый из которых хранит &lt;strong&gt;свой непересекающийся поднабор&lt;/strong&gt; строк. Это способ масштабировать &lt;strong&gt;запись&lt;/strong&gt; и объём данных за пределы одной машины.&lt;/li&gt;
&lt;li&gt;Не путать: &lt;strong&gt;репликация&lt;/strong&gt; = копии тех же данных (масштаб чтения и HA), &lt;strong&gt;партиционирование&lt;/strong&gt; = разбиение таблицы внутри &lt;strong&gt;одного&lt;/strong&gt; инстанса, &lt;strong&gt;шардирование&lt;/strong&gt; = разбиение по &lt;strong&gt;разным&lt;/strong&gt; инстансам.&lt;/li&gt;
&lt;li&gt;Стратегии: &lt;strong&gt;hash-based&lt;/strong&gt; (равномерность, но плохие range-запросы), &lt;strong&gt;range-based&lt;/strong&gt; (хорошие диапазоны, но hotspots), &lt;strong&gt;lookup/directory&lt;/strong&gt; (гибкость ценой лишнего хопа), &lt;strong&gt;geo&lt;/strong&gt; (локальность и data residency).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shard key&lt;/strong&gt; — главное архитектурное решение: равномерность распределения, отсутствие hotspots, co-location связанных данных. Изменить его потом крайне дорого.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent hashing&lt;/strong&gt; + виртуальные узлы минимизируют перемещение данных при resharding: при добавлении N+1-го шарда переезжает ~1/(N+1) ключей, а не «почти всё».&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-shard&lt;/strong&gt; запросы (scatter-gather, JOIN, агрегации) и транзакции (2PC, saga) дороги — хорошая схема шардирования их &lt;strong&gt;избегает&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;PostgreSQL: &lt;strong&gt;Citus&lt;/strong&gt; (расширение, distributed tables), Vitess (это про MySQL), либо &lt;strong&gt;application-level sharding&lt;/strong&gt; с роутингом в коде.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="шардирование-vs-репликация-vs-партиционирование"&gt;Шардирование vs репликация vs партиционирование&lt;a class="anchor" href="#%d1%88%d0%b0%d1%80%d0%b4%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-vs-%d1%80%d0%b5%d0%bf%d0%bb%d0%b8%d0%ba%d0%b0%d1%86%d0%b8%d1%8f-vs-%d0%bf%d0%b0%d1%80%d1%82%d0%b8%d1%86%d0%b8%d0%be%d0%bd%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Три разных оси масштабирования, которые часто путают на собеседовании.&lt;/p&gt;</description></item><item><title>Race Detector (гонки данных и -race)</title><link>https://go.vbloher.org/docs/02-concurrency/race-detector/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/race-detector/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Гонка данных — это два конкурентных доступа к одной памяти, где хотя бы один — запись, и между ними нет отношения happens-before. В Go такое поведение &lt;strong&gt;не определено&lt;/strong&gt;. Детектор гонок (&lt;code&gt;go test -race&lt;/code&gt;, &lt;code&gt;go run -race&lt;/code&gt;) построен на ThreadSanitizer: он динамически отслеживает happens-before через векторные часы и сообщает о реальных гонках, наблюдённых на конкретном прогоне. Цена высока (память ×5–10, скорость ×2–20), и он &lt;strong&gt;не доказывает отсутствие гонок&lt;/strong&gt; — только находит те, что фактически случились.&lt;/p&gt;</description></item><item><title>Saga Pattern</title><link>https://go.vbloher.org/docs/08-distributed-systems/saga/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/08-distributed-systems/saga/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Распределённые системы · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Saga — это способ управлять данными в нескольких сервисах/БД через последовательность &lt;strong&gt;локальных транзакций&lt;/strong&gt;, где каждый шаг публикует событие/команду, запускающую следующий шаг. Если шаг падает, выполняется серия &lt;strong&gt;компенсирующих транзакций&lt;/strong&gt;, откатывающих эффекты предыдущих шагов &lt;strong&gt;семантически&lt;/strong&gt; (а не настоящим rollback). Saga жертвует Isolation из ACID: вы получаете ACD без I. Два стиля координации: &lt;strong&gt;оркестрация&lt;/strong&gt; (центральный координатор дёргает участников командами) и &lt;strong&gt;хореография&lt;/strong&gt; (участники реагируют на события друг друга, без центра). Главные проблемы: отсутствие изоляции (dirty reads, lost updates), компенсации могут падать (нужны ретраи и идемпотентность), часть шагов невозможно компенсировать. Решается это контрмерами (semantic lock, commutative updates, pessimistic view, reread value, by value) и разделением шагов на &lt;strong&gt;compensatable / pivot / retriable&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>Система типов Go: defined types, alignment, memory layout</title><link>https://go.vbloher.org/docs/01-core-go/type-system/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/01-core-go/type-system/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Core Go · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Go — статически типизированный язык со структурной концепцией underlying type, на которой строятся правила assignability и convertibility. &lt;code&gt;type Foo int&lt;/code&gt; создаёт новый &lt;strong&gt;defined (named) type&lt;/strong&gt; с тем же underlying type, но другой identity; &lt;code&gt;type Foo = int&lt;/code&gt; — это &lt;strong&gt;alias&lt;/strong&gt;, полностью идентичный &lt;code&gt;int&lt;/code&gt;. Поверх системы типов лежит физический memory layout: компилятор выравнивает поля структур по их alignment, добавляя padding, поэтому порядок полей напрямую влияет на &lt;code&gt;Sizeof&lt;/code&gt;. Знание zerobase для &lt;code&gt;struct{}&lt;/code&gt;, правил comparable-типов и iota — обязательный senior-минимум.&lt;/p&gt;</description></item><item><title>Транзакции в PostgreSQL и Go (database/sql, pgx)</title><link>https://go.vbloher.org/docs/07-databases/transactions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/07-databases/transactions/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Базы данных · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ACID&lt;/strong&gt; = Atomicity (всё или ничего, реализуется через WAL и откат), Consistency (инварианты БД: constraints, triggers, FK), Isolation (параллельные транзакции не мешают друг другу — в PostgreSQL через &lt;strong&gt;MVCC&lt;/strong&gt; + уровни изоляции), Durability (после &lt;code&gt;COMMIT&lt;/code&gt; данные переживут крах — гарантируется &lt;strong&gt;WAL&lt;/strong&gt; + &lt;code&gt;fsync&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;PostgreSQL не имеет настоящих вложенных транзакций — &lt;code&gt;SAVEPOINT&lt;/code&gt; создаёт &lt;strong&gt;субтранзакции&lt;/strong&gt; (получают свой &lt;code&gt;subxid&lt;/code&gt;), которые дёшевы в небольшом количестве, но при &amp;gt;64 на бэкенд вызывают &lt;strong&gt;SLRU overflow&lt;/strong&gt; и резкую деградацию (&lt;code&gt;subtrans&lt;/code&gt; SLRU misses).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Длинные транзакции — главное зло в PostgreSQL&lt;/strong&gt;: удерживают &lt;code&gt;xmin&lt;/code&gt; горизонт → &lt;code&gt;VACUUM&lt;/code&gt; не может удалить dead tuples → bloat, распухание индексов, деградация. &lt;code&gt;idle in transaction&lt;/code&gt; особенно опасен.&lt;/li&gt;
&lt;li&gt;В Go: всегда &lt;code&gt;BeginTx(ctx, opts)&lt;/code&gt;, паттерн &lt;code&gt;defer tx.Rollback()&lt;/code&gt; (после &lt;code&gt;Commit&lt;/code&gt; это no-op), не забывать &lt;code&gt;ctx&lt;/code&gt; для отмены. В &lt;code&gt;pgx&lt;/code&gt; — &lt;code&gt;pgxpool&lt;/code&gt; + &lt;code&gt;tx.Begin&lt;/code&gt;/&lt;code&gt;pgx.BeginFunc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;На уровне &lt;code&gt;SERIALIZABLE&lt;/code&gt; (и &lt;code&gt;REPEATABLE READ&lt;/code&gt; при write skew конфликтах) транзакция может упасть с &lt;strong&gt;40001 (serialization_failure)&lt;/strong&gt; или &lt;strong&gt;40P01 (deadlock_detected)&lt;/strong&gt; — это &lt;strong&gt;нормально и ожидаемо&lt;/strong&gt;, надо реализовать &lt;strong&gt;retry с экспоненциальным backoff&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="acid-подробно"&gt;ACID подробно&lt;a class="anchor" href="#acid-%d0%bf%d0%be%d0%b4%d1%80%d0%be%d0%b1%d0%bd%d0%be"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ACID&lt;/strong&gt; — набор гарантий, которые СУБД даёт относительно транзакций. Важно понимать не определения из учебника, а &lt;em&gt;как именно&lt;/em&gt; они реализованы в PostgreSQL.&lt;/p&gt;</description></item><item><title>Планировщик GMP</title><link>https://go.vbloher.org/docs/02-concurrency/scheduler-gmp/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/scheduler-gmp/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior+&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Планировщик Go строится на трёх сущностях: G (горутина), M (поток ОС), P (логический процессор/контекст планирования). P держит локальную очередь runnable-горутин, число P = &lt;code&gt;GOMAXPROCS&lt;/code&gt;. Используются work stealing, кооперативная и (с 1.14) асинхронная вытесняющая преемпция, handoff P при блокирующих syscall и интегрированный netpoller для сетевого I/O.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="g-m-p"&gt;G, M, P&lt;a class="anchor" href="#g-m-p"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;G (&lt;code&gt;runtime.g&lt;/code&gt;)&lt;/strong&gt; — горутина: стек, контекст (&lt;code&gt;gobuf&lt;/code&gt;), статус.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M (&lt;code&gt;runtime.m&lt;/code&gt;)&lt;/strong&gt; — машина = поток ОС. Реально исполняет код. Чтобы исполнять Go-код, M должен владеть P.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P (&lt;code&gt;runtime.p&lt;/code&gt;)&lt;/strong&gt; — процессор/контекст: локальный runqueue (256 слотов, кольцевой буфер + &lt;code&gt;runnext&lt;/code&gt;), кэши аллокатора (&lt;code&gt;mcache&lt;/code&gt;), таймеры. Число P = &lt;code&gt;GOMAXPROCS&lt;/code&gt;. P — это «право исполнять Go-код».&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Инвариант: &lt;strong&gt;выполнять Go-код может только связка M+P&lt;/strong&gt;. Свободные P лежат в &lt;code&gt;sched.pidle&lt;/code&gt;; свободные M — в &lt;code&gt;sched.midle&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>select</title><link>https://go.vbloher.org/docs/02-concurrency/select/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/select/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;select&lt;/code&gt; ждёт готовности одного из нескольких канальных кейсов; при нескольких готовых выбирает &lt;strong&gt;псевдослучайно&lt;/strong&gt;. &lt;code&gt;default&lt;/code&gt; делает select неблокирующим. nil-каналы исключают кейс из выбора — основа для динамического включения/выключения и таймаутов/отмены через &lt;code&gt;time.After&lt;/code&gt;/&lt;code&gt;ctx.Done()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="семантика"&gt;Семантика&lt;a class="anchor" href="#%d1%81%d0%b5%d0%bc%d0%b0%d0%bd%d1%82%d0%b8%d0%ba%d0%b0"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Если готов &lt;strong&gt;один&lt;/strong&gt; кейс — он выполняется.&lt;/li&gt;
&lt;li&gt;Если готовы &lt;strong&gt;несколько&lt;/strong&gt; — выбирается случайный (равномерно, через &lt;code&gt;fastrandn&lt;/code&gt;), чтобы избежать голодания.&lt;/li&gt;
&lt;li&gt;Если &lt;strong&gt;ни один&lt;/strong&gt; не готов и есть &lt;code&gt;default&lt;/code&gt; — выполняется &lt;code&gt;default&lt;/code&gt; (неблокирующий select).&lt;/li&gt;
&lt;li&gt;Если ни один не готов и &lt;code&gt;default&lt;/code&gt; нет — горутина блокируется, пока какой-то кейс не станет готов.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Пустой &lt;code&gt;select{}&lt;/code&gt;&lt;/strong&gt; блокирует навсегда.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// отправили&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;After&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// таймаут&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ничего не готово прямо сейчас&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="под-капотом-runtimeselectgo"&gt;Под капотом (&lt;code&gt;runtime.selectgo&lt;/code&gt;)&lt;a class="anchor" href="#%d0%bf%d0%be%d0%b4-%d0%ba%d0%b0%d0%bf%d0%be%d1%82%d0%be%d0%bc-runtimeselectgo"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Кейсы перемешиваются в случайном &lt;code&gt;pollorder&lt;/code&gt; (для честного выбора).&lt;/li&gt;
&lt;li&gt;Каналы блокируются в фиксированном &lt;code&gt;lockorder&lt;/code&gt; по адресам — чтобы исключить дедлок при множественной блокировке.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Проход 1&lt;/strong&gt;: ищем уже готовый кейс (есть данные/место/закрыт). Нашли — выполняем.&lt;/li&gt;
&lt;li&gt;Если есть &lt;code&gt;default&lt;/code&gt; и готовых нет — выполняем &lt;code&gt;default&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Иначе&lt;/strong&gt;: регистрируем &lt;code&gt;sudog&lt;/code&gt; во всех каналах (в &lt;code&gt;sendq&lt;/code&gt;/&lt;code&gt;recvq&lt;/code&gt;), паркуемся.&lt;/li&gt;
&lt;li&gt;При пробуждении одним из каналов — снимаем регистрацию с остальных, выполняем выигравший кейс.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;То есть в худшем случае select дороже одиночной операции: O(числа кейсов) на регистрацию/дерегистрацию.&lt;/p&gt;</description></item><item><title>sync.WaitGroup</title><link>https://go.vbloher.org/docs/02-concurrency/waitgroup/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://go.vbloher.org/docs/02-concurrency/waitgroup/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Модуль: Concurrency · Уровень: Middle+/Senior&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="tldr"&gt;TL;DR&lt;a class="anchor" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WaitGroup&lt;/code&gt; — счётчик незавершённой работы: &lt;code&gt;Add(n)&lt;/code&gt; увеличивает, &lt;code&gt;Done&lt;/code&gt; уменьшает, &lt;code&gt;Wait&lt;/code&gt; блокирует, пока счётчик не дойдёт до нуля. Главное правило: &lt;strong&gt;&lt;code&gt;Add&lt;/code&gt; вызывать до запуска горутины&lt;/strong&gt; (в той же горутине, что и &lt;code&gt;Wait&lt;/code&gt;), а не внутри неё, иначе возможна гонка между &lt;code&gt;Wait&lt;/code&gt; и стартом. Go 1.25 добавил &lt;code&gt;WaitGroup.Go&lt;/code&gt;, который атомарно делает &lt;code&gt;Add(1)&lt;/code&gt; + запуск + &lt;code&gt;Done&lt;/code&gt;, устраняя самую частую ошибку.&lt;/p&gt;
&lt;h2 id="теория"&gt;Теория&lt;a class="anchor" href="#%d1%82%d0%b5%d0%be%d1%80%d0%b8%d1%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="контракт"&gt;Контракт&lt;a class="anchor" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitGroup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ДО go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// гарантированно даже при панике&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// блокируемся, пока счётчик != 0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Add(delta)&lt;/code&gt; — атомарно прибавляет delta (может быть отрицательным) к счётчику.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Done()&lt;/code&gt; — это &lt;code&gt;Add(-1)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Wait()&lt;/code&gt; — блокирует, пока счётчик не станет 0. Если уже 0 — возвращается сразу.&lt;/li&gt;
&lt;li&gt;Если счётчик становится &lt;strong&gt;отрицательным&lt;/strong&gt; → паника &lt;code&gt;sync: negative WaitGroup counter&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="устройство-под-капотом"&gt;Устройство под капотом&lt;a class="anchor" href="#%d1%83%d1%81%d1%82%d1%80%d0%be%d0%b9%d1%81%d1%82%d0%b2%d0%be-%d0%bf%d0%be%d0%b4-%d0%ba%d0%b0%d0%bf%d0%be%d1%82%d0%be%d0%bc"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;До Go 1.20 WaitGroup хранил счётчик и число ожидающих в одном 64-битном слове (с трюками выравнивания для 32-бит платформ). С Go 1.25 (рефакторинг под &lt;code&gt;WaitGroup.Go&lt;/code&gt;) внутреннее представление изменилось, но логически — это:&lt;/p&gt;</description></item></channel></rss>