Telegram Web Link
#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
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
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
Sphagnum. Часть 5. Техобзор. Репликация и отказоустойчивость в RabbitMQ
#sphagnum@eshu_coding

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

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

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

#sphagnum_theory@eshu_coding
#rabbitmq
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
P.S. При публикации потерял картинку ко 2й части, оставлю тут.
Sphagnum. Часть 6. Техобзор. Репликация и отказоустойчивость в Kafka
#sphagnum@eshu_coding

Кафка по умолчанию сохраняет все сообщения, которые проходят через нее. У каждого сообщения есть свой offset - его порядковый номер. Соответственно, реплики в случае отставания просто просят у мастера "дай мне все сообщения больше моего оффесета". Если отставания нет - получают их в режиме реального времени. По умолчанию сообщение может быть отправлено клиенту только после того, как все реплики подтвердят его получение. Kafka старается поддерживать реплики в состоянии in-sync replicas (ISR), то есть когда состояние реплик идентично.

Выбор нового мастера в случае отказа предыдущего осуществляются инстансом Кафки, имеющим статус Controller-a. Если контроллер оказался мертв, оставшиеся в живых реплики пытаются на скорость занять его место, кто успел - тот и новый контроллер. Из группы реплик, находящихся в статусе ISR выбирается один лидер, через которого осуществляется дальнейшая работа.

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

#sphagnum_theory@eshu_coding
#kafka
Sphagnum. Часть 6. Техобзор. Репликация и отказоустойчивость в Tarantool
#sphagnum@eshu_coding

Тарантул изначально проектировался как решение, которое можно без боли (ха-ха) раскатать в бесконечное по размеру облако. Вообще, про взаимодействие между инстансами можно почитать вполне себе неплохую статью на Хабре. Если кратко, инстансы обмениваются друг с другом "слухами" о происходящих событиях по UDP (с помощью механизма SWIM) и формируют картину мира текущее состояние кластера. В случае падения мастера, выборами занимаются специальные инстансы тарантула, имеющие роль failover coordinator. Текущее состояние кластера сохраняется или в etcd (распределенное хранилище ключ-значенин) или в специфическом инстансе тарантула.

И также неплохая статья про механизм репликации. Если коротко - между мастером и репликами распространяется журнал WAL мастера. При том, распространяется отдельными процессами, живущими рядом с основным тарантулом, по процессу на реплику. Репликация может осуществляться как асинхронно (транзакция считается выполненной до того, как все реплики подтвердят получение данных), так и синхронно. Для синхронной репликации используется алгоритм Raft.

Что мне очень понравилось в тарантуле - удобный ui для создания и управления кластером (входит в Tarantool Cartridge). В нем можно проставить репликам как дефолтные роли (failover coordinator, master, replica, router), так и пользовательские, включающие определенный набор "таблиц" и хранимок. При желании, этот же ui можно использовать для написания или правки хранимых процедур и распространения обновлений по кластеру. Все команды ui также могут быть продублированы в виде скриптов в командной строке (что я и делал).


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

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

#sphagnum_theory@eshu_coding
#tarantool
Sphagnum. Часть 7. Техобзор. Репликация и отказоустойчивость в NATS
#sphagnum@eshu_coding

NATS - брокер сообщений, написанный на Go как альтернатива мейнстримным решениям - RabbitMQ и Apache Kafka. Отличительная особенность - он может подключаться в приложения, разрабатываемые на Go как dll-ка. Собственно, этот проект и вдохновил меня на разработку своего брокера сообщений. Изначально NATS был легковесным
Standalone решением, не предполагающим
кластеризацию и персистентное хранение данных. Но вскоре эти
механизмы также завезли,
породив по сути два NATS-а - классический и с наворотами. В чём-то это
похоже на развитие Tarantool.

NATS в кластерной версии, как и Tarantool, использует алгоритм Raft для выявления лидера. Кроме того, данные, попавшие в инстанс, отдаются только его непосредственным соседям. А соседи дальше их не передают. Так формируются Stream groups - группы инстансов внутри кластера с синхронизированным состоянием. Впрочем, как я понял, есть пути пустить данные дальше. Аналогично могут формироваться Consumer groups - группировки инстансов по подключенным клиентам-подписчикам. Как я понимаю (возможно ошибочно), таким образом может достигаться ограниченное горизонтальное масштабирование. В NATS данные могут сохраняться как на диск, так и в СУБД, например - постгрес.


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

1. Мне очень понравилась идея дополнительной сегментации вроде бы единого кластера.
2. Идея сохранения данных не на диск, а в СУБД выглядит очень интересно, по крайней мере на этапе разработки, чтобы отложить в сторону вопросы оптимальной укладки данных на жёсткий диск.

#sphagnum_theory@eshu_coding
#nats
Вот и закончилась первая часть обзора. Я не ставил себе целью досконально описать каждый из продуктов, для моих целей достаточно общего понимания предметной области.

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

#sphagnum_theory@eshu_coding
2025/07/06 19:33:34
Back to Top
HTML Embed Code: