Linker-оптимизации, cold start и connection pool tuning
Зачем знать на Middle 3: Бинарь в Docker 80 МБ vs 30 МБ, cold start 800 мс vs 200 мс, HTTP-клиент с дефолтным
MaxIdleConnsPerHost=2упирается в производительность — всё это разница «продакшен работает / не работает». На уровне Senior: умеешь стрипать debug info с пониманием trade-off, знаешь, куда уходят миллисекунды на старте Lambda, тюнишь pool для каждой зависимости осознанно, не по cargo cult.
Содержание
Заголовок раздела «Содержание»- Концепция
- Глубже / production-практики (linker, cold start, HTTP/gRPC/DB/Redis pools)
- Gotchas
- Real cases
- Вопросы (20)
- Practice
- Источники
1. Концепция
Заголовок раздела «1. Концепция»1.1 Размер бинарника: что туда входит
Заголовок раздела «1.1 Размер бинарника: что туда входит»Стандартный Go binary содержит:
- Code (.text): ваши функции + stdlib + dependencies.
- Symbols (.symtab): имена функций, переменных — для stack traces.
- DWARF (.debug_*): для debugger, source mapping.
- gopclntab: PC → line mapping для panic traces (Go-specific).
- Type info (.rodata): для reflect, interfaces.
- Static data: строки, литералы.
Пример: простой HTTP-сервис с chi + pgx = 18 МБ. С -s -w = 12 МБ. Без cgo = 11 МБ. После UPX = 4 МБ.
1.2 Cold start: что происходит при запуске
Заголовок раздела «1.2 Cold start: что происходит при запуске»[t=0] Контейнер shedule-ится на node[t=ms] Pull image (если не cached)[t=ms] Start process (kernel exec)[t=ms] Load binary в memory (linker work)[t=ms] Initialize runtime (allocator, scheduler, GC)[t=ms] Sequential init() в каждом package (по importer order)[t=ms] main() начинает[t=ms] Read config / env / secrets[t=ms] Open DB pool, Redis pool — handshake[t=ms] Register routes, listen on port[t=ms] Сервис готов[t=ms] Первый запрос — first request handlingВ serverless (AWS Lambda Go runtime, GCP Cloud Functions) — каждый из этих шагов уважительно входит в «cold start» билинг. Для Go типично 100–500 мс cold start, vs Node.js 300–1000, Python 500–2000, Java/JVM 2–10 секунд.
1.3 Connection pool: зачем
Заголовок раздела «1.3 Connection pool: зачем»Открытие TCP-соединения — 1 RTT (50+ мс на cross-region). TLS handshake — ещё 1–2 RTT. Если каждый запрос открывает новое соединение — latency взлетает.
Pool держит open соединения, повторно использует. Trade-offs:
- Слишком маленький pool → contention, queue.
- Слишком большой → file descriptors, memory, server-side limit.
2. Глубже / production-практики
Заголовок раздела «2. Глубже / production-практики»2.1 Linker flags: -s -w
Заголовок раздела «2.1 Linker flags: -s -w»go build -ldflags="-s -w" -o app-s: strip symbol table (.symtab,.strtab).-w: strip DWARF debug information.
Эффект: бинарь меньше на 25–35%. Trade-off: panic stack traces всё ещё работают (gopclntab не трогается), но debugger (delve) не сможет ставить breakpoints.
⚠️ Continuous profiling через eBPF (Parca) теряет имена функций — нужен debuginfod сервер с unstripped версией.
2.2 -trimpath
Заголовок раздела «2.2 -trimpath»go build -trimpath -o appУбирает абсолютные пути из binary. Без него panic выдаёт /Users/abylay/go/src/.../file.go:123. С -trimpath — просто myrepo/file.go:123.
Преимущества:
- Reproducible builds: бинарь не зависит от расположения исходников.
- Security: не light утечки внутренних путей.
- Smaller binary: пути занимают KB-MB.
В production CI всегда используйте -trimpath.
2.3 Inject build info
Заголовок раздела «2.3 Inject build info»VERSION=$(git describe --tags --always)COMMIT=$(git rev-parse --short HEAD)BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
go build -ldflags=" -X main.Version=$VERSION -X main.Commit=$COMMIT -X main.BuildTime=$BUILD_TIME -X main.GoVersion=$(go version | awk '{print $3}')" -o appВ коде:
var ( Version = "dev" Commit = "unknown" BuildTime = "unknown")
func main() { log.Printf("starting %s (%s) built at %s", Version, Commit, BuildTime)}⚠️ -X работает только для var string. Для const — не работает.
2.4 CGO_ENABLED=0
Заголовок раздела «2.4 CGO_ENABLED=0»CGO_ENABLED=0 go build -o appЕсли ваш код не использует CGO напрямую (нет import "C"), это:
- Уменьшает бинарь (нет libc, libdl линковки).
- Делает binary полностью статичным — работает в
FROM scratchDocker image. - Faster startup (нет dynamic linker).
⚠️ Некоторые stdlib пакеты используют CGO по умолчанию:
net— DNS через libc resolver (cgo).os/user—getpwnamчерез libc.
С CGO_ENABLED=0 Go использует pure-Go fallback. DNS через netgo resolver — может быть медленнее в edge cases.
Альтернатива: go build -tags netgo,osusergo.
2.5 UPX compression
Заголовок раздела «2.5 UPX compression»upx --best appCompresses бинарь в 2–4x. Trade-offs:
- Startup slower: на старте decompress в memory (~100–500 мс).
- AV false positives: антивирусы любят отмечать UPX как malware.
- Не работает на macOS: Apple’s signature verification ругается.
Не рекомендуется для serverless. Подходит для on-premise embedded.
2.6 TinyGo
Заголовок раздела «2.6 TinyGo»TinyGo — альтернативный компилятор для embedded и WASM. Использует LLVM backend.
tinygo build -o app -target=wasm- Binaries radically smaller (KBs vs MBs).
- Поддерживает WASM, ESP32, Raspberry Pi, etc.
- НЕ drop-in replacement: не вся stdlib поддерживается (
net/http— частично), нет goroutine scheduler такой же зрелости.
Для микросервисов в Kubernetes — стандартный Go. TinyGo для edge / IoT / WASM-плагинов.
2.7 Build cache и module cache
Заголовок раздела «2.7 Build cache и module cache»~/.cache/go-build/ # compiled package artifacts~/go/pkg/mod/ # downloaded source archives~/go/pkg/sumdb/ # checksum databaseВ CI:
- uses: actions/cache@v3 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}С cached build: повторная сборка 10–50x быстрее.
GOMODCACHE и GOCACHE env vars позволяют управлять расположением.
2.8 Reducing binary size: чек-лист
Заголовок раздела «2.8 Reducing binary size: чек-лист»-ldflags="-s -w"— 25–35% reduction.-trimpath— minor, но полезно.CGO_ENABLED=0— может убрать 1–3 МБ.- Удалить unused imports (особенно large ones типа
aws-sdk-go-v2— модульно подключать только нужные сервисы). - Заменить тяжёлые dependencies (например, использовать
valyala/fasthttpвместоnet/http— спорно). go-binsize-vizдля анализа: что занимает место.
go install github.com/knz/go-binsize-viz@latestgo-binsize-viz -o report.html appПокажет sunburst chart, где gopclntab 4 МБ, lib/pq 1.2 МБ, etc.
2.9 Where cold start time goes
Заголовок раздела «2.9 Where cold start time goes»Замер:
var startTime = time.Now()
func init() { log.Printf("init() at %v", time.Since(startTime))}
func main() { log.Printf("main() at %v", time.Since(startTime)) // ... log.Printf("listening at %v", time.Since(startTime))}Типичный breakdown для 30 МБ Go binary в Lambda:
[0 ms] exec started[20 ms] runtime initialized[40 ms] init() functions начали выполняться[80 ms] init() закончились (40 packages)[100 ms] main() начался[150 ms] AWS SDK client created[200 ms] DB pool опционально open[250 ms] готов к запросамГде может «съесть» секунды:
- AWS SDK v1 init: 200–500 мс (читает credentials chain, region).
- gRPC pool с TLS: 100–300 мс на handshake.
- Vault / Secret Manager fetch: 100 мс — 1 сек.
- Schema migration check: 100–1000 мс.
2.10 Reducing cold start
Заголовок раздела «2.10 Reducing cold start»1. Smaller binary → faster load.
2. Lazy init:
var ( s3ClientOnce sync.Once s3Client *s3.Client)
func getS3Client() *s3.Client { s3ClientOnce.Do(func() { s3Client = s3.NewFromConfig(...) }) return s3Client}Не открывайте все clients в main(). Открывайте на первое использование.
3. Reduce import surface:
- Не импортируйте
aws-sdk-go-v2/awsцеликом — толькоservice/s3,service/dynamodb, etc. - Избегайте
init()функций в зависимостях (трудно контролировать).
4. Avoid heavy reflect at init: init() функция, которая парсит большие struct tags через reflect, добавит ms-s.
2.11 Serverless cold start considerations
Заголовок раздела «2.11 Serverless cold start considerations»AWS Lambda Go runtime:
- Container reused для warm invocations.
- Cold start = новый container.
- Provisioned concurrency: pre-warmed containers (не cold start, но платишь за idle).
Lambda Go vs Node.js:
- Go ~150–250 мс cold start typically.
- Node.js ~250–500 мс.
- Java ~1500–3000 мс (без SnapStart).
GCP Cloud Functions / Cloud Run:
- Cloud Run gen2 cold start ~100–500 мс для Go.
- Min instances = 1 убирает cold start, но платишь за idle.
⚠️ SnapStart (AWS) для Java делает snapshot JVM state. Для Go это не нужно — Go уже быстро стартует.
Keep-warm pattern: периодически вызываете Lambda (e.g. CloudWatch Events каждые 5 минут) чтобы избежать cold start. Но в 2026 это считается анти-паттерн — лучше provisioned concurrency.
2.12 PGO (Profile-Guided Optimization)
Заголовок раздела «2.12 PGO (Profile-Guided Optimization)»Go 1.21+ поддерживает PGO:
# 1. Build with profile collection enabledgo build -o app
# 2. Run в production, собрать pprof CPU profilecurl http://app:6060/debug/pprof/profile?seconds=60 > default.pgo
# 3. Поместить в module root, rebuildmv default.pgo ./go build -o app # подхватит default.pgo automaticallyЭффект: 2–7% improvement на hot path через лучший inlining decisions.
⚠️ PGO не помогает cold start — оптимизирует hot path который runs много раз.
2.13 HTTP client transport tuning
Заголовок раздела «2.13 HTTP client transport tuning»transport := &http.Transport{ MaxIdleConns: 100, // total idle across all hosts MaxIdleConnsPerHost: 50, // ⚠️ default = 2! MaxConnsPerHost: 100, // hard cap IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, ResponseHeaderTimeout: 30 * time.Second,
// DialContext customization DialContext: (&net.Dialer{ Timeout: 10 * time.Second, KeepAlive: 30 * time.Second, }).DialContext,
// ForceAttemptHTTP2: true, // default true в новых Go}
client := &http.Client{ Transport: transport, Timeout: 30 * time.Second,}⚠️ Главный gotcha: MaxIdleConnsPerHost = 2 по умолчанию. Это означает: каждый раз когда вы делаете 3+ concurrent request к одному hostname — открываются новые TCP, после ответа закрываются. На каждый RPS открытие TCP + TLS — это десятки ms.
В production set MaxIdleConnsPerHost ≥ peak concurrent connections to that host.
2.14 Default values gotchas
Заголовок раздела «2.14 Default values gotchas»| Setting | Default | Sane prod value |
|---|---|---|
MaxIdleConns | 100 | 100–500 |
MaxIdleConnsPerHost | 2 | 20–100 |
MaxConnsPerHost | 0 (unlimited) | 100–500 |
IdleConnTimeout | 90s | 90s OK |
TLSHandshakeTimeout | 0 (no timeout) | 10s |
ResponseHeaderTimeout | 0 (no timeout) | 30s |
client.Timeout | 0 (no timeout) | 30s |
⚠️ Без client.Timeout ваш сервис может зависнуть навсегда на slow downstream.
2.15 Disable keep-alive (rare)
Заголовок раздела «2.15 Disable keep-alive (rare)»transport := &http.Transport{ DisableKeepAlives: true,}Когда: behind load balancer, который sticky-route-ит по TCP connection — keep-alive нарушает rebalance. Это редкий случай, но встречается.
⚠️ Без keep-alive каждый запрос — full TCP handshake. На high RPS это catastrophic.
2.16 gRPC ClientConn pool
Заголовок раздела «2.16 gRPC ClientConn pool»gRPC создаёт один HTTP/2 connection с multiplexing. Один stream = один RPC. HTTP/2 поддерживает 100 concurrent streams по умолчанию.
Для high RPS (>1000 RPS на один backend), нужно несколько connections:
import "google.golang.org/grpc"
// Variant 1: round-robin balancer + DNS resolverconn, err := grpc.Dial("dns:///service.local:5000", grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`), grpc.WithTransportCredentials(insecure.NewCredentials()),)
// Variant 2: pool of ClientConn manuallytype ClientPool struct { conns []*grpc.ClientConn next atomic.Uint32}
func (p *ClientPool) Get() *grpc.ClientConn { i := p.next.Add(1) % uint32(len(p.conns)) return p.conns[i]}⚠️ Single grpc.ClientConn сегодня (с HTTP/2 multiplexing) часто достаточно. Pool нужен только когда: stream limit hit, high RPS, или нужна distribution по backends.
2.17 Database connection pool
Заголовок раздела «2.17 Database connection pool»database/sql:
db, err := sql.Open("postgres", connStr)
db.SetMaxOpenConns(25) // totaldb.SetMaxIdleConns(25) // == MaxOpenConns для steady-statedb.SetConnMaxLifetime(30 * time.Minute) // recycle для load balancer rebalancingdb.SetConnMaxIdleTime(5 * time.Minute)Rule of thumb: MaxOpenConns = (CPU cores на DB) × 2. Для Postgres с 4 ядрами — 8 connections per app. Если у вас 10 app instances → 80 connections к DB.
⚠️ Если MaxOpenConns слишком высокий — connection storm после restart. Если слишком низкий — request queue.
⚠️ ConnMaxLifetime = 0 означает «forever». В cloud DB это плохо: load balancer не сможет dropp connections, rebalance не работает. Set 30 мин.
2.18 pgbouncer modes
Заголовок раздела «2.18 pgbouncer modes»Между app и Postgres ставят pgbouncer для connection multiplexing:
- Session pooling: одна client connection → одна server connection пока активна. Минимальный overhead, но ограниченный throughput.
- Transaction pooling: одна client connection → server connection на время транзакции. После commit/rollback — server connection возвращается в pool. Высокий throughput. Limitation: нельзя использовать session features (prepared statements в Postgres < 14, advisory locks, LISTEN/NOTIFY).
- Statement pooling: одна connection на один statement. Очень редко, almost no Go драйвер с этим работает.
В Postgres 14+ + pgx с protocol_simple или mode: 'session' для prepared statements можно.
2.19 Redis pool (go-redis)
Заголовок раздела «2.19 Redis pool (go-redis)»import "github.com/redis/go-redis/v9"
rdb := redis.NewClient(&redis.Options{ Addr: "redis:6379", PoolSize: 50, // default = 10 * NumCPU MinIdleConns: 10, MaxIdleConns: 50, PoolTimeout: 4 * time.Second,
DialTimeout: 5 * time.Second, ReadTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second,})⚠️ PoolSize default 10 * NumCPU — на 8-core машине = 80. Для small services это перебор; для high-throughput может не хватать.
⚠️ Pipeline / transaction holds connection на всё время. Если pipeline крупный — pool exhaust.
2.20 Monitoring pool stats
Заголовок раздела «2.20 Monitoring pool stats»database/sql:
stats := db.Stats()log.Printf("open=%d, idle=%d, inuse=%d, waitCount=%d, waitDuration=%v", stats.OpenConnections, stats.Idle, stats.InUse, stats.WaitCount, stats.WaitDuration)WaitCount > 0 = ваш pool exhaust. Increase MaxOpenConns или диагностировать slow queries.
go-redis:
stats := rdb.PoolStats()// Hits, Misses, Timeouts, TotalConns, IdleConns, StaleConnsЭкспортируйте в Prometheus, alert когда wait_duration растёт.
3. Gotchas
Заголовок раздела «3. Gotchas»⚠️ -ldflags="-s -w" ломает eBPF profiling. Не имена функций — адреса. Решение: debuginfod.
⚠️ -trimpath ломает IDE stack trace navigation. В dev не использовать, только в release builds.
⚠️ CGO_ENABLED=0 меняет DNS resolver. Pure-Go resolver не понимает /etc/nsswitch.conf сложности. Edge cases возможны.
⚠️ UPX не работает на macOS (Apple notarization). Не используйте для macOS dist.
⚠️ init() functions выполняются sequentially в alphabetical order по dependency graph. Тяжёлый init в нижнем пакете тормозит весь startup.
⚠️ Lambda Go runtime в 2026 — provided.al2 или provided.al2023 (Amazon Linux 2/2023). go1.x runtime deprecated. Build с GOOS=linux GOARCH=arm64 для Graviton (дешевле).
⚠️ MaxIdleConnsPerHost=2 default — главная причина «high latency» в HTTP-клиентах. Чек-лист: всегда override.
⚠️ HTTP/2 over HTTPS — ForceAttemptHTTP2: true для use HTTP/2, requires TLS.
⚠️ gRPC keepalive on idle. Если connection idle > N минут, server может закрыть. grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: 30 * time.Second}).
⚠️ ConnMaxLifetime = 0 = connections never recycle = load balancer rebalance не работает = stale routing.
⚠️ Transaction pooling в pgbouncer ломает prepared statements в pgx (есть workarounds для PG14+).
⚠️ Connection storm после restart. Если 10 instances одновременно стартуют и open MaxOpenConns=25 each → 250 connections instantly. DB может не справиться. Решение: stagger startup, gradual ramp.
⚠️ PGO profile должен быть representative. Если вы взяли profile только с low traffic — оптимизация для wrong path.
4. Real cases
Заголовок раздела «4. Real cases»Case 1: 80 МБ → 25 МБ binary
Заголовок раздела «Case 1: 80 МБ → 25 МБ binary»Контекст: микросервис на Go использует AWS SDK v1 целиком (github.com/aws/aws-sdk-go). Бинарь 80 МБ.
Действия:
- Migrate to AWS SDK v2 (
github.com/aws/aws-sdk-go-v2). Modular imports. - Import только
service/s3,service/sqs(не v1 monolith). -ldflags="-s -w" -trimpath.CGO_ENABLED=0.
Результат: 25 МБ. Container image (alpine) 35 МБ vs 90 МБ. Pull time ↓ 60%.
Case 2: Lambda cold start 800ms → 250ms
Заголовок раздела «Case 2: Lambda cold start 800ms → 250ms»Контекст: AWS Lambda на Go, p99 cold start 800 мс.
Анализ: добавили log.Printf на каждом этапе.
- 100 мс —
init()функции. - 300 мс —
aws.Configcreation (credentials chain). - 200 мс — DB pool open (1 connection eager).
- 100 мс — schema migration check.
Действия:
- Lazy init AWS clients.
- DB pool open в первом request, не в
main(). - Migration check вынесли в separate deploy step.
- Replace heavy YAML parsing с JSON для config.
Результат: cold start 250 мс. Cost экономия ~$300/mo (provisioned concurrency не нужна).
Case 3: HTTP-client throughput 10x
Заголовок раздела «Case 3: HTTP-client throughput 10x»Контекст: payment service делает 5000 RPS к downstream API. p99 latency 2 секунды.
Анализ: dump goroutines — 80% в tls.Handshake. Default MaxIdleConnsPerHost=2 означает: на 5000 RPS, 99% requests открывает новый TCP. TLS handshake → ~30 мс. На concurrent request это 2-секундный queue.
Fix:
transport.MaxIdleConnsPerHost = 100transport.MaxConnsPerHost = 200transport.IdleConnTimeout = 90 * time.SecondРезультат: p99 200 мс. Throughput удвоился.
Case 4: Postgres connection storm
Заголовок раздела «Case 4: Postgres connection storm»Контекст: deploy 20 instances одновременно. Каждый MaxOpenConns=25. DB max_connections=300.
Симптом: 5 минут после deploy — много FATAL: too many connections.
Анализ: 20 × 25 = 500 connections. DB лимит 300.
Fix:
- pgbouncer в transaction mode между app и DB. App ↔ pgbouncer (500 connections OK). pgbouncer ↔ DB (50 connections).
MaxOpenConns = 10per instance (был 25). 20 × 10 = 200.
Результат: stable.
Case 5: Redis pool тестинг
Заголовок раздела «Case 5: Redis pool тестинг»Контекст: high-throughput caching, периодические pool timeout.
Анализ: pool size 30 (NumCPU=3 × 10). При пиках concurrent requests > 30 → timeout.
Fix:
PoolSize = 100.MinIdleConns = 20— warm pool.- Pipelining для bulk operations.
Результат: zero pool timeouts.
5. Вопросы (20)
Заголовок раздела «5. Вопросы (20)»- Что делает
-ldflags="-s -w"и какой trade-off? - Зачем
-trimpathв production builds? - Как inject version/commit в Go binary через linker?
- CGO_ENABLED=0: что меняется в DNS resolution?
- Когда UPX-компрессия Go binary оправдана, когда — нет?
- TinyGo: для чего и когда не подходит?
- Что такое gopclntab и почему его strip не получается стандартным
-s? - Опишите этапы cold start Go-приложения.
- Где обычно «теряются» миллисекунды на старте Lambda?
- Lazy init через
sync.Once: пример для AWS SDK client. - PGO в Go 1.21+: что оптимизирует, а что — нет?
MaxIdleConnsPerHostdefault = 2. Чем это плохо в production?- Опишите все ключевые timeouts в
http.Transportиhttp.Client. - Когда
DisableKeepAlives: trueоправдан? - gRPC: нужен ли pool ClientConn для high RPS? Когда?
SetConnMaxLifetime= 0 — почему плохо в cloud?- pgbouncer modes: session vs transaction vs statement. Когда какой?
- go-redis PoolSize default — какое значение и в чём подвох?
- Какие метрики connection pool важно alert-ить?
- Опишите кейс connection storm после deploy и его решение.
6. Practice
Заголовок раздела «6. Practice»Задача 1: Собрать Go-сервис в трёх вариантах: default, с -s -w -trimpath, и с CGO_ENABLED=0. Сравнить размеры. Использовать go-binsize-viz для анализа.
Задача 2: Замерить cold start локально — добавить log.Printf в init() и main(). Найти, где «уходит» время.
Задача 3: Развернуть Go-сервис в AWS Lambda с default config и с optimizations (lazy init, smaller binary, ARM64 Graviton). Сравнить p99 cold start.
Задача 4: Написать HTTP-клиент с default transport и с tuned transport (MaxIdleConnsPerHost=50). Замерить throughput через wrk на endpoint, который делает downstream call.
Задача 5: Реализовать httpClient.Stats() (через middleware), экспортировать в Prometheus.
Задача 6: Сэмулировать pool exhaustion: сервис с MaxOpenConns=5, load 50 concurrent requests. Замерить WaitCount, WaitDuration.
Задача 7: Поднять pgbouncer (docker), переключиться в transaction mode, протестировать с prepared statements (должно сломаться без protocol_simple).
Задача 8: Применить PGO к Go-сервису: собрать profile, rebuild, замерить difference.
7. Источники
Заголовок раздела «7. Источники»- The Go Blog, “Smaller Go 1.7 binaries”, https://go.dev/blog/go1.7-binary-size
- The Go Blog, “Profile-guided optimization in Go 1.21”, https://go.dev/blog/pgo
- Filippo Valsorda, “Reducing Go binary size”, 2018.
- AWS Lambda Go runtime documentation, https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html
- Cloudflare blog, “Optimizing Go for HTTP/2 production”, 2019.
- Vincent Blanchon, “Tuning Go HTTP client”, 2020.
- Marcus Olsson, “Database connection pooling in Go”, 2021.
- PgBouncer documentation, https://www.pgbouncer.org/usage.html
- go-redis documentation, https://redis.uptrace.dev/
- Brad Fitzpatrick, “net/http internals”, GopherCon talks.
- The Go runtime source:
src/runtime/proc.gofor startup sequence. - AWS Lambda Powertools for Go.
- Russ Cox, “The Go Linker”, talks 2017–2022.
- Damian Gryski, “Go performance recipes”, https://github.com/dgryski/go-perfbook
- Bram Gruneir, “Tuning Postgres for cloud”, talks 2022.