Gopher's weekdays

Description
Заметки гофера.
Краткие аннотации к статьям, ссылки на сниппеты, deep dive into source code
Advertising
We recommend to visit
HAYZON
HAYZON
5,992,507 @hayzonn

لا اله الا الله محمد رسول الله

👤 𝐅𝐨𝐮𝐧𝐝𝐞𝐫: @Tg_Syprion
🗓 ᴀᴅᴠᴇʀᴛɪsɪɴɢ: @SEO_Fam
Мои каналы: @mazzafam

Last updated 4 weeks ago

Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.

Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support

Last updated 3 weeks, 1 day ago

Канал для поиска исполнителей для разных задач и организации мини конкурсов

Last updated 1 month, 1 week ago

5 months, 2 weeks ago

River. Background jobs по-взросломуКогда у приложения всего один инстанс, то реализовать фоновые задания несложно. В большинстве случаев достаточно time.Ticker. С настройками для заданий придется еще немного повозится, но табличка с json решит и эту проблему.

А вот когда инстансов приложения несколько возникают сложности. River проект, реализующий работу с фоновыми заданиями поверх PostgreSQL, расчитанный на выполнение заданий сразу на нескольких инстансах.

Типичные проблемы в таких системах:
- как реализовать распределение задания по инстансам
- что делать, если инстанс упал
- как справится с нагрузкой, если заданий много

В River каждый инстанс приложения (в терминологии River client) самостоятельно отбирает задания из общей очереди SELECT FOR UPDATE SKIP LOCK > UPDATE. Чтобы не получить проблемы при высокой нагрузке эти две команды выполняются в одном запросе к БД, что гарантирует минимальное время блокировки строки. В секции UPDATE устанавливается статус задания.

Для того, чтобы отслижвать зависшие задания client'ы (инстансы) выбирают между сабой лидера - client (instance), выполняющий сервисные функции. В том числе лидер следит за долговыполняющимися заданиями и меняет их статус, чтобы живые client'ы могли снова взять их в работу.

Алгоритм выбора лидера очень прост - в служебную таблицу client пишет свой идентификатор (случайный по умолчанию или можно задать через конфигурацию). Кто первый успел записать свой идентификатор - тот и лидер.

Лидер не слидит за остальными client'ами. Он проверяет только долговыполняющиеся задания. Т.е. для client'ов не реализованы healthcheck'и.

Стоит ли использовать River?

Код River показался слегка переусложненным и прочитать его с первого раза не получится, нужно немного посидеть и вникнуть в идеи автора. В River реализовано достаточно много сервисных функций (в том числе и UI). В крупных проектах его использовать не целесообразно, обычно есть время и ресурсы на реализацию своих пакетов. А в стартапах River может съэкономить время.

Идеи используемые в River могут лечь в основу вашего собственного пакета.

5 months, 3 weeks ago

Давно не писал в канал, но тут появился повод - прочитал небольшой доклад на митапе от Evrone про распределенные транзакции.
Формат митапа (выступление до получаса) не позволил углубиться в детали, так что прошелся по верхам.

YouTube

Распределенные транзакции: выбор реализации — Go Evrone Meetup

Подписывайтесь на наш канал здесь и в телеграм https://t.me/meetups\_evrone, чтобы не пропускать полезные доклады! В этом докладе рассмотрим выбор стратегии реализации процесса, представляющего собой распределенную транзакцию, на примере проведения платежа.…

10 months, 1 week ago

NATS JetStream internals. Part 1NATS - брокер сообщений, пытающийся занять место RabbitMQNATS JetStream, в отличии от NATS Core, позволяет сохранять сообщения на диск.
Давайте разберемся как NATS это делает.

Запись в JetStreamДля пишущего в NATS клиента запись в JetStream нечем не отличается от работы с NATS.Core. JetStream подписывается на чтение сообщений, прозрачно для клиента.

Хранение сообщенийКаждый stream представляет собой набор сообщений, у которых есть тема и номер. Тема - строка, по части или по точному совпадению с которой можно осуществлять поиск сообщений, их фильтрацию. Stream не хранит информацию о том, какие сообщения были доставлены, а какие нет. Этим занимается консьюмер, о нем мы поговорим чуть позже.
Для хранения сообщений NATS группирует сообщения в блоки. Каждый блок содержит последовательно записанные сообщения. Для работы с блоком мы должны сначала его полностью прочитать. А дальше можем найти нужное нам сообщение по оффсету или добавить новое сообщение. Старые сообщения не изменяются. NATS хранит в памяти недавно записанные сообщения. Работа с ними происходит максимально быстро. Сообщения вытесняются из кеша после прочтения или по времени / приближению к границе размера кеша.

Для быстрого поиска внутри блоков все темы сообщений хранятся в памяти в префиксном дереве (radix-tree). При старте сервера все блоки прочитываются и из них формируется новое префиксное дерево.

Конкурентная запись в блоки обеспечивается использованием мьютексов. Они щедро рассыпаны по коду и блокировка ставится почти на любое действие.

Хранение оффсетовОфсеты клиентов хранятся отдельно от сообщений, в consumer'e. В NATS есть два вида консьюмеров: эфемерный и устойчивый. Эфемерный не сохраняет офсеты, а вот устойчивый хранит их на диске. Консьюмер - это не только хранилище для офсетов, но и набор фильтров для отбора сообщений по теме.

При этом консьюмеры имеют имена. И при переподключении клиентов с одним именем косньюмера чтение сообщений продолжится с того места, на котором пришлось остановится. Чтение сообщений осуществляется через очередь в памяти, также как и изменение офсетов. Для каждого консьюмера NATS создает отдельный файл на диске. При изменении значений офсетов, изменения копятся и по таймеру сбрасываются на диск.

P.S. Если у вас есть вопросы по NATS пишите их в комментариях. Постараюсь найти на них ответы

10 months, 3 weeks ago

Разделяй и страдайСегодня речь пойдет не столько о go или о чем-то рядом, а о партиционировании. А точнее о том как его готовят в Discord.

Недавно вышла статья о переезде с Cassandra на ScyllaDB, что само по себе интресно. В этой статье вскользь упоминалась проблема горячих партиций. Вот с ней и будем разбираться.

1️⃣Что такое партиционирование?Наверняка читатель знает, что такое партиционирование. Но к сожалению, у термина есть 2 значения в SQL и NoSQL мирах.
В SQL мире партиционирование - это раздление множества строк таблицы на подмножества по ключу партиционирования в пределах одного узла.Такое разделение решает проблему производительности, когда наш индекс настолько большой, что не помещается в оперативную память и из-за этого поиск выполняется медленно. С помощью партиицонирования мы разбиваем один индекс на много маленьких, разделенных по ключу. Если запрос включает ключ партиицонирования, то мы можем поднимать в оперативную память только необходимую часть индекса. А если нет, то будем выполнять скан всех партиций.

А вот в NoSQL мире под партиционирование сразу подразумевается разделение на узлы, т.е. шардирование.

2️⃣Выбор ключа партиционированияDiscord хранит сообщения, очень много сообщений, сгрупированных в каналы. В каналах сообщения пользователю показываются в отсортированном по времени порядке. В мире SQL лет 10 назад такую проблему решали либо навешиванием индекса по created_at, либо монотонно возрастающим первичным ключом. Но у нас так много сообщений, что они не влазят на один узел. Монотонно возрастающий первичный ключ не подходит. Да и индекс тоже так себе идея, мы были бы вынуждены повторно ходить в реальную таблицу после сканирования по индексу, что на огромной таблице дорого даже для одного узла. А на нескольких узлах мы такого провернуть не можем.

Поэтому для подобных задач у нас есть варианты uuid, гарантирующие сортировку по времени.
Сейчас есть два варианта, подходящих для системы с несколькими узлами и гарантирующие уникальность:
- snowflake id | timestamp | mashine id | mashine sequence number | - uuid v7 | timestamp | meta | random |SnowflakeID требует регистра идентификаторов узлов (аля consul), uuid v7 нет.

Когда discord писался, то uuid v7 не было, т.ч. их выбор пал на snowflake. Но они не могли использовать просто uuid сообщения в качестве ключа партиционирования, потому что при распределении по узлам cassandra использует не просто диапазоны значений uuid, а согласованное хеширование murmur3 для очень большого диапазона виртуальных узлов. Поэтому при выборе uuid как ключа партиционирования мы бы не смогли быстро получить отсортированные по времени сообщения, т.к. они лежат на разных узлах в почти случайном порядке.

Поэтому закономерным итогом стал выбор ключа партиционирования (channel id, bucket), где бакет монотонно возрастающее значение. Добавление bucket нужно, чтобы ограничить размер партиции. Ведь в канале может быть очень очень много сообщений.

3️⃣Проблема горячих партицийМы разобрались со структурой данных в Discord. Выбранная структура держит нагрузку почти во всех случаях. Но особенность предметной области в том, что при наличии каких-то значимых для сообществ событий кол-во сообщений в одном канале может расти, а кол-во просмотров может расти еще больше.

В случае такого пика вся нагрузка падает на один узел. И тут спасает только вертикальное масштабирование - увеличение пропускной способности одного узла. Именно этого добивались инженеры Discord при переходе с Cassandra на ScyllaDB.

Также они добавили небольшой кеширующий слой, который позволяет при большом кол-ве одинаковых запросов в одно время выполнять только один запрос к БД. Кстати, в гошке для этого есть специальный пакет.

#architecture

12 months ago

Otter - самый быстрый кеш на диком западеНедавно на глаза попался интересный проект - otter. Он еще не вышел в production, но заглянуть в него очень интересно. Пройдемся по основным решениям, который принял автор.

1️⃣S3-FIFOОдин из самых основных параметров, по которому оценивается кеш - количество промахов. Он зависит от алгоритма вытеснения старых записей. Обзор самых простых алгоритмов можно посмотреть в видео Владимира Балуна.

В otter используется S3-FIFO, которный на текущий момент является одним из самых лучших по cash miss ratio. Он состоит из 3-х очередей.

2️⃣Собственная реализация concurrent mapКак известно, стандартная библиотека предлагает два варианта хешмапы для конкурентного доступа: sync.Map и стандратная непотокобезопасная мапа, закрытая мьютексами.

sync.Map имеет очень ограниченные сценарии применения - большое кол-во чтений без большого кол-ва удалений или изменений старых ключей.

В Otter реализована своя hashmap, в которой блокировки ставятся на уровне бакета, а увеличение мапы происходит за раз. При этом блокируются только операции записи. Для кеша фиксированного размера сценарий изменения размера мапы не так значим.
Реализация очень похожа на пакет cmap.

3️⃣Асинхронное заполнение структур для политик вытесненияДля S3-FIFO нужны реализации 3-х очередей (основаны на двусвязном списке).

В Otter элемент сначала попадает в общую хешмапу и добавляется в отдельную очередь операций. В фоне, в один поток, данные из очереди операций вычитываются и выполняется изменение очередей S3-FIFO. Через очередь операций также выполняется обслуживание других политик.

В go есть встроенная реализация очереди FIFO - каналы. А также можно найти реализацию очерди для одного писателй и многих читателей внутри sync.Pool.

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

4️⃣Поиск внутри очередей S3-FIFOДляS3-FIFO нам необходимо определять в какой очереди находится элемент. Для быстрого поиска элемента в очередях в Otter используется swiss-map от DoltHub.

1 year ago

DDD в golang. Превозмогая трудности
Написал пост с описанием организации бизнес-логики в DDD на golang.
Основная проблема при реализации подхода DDD становится инкапсуляция бизнес-логики внутри модели и избегания неконсистентности состояния сущности при росте проекта.

В golang очень неудобно делать поля сущности приватными из-за необходимости тогда писать свои маршалеры / анмаршалеры, конвертеры.
В посте описал примеры орагиназции логики изменения сущности, которая позволяет оставить поля публичными, но при этом не давать проивзольно сохранять сущность в неконсистентном состоянии.

Основная идея подхода сразу использовать описания изменений через события, как в event sourcing.

https://habr.com/ru/articles/778186/

Хабр

DDD в golang. Превозмогая трудности

В последнее время достаточно много выступлений, посвященных реализации подходов Domain Driven Design(DDD) в golang. Я не буду останавливаться на value object, они в golang хорошо реализуются с помощью...

**DDD в golang. Превозмогая трудности**
1 year, 1 month ago

К сожалению последнее время накопилось много дел и в канал я не писал довольно давно.
Но появился повод!

Выступил на митапе от Evrone, рассказал про варианты обработки ошибок
https://www.youtube.com/watch?v=9WLhqx1U5XU

YouTube

Обработка ошибок в go в 2023

Подписывайтесь на наш канал здесь и в телеграмм https://t.me/meetups\_evrone, чтобы быть в курсе будущих митапов и не пропускать полезные доклады! Доклад дает конкретный алгоритм выбора способа обработок ошибок в go для проекта. Плюс маленький обзор пакетов…

1 year, 4 months ago

Здравствуй slog. Прощай zap.

В версии go 1.21 появился пакет для структурного логгирования. Получился

Функциональные отличия и заимствования от zap
- также как в zap реализована функция With, которая позовляет уменьшить время анмаршалинга сделав анмаршалинг полей заранее
- также как в zap есть возможность заменить ядро логгера - ту часть, которая обрабатывает запись (называется Handler```)`. `Стоит отметить, что настроек у ядра очень мало (даже выбор уровня не сделали), а все настройки предлагается реализовывать с помощью своего`Handler` \- также как у zap есть возможность переопределять маршалинг конкретных типов, но сделано это проще. Не нужно писать свой анмаршалер,slogтребует написать конвертацию вValue, внутреннее представление аттрибута записи \- в отличии от zap оставили толлько методы логгирования, в которых аттрибуты передаются какany(SuggaredLogger вариант) \- в отличии от zap есть методы логгирования, содержащиеcontext`
- добавили возможность группировать аттрибуты

```

"group": {
"atr1": 1,
"atr2": 2
}

```

Применяемые оптимизации и производительность
Логгер получился довольно шустрый, чуть менее быстрый чем zerolog при маленьком кол-ве аттрибутов:
300 нс zerolog vs 1 мс у slog при 10 атрибутах и кодировании в json

Такая разница прежде всего связана с отказом от pool'a объектов для записи лога и атрибутов в угоду удобства интерфейсов для пользователя. Пул для буферов остался.

Интересная оптимизация применена для атрибутов. В записи лога (Record) для аттрибутов есть два поля:
1-ое с типом array и хранит небольшое кол-во записей (5), 2-ое слайс. Так сделано для уменьшения аллокаций ведь слайс без заранее определенного размера всегда будет аллоцирован на куче.

1 year, 5 months ago

Coroutines . Be or not to beРасc Кокс (Russ Cox), один из разработчиков golang, опубликовал в своем блоге довольно резонансную статью "Coroutines for go", в которой описал реализацию корутин на go.
Давайте попробуем разобраться о чем говорит Расс и зачем в го корутины, если уже есть горутины.

Под корутинами Расс понимает выполнение независимых фрагментов кода последовательно с переключением контекста (event loop). Помните конкурентность != параллелизм. Вот тут у нас конкурентность, параллелизма нет, код будет выполнен последовательно.

Прежде, чем говорить о корутинах обратим внимание на предыдущий пост Расса "Storing Data in Control Flow ". В нем иллюстрируется довольно простая мысль: "некоторые алгоритмы будут выглядеть проще, если описывать их в терминах событий". То есть мы можем представить программу как набор состояний, которые изменяются при наступлении того или иного события. А уж как передавать события в golang мы знаем - через каналы.
В качестве примера приводится парсер строки.

В посте про корутины Расс развивает мысль предыдущего поста и с помощью события "дальше" показывает как можно реализовать обход дерева. При этом парсер строки и обход дерева алгоритмы по своей природе последовательные, при этом их можно написать довольно изящно в конкурентной манере, используя паттерн генератор.

В обоих случаях есть часть кода, которая создает следующее значение по определенному правилу и часть кода, которая значение обрабатывает.

Сейчас конкурентная реализация на каналах в golang будет медленнее обычного последовательного алгоритма из-за переключения контекста горутин. Если сравнить реализации генератора чисел от 0 до 1_000_000, то разница будет в 100 раз. Но у реализации на каналах есть одно преимущество - более лаконичный синтаксис через range.

Такая разница и неудобный синтаксис последовательного варианта (см. ссылку в конце поста с примерами) основная причина существования дискуссии о введении в язык итераторов, хотя казалось бы итераторы спокойно реализуются на каналах.

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

На всякий случай оставлю ссылочку на варианты реализации генератора чисел без каналов.

1 year, 5 months ago

Обработка ошибок в goОбработка ошибок в go всегда остается холиварной темой.
Написал статью о способах обработки ошибок, которые видел в различных проектах.

Стандартный подход через fmt.Errof при росте кодовой базы требует слишком много внимания и при большой команде может вызывать проблемы.
Альтернативы есть, если потеря 0.5-1 мс для вас не критично. Подробно разбирал проблему тут.

Из вариантов:
- логгирование по месту с логгером из контекста или через DI
- собственный тип ошибок со стектрейсом

We recommend to visit
HAYZON
HAYZON
5,992,507 @hayzonn

لا اله الا الله محمد رسول الله

👤 𝐅𝐨𝐮𝐧𝐝𝐞𝐫: @Tg_Syprion
🗓 ᴀᴅᴠᴇʀᴛɪsɪɴɢ: @SEO_Fam
Мои каналы: @mazzafam

Last updated 4 weeks ago

Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.

Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support

Last updated 3 weeks, 1 day ago

Канал для поиска исполнителей для разных задач и организации мини конкурсов

Last updated 1 month, 1 week ago