New We are sorting MAX channels into categories and territories. Read the news
Public channel ID 72625731109378

Библиотека Go (Golang) разработчика

Полезные материалы по всему, что может быть полезно Golang разработчику.

Channels in category
2 098
This channel's rank
№ 41
Open category →
Channels in category
20 116
This channel's rank
№ 168
Open category →
Open in MAX

Posts for the period

By reactions

Search and the whole feed — after free registration. Custom dates — with a subscription; the channel owner sees them for free. Sorting by reactions — with a subscription. Sign up

Loading…

📝 log/slog: Хватит парсить логи глазами (и тащить Logrus в проект)

Два часа ночи. Прод лежит. Вы сидите в терминале и пытаетесь выцепить причину через grep "error" | grep "user_1234". Знакомая жиза?

Долгие годы стандартный пакет log в Go был... ну, скажем честно, спартанским. Он писал простой текст. А современные системы мониторинга (ELK, Datadog, Grafana Loki) любят структурированные данные (JSON или Key-Value), чтобы по ним можно было строить графики и алерты.

Поэтому каждый Go-разработчик тащил в проект сторонние комбайны: logrus (который официально в режиме maintenance с 2020 года), zerolog или uber-go/zap.

С выходом Go 1.21 праздник наступил и на нашей улице - в стандартную библиотеку завезли log/slog.

Почему slog - это круто?
Он быстрый, расширяемый и, главное, избавляет от сторонних зависимостей.



Как это выглядит:

import "log/slog"

func main() {
// Создаем JSON-хендлер (идеально для прода)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

// Делаем его логгером по умолчанию для всего приложения
slog.SetDefault(logger)

// Пишем структурированно
slog.Info("user logged in",
slog.Int("user_id", 42),
slog.String("ip", "192.168.1.1"),
)
}
// Вывод:
// {"time":"2023-10-26T15:04:05Z", "level":"INFO", "msg":"user logged in", "user_id":42, "ip":"192.168.1.1"}


🔥 Нюанс для Senior-ов: Интерфейс LogValuer

Самая частая проблема логирования, случайно слить в логи пароль, токен или кредитную карту (PII данные), передав структуру целиком.
В slog есть гениальная вещь - интерфейс LogValuer. Если ваша структура его реализует, логгер вызовет его до сериализации.

type User struct {
ID int
Name string
Password string // СЕКРЕТ!
}

// Учим структуру безопасно логировать саму себя
func (u User) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("id", u.ID),
slog.String("name", u.Name),
slog.String("password", "***REDACTED***"), // Скрыли!
)
}

func main() {
user := User{ID: 1, Name: "Ivan", Password: "super_secret_123"}

// Даже если джун случайно залогирует юзера целиком:
slog.Info("created user", slog.Any("user", user))
// В логах будет: "password": "***REDACTED***". Прод спасен.
}

Конечно, zap всё ещё чуть быстрее на экстремальных нагрузках благодаря жесткой кодогенерации, но для 95% микросервисов slog - это новый абсолютный стандарт.

#golang #logging #slog #bestpractices #observability

👉 @golang_lib
Views: 85
Go to post Open in MAX Report an inaccuracy
👾 ЛЦТ.Фест 2026 – знакомьтесь с технологиями, которые меняют город и бизнес

30 октября в кластере «Ломоносов» в Москве встретятся студенты ИТ- и инженерных направлений, специалисты технологической отрасли и все, кому интересны современные цифровые разработки России.

В программе: выступления экспертов, интерактивные площадки и знакомство с решениями для города и бизнеса. Приходите узнать больше о технологиях, пообщаться с представителями индустрии, узнать о стажировках и вакансиях в крупных компаниях, найти единомышленников и узнать, кто разделит призовой фонд хакатона «Лидеры цифровой трансформации» в 40 млн рублей.

📍 Москва, Раменский бульвар, 1
Вход свободный по предварительной регистрации (количество мест ограничено)
Views: 198
Go to post Open in MAX Report an inaccuracy
🧠 Go Memory Model: Почему ваша горутина слепая (и что такое Happens-Before)

Напишем простейший код. Одна горутина меняет флаг, другая ждет этого изменения:

var done bool
var msg string

func setup() {
msg = "hello, world"
done = true
}

func main() {
go setup()

// Ждем, пока setup() закончит работу
for !done {
}

fmt.Println(msg)
}

Джун посмотрит на этот код и скажет: "Ну, он выведет hello, world".
Сеньор посмотрит на этот код и скажет: "Этот код может уйти в бесконечный цикл, может вывести пустую строку, а может работать нормально. Это лотерея".

Добро пожаловать в жестокий мир Go Memory Model.

Иллюзия мгновенной памяти

Мы привыкли думать, что память - это единый монолит. Записал переменную - она тут же стала доступна всем. Но в реальности код выполняется на современных многоядерных процессорах. У каждого ядра есть свой сверхбыстрый локальный кэш (L1/L2).

Когда горутина setup пишет done = true, процессор может положить это значение в свой локальный кэш и не сбрасывать его в общую оперативную память (RAM) еще миллисекунды. А горутина main, работающая на другом ядре, будет бесконечно читать старое значение done == false из своего собственного кэша.

Но и это не всё! Компилятор Go невероятно умен. Он смотрит на цикл for !done {} и думает: "Внутри этого цикла переменная done не меняется. Зачем мне читать её из памяти на каждой итерации? Прочитаю один раз, сохраню в регистр процессора и буду проверять его". Итог - вечный цикл (Infinite Loop).

Святой Грааль: Happens-Before (Происходит-До)

Чтобы навести порядок в этом хаосе, создатели Go написали формальный документ - Модель Памяти (Memory Model). В её основе лежит концепция Happens-Before.

Если событие A происходит до события B, то Модель Памяти гарантирует, что горутина, выполняющая B, абсолютно точно увидит изменения, сделанные в A.

Если между действиями нет связи Happens-Before, компилятор и процессор имеют право переупорядочивать инструкции как им вздумается, а кэши могут не синхронизироваться.

Как создать связь Happens-Before?

Вам нужны примитивы синхронизации. Они работают как жесткие барьеры памяти (Memory Barriers), заставляя процессоры сбросить кэши, а компилятор - прекратить умничать с перестановкой строк кода.

✅ 1. Каналы (Channels)
Отправка данных в канал происходит до завершения чтения из этого канала в другой горутине.

c := make(chan struct{})
go func() {
msg = "hello"
c <- struct{}{} // Happens-Before
}()
<-c // Ждем
fmt.Println(msg) // 100% увидит "hello"


✅ 2. Мьютексы (Mutexes)
Снятие блокировки mu.Unlock() происходит до успешного захвата mu.Lock() другой горутиной.

✅ 3. Атомики (sync/atomic)
Начиная с Go 1.19, в Модель Памяти официально добавили пакет atomic. Атомарные операции теперь гарантированно создают связь Happens-Before.

✅ 4. Жизненный цикл горутины
Создание горутины (go func()) происходит до начала выполнения её кода. А вот завершение горутины не имеет гарантий! (Именно поэтому нам нужен sync.WaitGroup, чтобы дождаться её финала).

🔥 Senior Tip: Не пытайтесь обмануть систему

Существует соблазн написать код, который работает "без блокировок", опираясь на задержки: time.Sleep(time.Millisecond). Это называется data race (состояние гонки). Даже если на вашей машине с процессором Intel код работает идеально, завтра его скомпилируют под ARM-архитектуру (Apple Silicon или AWS Graviton), у которой гораздо более слабая модель памяти процессора, и ваш код рассыплется в пыль.

Всегда используйте флаг -race во время локальной разработки и CI: go test -race ./.... Он ловит отсутствие связей Happens-Before с хирургической точностью.

#golang #underhood #architecture #performance #memorymodel

👉 @golang_lib
Views: 441
Go to post Open in MAX Report an inaccuracy
🛑 Graceful Shutdown: Хватит обрывать запросы при деплое

Знакомая ситуация: вы катите новую версию микросервиса в Kubernetes, и в этот момент графики в Grafana на пару секунд краснеют от 502/503 ошибок. А потом всё снова хорошо.

Что произошло? Оркестратор убил старый под, а ваш Go-сервис моментально схлопнулся, оборвав на полуслове десятки текущих HTTP-запросов, сломав пару транзакций к базе и бросив недокачанные файлы.

Как это работает в здоровом проде?
Когда Kubernetes хочет обновить под, он не стреляет ему в голову сразу. Он посылает вежливый сигнал SIGTERM и дает время (обычно 30 секунд), чтобы собрать вещи.
Мы должны перехватить этот сигнал и сказать нашему HTTP-серверу: "Новые запросы не принимай, а те, что уже в работе - спокойно доделывай".

Как это написать:
Забудьте про ручное создание make(chan os.Signal). Используйте signal.NotifyContext.

func main() {
// 1. Создаем контекст, который отменится при SIGINT (Ctrl+C) или SIGTERM (Docker/K8s)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

srv := &http.Server{Addr: ":8080", Handler: mux}

// 2. Запускаем сервер в отдельной горутине!
go func() {
// http.ErrServerClosed возвращается при успешном Shutdown, это не ошибка
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("Критическая ошибка сервера: %v", err)
}
}()

// 3. Блокируемся и ждем сигнала от ОС
<-ctx.Done()
log.Println("Получен сигнал завершения. Тушим свет...")

// 4. Создаем контекст с таймаутом для Shutdown
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// 5. Плавно останавливаем сервер (ждет завершения текущих хендлеров)
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("Сервер не смог остановиться плавно: %v", err)
}

// 6. Здесь закрываем коннекты к БД, RabbitMQ, сбрасываем кэши в Redis
// db.Close()

log.Println("Сервер успешно завершил работу.")
}

🔥 Нюансы для Senior-ов:
• Обязательно ставьте таймаут на Shutdown(). Если клиент с медленным интернетом качает от вас гигабайтный файл, srv.Shutdown будет ждать его вечно. В итоге K8s устанет ждать свои 30 секунд и жестко убьет под через SIGKILL (kill -9). Контролируйте таймаут сами (Шаг 4).

• Порядок важен. Сначала останавливаем HTTP-сервер (чтобы перестали прилетать новые задачи), и только потом закрываем пул соединений с БД. Если сделать наоборот, текущие HTTP-запросы упадут с ошибкой sql: database is closed.

#golang #architecture #kubernetes #bestpractices #devops

👉 @golang_lib
Views: 615
Go to post Open in MAX Report an inaccuracy
🚀 Подборка полезных IT каналов в Max


Системное администрирование, DevOps 📌

https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.
https://max.ru/tipsysdmin Типичный Сисадмин
https://max.ru/channel_win_sysadmin Системный Администратор Windows
https://max.ru/channel_linux_admin Linux: Системный администратор
https://max.ru/channel_linuxmod Linux
https://max.ru/channel_i_linux Системный администратор
https://max.ru/channel_devopslib DevOps, SRE, Sysadmin
https://max.ru/channel_devops_star DevOps Star (Звезда Девопса)

Excel лайфхак 📌
https://t.me/Excel_lifehack Excel лайфхак

Английский с нуля 🇬🇧
https://max.ru/UchuEnglish

1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С
https://max.ru/channel_DevLab1C 1С:Предприятие 8

Программирование C++📌
https://max.ru/cpp_lib Библиотека C/C++ разработчика
https://max.ru/channel_cpp_geek C++ geek

Программирование Go📌
https://max.ru/golang_lib Библиотека Go (Golang) разработчика

Программирование React📌
https://max.ru/react_lib React

Программирование Rust📌
https://max.ru/channel_rust_lib

Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика

Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика
https://max.ru/channel_java_geek Java Geek

GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub

Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных

Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков

Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов

Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻

Шутки программистов 📌
https://max.ru/itumor Шутки программистов

Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free

Книги, статьи для дизайнеров 📌
https://max.ru/odesigners Статьи, книги для дизайнеров

Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике

Вакансии в IT📌
https://max.ru/progjob
https://max.ru/channel_rabotait

Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных

Городские📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
https://max.ru/piterspb Питер Новости: Санкт-Петербург / СПБ / ДТП
https://max.ru/channel_krasnodar_novosty Краснодар Новости
https://max.ru/channel_novosibirsk_novosti Новосибирск
https://max.ru/channel_samara_novosti Новости Самары
https://max.ru/channel_ekaterinburg_novosti Новости Екатеринбурга
https://max.ru/channel_kazan_novosti Новости Казани
https://max.ru/channel_omsk_novosti Новости Омска
https://max.ru/channel_moskva_24 Москва 24
Views: 496
Go to post Open in MAX Report an inaccuracy
🐳 Идеальный Dockerfile для Go. Худеем с 800 МБ до 10 МБ

Знакомая картина: джун приносит микросервис, который делает пару запросов к БД, а Docker-образ весит под гигабайт. Почему? Потому что в FROM гордо красуется golang:latest.

Вместе с вашим кодом в продакшен уезжает весь тулчейн компилятора, операционная система, куча утилит (шелл, curl) и, как бонус, пачка потенциальных уязвимостей.

Прелесть Go в том, что он компилирует всё в один независимый бинарный файл. Нам не нужен Go в рантайме! Наш путь - Multi-stage builds (Многоэтапная сборка).

Вот "золотой стандарт" Dockerfile, который работает в 99% случаев:

# === Этап 1: Сборка (Builder) ===
FROM golang:1.22-alpine AS builder

WORKDIR /app

# Шаг 1: Кэшируем зависимости
# Этот слой пересоберется ТОЛЬКО если изменились go.mod или go.sum
COPY go.mod go.sum ./
RUN go mod download

# Шаг 2: Копируем код
COPY . .

# Шаг 3: Собираем бинарник
# CGO_ENABLED=0 - критически важно для статической линковки
# -ldflags="-w -s" - вырезаем отладочную информацию
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/bin/service ./cmd/server/main.go

# === Этап 2: Финальный образ (Runner) ===
# Используем distroless вместо alpine или scratch
FROM gcr.io/distroless/static-debian12:nonroot

WORKDIR /app

# Забираем только готовый бинарник из первого этапа
COPY --from=builder /app/bin/service .

# Контейнер будет работать от непривилегированного пользователя
USER nonroot:nonroot

EXPOSE 8080
ENTRYPOINT ["/app/service"]


Разбираем нюансы под капотом (Senior Edition):

• Зачем нужен CGO_ENABLED=0? По умолчанию Go может динамически линковаться с системными C-библиотеками (например, libc). Если вы соберете такой бинарник и положите в пустой образ, он упадет при старте. Отключая CGO, мы принудительно делаем статическую линковку - всё необходимое запекается прямо внутрь файла.

• Флаги -ldflags="-w -s": Они вырезают DWARF-таблицы и символы отладки. Это бесплатно срезает ~20-30% веса бинарника. Не переживайте, трейсы паник (stack traces) всё ещё будут показывать номера строк, так что для прода это безопасно.

• Почему distroless/static, а не scratch или alpine? * В scratch (абсолютно пустом образе) нет корневых сертификатов (ca-certificates). Как только ваш сервис попытается сделать внешний HTTPS-запрос, он упадет. Там также нет таймзон (tzdata).
- В alpine есть sh (шелл) и пакетный менеджер apk - это векторы атаки для хакеров, если они найдут RCE в вашем коде.
- Google Distroless содержит только сертификаты, таймзоны и юзера nonroot. Идеальный баланс микроскопического размера (~2 МБ) и безопасности.

У кого образы до сих пор весят больше 50 МБ - срочно переписывать!

#golang #docker #devops #architecture #security

👉 @golang_lib
Views: 538
Go to post Open in MAX Report an inaccuracy
🧠 Go Memory Model: Почему ваша горутина слепая (и что такое Happens-Before)

Напишем простейший код. Одна горутина меняет флаг, другая ждет этого изменения:

var done bool
var msg string

func setup() {
msg = "hello, world"
done = true
}

func main() {
go setup()

// Ждем, пока setup() закончит работу
for !done {
}

fmt.Println(msg)
}


Джун посмотрит на этот код и скажет: "Ну, он выведет `hello, world`".
Сеньор посмотрит на этот код и скажет: "Этот код может уйти в бесконечный цикл, может вывести пустую строку, а может работать нормально. Это лотерея".

Добро пожаловать в жестокий мир Go Memory Model.

Иллюзия мгновенной памяти

Мы привыкли думать, что память - это единый монолит. Записал переменную — она тут же стала доступна всем. Но в реальности код выполняется на современных многоядерных процессорах. У каждого ядра есть свой сверхбыстрый локальный кэш (L1/L2).

Когда горутина setup пишет done = true, процессор может положить это значение в свой локальный кэш и не сбрасывать его в общую оперативную память (RAM) еще миллисекунды. А горутина main, работающая на другом ядре, будет бесконечно читать старое значение done == false из своего собственного кэша.

Но и это не всё! Компилятор Go невероятно умен. Он смотрит на цикл for !done {} и думает: "Внутри этого цикла переменная done не меняется. Зачем мне читать её из памяти на каждой итерации? Прочитаю один раз, сохраню в регистр процессора и буду проверять его". Итог - вечный цикл (Infinite Loop).

Святой Грааль: Happens-Before (Происходит-До)

Чтобы навести порядок в этом хаосе, создатели Go написали формальный документ - Модель Памяти (Memory Model). В её основе лежит концепция Happens-Before.

> Если событие A происходит до события B, то Модель Памяти гарантирует, что горутина, выполняющая B, абсолютно точно увидит изменения, сделанные в A.

Если между действиями нет связи Happens-Before, компилятор и процессор имеют право переупорядочивать инструкции как им вздумается, а кэши могут не синхронизироваться.

Как создать связь Happens-Before?

Вам нужны примитивы синхронизации. Они работают как жесткие барьеры памяти (Memory Barriers), заставляя процессоры сбросить кэши, а компилятор - прекратить умничать с перестановкой строк кода.

✅ 1. Каналы (Channels)
Отправка данных в канал происходит до завершения чтения из этого канала в другой горутине.

c := make(chan struct{})
go func() {
msg = "hello"
c <- struct{}{} // Happens-Before
}()
<-c // Ждем
fmt.Println(msg) // 100% увидит "hello"


✅ 2. Мьютексы (Mutexes)
Снятие блокировки mu.Unlock() происходит до успешного захвата mu.Lock() другой горутиной.

✅ 3. Атомики (sync/atomic)
Начиная с Go 1.19, в Модель Памяти официально добавили пакет atomic. Атомарные операции теперь гарантированно создают связь Happens-Before.

✅ 4. Жизненный цикл горутины
Создание горутины (go func()) происходит до начала выполнения её кода. А вот завершение горутины не имеет гарантий! (Именно поэтому нам нужен sync.WaitGroup, чтобы дождаться её финала).

🔥 Senior Tip: Не пытайтесь обмануть систему

Существует соблазн написать код, который работает "без блокировок", опираясь на задержки: time.Sleep(time.Millisecond). Это называется data race (состояние гонки). Даже если на вашей машине с процессором Intel код работает идеально, завтра его скомпилируют под ARM-архитектуру (Apple Silicon или AWS Graviton), у которой гораздо более слабая модель памяти процессора, и ваш код рассыплется в пыль.

Всегда используйте флаг -race во время локальной разработки и CI: go test -race ./.... Он ловит отсутствие связей Happens-Before с хирургической точностью.

#golang #underhood #architecture #performance #memorymodel

👉 @golang_lib
Views: 558
Go to post Open in MAX Report an inaccuracy
📢 sync.Cond: Как разбудить 10 000 горутин одним вызовом (и не сломать планировщик)

Представьте задачу: у вас запущено 10 000 горутин-воркеров, которые должны начать обработку данных строго одновременно, но только после того, как в память загрузится тяжелый кэш или конфигурация.

Как передать им сигнал "на старт"?

Вариант 1: Каналы.
Если мы отправим сигнал в канал ch <- struct{}{}, проснется только одна горутина. Чтобы разбудить 10 000, придется писать цикл. Долго и неэффективно.
Можно сделать close(ch) - закрытие канала мгновенно разбудит все горутины, читающие из него. Но канал можно закрыть только один раз. Что если нам нужно подавать такой массовый сигнал каждые 5 минут при обновлении кэша?

Здесь на сцену выходит sync.Cond (Condition Variable) - один из самых недооцененных примитивов синхронизации.

Это рупор, через который одна горутина может крикнуть толпе спящих горутин: "Событие произошло, просыпайтесь!"

Как это работает

У sync.Cond есть три главных метода:

* Wait() - усыпить текущую горутину до получения сигнала.
* Signal() - разбудить одну случайную спящую горутину.
* Broadcast() - разбудить все спящие горутины разом.

Чтобы sync.Cond работал, ему обязательно нужен мьютекс (sync.Mutex или sync.RWMutex), который защищает условие.

✅ Как это выглядит в коде:

type AppState struct {
mu sync.Mutex
cond *sync.Cond
configLoad bool
}

func NewAppState() *AppState {
a := &AppState{}
// Cond всегда связывается с мьютексом
a.cond = sync.NewCond(&a.mu)
return a
}

func (a *AppState) Worker(id int) {
a.mu.Lock() // Обязательно лочим мьютекс перед Wait!

// 🤡 Джун напишет тут 'if'. Сеньор пишет 'for'. Почему — ниже.
for !a.configLoad {
// Wait атомарно снимает лок с a.mu и усыпляет горутину.
// Когда она проснется, Wait атомарно вернет лок на место.
a.cond.Wait()
}

a.mu.Unlock() // Разлочили и пошли работать
fmt.Printf("Воркер %d начал работу!\n", id)
}

func (a *AppState) UpdateConfig() {
a.mu.Lock()
a.configLoad = true
a.mu.Unlock()

// БУМ! Будим все тысячи спящих горутин одним вызовом
a.cond.Broadcast()
}


🔥 Senior Tip: Почему Wait() всегда пишут в цикле for?

Главная ловушка sync.Cond заключается в том, что вызов Wait() нельзя использовать внутри if !condition { cond.Wait() }.
Спецификация POSIX-систем и рантайм Go допускают Spurious Wakeups (Ложные пробуждения). Горутина может проснуться просто так, даже если никто не вызывал Broadcast.
Кроме того, между моментом, когда горутина проснулась, и моментом, когда она захватила мьютекс для проверки, другая горутина могла успеть поменять состояние configLoad обратно на false.
Цикл for гарантирует, что проснувшаяся горутина обязательно еще раз проверит условие, прежде чем пойти дальше.

Когда sync.Cond НЕ нужен?
Если событие происходит только один раз за время жизни приложения (например, Graceful Shutdown) - не тащите sync.Cond. Обычного закрытия канала close(done) или использования context.Context хватит за глаза. sync.Cond блистает именно в сценариях многоразовых массовых оповещений.

#golang #concurrency #sync #underhood #architecture

👉 @golang_lib
Views: 570
Go to post Open in MAX Report an inaccuracy
🔎 pprof: Как найти функцию, которая жрет 80% CPU

Сервис на проде внезапно упирается в полку по процессору.

Что делает новичок? Сидит и смотрит в исходники взглядом гипнотизера, пытаясь угадать: "Ну, наверное, это регулярка тормозит". Или обкладывает весь код вызовами time.Now() в начале и time.Since() в конце каждой функции.

Сеньор открывает терминал, пишет одну команду и через 30 секунд получает точное имя функции, номер строки и процент украденного CPU. Наш инструмент pprof.

Он встроен прямо в стандартную библиотеку Go. Работает по принципу семплирования: раз в 10 миллисекунд рантайм Go "замирает", смотрит на стеки всех запущенных горутин и записывает: "Так, в эту микросекунду процессор выполнял функцию json.Unmarshal.

Давайте проведем вскрытие за 4 шага.

Шаг 1. Включаем «жучка» в коде

Всё, что нужно сделать в вашем сервисе - импортировать один пакет со знаком подчеркивания:

package main

import (
"net/http"
_ "net/http/pprof" // <-- Подключаем магию
)

func main() {
// ... ваша основная бизнес-логика ...

// Вешаем pprof на отдельный внутренний порт!
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}


Нюанс для Senior-ов: Магия _ "net/http/pprof" работает только с дефолтным http.DefaultServeMux. Если вы используете chi, gin или кастомный http.NewServeMux(), роуты профилировщика нужно регистрировать в ваш роутер руками (они лежат в пакете net/http/pprof как обычные хендлеры).

Шаг 2. Натравливаем профилировщик

Сервис крутится под нагрузкой. Открываем терминал на своей рабочей машине и говорим Go: *"Послушай этот сервер 30 секунд и собери мне статистику"*:

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

Терминал подумает полминуты и превратится в интерактивную консоль (pprof).

Шаг 3. Магия двух колонок

Внутри консоли пишем команду top10:

Showing nodes accounting for 2.45s, 88.1% of 2.78s total
flat flat% sum% cum cum%
1.82s 65.5% 65.5% 1.82s 65.5% regexp.(*bitState).reset
0.34s 12.2% 77.7% 0.45s 16.2% encoding/json.Unmarshal
0.15s 5.4% 83.1% 2.50s 89.9% main.processOrder
Главная ловушка новичка - смотреть на колонку cum.

• flat - сколько времени процессор провел исключительно внутри тела этой функции.
• cum (cumulative) - сколько времени процессор провел в этой функции + во всех функциях, которые она вызвала внутри себя.

Пример: У функции main.processOrder показатель cum равен 89.9%, но flat всего 5.4%. Это значит, что сама она почти ничего не считает, она просто вызвала тяжелую регулярку (regexp.reset, у которой flat аж 65.5%). Лечить нужно регулярку, а не processOrder!

Шаг 4. Визуальный экстаз (Flame Graph)

Смотреть в ASCII-таблицы в 2026 году больно. Выходим из консоли (Ctrl+D) и запускаем ту же команду, но добавив флаг -http:

go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30

В браузере мгновенно откроется шикарный веб-интерфейс. Переключаем вид сверху в режим Flame Graph (Огненный граф):

1. Перед вами "столбы". Чем шире столб по горизонтали - тем больше CPU сожрала эта функция. 2. Кликаем мышкой на самый широкий красный столб.
2. Переключаемся во вкладку Source.
3. Видим наш исходный код, где подсвечена конкретная строчка var re = regexp.MustCompile(...), компилирующая регулярку внутри цикла в хендлере. Прод спасен.


🔥 Senior Warning: Кровавая цена дефолта

Никогда не выставляйте порт 6060 наружу в интернет!

По адресу /debug/pprof/goroutine?debug=2 любой школьник без авторизации скачает полный текстовый дамп всех запущенных горутин. В их стеках будут лежать ваши пароли от БД в сыром виде, Bearer-токены пользователей и приватные ключи.

В продакшене pprof обязан висеть строго на localhost, куда разработчик заходит через kubectl port-forward или SSH-туннель.

👉 @golang_lib
Views: 497
Go to post Open in MAX Report an inaccuracy
🗺️ sync.Map: Почему эта «серебряная пуля» иногда пробивает дно производительности

Как только новичок ловит свою первую панику fatal error: concurrent map writes, он тут же гуглит проблему, находит sync.Map и радостно заменяет все свои словари на него. Казалось бы, идеальное решение из коробки.

Но если вы откроете исходники стандартной библиотеки Go, то увидите, что сами разработчики языка используют sync.Map крайне редко. В 95% случаев обычный map + sync.RWMutex порвет sync.Map по производительности.

Давайте разберем, как эта штука устроена под капотом и почему она может тормозить ваш сервис.

Архитектура двух корзин
Внутри sync.Map лежат не одна, а две мапы:

1. read: Доступна для чтения вообще без блокировок.
2. dirty: Защищена классическим мьютексом.

Когда вы делаете Load(key), Go сначала бежит в read. Если ключ найден - супер, мы прочитали его lock-free.
Но если ключа там нет, Go вынужден брать мьютекс и искать его в dirty мапе. Это называется cache miss. Если промахов становится слишком много, sync.Map принимает тяжелое решение: он берет всю dirty мапу и копирует её в read.

Когда sync.Map - это катастрофа?

• Частая запись новых ключей: Каждый новый ключ попадает в dirty. Это вызывает постоянные промахи при чтении, мьютекс постоянно лочится, а затем происходит дорогое копирование всей мапы. Вы получаете двойной удар по CPU.
• Удаление и перезапись: Стандартный RWMutex справится с этим гораздо эффективнее.

Когда sync.Map - это шедевр? (Два идеальных юзкейса)
Официальная документация выделяет ровно два сценария, где этот тип оправдан:

1. Append-only кэши: Когда записи добавляются один раз и живут вечно (например, кэш скомпилированных регулярных выражений или конфигураций).
2. Disjoint keys (Разделенные ключи): Когда у вас есть 1000 горутин, и каждая читает/пишет строго в свой собственный ключ, никогда не пересекаясь с соседями.

🔥 Senior Tip: Sharded Map (Шардирование)
Что делать, если у вас кэш на 5 миллионов записей, куда постоянно идут конкурентные чтение и запись? RWMutex станет узким местом (будет блокировать всю мапу целиком).
В высоконагруженном проде используют Шардирование. Вы создаете слайс из 256 обычных мап, у каждой из которых свой маленький мьютекс. Хэш от ключа определяет, в какую именно мапу пойдет запрос. Это снижает конкуренцию за лок в 256 раз! (Готовые решения: orcaman/concurrent-map или alphadose/haxmap).

#golang #concurrency #architecture #performance #underhood

👉 @golang_lib
Views: 546
Go to post Open in MAX Report an inaccuracy
The full feed — after free sign-up
VKontakte Telegram Odnoklassniki
Favorites at datool.ru

Follow the channel in your account

Sign up at datool.ru to add the channel “Библиотека Go (Golang) разработчика” to favorites and track its metrics.

Sign up
For channel owners

Verify your channel

Sign in or sign up at datool.ru, then add our bot to the channel following the instructions in your account. Once ownership is verified, the lock disappears and channel management becomes available.

Sign in or sign up

Report an inaccuracy

Describe what needs fixing. If you have a source, add a link.

You are leaving datool.ru

The link leads to an external website. datool.ru is not responsible for its content — check the address before entering passwords or payment details.

Redirecting in 10 s

Go