Меня зовут Александр Межов.
Это канал, в котором я собираюсь делиться знаниями, опытом, мыслями и идеями относительно архитектуры и разработки программного обеспечения. Я умею объяснять сложные вещи простым языком и очень люблю это делать. Меня мотивируют успехи людей, у которых я был наставником и которым смог помочь. Имея серьезный профессиональный опыт за плечами, думаю, мне есть что сказать и чем поделиться.
Я занимаюсь архитектурой и разработкой backend без малого два десятилетия. За это время мне посчастливилось принять участие в крупных и социально значимых проектах из различных сфер: образование и здравоохранение, SCADA-системы и IoT, финансы предприятия, продажа недвижимости. Всегда удавалось работать в классных компаниях, в коллективе талантливых и профессиональных людей.
Структурирую, систематизирую и обобщаю всё, с чем сталкиваюсь. Навожу порядок в работе и умах. Охотно выступаю в роли наставника. Когда есть что сказать, выступаю на конференциях. Имею парочку своих open-source-проектов, которыми пользуются люди. По возможности вношу свой вклад и в другие open-source-инициативы.
Областью моих интересов является архитектура программного обеспечения, культура и инструменты разработки, системное программирование, базы данных, DevOps-технологии. Люблю разбираться во внутреннем устройстве технологий, с которыми приходится работать.
Навигация
#pin – навигация по каналу
#arch – архитектура
#dev – разработка
#db – базы данных
#devops – DevOps-технологии
#tech – технологии
#conf - конференции
#view – точка зрения
#tools - инструменты
#tip - советы и решения
#ai - про AI/ML
Мои профили
ℹ️ Сайт
📱 Дзен
📱 GitHub
🐶 GitVerse
Буду рад новым знакомствам! Присоединяйтесь!💬
Это канал, в котором я собираюсь делиться знаниями, опытом, мыслями и идеями относительно архитектуры и разработки программного обеспечения. Я умею объяснять сложные вещи простым языком и очень люблю это делать. Меня мотивируют успехи людей, у которых я был наставником и которым смог помочь. Имея серьезный профессиональный опыт за плечами, думаю, мне есть что сказать и чем поделиться.
Я занимаюсь архитектурой и разработкой backend без малого два десятилетия. За это время мне посчастливилось принять участие в крупных и социально значимых проектах из различных сфер: образование и здравоохранение, SCADA-системы и IoT, финансы предприятия, продажа недвижимости. Всегда удавалось работать в классных компаниях, в коллективе талантливых и профессиональных людей.
Структурирую, систематизирую и обобщаю всё, с чем сталкиваюсь. Навожу порядок в работе и умах. Охотно выступаю в роли наставника. Когда есть что сказать, выступаю на конференциях. Имею парочку своих open-source-проектов, которыми пользуются люди. По возможности вношу свой вклад и в другие open-source-инициативы.
Областью моих интересов является архитектура программного обеспечения, культура и инструменты разработки, системное программирование, базы данных, DevOps-технологии. Люблю разбираться во внутреннем устройстве технологий, с которыми приходится работать.
Навигация
#pin – навигация по каналу
#arch – архитектура
#dev – разработка
#db – базы данных
#devops – DevOps-технологии
#tech – технологии
#conf - конференции
#view – точка зрения
#tools - инструменты
#tip - советы и решения
#ai - про AI/ML
Мои профили
Буду рад новым знакомствам! Присоединяйтесь!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥2
Ускорение потоков данных
Недавно я принял участие в конференции TeamLeadConf 2024, которая на этот раз прошла в Сколково. Я выступал в треке TeachLead с докладом "Кратно ускоряем потоки данных. Практичные архитектурные приёмы" (презентация ниже⬇️ ). Пока монтируют видео моего доклада, изложу основные тезисы.
Как правило мы находимся в условиях, когда за короткий срок нужно выдать максимум функциональности, обеспечив при этом должный уровень производительности приложения. И если функциональным требованиям уделяют основное внимание, то вопрос производительности воспринимают как само собой разумеющееся. А что делать, если сроки сжаты до предела, уже имеется какой-то рабочий прототип, а нагрузка к моменту основного релиза обещает вырасти в десятки раз? В такой момент мы хотим найти простое решение, которое избавит нас от большей части проблем.
В рамках доклада я предлагаю рассмотреть 5 простых и практичных архитектурных приемов, которые позволяют кратно ускорить потоки данных.
1️⃣ Расширение контекста
2️⃣ Типизация событий
3️⃣ Конвейеризация вычислений
4️⃣ Распараллеливание вычислений
5️⃣ Локализация данных
Далее я раскрою суть и условия применимости каждого приема.
📱 Ссылки на видео доклада — YouTube, VK
📱 Статья по мотивам доклада — Дзен
#conf
Недавно я принял участие в конференции TeamLeadConf 2024, которая на этот раз прошла в Сколково. Я выступал в треке TeachLead с докладом "Кратно ускоряем потоки данных. Практичные архитектурные приёмы" (презентация ниже
Как правило мы находимся в условиях, когда за короткий срок нужно выдать максимум функциональности, обеспечив при этом должный уровень производительности приложения. И если функциональным требованиям уделяют основное внимание, то вопрос производительности воспринимают как само собой разумеющееся. А что делать, если сроки сжаты до предела, уже имеется какой-то рабочий прототип, а нагрузка к моменту основного релиза обещает вырасти в десятки раз? В такой момент мы хотим найти простое решение, которое избавит нас от большей части проблем.
В рамках доклада я предлагаю рассмотреть 5 простых и практичных архитектурных приемов, которые позволяют кратно ускорить потоки данных.
Далее я раскрою суть и условия применимости каждого приема.
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Приём №1: Расширение контекста
ℹ️ Под событием подразумеваю какой-то сигнал в информационной системе; а под контекстом - данные, которые с ним связаны. Контекст используется при обработке события. Физическое представление события, передаваемое по сети, буду называть сообщением.
🕚 Если данных контекста недостаточно, обработчик вынужден запрашивать их извне: из базы данных, у внешних служб и т.п. Обычно такое происходит, когда хотят минимизировать размер сообщения. Например, в сообщении вместо содержимого документа передается только его идентификатор.
🕚 При таком подходе возможны две проблемы. Во-первых, появляется альтернативный канал связи, что провоцирует гонку по данным и увеличивает требования к хранилищу данных (см. Linearizability и Read Your Writes). Во-вторых, время, которое тратится на восстановление нужного контекста, гарантированно снижает производительность обработчика, особенно в момент нагрузки на систему.
❗️ Расширяйте контекст события, если это не приведет к недопустимой нагрузке на сетевую и дисковую подсистему. Для этого проанализируйте существующий или возможный поток данных: интенсивность возникновения событий и размер сообщений.
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Контекст события ДОЛЖЕН содержать необходимую и достаточную информацию для его обработки, ЕСЛИ ожидаемый поток событий не приводит к превышению лимитов по выделенным ресурсам.
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
Приём №2: Типизация событий
❗️ Вроде всё понятно, но есть коварный случай – неявная типизация. Это тот случай, когда реально разные типы событий попадают в один топик и обрабатываются одним и тем же обработчиком. Казалось бы, бред! Но в реальности такое можно увидеть достаточно часто. В основном это вызвано недостаточным знанием предметной области.
Если мы недостаточно знаем предметную область, то можем сделать ложное обобщение. Например, для логически разных событий использовать универсальную структуру данных. Об этом же предостерегает и Роберт Мартин в книге "Чистая архитектура" (см. главу "Ложное дублирование").
В чем же основной недостаток иметь один топик для разных типов событий? В первую очередь - это невозможность контроля в распределении ресурсов. Разные типы событий могут иметь разную частоту возникновения, разную скорость обработки. Скорей всего, для наиболее частотных и/или медленно обрабатываемых событий вы захотите выделить больше ресурсов для их обработки.
Итак, как же выявить неявную типизацию? Можно выделить следующие отличительные признаки.
1️⃣ Контекст события уже на этапе публикации содержит какой-то ключ-классификатор (один или несколько атрибутов), по которому можно судить о типе события.
2️⃣ Алгоритм обработчика события имеет ветвление, которое производится по некоторым атрибутам контекста события. При этом логика обработки каждой ветки существенно разнится. В этом случае можно говорить, что типизация событий производится на стороне получателя.
3️⃣ Код обработчика не имеет ветвлений, но время обработки событий существенно варьируется. В этом случае можно говорить о вариативной вычислительной сложности обработки. Как правило, такое происходит, если обработчик делегирует основную работу внешнему компоненту. Если так, то см. код внешнего компонента и п. 2.
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
События ДОЛЖНЫ направляться в разные топики, ЕСЛИ события явно ИЛИ неявно относятся к разным типам, И ЕСЛИ тип события можно определить перед отправкой.
Если мы недостаточно знаем предметную область, то можем сделать ложное обобщение. Например, для логически разных событий использовать универсальную структуру данных. Об этом же предостерегает и Роберт Мартин в книге "Чистая архитектура" (см. главу "Ложное дублирование").
В чем же основной недостаток иметь один топик для разных типов событий? В первую очередь - это невозможность контроля в распределении ресурсов. Разные типы событий могут иметь разную частоту возникновения, разную скорость обработки. Скорей всего, для наиболее частотных и/или медленно обрабатываемых событий вы захотите выделить больше ресурсов для их обработки.
Итак, как же выявить неявную типизацию? Можно выделить следующие отличительные признаки.
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Приём №3: Конвейеризация вычислений
❗️ Иначе говоря, мы рассматриваем ситуацию, когда алгоритм обработки состоит из нескольких стадий, где выходные данные одной являются входными для другой - следующей (или следующих в случае, если есть условия выбора следующей стадии). И при этом каждая стадия вносит существенный вклад в общее время обработки. В этом случае следует задуматься о конвейеризации и для каждой стадии конвейера предусмотреть отдельный тип события и топик.
Почему это должно ускорить поток данных? Тут следует начать с того, что конвейер позволяет осуществлять внеочередное исполнение. Как это происходит, лучше посмотреть на простом арифметическом примере.🤓 Допустим, наш алгоритм состоит из двух стадий:
Если у нас один обработчик, который последовательно выполняет все стадии, общее время обработки двух событий будет равно сумме задержек на всех этапах:
Если у нас конвейерная обработка, т.е. стадии
❗️ Отдельно стоит отметить тот случай, когда решение об окончании обработки можно принять на ранних стадиях, досрочно закончив весь цикл, откинув ненужных "хвост" конвейера.
А теперь, отбросив всю эту теорию, приведу пару практических примеров для закрепления материала (можете добавлять в этот список свои более сложные и интересные кейсы).
🕚 Проверка возможности выполнения обработки и обработка. Например, проверяем счет клиента, а уже затем формируем для него коммерческое предложение.
🕚 Подготовка к обработке данных и обработка. Например, перед формированием отчета аккумулируем всех необходимые данные в одном месте.
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Обработчик события ДОЛЖЕН быть поделен на стадии, где каждая стадия – отдельный топик, ЕСЛИ выполнение следующей стадии зависит от предыдущей, И ЕСЛИ время выполнения каждой стадии ощутимо большое.
Почему это должно ускорить поток данных? Тут следует начать с того, что конвейер позволяет осуществлять внеочередное исполнение. Как это происходит, лучше посмотреть на простом арифметическом примере.
A и B; а в очереди находится два события: E1 и E2.Если у нас один обработчик, который последовательно выполняет все стадии, общее время обработки двух событий будет равно сумме задержек на всех этапах:
Ts=A(E1)+B(E1)+A(E2)+B(E2).E1: A(E1) -> B(E1) ->
E2: ----------------> A(E2) -> B(E2)
Если у нас конвейерная обработка, т.е. стадии
A и B выполняют разные обработчики, то для разных событий они могут работать параллельно и независимо друг от друга. И в таком случае общее время обработки двух событий будет равно сумме: Tp=A(E1)+max(B(E1),A(E2))+B(E2). Не трудно увидеть, что Ts>Tp.E1: A(E1) -> B(E1)
E2: -------> A(E2) -> B(E2)
А теперь, отбросив всю эту теорию, приведу пару практических примеров для закрепления материала (можете добавлять в этот список свои более сложные и интересные кейсы).
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3
Решил сделать отдельный пост-справку для тех, кто не знаком с одним или другим видом брокеров. На нее я буду ссылаться в дальнейшем. 🤓
🟢 Log-based-брокеры
Имеют постоянно пополняемый лог сообщений, хранимый на диске. Именованный лог называется топиком (topic) и обычно хранит сообщения с одинаковой структурой данных (одного типа). Источник отправляет сообщения, записывая их в топик; приемник, называемый потребителем (consumer), получает сообщения, читая и обрабатывая их последовательно. При успешной обработке потребитель сдвигает указатель (offset) к следующему сообщению. Положение указателя также хранится на диске и ассоциировано с потребителем. У каждого топика может быть только один потребитель, относящийся к одной группе (consumer group). Чаще всего группа ассоциируется с каким-то одним приложением (например, микросервисом).
В целях масштабирования и увеличения пропускной способности топик можно разбить на части - партиции (partitions), каждая из которых может быть размещена на различных узлах кластера. В какую партицию будет направлено сообщение решает источник; какую партицию будет обрабатывать потребитель решает брокер. Брокер отслеживает жизненный цикл каждого потребителя и производит процедуру балансировки - перераспределения топиков между активными потребителями. При этом важно то, что одну партицию может обслуживать только один потребитель. Так гарантируется порядок обработки сообщений в рамках одной партиции.
❗️ Самые главные преимущества Log-based-брокеров - это лог, хранимый на диске; возможность перемещать указатель (offset) в любое положение; высокие гарантии соблюдения строгого порядка обработки. Таким образом, есть история возникновения событий и возможность ее многократной обработки.
Типичные представители: Apache Kafka, Redpanda.
🟢 Queue-based-брокеры
Имеют именованные очереди сообщений и, как правило, предоставляют более гибкий способ маршрутизации от источника к приемнику. Иначе говоря, приемник может подписаться не только на топик, но и указать более сложные правила. Например, подписаться на сообщения, у которых топик удовлетворяет какой-то маске или которые имеют какой-то определенный заголовок. При успешной обработке сообщения всеми обработчиками очереди, сообщение удаляется из очереди. Сообщения могут храниться как на диске, так и в ОЗУ.
За маршрутизацию сообщений отвечает брокер. Одну и ту же очередь может обслуживать любое количество обработчиков. Как только обработчик заканчивает обработку очередного сообщения, брокер передает ему новое. В общем случае порядок обработки не гарантируется, т.к. основная задача - побыстрей обработать очередь.
❗️ Самые главные преимущества Queue-based-брокеров - это простота и гибкость обмена сообщениями; адаптивность к вариативной вычислительной сложности обработки; отсутствие простоев обработчиков.
Типичные представители: AMQP/JMS-based - RabbitMQ, Apache ActiveMQ; MQTT-based - Eclipse Mosquitto, HiveMQ.
Итого
Log-based- и Queue-based-брокеры спроектированы для разных типов задач, о характере которых можно судить, посмотрев на главные преимущества этих брокеров. В дальнейшем я раскрою тему выбора более подробно.
#tech
Имеют постоянно пополняемый лог сообщений, хранимый на диске. Именованный лог называется топиком (topic) и обычно хранит сообщения с одинаковой структурой данных (одного типа). Источник отправляет сообщения, записывая их в топик; приемник, называемый потребителем (consumer), получает сообщения, читая и обрабатывая их последовательно. При успешной обработке потребитель сдвигает указатель (offset) к следующему сообщению. Положение указателя также хранится на диске и ассоциировано с потребителем. У каждого топика может быть только один потребитель, относящийся к одной группе (consumer group). Чаще всего группа ассоциируется с каким-то одним приложением (например, микросервисом).
В целях масштабирования и увеличения пропускной способности топик можно разбить на части - партиции (partitions), каждая из которых может быть размещена на различных узлах кластера. В какую партицию будет направлено сообщение решает источник; какую партицию будет обрабатывать потребитель решает брокер. Брокер отслеживает жизненный цикл каждого потребителя и производит процедуру балансировки - перераспределения топиков между активными потребителями. При этом важно то, что одну партицию может обслуживать только один потребитель. Так гарантируется порядок обработки сообщений в рамках одной партиции.
Типичные представители: Apache Kafka, Redpanda.
Имеют именованные очереди сообщений и, как правило, предоставляют более гибкий способ маршрутизации от источника к приемнику. Иначе говоря, приемник может подписаться не только на топик, но и указать более сложные правила. Например, подписаться на сообщения, у которых топик удовлетворяет какой-то маске или которые имеют какой-то определенный заголовок. При успешной обработке сообщения всеми обработчиками очереди, сообщение удаляется из очереди. Сообщения могут храниться как на диске, так и в ОЗУ.
За маршрутизацию сообщений отвечает брокер. Одну и ту же очередь может обслуживать любое количество обработчиков. Как только обработчик заканчивает обработку очередного сообщения, брокер передает ему новое. В общем случае порядок обработки не гарантируется, т.к. основная задача - побыстрей обработать очередь.
Типичные представители: AMQP/JMS-based - RabbitMQ, Apache ActiveMQ; MQTT-based - Eclipse Mosquitto, HiveMQ.
Итого
Log-based- и Queue-based-брокеры спроектированы для разных типов задач, о характере которых можно судить, посмотрев на главные преимущества этих брокеров. В дальнейшем я раскрою тему выбора более подробно.
#tech
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍1
Приём №4: Распараллеливание вычислений
Вот с такого простого и очевидного утверждения начинается долгая история. 😃
Когда мы говорим о распараллеливании, нужно помнить про условия обеспечения эффективности параллелизма. И тут я выделяю два условия.
1️⃣ Основное время уходит на полезную работу. Тут мы сводим к минимуму программные блокировки и ожидания результатов при вызове внешних компонентов. Избавляемся от необходимости в синхронизации потоков, минимизируем количество IPC, оптимизируем запросы к БД и т.п. Пожалуй, именно с выполнения этого условия и нужно начинать процесс повышения эффективности параллелизма.
2️⃣ Вычислительная сложность обработки одинакова для всех событий. В самом деле, выполнение этого условия дает шансы на то, что время обработки каждого события будет плюс-минус одинаковое. Это значит, что простои обработчиков будут сведены к минимуму, поскольку они будут начинать и закачивать работу примерно в одно и тоже время.
Если же вычислительная сложность разнится, то и время обработки каждого события будет разниться, провоцируя простой самых быстрых обработчиков в группе.
Хорошо, и что мне теперь делать с этой теорией?! Запомнить и переходить к практике. 😃 Ниже я рассмотрю как эта теория соотносится с Log-based- и Queue-based-брокерами, что может повлиять на выбор брокера или на пересмотр сценариев по работе с ними.
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
События в топике ДОЛЖНЫ обрабатываться параллельно, ЕСЛИ не важен порядок их обработки.
Вот с такого простого и очевидного утверждения начинается долгая история. 😃
Когда мы говорим о распараллеливании, нужно помнить про условия обеспечения эффективности параллелизма. И тут я выделяю два условия.
┌─> AAA ─┐
fork >─┼─> BBB ─┼─> join
└─> CCC ─┘
Если же вычислительная сложность разнится, то и время обработки каждого события будет разниться, провоцируя простой самых быстрых обработчиков в группе.
┌─> AAAAAAAAA ─┐
fork >─┼─> BBBBB.... ─┼─> join
└─> CCC...... ─┘
Хорошо, и что мне теперь делать с этой теорией?! Запомнить и переходить к практике. 😃 Ниже я рассмотрю как эта теория соотносится с Log-based- и Queue-based-брокерами, что может повлиять на выбор брокера или на пересмотр сценариев по работе с ними.
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🤔1
Распараллеливание вычислений в Log-based-брокерах
Начало см. выше.
В контексте распараллеливания вычислений я выделяю две важных особенности Log-based-брокеров:
1️⃣ Уровень параллелизма зависит от количества партиций. Мы не можем создать обработчиков больше, чем количество партиций.
2️⃣ Неравномерная загрузка обработчиков, если время обработки сильно варьируется. В разные партиции могут попасть сообщения с разной вычислительной сложностью.
Первая проблема с нехваткой обработчиков решается двумя способами, которые можно использовать совместно:
🕚 Увеличение числа партиций (сразу много или добавляем по мере возрастания нагрузки). Существует ряд недостатков данного решения, в том числе дополнительные накладные расходы на обслуживание партиций; увеличение времени балансировки; значительные сложности при сокращении числа партиций.
🕚 Параллельная обработка пачки сообщений в разных потоках одного процесса ОС. Иначе говоря, потребитель считывает N-сообщений и каждое обрабатывает в отдельном потоке. Когда заканчивается обработка всех N-сообщений, происходит смещение к следующей пачке. Недостатки - необходимость ожидания окончания обработки всей пачки сообщений. Подход подробно описан тут (VPN), имеет готовую реализацию. Более глобально эту проблему пытаются решить на уровне Apache Kafka в рамках KIP-932.
Вторая проблема куда более значительная для Log-based-брокеров, поскольку по замыслу они не предназначены для нагрузки подобного рода. Если от сообщения к сообщению время обработки варьируется существенно, то это может привести к ребалансировке. Один из потребителей может уйти в долгую обработку, перестав подавать признаки жизни, в результате чего брокер может счесть потребителя нерабочим и начать процесс перераспределения партиций. Это сложный и долгий процесс, который частично или полностью останавливает обработку событий топика.
В случае с Apache Kafka решить проблему можно "на глазок": установить более точные настройки для max.poll.records (сделать поменьше) и/или max.poll.interval.ms (сделать побольше). Или усложнить логику взаимодействия с брокером так, чтобы потребитель подавал брокеру признаки жизни, даже в случае долгой/зависшей обработки (см. методы
Иногда эту проблему удается решить частично с помощью приёма "Типизация событий". Например, предположим, что событием является запрос на формирование сложного финансового отчета. Если пользователь укажет слишком большой календарный период, придется анализировать слишком много данных, это увеличит время формирования отчета. Если пользователь задаст более точный фильтр, это ожидаемо сократит время обработки. Таким образом, на стороне отправителя можно сделать небольшую эвристику - типизацию отчётов, например, на "простые" ("быстрые") и "сложные" ("медленные"). Для каждого типа завести свой тип и топик. Обработчик каждого типа будет обслуживать очередь в своем темпе, со своими настройками.
#conf
Начало см. выше.
В контексте распараллеливания вычислений я выделяю две важных особенности Log-based-брокеров:
Первая проблема с нехваткой обработчиков решается двумя способами, которые можно использовать совместно:
Вторая проблема куда более значительная для Log-based-брокеров, поскольку по замыслу они не предназначены для нагрузки подобного рода. Если от сообщения к сообщению время обработки варьируется существенно, то это может привести к ребалансировке. Один из потребителей может уйти в долгую обработку, перестав подавать признаки жизни, в результате чего брокер может счесть потребителя нерабочим и начать процесс перераспределения партиций. Это сложный и долгий процесс, который частично или полностью останавливает обработку событий топика.
В случае с Apache Kafka решить проблему можно "на глазок": установить более точные настройки для max.poll.records (сделать поменьше) и/или max.poll.interval.ms (сделать побольше). Или усложнить логику взаимодействия с брокером так, чтобы потребитель подавал брокеру признаки жизни, даже в случае долгой/зависшей обработки (см. методы
pause/resume).Иногда эту проблему удается решить частично с помощью приёма "Типизация событий". Например, предположим, что событием является запрос на формирование сложного финансового отчета. Если пользователь укажет слишком большой календарный период, придется анализировать слишком много данных, это увеличит время формирования отчета. Если пользователь задаст более точный фильтр, это ожидаемо сократит время обработки. Таким образом, на стороне отправителя можно сделать небольшую эвристику - типизацию отчётов, например, на "простые" ("быстрые") и "сложные" ("медленные"). Для каждого типа завести свой тип и топик. Обработчик каждого типа будет обслуживать очередь в своем темпе, со своими настройками.
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Распараллеливание вычислений в Queue-based-брокерах
Начало см. выше.
Пожалуй, я начну с совета:
Этот совет особенно ценен в случае, когда время обработки событий сильно варьируется, поскольку это та нагрузка, к которой Queue-based-брокеры особенно хорошо приспособлены. Как только обработчик заканчивает обработку очередного сообщения, брокер передает ему новое. Обработчики работают non-stop, исключая простои, увеличивая пропускную способность и по-максимуму утилизируя выделенные ресурсы.
Можно сделать мини-вывод, что Queue-based-брокеры отлично подходят:
🕚 для задач с малопредсказуемой продолжительностью выполнения;
🕚 когда гораздо важней как можно быстрей выполнить весь объем задач.
Примеры идеальных задач для Queue-based-брокеров:
🕚 задача формирование отчета по запросу-фильтру пользователя;
🕚 поиск и анализ ссылок в документе;
🕚 сборка мусора - удаление ненужных данных и т.п.
В свою очередь Log-based-брокеры хорошо подходят для потоковой обработки событий, когда важен порядок следования и обработки, нужен лог событий и возможность запуска повторной обработки.
На этом эпопею с брокерами считаю законченной. 😃
#conf
Начало см. выше.
Пожалуй, я начну с совета:
Для увеличения параллелизма МОЖНО отказаться от Log-based-брокера в пользу Queue-based, ЕСЛИ не важен порядок обработки событий, И ЕСЛИ не нужен лог событий.
Этот совет особенно ценен в случае, когда время обработки событий сильно варьируется, поскольку это та нагрузка, к которой Queue-based-брокеры особенно хорошо приспособлены. Как только обработчик заканчивает обработку очередного сообщения, брокер передает ему новое. Обработчики работают non-stop, исключая простои, увеличивая пропускную способность и по-максимуму утилизируя выделенные ресурсы.
Можно сделать мини-вывод, что Queue-based-брокеры отлично подходят:
Примеры идеальных задач для Queue-based-брокеров:
В свою очередь Log-based-брокеры хорошо подходят для потоковой обработки событий, когда важен порядок следования и обработки, нужен лог событий и возможность запуска повторной обработки.
На этом эпопею с брокерами считаю законченной. 😃
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
Приём №5: Локальность данных
Пожалуй, это один из основных принципов обработки данных в распределенных системах. Самое простое определение звучит так: держите данные ближе к месту их обработки. Этот приём направлен на оптимизацию вычислений, чтобы основное время уходило на полезную работу. Такой подход особенно оправдан при частом и одновременном обращении к некоторой части данных.
Я предлагаю присмотреться к частному случаю этого принципа, т.к. обычно он более прост в реализации и, как правило, накладывает меньше архитектурных ограничений.
Существует множество способов реализации этого приёма, выбор зависит от решаемой задачи, доступного времени и средств. Примеры реализации:
🕚 Локальный (файловый) кэш/индекс редко меняющихся данных. Сюда же можно отнести алгоритм "Hash Join". В большинстве случаев значительно ускоряет обработку, но требует усилий по поддержанию кэша/индекса в актуальном состоянии.
🕚 Использование общей области памяти (shared memory) или общего диска. Также значительно ускоряет обработку, но в какой-то степени увеличивает связность между взаимодействующими компонентами, добавляет зависимость от технологии взаимодействия. "Читатель" знает, куда, что и как пишет "писатель", и наоборот. Помимо этого, взаимодействующие компоненты могут не ужиться на одной машине (разные требования к окружению, ресурсам, масштабированию и т.п.); общий диск может оказаться сетевым, что еще хуже скажется на производительности.
❗️ Теперь про жесткие архитектурные ограничения. Если рассматривать принцип локальности данных в целом, способ его реализации может существенным образом повлиять на всё решение. Поэтому важно проанализировать все достоинства и недостатки каждого варианта. В качестве иллюстрации можно рассмотреть следующие примеры.
🕚 Модель хранения данных - это тоже способ локализации данных. Например, key-value, column-family, индексы, да даже файловая система. Мы выбираем ту модель, которая наиболее удобна для большинства сценариев приложения. И я думаю, что многие сталкивались с ситуацией, когда модель хранения не отвечает существующим требованиям. Это способствует появлению неуклюжей и запутанной логики в коде.
🕚 Использование денормализации данных или даже документно-ориентированных хранилищ (например, MongoDB). Если необходимые данные распределены по множеству таблиц, потребуются достаточные усилия, чтобы собрать их вместе. Это создаст нагрузку на дисковую и сетевую инфраструктуру, увеличит время обработки. С другой стороны, мы получим избыточность данных и, если мы будем использовать только малую часть большого документа, это может негативно отразиться на сценариях чтения и записи.
🕚 Использование локальной файловой системы для генерации отчета вместо того, чтобы генерировать его в оперативной памяти или сразу производить запись в BLOB-хранилище. Преимущества очевидны - минимальное потребление памяти, сокращение количества сетевых обращений. Недостатки - места на локальном диске может не хватить; приложение должно позаботиться об удалении временных файлов.
🕚 Алгоритм "Partitioned Hash Join" - партиционирование разных потоков данных по одному ключу, чтобы иметь возможность эффективно объединять потоки на стороне обработчика. Этот подход применим только в случае, когда партиции могут быть помещены в оперативную память.
Итого
❗️ Локализация данных позволяет добиться значительных или даже прорывных успехов в сокращении времени обработки. Это очень сильный приём, но в этом же кроется и его слабость, т.к. за мощную оптимизацию приходится платить. И если на сегодняшний день эта цена может показаться не такой большой, то завтра, когда поменяются требования к системе, это может стать серьезным препятствием, чтобы двигаться дальше.
Докидывайте свои кейсы и случаи, как ускорялись, и как подобные решения проявились в дальнейшем. 😉
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Пожалуй, это один из основных принципов обработки данных в распределенных системах. Самое простое определение звучит так: держите данные ближе к месту их обработки. Этот приём направлен на оптимизацию вычислений, чтобы основное время уходило на полезную работу. Такой подход особенно оправдан при частом и одновременном обращении к некоторой части данных.
Я предлагаю присмотреться к частному случаю этого принципа, т.к. обычно он более прост в реализации и, как правило, накладывает меньше архитектурных ограничений.
Данные ДОЛЖНЫ находиться в месте их использования, ЕСЛИ они изменяются редко.
Существует множество способов реализации этого приёма, выбор зависит от решаемой задачи, доступного времени и средств. Примеры реализации:
Итого
Докидывайте свои кейсы и случаи, как ускорялись, и как подобные решения проявились в дальнейшем. 😉
Данный пост поясняет тезисы моего доклада с конференции TeachLeadConf 2024.
#conf
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍2
Прошло полтора года, как я участвую в проекте "Виртуальная лаборатория: информатика и программирование". Это учебный модуль информационного портала "Московской Электронной Школы" (МЭШ), где ученики могут проходить курсы по информатике и программированию, выполнять различные тематические задания, в том числе решать задачи по программированию. Нам присылают код, который мы должны выполнить многократно, используя набор тестов предопределенный автором задачи. И каким бы подозрительным не был код, выполнить мы его обязаны в любом случае. Таково функциональное требование заказчика.
На проект я зашел в тот момент, когда нам объявили, что через три месяца на базе нашего модуля необходимо начать проводить олимпиады по программированию, а нагрузка в связи с этим возрастет в несколько десятков раз. То, с чем пришлось столкнуться, я рассказывал на UWDC 2024, а уже после этого я подготовил более проработанный доклад на TeachLeadConf 2024.
Я намеренно обходил моменты, связанные с особенностью прикладной области этого проекта. Не каждому интересно то, как мы компилируем и исполняем код, который нам прислали школьники. 👨🏻🎓 Однако, несмотря на кажущуюся простоту, эта задача, действительно, оказалась нелегкой. Что нам только не присылают, как только не пытаются нас "сломать" и "положить". И некоторым это удавалось сделать, за что пытливым умам можно выразить отдельный респект.🔥 Однако за последний год это удалось сделать только один раз, после чего мы исправили уязвимость. 😃
Начиная с UWDC 2024 ко мне обращались люди и интересовались деталями того, как именно мы справляемся с подобными вызовами. Пока лишь могу сказать, что у нас многоуровневая защита - комплекс мер, и детали этого трудно раскрыть в одном коротком посте. Поскольку интерес к этой теме сохраняется, я постараюсь раскрыть детали и особенности того, как безопасно исполнять чужой код/программы/скрипты.
А у вас была необходимость в запуске подозрительного кода?Подозреваю, что для QA любой код выглядит подозрительно. 😃
#untrusted_code #summary
На проект я зашел в тот момент, когда нам объявили, что через три месяца на базе нашего модуля необходимо начать проводить олимпиады по программированию, а нагрузка в связи с этим возрастет в несколько десятков раз. То, с чем пришлось столкнуться, я рассказывал на UWDC 2024, а уже после этого я подготовил более проработанный доклад на TeachLeadConf 2024.
Я намеренно обходил моменты, связанные с особенностью прикладной области этого проекта. Не каждому интересно то, как мы компилируем и исполняем код, который нам прислали школьники. 👨🏻🎓 Однако, несмотря на кажущуюся простоту, эта задача, действительно, оказалась нелегкой. Что нам только не присылают, как только не пытаются нас "сломать" и "положить". И некоторым это удавалось сделать, за что пытливым умам можно выразить отдельный респект.
Начиная с UWDC 2024 ко мне обращались люди и интересовались деталями того, как именно мы справляемся с подобными вызовами. Пока лишь могу сказать, что у нас многоуровневая защита - комплекс мер, и детали этого трудно раскрыть в одном коротком посте. Поскольку интерес к этой теме сохраняется, я постараюсь раскрыть детали и особенности того, как безопасно исполнять чужой код/программы/скрипты.
А у вас была необходимость в запуске подозрительного кода?
#untrusted_code #summary
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2❤1
Фэйл с RabbitMQ и Kubernetes API
Сегодня будет немного личная история, но с моралью в конце. На самом деле заголовок не совсем точный, но на момент, когда произошел описываемый казус, всё выглядело именно так. 😃
Ранее я давал пояснения, почему Kafka плохо подходит для случаев, когда время обработки однотипных сообщений существенно варьируется. По этой причине мы сделали выбор в пользу Queue-based-брокера и начали миграцию с Kafka на RabbitMQ. Поскольку для нас не была важна история событий, а работа с очередью была инкапсулирована в рамках соответствующего порта, технический переход на RabbitMQ прошел быстро и безболезненно. Самое интересное нас ждало впереди.
Спустя какое-то время после релиза мы начали замечать, что некоторые обработчики стали отваливаться с ошибкой Delivery Acknowledgement Timeout. Если коротко, то данная ошибка происходит в том случае, если сообщение, взятое из очереди, не получило подтверждения обработки спустя длительное время (по умолчанию 30 минут). В этом случае сообщение возвращается обратно в очередь брокера, соединение со старым обработчиком разрывается, и происходит переназначение обработчика сообщения.
Всё выглядело очень странно, и мы никак не могли добиться воспроизведения злосчастного бага. Код обработчика не менялся очень давно, поэтому все подозрения пали на RabbitMQ. Сначала мы перепроверили Helm Chart нашего кластера RabbitMQ; параллельно с этим перепотрошили все настройки RabbitMQ-клиента. В итоге сделали более тонкую и точную конфигурацию, которая соответствовала всем справочным рекомендациям по работе с RabbitMQ. Не скажу, что всё было напрасно, но ситуацию это не исправило. 😭
В тот момент, когда я начал замечать косые взгляды и намеки в стиле: «A вот с Kafka у нас всё работало, пока ты не пришёл со своим RabbitMQ!» — я понял, что дело не в брокере, а в самом обработчике. Я сделал концептуально правильный выбор, он полностью соответствовал нашим текущим потребностям, и в этом не было никаких сомнений. А раз дело в обработчике, значит, нужно искать виновника зависаний. Поскольку я хорошо знал алгоритм обработки, я практически сразу понял, с чем это может быть связано... 🎯
Сегодня будет немного личная история, но с моралью в конце. На самом деле заголовок не совсем точный, но на момент, когда произошел описываемый казус, всё выглядело именно так. 😃
Ранее я давал пояснения, почему Kafka плохо подходит для случаев, когда время обработки однотипных сообщений существенно варьируется. По этой причине мы сделали выбор в пользу Queue-based-брокера и начали миграцию с Kafka на RabbitMQ. Поскольку для нас не была важна история событий, а работа с очередью была инкапсулирована в рамках соответствующего порта, технический переход на RabbitMQ прошел быстро и безболезненно. Самое интересное нас ждало впереди.
Спустя какое-то время после релиза мы начали замечать, что некоторые обработчики стали отваливаться с ошибкой Delivery Acknowledgement Timeout. Если коротко, то данная ошибка происходит в том случае, если сообщение, взятое из очереди, не получило подтверждения обработки спустя длительное время (по умолчанию 30 минут). В этом случае сообщение возвращается обратно в очередь брокера, соединение со старым обработчиком разрывается, и происходит переназначение обработчика сообщения.
Всё выглядело очень странно, и мы никак не могли добиться воспроизведения злосчастного бага. Код обработчика не менялся очень давно, поэтому все подозрения пали на RabbitMQ. Сначала мы перепроверили Helm Chart нашего кластера RabbitMQ; параллельно с этим перепотрошили все настройки RabbitMQ-клиента. В итоге сделали более тонкую и точную конфигурацию, которая соответствовала всем справочным рекомендациям по работе с RabbitMQ. Не скажу, что всё было напрасно, но ситуацию это не исправило. 😭
В тот момент, когда я начал замечать косые взгляды и намеки в стиле: «A вот с Kafka у нас всё работало, пока ты не пришёл со своим RabbitMQ!» — я понял, что дело не в брокере, а в самом обработчике. Я сделал концептуально правильный выбор, он полностью соответствовал нашим текущим потребностям, и в этом не было никаких сомнений. А раз дело в обработчике, значит, нужно искать виновника зависаний. Поскольку я хорошо знал алгоритм обработки, я практически сразу понял, с чем это может быть связано... 🎯
🔥2👍1
Фэйл с RabbitMQ и Kubernetes API (продолжение)
Во время обработки происходит обращение к Kubernetes API (такова специфика нашей прикладной задачи). И вот на этом шаге всё и зависало, поскольку не был установлен таймаут ожидания результатов вызова. 🙉 Дело в том, что для вызова Kubernetes API мы используем fabric8io/kubernetes-client, у которого, как выяснилось, по умолчанию был установлен некоторый Rate Limit. 🙊 Превышение этого лимита приводило к зависанию при обращении к Kubernetes API. В результате проблема была решена добавлением таймаута ожидания результатов, подбором и установкой подходящих значений для Rate Limit, выносом этих настроек в конфигурационный файл приложения.
По итогу я был рад не столько тому, что нашёл и исправил проблему, сколько тому, что моё архитектурное решение было правильным, а возникшая ситуация подтвердила это ещё раз. В Kafka всё работало, и проблема не проявлялась только потому, что она не приспособлена к ситуациям, когда время обработки может варьироваться. В итоге пропускная способность системы на базе Kafka была ниже, мы никогда не упирались в Rate Limit для Kubernetes API и даже не знали о существовании этой проблемы. С переходом на RabbitMQ пропускная способность увеличилась, проблема материализовалась, опустившись на тот уровень, где она всегда и была, а мы стали упираться в Rate Limit.
❗️ Из этой истории можно извлечь, как минимум, два урока.
1️⃣ Если в коде приложения есть синхронные обращения к сторонним службам, то всегда нужно контролировать время ожидания (timeout). Также не забывать и про другие шаблоны устойчивости, а именно: не злоупотреблять шаблоном "Повтор" (Retry), в нужные моменты использовать "Предохранитель" (Circuit Breaker), и понимать, что "Ограничитель скорости" (Rate Limiter) может быть установлен с двух сторон (на клиенте и сервере). Однажды я не уделил этому должного внимания, спровоцировав неприятности в дальнейшем.
2️⃣ Не нужно сомневаться в том, как на концептуальном уровне работают инструменты, в частности брокеры. Если что-то идет не так, проблема может быть в развертывании, но, скорей всего, в коде приложения. Нужно убедиться, что настройки клиентского драйвера выполнены в соответствии с рекомендациями; не осталось ни одной настройки по умолчанию, в которых у вас есть сомнения. Я начал поиски проблемы не с того и поплатился временем.
Делитесь своими фэйлами, будем учиться на ошибках друг друга. 😃
Во время обработки происходит обращение к Kubernetes API (такова специфика нашей прикладной задачи). И вот на этом шаге всё и зависало, поскольку не был установлен таймаут ожидания результатов вызова. 🙉 Дело в том, что для вызова Kubernetes API мы используем fabric8io/kubernetes-client, у которого, как выяснилось, по умолчанию был установлен некоторый Rate Limit. 🙊 Превышение этого лимита приводило к зависанию при обращении к Kubernetes API. В результате проблема была решена добавлением таймаута ожидания результатов, подбором и установкой подходящих значений для Rate Limit, выносом этих настроек в конфигурационный файл приложения.
По итогу я был рад не столько тому, что нашёл и исправил проблему, сколько тому, что моё архитектурное решение было правильным, а возникшая ситуация подтвердила это ещё раз. В Kafka всё работало, и проблема не проявлялась только потому, что она не приспособлена к ситуациям, когда время обработки может варьироваться. В итоге пропускная способность системы на базе Kafka была ниже, мы никогда не упирались в Rate Limit для Kubernetes API и даже не знали о существовании этой проблемы. С переходом на RabbitMQ пропускная способность увеличилась, проблема материализовалась, опустившись на тот уровень, где она всегда и была, а мы стали упираться в Rate Limit.
Делитесь своими фэйлами, будем учиться на ошибках друг друга. 😃
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1😁1
Надёжность, прочность, устойчивость
При обсуждении свойств программной системы мы часто оперируем такими терминами, как надежность, прочность и устойчивость, не проводя четких различий между ними. Однако, при формулировании требований эти понятия следует рассматривать отдельно. Если вы думаете, что это не столь важно, то инженеры NASA, которые занимаются проектированием систем жизнеобеспечения, так не считают.🚀 Как мне кажется, данный опыт вполне применим и при проектировании программных систем.
1️⃣ Надёжность (Reliability) - вероятность того, что система не выйдет из строя в течении определенного периода времени и при определенных условиях эксплуатации. Иначе говоря, в заданных условиях эксплуатации система стабильна и отвечает проектным требованиям.
2️⃣ Прочность (Robustness) - способность системы работать удовлетворительно или даже в соответствии с заявленными требованиями, несмотря на воздействия, выходящие за рамки предопределенных условий эксплуатации, но при этом являющимися возможными. Прочность рассматривается в контексте предвидимых проблем.
3️⃣ Устойчивость (Resilience) - способность системы быстро восстанавливаться или адаптироваться к непредвиденным происшествиям или изменениям, которые нарушают базовые проектные предположения. Устойчивость рассматривается в контексте непредвиденных или маловероятных проблем.
Таким образом, надежность, прочность и устойчивость определяют поведение системы на трех уровнях сложности условий эксплуатации. Сначала в заданной среде; затем в среде с вероятными проблемами; наконец, во враждебной, деструктивной среде.
Предполагается, что по мере развития системы уточняются условия её эксплуатации, модель оперативного окружения становится более точной и реалистичной. Мы сталкиваемся с непредвиденными проблемами, понимаем причины их возникновения, а затем решаем эти проблемы, переводя их в статус известных. Таким образом, совершенствование системы заключается в движении от уровня устойчивости к уровню надежности. Когда мы понимаем, что возникшая непредвиденная ситуация является нормой, которую мы не учли на ранних стадиях проектирования, мы уточняем требования к прочности или даже надежности.
Всегда ли необходимо заботиться об устойчивости? Зависит от рисков, которые несет бизнес при возникновении таких ситуаций. Последствия могут включать временны́е, репутационные, финансовые риски или даже создавать угрозу жизни людей. С одной стороны, совсем игнорировать уровень непредвиденных ошибок, упрощая модель системы, конечно, было бы глупо. С другой, попытка учесть всевозможные ситуации для большинства программных систем, является неоправданным усложнением.
К посту прикрепляю оригинал статьи "Going Beyond Reliability to Robustness and Resilience in Space Life Support Systems", Harry W. Jones, 2021.⬇️
По итогу хочу всем пожелать в Новом 2025 году надежности, прочности и устойчивости! А если будут возникать непредвиденные ситуации, то пусть они будут только приятными. 🎉🎄☃️
#arch
При обсуждении свойств программной системы мы часто оперируем такими терминами, как надежность, прочность и устойчивость, не проводя четких различий между ними. Однако, при формулировании требований эти понятия следует рассматривать отдельно. Если вы думаете, что это не столь важно, то инженеры NASA, которые занимаются проектированием систем жизнеобеспечения, так не считают.
Таким образом, надежность, прочность и устойчивость определяют поведение системы на трех уровнях сложности условий эксплуатации. Сначала в заданной среде; затем в среде с вероятными проблемами; наконец, во враждебной, деструктивной среде.
Предполагается, что по мере развития системы уточняются условия её эксплуатации, модель оперативного окружения становится более точной и реалистичной. Мы сталкиваемся с непредвиденными проблемами, понимаем причины их возникновения, а затем решаем эти проблемы, переводя их в статус известных. Таким образом, совершенствование системы заключается в движении от уровня устойчивости к уровню надежности. Когда мы понимаем, что возникшая непредвиденная ситуация является нормой, которую мы не учли на ранних стадиях проектирования, мы уточняем требования к прочности или даже надежности.
Всегда ли необходимо заботиться об устойчивости? Зависит от рисков, которые несет бизнес при возникновении таких ситуаций. Последствия могут включать временны́е, репутационные, финансовые риски или даже создавать угрозу жизни людей. С одной стороны, совсем игнорировать уровень непредвиденных ошибок, упрощая модель системы, конечно, было бы глупо. С другой, попытка учесть всевозможные ситуации для большинства программных систем, является неоправданным усложнением.
К посту прикрепляю оригинал статьи "Going Beyond Reliability to Robustness and Resilience in Space Life Support Systems", Harry W. Jones, 2021.
По итогу хочу всем пожелать в Новом 2025 году надежности, прочности и устойчивости! А если будут возникать непредвиденные ситуации, то пусть они будут только приятными. 🎉🎄☃️
#arch
Please open Telegram to view this post
VIEW IN TELEGRAM
🎄4🔥3☃1👍1