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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#conf
😁3
#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
👍1
#dotnext, день 2, доклад 2.

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

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

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

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

#conf
👍1
#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
👍1😁1
#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
🔥1
Попробовал небольшую махинацию на связке ORM Entity Framework и СУБД PostgreSQL.

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

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

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

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

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

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

#postgresql
👍1
Sphagnum. Часть 3. Техобзор, начало.
#sphagnum@eshu_coding

Несмотря на нехватку времени продолжаю потихоньку заниматься новым проектом.

Прежде чем садиться писать код, нужно изучить некоторые моменты, чтобы потом не строить велосипеды из костылей. В науке эту часть работы называют "литобзор", а у меня пусть будет "техобзор" (далее под тегом #sphagnum_theory@eshu_coding)

Набросал примерный план для изучения:
1. Как организовано взаимодействие между инстансами (в т.ч. отказоустойчивость) в рамках одного replica set у следующих продуктов:
А. PostgreSQL
B. MongoDB
C. RabbitMQ
D. Kafka
E. Tarantool
F. NATS

2. Механизмы реализации (и отката, в т.ч. в нескольких инстансах, если их несколько) транзакций у:
A. PostgreSQL
B. RabbitMQ
C. Kafka
D. MongoDB

3. Механизмы шардирования у:
A. Tarantool
B. MongoDB
C. Kafka

4. Сделать обзор, что пишут известные люди в умных книжках по теме распределенных транзакций.

5. Обзор организации обмена сообщениями (Queue, Exchange, Topic etc.) в следующих брокерах сообщений:
1. RabbitMQ
2. Kafka
3. NATS
Sphagnum. Часть 4. Техобзор. Репликация и отказоустойчивость в PostgreSQL.
#sphagnum@eshu_coding

PostgreSQL предоставляет два вида репликации: физическую и логическую. Физическая репликация создаёт полную копию master-ноды, из которой при желании можно читать, но писать нельзя. Между нодами передаются байтики, записываемые в WAL. В режиме одиночного инстанса WAL удаляется после применения транзакции к данным. При включенной физической репликации - WAL сохраняются дольше и по мере необходимости отдаются репликам.

Логическая репликация передает между инстансами SQL команды. В контексте разработки брокера сообщений - не интересна.

Из коробки механизма поддержания отказоустойчивого кластера в постгресе нет, но есть сторонние решения, например - patroni. Каждому из инстансов replica-set-а постгресов закрепляется по надзирающему сервису. Эти сервисы общаются между собой. В случае падения мастера - выбирается новый мастер, простым голосованием. Прочёл про интересный механизм: если мастер остается изолирован от других реплик, он не начинает думать что он единственный такой в мире, а принудительно гасится patroni, дабы минимизировать шансы грядущей синхронизации двух мастеров.

Итого, из того, что мне пока хотелось бы взять для своего проекта:
1. Механизм распространения WAL для синхронизации.
2. Принудительное гашение мастера при изоляции его от остальной части кластера.

#sphagnum_theory@eshu_coding
#postgresql
🔥1
Sphagnum. Часть 5. Техобзор. Репликация и отказоустойчивость в MongoDB
#sphagnum@eshu_coding

MongoDB в качестве механизма репликации использует распространение между ведомыми нодами oplog-а. Изменения сперва записываются в WAL (Journaling в терминологии MongoDB), затем применяются к master-у (primary node), а затем - записываются в oplog - operation log - отдельную коллекцию (таблицу), из которой асинхронно расходятся по репликам (secondary).

Для организации отказоустойчивого кластера достаточно средств самой MongoDB. Кластер представляет собой primary-ноду и некоторое количество реплик. По ситуации могут быть добавлены специальные инстансы монги для поддержания целостности кластера: один или несколько arbiter. Выбор нового мастера может осуществляться двумя путями:
1. Совместным голосованием оставшихся в живых secondary инстансов
2. Голосованием с участием арбитров.

Ноды в replica set-е раз в две секунды пингуют друг друга. Если какая-то нода не отвечает 10 секунд - она объявляется недоступной. В случае, если это - primary, одна из нод может запустить выборы. Ноды имеют приоритет в очереди на престолонаследие и неравные веса голосов: целое число, минимум - 0, дефолт - 1.

Ноды с 0 голосов голосовать не могут вообще, но при этом могут наложить вето на запуск процедуры голосования.

Итого, из того, что мне пока хотелось бы взять для своего проекта:
1. Механизм вето запуска голосования.
2. Использование арбитров мне пока видится избыточным, но при проектировании системы стоит предусмотреть такую роль на будущее.

#sphagnum_theory@eshu_coding
#mongodb
😁1
Sphagnum. Часть 5. Техобзор. Репликация и отказоустойчивость в RabbitMQ
#sphagnum@eshu_coding

RabbitMQ поддерживает создание отказоустойчивых кластеров, но есть небольшой нюанс. Сообщения в обычных очередях не передаются между инстансами. То есть мы имеем отказоустойчивый кластер из 3 кроликов. С мастера на реплики продублированы все настройки, пользователи и т.д. Приходит в RabbitMQ цунами из сообщений, мастер ест их пока не захлебнется, а потом падает. И тут на его месте возникает радостная реплика с криками "я новый мастер, я!" и начинает работу. А данные, зависшие в старом мастере - ну когда-нибудь может быть будут отработаны и переданы. Правда, есть специальные очереди, Quorum Queues, сообщения между которыми таки распространяются по репликами и в случае нештатной ситуации таки будут отработаны. По дефолту такие очереди реплицируются по трем инстансам, но можно настроить и большее количество.

Занятно организовано голосование за право быть новым мастером. Когда мастер пропадает, реплики начинают кричать "я новый мастер!", кто успел крикнуть первым - тот и становится мастером. Если голоса равны, происходит повторное голосование. Я конечно утрирую, но логика примерно такая.

Итого, из того, что мне пока хотелось бы взять для своего проекта:
1. Идея разделения данных на реплицируемые и нет - огонь, но в качестве дефолта я бы взял реплицируемые.
2. Механизм голосования довольно забавный, но тут довольно сомнительно.

#sphagnum_theory@eshu_coding
#rabbitmq
🤡1
Forwarded from Sandbox
Дорогие друзья если у вас есть возможность пройти этот маленький АНОНИМНЫЙ опрос о стажировках
я буду благодарен.
Если вы сможете поделиться им с друзьями, будет чудесно.
А если попросите их распространить еще дальше
Я буду на 7м небе от счастья
https://forms.gle/VZNzzb7ugtuQwnMDA
Гитлаб для чайников. Часть 1.

Во второй заход осилил на практике девопсячий минимум, необходимый для построения полнофункциональной системы CI/CD (автодеплой в просторечии) на базе gitlab. То есть ты коммитишь - на сервере автоматически поднимается рабочий экземпляр твоего приложения. В качестве основы используется gitlab. Про первый заход писал выше.

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

Необходимая база:
1. Командная строка линукса, хотя бы минимум
2. docker, docker-compose
3. Основы git, чтобы не терять сознание от веток, мержей и коммитов.

Заодно добавлю новый тэг - #devops

#gitlab
Гитлаб для чайников. Часть 2.

Система выглядит примерно таким образом:
На первый сервер ставится гитлаб. В нем включается функционал хранения докер-образов.

На второй сервер ставится BaGet - максимально простое хранилище NuGet пакетов (модулей), чтобы шарить функционал и абстракции между проектами.

На третий сервер ставится gitlab runner, который будет собирать образы и NuGet пакеты.

И какое-то количество серверов, на которых будет крутиться то, что мы разработаем. На каждый из серверов ставим по runner-у.

Логика работы примерно такая: когда кто-то коммитит в репозиторий, запускается скрипт ci cd. Первая часть собирает пакеты или образа на сервере 3. Пакеты отправляются в BaGet на втором сервере. Образа - во встроенное в Gitlab хранилище.

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

Оба этапа предваряются логином к хранилищу образов и зачисткой кэша образов докера на машине с runner-ом. Я логинился с помощью Deploy Token-ов.

#gitlab
#devops
Гитлаб для чайников. Часть 3.

В гитлабе нам понадобится создать группу проектов, в ней добавить runner, общий на всю группу (для сборки образов/пакетов во всей группе). Runner может быть как групповым, так и выделенным проекту.

После этого скопировать сгенерированную команду для подключения на сервер номер 3. Выбрать тип runner-а shell, то есть он будет тупо выполнять консольные команды, прописанные в скрипте автодеплоя в гитлабе.

Давлее, повторить процедуру для всех серверов, куда будет идти деплой, runner-ы по ситуации цепляются или к проектам, или к группе.

Если общение с gilab-ом проходит по http, а не по https, надо не забыть добавить gitlab в перечень разрешенных небезопасных источников для докера в файле /etc/docker/daemon.json.

Также надо не забыть выдать пользователю-докеру прав на работу в линухе.

#gitlab
#devops
Гитлаб для чайников. Часть 4.

Всю информацию, необходимую для деплоя и работы сервисов, которая не является статичной/открытой я поместил в переменные (Settings => CI/CD => Variables).

Переменные подтягиваются при выполнении скрипта деплоя двумя способами:
1. Обычные переменные просто подставляются в текст скрипта синтаксисом вида ${VAR_NAME}
2. Переменные типа File можно подсунуть в файловую систему проекта во время строки синтаксисом вида


cp  ${FILE_VAR_NAME} prod.env
В обычных переменных я держу пароли, адреса серверов и т.д. А в файловые сохраняю .env файлы для подтягивания переменных окружения docker-compose-ом.

При деплое используется два типа docker-compose файлов. Первый для построения образов, второй - собственно для деплоя.

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

В docker docker-compose файлы второго типа через переменные окружения передается вся сопуствующая информация: строки подключения к базам данных, данные для подключения к брокерам сообщений, адреса других микросервисов.

В Gitlab CI/CD скриптах можно менять поведение в зависимости от ветки Гита, пока самое удобное для меня место - секция workflow в скрипте. Если ветка - dev, то подтягиваем из переменных файлы DEV_BUILD_ENV и DEV_DEPLOY_ENV, а также вызываем для деплоя runner, помеченный тегом dev_deploy_runner. И MASTER_BUILD_ENV, MASTER_DEPLOY_ENV, master_deploy_runner для ветки master соответственно.

После коммита прогресс выполнения скриптов отображается в разделе Pipelines. В нём можно перезапускать любую из стадий выполнения скрипта деплоя.

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

Механизм подсовывания примерно такой. В файлы с переменными окружения, после выполнения операции cp ${FILE_VAR_NAME} .env подсовывается строка tag_suffix = $CI_COMMIT_SHORT_SHA, и соответствующее значение начинает подтягиваться в тэг образа.

Сей поток сознания - изложение опыта работы с гитлабом, полученного примерно за 16 часов, 8 в июле, 8 - на этой неделе. А предваряла им пара лет эпизодических мучений с GitHub Actions и докером. Естественно, нормальный девопс сделает лучше и быстрее, но если его нет под рукой, вполне реально завести небольшое хозяйство самостоятельно.

#gitlab
#devops
2025/07/08 23:37:24
Back to Top
HTML Embed Code: