Telegram Web Link
Сегодня продолжил трогать экосистему Prometheus.

Наконец дошли руки до системы сбора логов - Loki. Вообще, при развертывании "по фен-шую" между сервисами, генерирующими логи и Loki должна использоваться прокладка - Promtail. Promtail осуществляет сбор, анализ, агрегацию и ротацию логов, после чего укладывает их в Loki. Визуализация осуществляется, как и в случае с Prometheus с помощью Graphana.

Но я немного схалтурил и настроил прямую запись из приложения в Loki. Заводится все с ходу, на дефолтных официальных докер образах.

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

В Loki в моей конфигурации об индексировании приходится думать на этапе разработки приложения: надо заложить в сообщения логов метки (например текст {errorType42}), и сообщить отправителю, что их надо индексировать.

А затем, в окне просмотра логов в любой момент можно быстро найти все errorType42 за определенный период времени. И с этой выдачей уже работать другими встроенными инструментами.

#prometheus
На неделе познал дзен DDD - domain driven design - подхода к проектированию ПО. Основная суть - разделение мух и котлет - бизнес логики (domain), технического кода по трансформации данных при движении по приложению (application) и взаимодействия с внешним миром (infrastructure).

Раньше во всех моих проектах был сильный перекос: в одном случае 90% проекта приходилось на domain, в другом - infrastructure с примесью application.

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

В общем-то самое главное, что даёт "познание дзена DDD" в моем случае - не запоминание догматики правильного построения приложения, а возможность осознанно посмотреть на задачу с новой точки зрения.
На неделе перешагнул ещё одну важную ступеньку: освоил гитлаб на базовом уровне. Собрал пока экспериментальный стенд вида: мы коммитим в репозиторий, сам собирается докер образ, сохраняется в хранилище образов, после чего вытягивается оттуда и запускается на машине.

Система непрерывной поставки/развертывания с гитлабом выглядит примерно так: есть центральная машина на которой стоит гитлаб. На той же или на соседних машинах установлены Gitlab Runner-ы, выполняющие команды гитлаба.

Вариантов использования Runner-ов масса, я пока остановился на самом простом: выполнение последователльности консольных команд, забитых в скрипте CI/CD.

При коммите Runner, отвечающий за сборку образов, собирает новый образ и сохраняет его в хранилище. Если сборка и сохранение прошли успешно - в дело вступает второй Runner, на машине, где собственно крутится проект. Вытягивает последнюю версию, гасит старый контейнер, запускает новый из нового образа.

Изучать гитлаб можно бесконечно, он неисчерпаем, как электрон, но теперь мне хватает навыков для построения CI/CD на относительно небольшое хозяйство.

#devops
#gitlab
Sphagnum. Часть 1. Начало.
#sphagnum@eshu_coding

Начинаю новый пет-проект: свой брокер сообщений. Обозвал я его пока Sphagnum, проект планируется в качестве чисто образовательного, под лицензией MIT. Что я хочу получить на выходе:
1. По основному функционалу - клон RabbitMQ, возможно несколько урезанный.
2. Возможность встраивать брокер в другие приложения на .net для организации доступа к проходящим через брокер данным и уменьшению накладных расходов на передачу данных по сети.
3. Очень хочется кафкоподобное горизонтальное масштабирование.
4. Очень хочется возможность сохранения истории прошедших через брокер данных с вычиткой их с произвольного момента.
5. Очень хочется сделать шажочек в сторону работы с динамическими ip некоторых интстансов в системе.

В дальнейшем всё связанное с проектом будет публиковаться под тегами #sphagnum@eshu_coding или #sphagnum. В ближайшее время я буду читать всякое на смежные темы и выкладывать разные теоретические заметки. Код будет ориентировочно зимой.

Пока видится несколько этапов:
1. Отработка основных шаманств по передаче данных, скорее всего поверх сокетов.
2. Реализация хранения данных, думаю надо уметь как NATS - и на диск в самопальные файлики и в базу данных.
3. Админка, видимо в двух вариациях: через rest api и что-то интерактивное, с ui, на базе blazor как вариант.
4. Тесты и оптимизации, оптимизации и тесты. Если удастся догнать RabbitMQ - это будет отличным результатом.
5. Выкатывание проекта в открытый доступ: nuget пакет для установки, несколько образов на docker hub, клиентские библиотеки для основных языков программирования - c#, java, js, python, golang

#проекты
Sphagnum. Часть 2. Зачем, а главное нафига?
#sphagnum@eshu_coding

1. Саморазвитие. Читать умные книги это очень познавательно, но намного лучше я усваиваю информацию, когда решаю какую-то задачу, интересную мне. А тут ожидается и изучение существующих архитектурных решений, и выдавливание максимума производительности из c# кода.

2. Для встраиваемого брокера сообщений в экосистеме .Net вполне найдется место. Я вижу как минимум две ниши:
a) Организация сетевого взаимодействия в геймдеве на Unity или в мобильной разработке с использованием MAUI.

b) Онлайн анализ протекающих через брокер данных: например поиск ключевых слов в потоке логов/текстов, которые отправляются на архивирование. Можно конечно ответвить поток данных на отдельного потребителя, осуществляющего анализ. Но в случае цунами из данных, анализ может произойти слишком поздно. А со встроенным брокером - просто скопировать данные (или вообще ссылку на них) внутри процесса. Если бы я запускал #палантир@eshu_coding сейчас, я бы точно не отказался от такого инструмента.
Послушал открытый урок Otus по некоторым особенностям оптимизации работы кластеров PostgreSQL. В целом, получилось очень познавательно: некоторые из услышанных вещей, упомянутых лишь краем, вызвали реакцию "хм, а так было можно?"

Вот перечень запомнившихся моментов:
1. Оптимизация скорости работы базы данных за счёт разнесения разных таблиц по разным дискам с помощью указания табличных пространств (tablespace). Я про что-то подобное мельком читал во время работы над Палантиром #палантир@eshu_coding, но воспринимал эти махинации исключительно с т.з. размещения большой базы, не влезающей на диск.

2. Оптимизация путем указания стоимости операции одиночного чтения с произвольного места диска. С классических hdd намного быстрее читать последовательно записанные данные. Потому в случае соотношения размера таблицы к размеру индекса 4 к 1 читать с hdd всю таблицу может быть быстрее, чем сканировать индекс и брать нужное из таблицы. А вот на SSD такой разницы нет, и выставив параметр конфигурации random_page_cost=1 мы ощутимо ускорим чтение данных.

3. Махинации с типом репликации. В случае, когда производительность на запись/изменение совсем поджимает, можно пожертвовать надёжностью реплицирования, но выиграть до порядка по быстродействию, переключив репликацию с синхронной на асинхронную. А ещё можно делать реплику, на которую изменения с мастера будут применяться с установленной задержкой, например - час. И в случае катастрофы, например - удаления базы целиком, будет какое-то время оживить систему с меньшими потерями, чем из вчерашнего бэкапа.

4. Узнал про возможность конкуррентного переиндексирования. Предположим, индекс раздулся и начал тупить и есть много места. Можно его удалить и перестроить с нуля. Но "на живую" это приведет к большим проблемам. А оказывается можно запустить фоновое переиндексирование.

5. Узнал, что в постгрес таки есть механизм подсказок планировщику запросов, что ему делать. Для этого нужно расширение pg_hint_plan.

6. Услышал оговорку "обычно балансировку запросов между мастером и репликами у нас делают сами приложения". А так было можно?! Полез читать - оказывается, у стандартного шарпового коннектора Npgsql и балансировщик и координатор выборов нового мастера в случае сбоя есть под капотом.

Курс я конечно покупать не буду, но за 1.5 часа времени информация получена очень полезная.

#postgresql
Эшу быдлокодит
И в четвертый раз, на те же грабли, в том же месте! Да, я пока ленюсь написать нормальный генератор данных.
Когда я занес ногу в пятый раз, уже на другом проекте на другой работе - я попустился и таки сел писать генератор тестовых данных.
Дочитал Рихтера до типов перечислений (enum) и наткнулся на дополнительное направление их применения, которое я многократно использовал в чужих библиотеках, но ни разу не задавался вопросом, что там под капотом?

Вообще, enum - штука очень удобная, использую их постоянно. Представляет собой конечный набор значений, которые под капотом - лёгкий целочисленный тип, а в коде представлены в человекочитаемой форме.

А тут я осознал, что можно использовать enum как битовые флаги, дополняя битовые операции человекочитаемостью! Достаточно пометить свой enum атрибутом [Flags] и можно развлекаться с байтиками, не отправляя читателя кода в недра клинописи вида 0x5A3 | 0x5A4. Отличный пример приведен мы официальной доке от MS:

[Flags]
public enum Days
{
None = 0b_0000_0000,
Mon = 0b_0000_0001,
Tue = 0b_0000_0010,
Wed = 0b_0000_0100,
Thu = 0b_0000_1000,
Fri = 0b_0001_0000,
Sat = 0b_0010_0000,
Sun = 0b_0100_0000,
Weekend = Sat | Sun
}


Помнил бы я об этих махинациях год назад - поэкономил бы байтиков от души.

#Рихтер
#книги

P.S. Другой вариант записи enum-а из примера:

[Flags]
public enum Days
{
None = 0,
Mon = 1,
Tue = 2,
Wed = 4,
Thu = 8,
Fri = 16,
Sat = 32,
Sun = 64,
Weekend = Sat | Sun
}
На прошлых выходных был на конференции по экосистеме .Net, к которой относится мой основной язык - c#: DotNext.

В первый день прослушал 4 доклада, во второй - 5.

Далее будет серия постов по прослушанным докладам, с тегом #dotnext@eshu_coding

#conf
#dotnext, день 1, доклад 1.

Workflow архитектура сервисов в .Net

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

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

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

Также в докладе рассказали о готовых либах, реализующих workflow в c#. По случаю может и пригодится.

P.S. Отдельным бонусом к либам идёт возможность генерации блок схемы наших workflow

#conf
#dotnext, день 1, доклад 2.

Когда 100% cpu ничего не значит.

Доклад был посвящен некоторым нюансам, понижающим производительность приложений когда в коде все нормально.

Основная полезная информация, которую я вынес - о механизме устройства конвейера виртуальных машин, за мощности которых мы платим хостерам.

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

И тут к нам приходит лавинообразная нагрузка. Реально выделенных (а не тех, за которые уполчено) выделенных ресурсов нам перестает хватать, автоматика хостера пытается как-то выдать нам оплаченное и теперь востребованное - и тратит на это время, в течение которого наш сервис может захлёбываться на ровном месте.

Другой интересный момент - взятие лишнего десятка ядер CPU, как раз таки на такой случай, не спасает, а наоборот делает хуже. Чем больше запрашиваемые нами ресурсы - тем сложнее приткнуть нашу виртуалку туда, где они есть.

Ещё было занятно про k8s и гармоничные настройки потребления памяти, но кубером я пока не проникся, потому прошло мимо.

#conf
#dotnext, день 1, доклад 3.

Кафка и распродажи в Озоне.

Доклад был посвящен перемалыванию пиковых нагрузок в Озоне. Довольно много касалось Apache Kafka, ну и рассказывали что и как у них устроено. В целом - очень интересно, но прям совсем ничего нового. Хотя, поиграться с такими нагрузками желание появилось.

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

#conf
#dotnext, день 1, доклад 4.

Dependecy Injection в тестах.

Автор из Яндекса пропагандировал использование стандартных практик написания кода внутри тестов, ну и рассказал об основных тестовых фреймворках в .Net (я и так знал, что использую самый дремучий - MSTest).

Звучит круто и логично, но на моей практике пока ничего хорошего из попыток написать тесты также как обычный код не вышло: поддержка из редактура становилась слишком трудоёмкой.

Пока наиболее оптимальным выглядит путь максимального говнокода с копипастой. И совсем чуть-чуть выноса повторяющихся простынь в отдельные методы.

#conf
#dotnext, день 2, доклад 1.

Экономия памяти в .Net.

Известно, что приложения на java/c# съедают память столько, сколько им дадут. Но если заморочиться - можно сократить потребление.

В докладе занимались загрузкой файлов со средним размером ~123 Мб. В среднем на загрузку такого файла уходило ~280 Мб памяти.

Путем разных непристойных действий с байтами автор ужал потребление памяти до ~2.7 Мб, т.е. в 100 раз. И даже не особо потерял в читаемости кода.

Во основном использовалось два подхода:
1. Пулинг объектов
2. Выделение памяти на стеке вместо кучи

В c# есть встроенный пул массивов, которые можно использовать как буферные объекты, не создавая новые и не забивая память новосоздаваемыми одноразовыми массивами.

А если таки надо создавать буферные короткоживущие объекты - можно попробовать разместить их на стеке. Использовать структуру. Или вообще аллоцировать кусочек стека и использовать его по своему усмотрению (главное не переусердствовать и не завалить все приложение со Stack Overflow Exception).

Список колдунств, применённых автором, просто чтобы не забыть:
1. stackalloc
2. ArrayPool
3. readonly ref struct
4. ValueStringBuilder (вытащенный внутренний класс из недр рантайма c#, базирующийся на стеке билдер строк).
5. TryFormat

Ещё была важная мысль - использовать бенчмарки в докере, чтобы сравнивать как работает код в целевой ОС, а не на Винде, где идёт разработка.

#csharp
#conf
#dotnext, день 2, доклад 2.

Убийцы производительности в c#.

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

1. Следить за логгером
2. Следить за использованием кучи больших объектов
3. Прежде чем тащить ArrayPool в проект - почитать Best practices по его использованию.

Ну и некоторые новые пакеты/инструменты для повышения производительности, просто чтобы не потерять:
RecyclableMemoryStream
Pipelines.Sockets.Unofficial
ArraySegment

#conf
#dotnext, день 2, доклад 3.

Знакомство с функциональной парадигмой и F#.

В целом, код в функциональной парадигме выглядит для привыкшего к ООП человека очень странно.

Но вообще, достаточно занятно, как вариант для читаемого клея для страшных многосоставных операций - вполне себе вариант.

Но острого желания вот прямо сейчас изучить F# не возникло.

#conf
#dotnext, день 2, доклад 4.

PureDI.

Человек рассказывал о своем пет-проекте - фреймворке для реализации Dependency Injection. В целом, довольно нишевая штука, хотя и интересная.

Обладает некоторыми преимуществами перед стандартными средствами.

Главная проблема на мой взгляд - чтобы подружить эту библиотеку и стандартные средства для реализации Dependency Injection требуются дополнительные приседания.

Ну и заодно доклад проиллюстрировал, для чего на практике нужен механизм SourceGen в .Net (при билде проекта запускается специальный инструмент и генерирует исходный код по сконфигурированным правилам, а потом собственно билдит проект с этим новргенеренным кодом).

#conf
#dotnext, день 2, доклад 5.

Linq: искусство запрашивать данные.

В c# принято общаться с базой через ORM - Entity Framework (EF). Он позволяет пропустить фазу написания sql запросов путем трансляции внутреннего шарпового языка написания запросов к коллекциям с данными - Linq - в sql запросы.

Но иногда эти запросы разрастаются и становятся нечитаемыми даже в c# даже без единой строки SQL.

В докладе давался обзор фреймворков, позволяющих сделать запросы читаемее. Вообще, подобная проблема передо мной уже стояла при создании сложных запросов к MongoDB. В итоге получилось редкостное уродство, ставшее легаси в момент коммита, так что я понимаю боль авторов. На случай возможного будущего погружения в те же дебри, но с EF, ниже приведу просто набор ключевых слов для поиска:

Подход к упрощению запросов - паттерн спецификация
AddHocSpecification
ReplaceVisitor
Linqkit
nein-linq
Projectable
EF7 - IQueryExpressionInterseptor
Sieve - для комбинации фильтров
AutoFilterer


#conf
#dotnext, просмотр в записи.

B-Tree индексы в PostgreSQL.

Доклад получился крайне полезным: в связной легкодоступной форме пересказали то, что я в общем-то знал по устройству внутрянки BTree индексов.

Кроме того, в процессе доклада всплыла ещё масса полезностей, которые ускользнули от моего внимания ранее;

1. explain (analyze, costs off, buffers) - синтаксис для просмотра плана запроса с прогнозом числа чтений страниц. Heap fetches - тот самый прогноз походов в таблицу после сканирования индекса для проверки наличия записи в таблице (если она ещё не зачищена от мусора вакуумом).

2. Спикер заострил внимание на разнице index scan и index only scan в плане выполнения запросов (последний не полезет читать доп информацию из таблицы после выполнения, ему для выполнения хватит инфы их индекса).

3. Спикер рассмотрел разницу по работе с BTree индексом в случае индексирования по bigint и по uuid. bigint при добавлении растит индекс в одну сторону. uuid - растит хаотично, расходует больше оперативной памяти в процессе работы. Разница по быстродействию может доходить до 7 раз. Отчасти проблему решает использование uiid v7, в котором используется временная метка. Но это не про стандартную библиотеку c#:)

4. Удаленные из таблицы данные остаются какое-то время залипать в индексе и накручивают Heap fetches в плане выполнения запроса. Помогает не дожидаться долго и тяжёлого вакуума (процесс очистки от мусора) фича из PostgreSQL 14 - bottom-up deletion - зачистка индекса от мусора в процессе вставки новых данных.

5. Спикер упомянул интересный путь оптимизации запросов: учитывать сортировку в индексе и идти к результату с определенной стороны с помощью CTE, например вызовом рекурсивного подзапроса > treshold_value, с постепенным нарастанием treshold_value.

#postgresql
#conf
Попробовал небольшую махинацию на связке ORM Entity Framework и СУБД PostgreSQL.

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

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

Для отработки механики сделал что-то вроде DataBase first, чтобы "натянуть" ORM на секционированную таблицу и удостовериться, что всё нормально работает.

Экспортнул результат автогенерации EF при создании базы и доработал напильником: снес старую таблицу, добавил вместо неё партицированную. Чтобы жизнь медом не казалась - сделал у таблицы foreign key, указывающий на саму себя (теперь можно хранить древовидные структуры).

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

Результат вылил на гитхаб. SQL живет в файле.

#postgresql
2025/07/07 15:10:10
Back to Top
HTML Embed Code: