День 2317. #ЗаметкиНаПолях
Почему Перебор List Быстрее, чем IList?
Если у нас есть List<T> и IList<T>, перебор List<T> быстрее, чем IList<T>. Почему?
Запустим бенчмарк
Результаты будут примерно такие:
40 байт
Откуда взялись эти 40 байт? Когда вы вызываете List<T>.GetEnumerator() (что будет в каждом цикле foreach), вы получаете структуру с именем Enumerator. Когда вы вызываете IList<T>.GetEnumerator(), вы получаете переменную типа IEnumerator, которая содержит упакованную версию указанного перечислителя. Это объясняет 40 байт выделения в куче и некоторую разницу в производительности.
Вызовы виртуальных методов
Но есть и второе измерение. Когда вы вызываете IList<T>.GetEnumerator(), он должен вызвать виртуальный метод, что медленнее, чем вызов невиртуального метода. По сути, кто-то должен проверить, какой конкретный тип действительно используется, а затем вызвать правильный метод. Это не относится к List<T>, который уже является конкретным типом.
Источник: https://steven-giesel.com/blogPost/c624c170-c16c-4414-8506-2ae25ba54df7/why-is-enumerating-over-list-faster-than-ilist
Почему Перебор List Быстрее, чем IList?
Если у нас есть List<T> и IList<T>, перебор List<T> быстрее, чем IList<T>. Почему?
Запустим бенчмарк
[MemoryDiagnoser]
public class Benchmark
{
private List<int> list
= [.. Enumerable.Range(0, 10_000)];
private IList<int> ilist
= [.. Enumerable.Range(0, 10_000)];
[Benchmark(Baseline = true)]
public int GetSumOfList()
{
var sum = 0;
foreach (var n in list) { sum += n; }
return sum;
}
[Benchmark]
public int GetSumOfIList()
{
var sum = 0;
foreach (var n in ilist) { sum += n; }
return sum;
}
}
Результаты будут примерно такие:
|Method |Mean |…|Allocated|
|-------------|--------:|…|--------:|
|GetSumOfList | 4.214 us|…| -|
|GetSumOfIList|19.508 us|…| 40 B|
40 байт
Откуда взялись эти 40 байт? Когда вы вызываете List<T>.GetEnumerator() (что будет в каждом цикле foreach), вы получаете структуру с именем Enumerator. Когда вы вызываете IList<T>.GetEnumerator(), вы получаете переменную типа IEnumerator, которая содержит упакованную версию указанного перечислителя. Это объясняет 40 байт выделения в куче и некоторую разницу в производительности.
Вызовы виртуальных методов
Но есть и второе измерение. Когда вы вызываете IList<T>.GetEnumerator(), он должен вызвать виртуальный метод, что медленнее, чем вызов невиртуального метода. По сути, кто-то должен проверить, какой конкретный тип действительно используется, а затем вызвать правильный метод. Это не относится к List<T>, который уже является конкретным типом.
Источник: https://steven-giesel.com/blogPost/c624c170-c16c-4414-8506-2ae25ba54df7/why-is-enumerating-over-list-faster-than-ilist
День 2318. #ЗаметкиНаПолях
DTO и Маппинг: Хорошее, Плохое, Лишнее. Начало
DTO — это объекты, используемые для передачи данных между различными слоями или уровнями приложения. Они часто используются для сопоставления (маппинга) данных из одного представления в другое, особенно при пересечении границ, например, между БД и слоем UI или API.
Часто возникает путаница вокруг термина «сущность». Некоторые думают о сущностях в контексте ORM, такого как Entity Framework в .NET. В этом случае сущность — это просто сопоставление модели данных с таблицей БД. Другие думают о сущностях в смысле DDD, где это модель с поведением и идентичностью в пределах предметной области.
Зачем добавляют DTO?
Самая распространённая причина - нежелание раскрывать свою базовую модель данных или модель домена. Например, если сущность ORM напрямую отображается в схему БД, вы можете не захотеть отправлять именно эту сущность через API или на другой уровень приложения. Вместо этого вы создаёте DTO (отображение этой сущности), который отправляете дальше. Это справедливо в некоторых контекстах. Но почему просто не возвращать базовую сущность?
Сервисы сущностей и HTTP API: корень некоторых догм DTO
Часто люди создают DTO, почти идентичные своим сущностям. Конечные точки API сопоставляют операции CRUD напрямую с таблицами БД:
- GET – получение заказа,
- POST – создание,
- PUT – обновление,
- PATCH - частичное обновление,
- DELETE – удаление.
При этом API напрямую отображает методы HTTP на CRUD-операции БД. В этом поначалу нет ничего плохого, но это заставляет вас следовать определённым путем. В итоге вы начинаете думать об API просто как о тонком слое поверх схемы БД. Но API не должен быть таким. Ресурсы, предоставляемые API, не должны быть идентичны записям БД. В приложении, где UI создаётся на стороне сервера, вы бы собирали данные из нескольких источников, чтобы создать полноценное HTML-представление, а не брали бы голые данные из одной таблицы.
Например, если вы возвращаете DTO созданного заказа вы бы включили только его ID, дату создания и статус. Но если вы создаёте UI на стороне сервера, вы бы добавили информацию о клиенте (например, имя), которой может не быть непосредственно в объекте заказа. Также вы бы отобразили возможные действия, которые может выполнить пользователь, вроде «Выполнить», «Отменить» или «Отметить оплату».
Когда вы думаете исключительно в терминах сервисов сущностей и записей БД, вы упускаете эту более богатую композицию. В итоге вы получаете DTO, которые являются просто тонкими оболочками вокруг таблиц БД, не сильно отличающимися от самих сущностей.
Реальная ценность DTO
Есть два основных преимущества использования DTO:
1. Управление связанностью.
DTO помогают контролировать, насколько тесно внешние слои или потребители связаны с внутренними моделями данных.
2. Композиция
DTO позволяют составлять данные из нескольких источников или моделей в единую удобную форму, оптимизированную для конкретного варианта использования или потребителя.
Если вы не делаете никакой композиции и ваши DTO почти идентичны записям БД, то вы, скорее всего, делаете лишнюю работу. Каждый раз при смене базовой модели данных, вы вынуждены обновлять несколько DTO на разных уровнях, что добавляет сложности и накладных расходов на обслуживание, не обеспечивая при этом реальной ценности.
Но, если вы хорошо управляете связанностью и контролируете, как отображаются ваши внутренние модели, DTO становятся очень полезными. Они действуют как защитный слой, который позволяет развивать внутренние модели данных, не нарушая внешних потребителей. Если у вас пока нет этой проблемы (у вас немного потребителей, и вы хорошо управляете связями), то создание нескольких DTO «на всякий случай» может оказаться преждевременной оптимизацией.
Окончание следует…
Источник: https://codeopinion.com/dtos-mapping-the-good-the-bad-and-the-excessive/
DTO и Маппинг: Хорошее, Плохое, Лишнее. Начало
DTO — это объекты, используемые для передачи данных между различными слоями или уровнями приложения. Они часто используются для сопоставления (маппинга) данных из одного представления в другое, особенно при пересечении границ, например, между БД и слоем UI или API.
Часто возникает путаница вокруг термина «сущность». Некоторые думают о сущностях в контексте ORM, такого как Entity Framework в .NET. В этом случае сущность — это просто сопоставление модели данных с таблицей БД. Другие думают о сущностях в смысле DDD, где это модель с поведением и идентичностью в пределах предметной области.
Зачем добавляют DTO?
Самая распространённая причина - нежелание раскрывать свою базовую модель данных или модель домена. Например, если сущность ORM напрямую отображается в схему БД, вы можете не захотеть отправлять именно эту сущность через API или на другой уровень приложения. Вместо этого вы создаёте DTO (отображение этой сущности), который отправляете дальше. Это справедливо в некоторых контекстах. Но почему просто не возвращать базовую сущность?
Сервисы сущностей и HTTP API: корень некоторых догм DTO
Часто люди создают DTO, почти идентичные своим сущностям. Конечные точки API сопоставляют операции CRUD напрямую с таблицами БД:
- GET – получение заказа,
- POST – создание,
- PUT – обновление,
- PATCH - частичное обновление,
- DELETE – удаление.
При этом API напрямую отображает методы HTTP на CRUD-операции БД. В этом поначалу нет ничего плохого, но это заставляет вас следовать определённым путем. В итоге вы начинаете думать об API просто как о тонком слое поверх схемы БД. Но API не должен быть таким. Ресурсы, предоставляемые API, не должны быть идентичны записям БД. В приложении, где UI создаётся на стороне сервера, вы бы собирали данные из нескольких источников, чтобы создать полноценное HTML-представление, а не брали бы голые данные из одной таблицы.
Например, если вы возвращаете DTO созданного заказа вы бы включили только его ID, дату создания и статус. Но если вы создаёте UI на стороне сервера, вы бы добавили информацию о клиенте (например, имя), которой может не быть непосредственно в объекте заказа. Также вы бы отобразили возможные действия, которые может выполнить пользователь, вроде «Выполнить», «Отменить» или «Отметить оплату».
Когда вы думаете исключительно в терминах сервисов сущностей и записей БД, вы упускаете эту более богатую композицию. В итоге вы получаете DTO, которые являются просто тонкими оболочками вокруг таблиц БД, не сильно отличающимися от самих сущностей.
Реальная ценность DTO
Есть два основных преимущества использования DTO:
1. Управление связанностью.
DTO помогают контролировать, насколько тесно внешние слои или потребители связаны с внутренними моделями данных.
2. Композиция
DTO позволяют составлять данные из нескольких источников или моделей в единую удобную форму, оптимизированную для конкретного варианта использования или потребителя.
Если вы не делаете никакой композиции и ваши DTO почти идентичны записям БД, то вы, скорее всего, делаете лишнюю работу. Каждый раз при смене базовой модели данных, вы вынуждены обновлять несколько DTO на разных уровнях, что добавляет сложности и накладных расходов на обслуживание, не обеспечивая при этом реальной ценности.
Но, если вы хорошо управляете связанностью и контролируете, как отображаются ваши внутренние модели, DTO становятся очень полезными. Они действуют как защитный слой, который позволяет развивать внутренние модели данных, не нарушая внешних потребителей. Если у вас пока нет этой проблемы (у вас немного потребителей, и вы хорошо управляете связями), то создание нескольких DTO «на всякий случай» может оказаться преждевременной оптимизацией.
Окончание следует…
Источник: https://codeopinion.com/dtos-mapping-the-good-the-bad-and-the-excessive/
День 2319. #ЗаметкиНаПолях
DTO и Маппинг: Хорошее, Плохое, Лишнее. Окончание
Начало
CQRS для иллюстрации DTO в действии
В CQRS вы разделяете систему на две части:
1. Запросы отвечают за извлечение и составление данных, возвращая данные, адаптированные для конкретных вариантов использования.
2. Команды отвечают за намерение и действия, такие как обновление или удаление данных.
При этом ваши ответы на запросы по сути являются DTO, которые содержат все объединённые данные, необходимые для конкретного варианта использования. Например, если вы создаёте UI для отображения сведений о заказе, DTO запроса может включать не только данные заказа, но и сведения о клиенте и доступные действия, такие как «Выполнить» или «Отменить».
Команды, с другой стороны, представляют собой конкретные действия, которые вы хотите выполнить. Вместо того чтобы думать в терминах общих операций CRUD, вы думаете в терминах команд, специфичных для домена, таких как «ОтменитьЗаказ» или «ОтметитьЗаказОплаченным». Эти команды инкапсулируют намерение и поведение, а не просто обновления данных.
CQRS не требует разных БД для команд и запросов. Он больше про то, как вы моделируете свою систему и её взаимодействия. Команды и запросы могут работать с одной БД, но представлять разные модели и DTO, адаптированные для их конкретных целей.
Размышления о DTO
1. Внешние и внутренние данные
- Внутри - данные приватные для системы, детали реализации: схема БД и модели домена.
- Снаружи - данные, предоставляемые публично: контракты API или сообщения в очереди.
Цель в сохранении конфиденциальности внутренних данных, чтобы вы могли развивать и изменять их, не влияя на внешних потребителей. Внешние данные следует рассматривать как контракт, который необходимо тщательно версионировать, поскольку вы не контролируете всех потребителей.
Если вы владеете обеими сторонами взаимодействия и можете эффективно управлять связанностью, возможно, DTO не нужны везде. Но если вы предоставляете данные внешним сторонам или хотите разделить слои, чтобы обеспечить независимую эволюцию, DTO бесценны.
2. Количество потребителей
Сколько потребителей зависят от вашей внутренней модели данных? Если всего пара, и вы хорошо управляете связью, вы можете спокойно возвращать сущность напрямую без DTO. Если 50+, это, вероятно, признак того, что нужно ввести уровень абстракции с DTO, чтобы избежать критических изменений и защитить внутренние модели.
Не перебарщивайте с DTO
Часто люди увлекаются и создают множество слоёв отображений DTO-на-DTO-на-другие-DTO. Это только добавляет сложности без реальной выгоды. Если ваши DTO и сущности почти идентичны, и вы просто копируете данные туда-сюда, это уже лишняя работа.
DTO должны служить чёткой цели, например:
- Разделение внутренних моделей данных от внешних контрактов;
- Составление данных из нескольких источников для конкретных вариантов использования;
- Добавление версионирования для публичного API.
Итого
- DTO в первую очередь предназначены для управления связями и композицией данных. Они помогают избежать раскрытия деталей внутренней реализации и позволяют формировать данные, адаптированные для конкретных потребителей.
- Не создавайте DTO просто так. Если внутренние модели и внешние потребители жёстко контролируются и ограничены, может быть удобно возвращать сущности напрямую.
- Остерегайтесь чрезмерных слоёв сопоставления. Они только добавляют ненужной сложности.
- Используйте шаблоны, вроде CQRS, чтобы по-другому думать о запросах и командах. Это помогает моделировать данные и действия таким образом, чтобы они естественным образом соответствовали DTO и композиции.
- Думайте о данных как о внутренних (частных) и внешних (публичный контракт). Такой образ мышления поможет решить, где необходимы DTO и как версионировать ваш API.
Источник: https://codeopinion.com/dtos-mapping-the-good-the-bad-and-the-excessive/
DTO и Маппинг: Хорошее, Плохое, Лишнее. Окончание
Начало
CQRS для иллюстрации DTO в действии
В CQRS вы разделяете систему на две части:
1. Запросы отвечают за извлечение и составление данных, возвращая данные, адаптированные для конкретных вариантов использования.
2. Команды отвечают за намерение и действия, такие как обновление или удаление данных.
При этом ваши ответы на запросы по сути являются DTO, которые содержат все объединённые данные, необходимые для конкретного варианта использования. Например, если вы создаёте UI для отображения сведений о заказе, DTO запроса может включать не только данные заказа, но и сведения о клиенте и доступные действия, такие как «Выполнить» или «Отменить».
Команды, с другой стороны, представляют собой конкретные действия, которые вы хотите выполнить. Вместо того чтобы думать в терминах общих операций CRUD, вы думаете в терминах команд, специфичных для домена, таких как «ОтменитьЗаказ» или «ОтметитьЗаказОплаченным». Эти команды инкапсулируют намерение и поведение, а не просто обновления данных.
CQRS не требует разных БД для команд и запросов. Он больше про то, как вы моделируете свою систему и её взаимодействия. Команды и запросы могут работать с одной БД, но представлять разные модели и DTO, адаптированные для их конкретных целей.
Размышления о DTO
1. Внешние и внутренние данные
- Внутри - данные приватные для системы, детали реализации: схема БД и модели домена.
- Снаружи - данные, предоставляемые публично: контракты API или сообщения в очереди.
Цель в сохранении конфиденциальности внутренних данных, чтобы вы могли развивать и изменять их, не влияя на внешних потребителей. Внешние данные следует рассматривать как контракт, который необходимо тщательно версионировать, поскольку вы не контролируете всех потребителей.
Если вы владеете обеими сторонами взаимодействия и можете эффективно управлять связанностью, возможно, DTO не нужны везде. Но если вы предоставляете данные внешним сторонам или хотите разделить слои, чтобы обеспечить независимую эволюцию, DTO бесценны.
2. Количество потребителей
Сколько потребителей зависят от вашей внутренней модели данных? Если всего пара, и вы хорошо управляете связью, вы можете спокойно возвращать сущность напрямую без DTO. Если 50+, это, вероятно, признак того, что нужно ввести уровень абстракции с DTO, чтобы избежать критических изменений и защитить внутренние модели.
Не перебарщивайте с DTO
Часто люди увлекаются и создают множество слоёв отображений DTO-на-DTO-на-другие-DTO. Это только добавляет сложности без реальной выгоды. Если ваши DTO и сущности почти идентичны, и вы просто копируете данные туда-сюда, это уже лишняя работа.
DTO должны служить чёткой цели, например:
- Разделение внутренних моделей данных от внешних контрактов;
- Составление данных из нескольких источников для конкретных вариантов использования;
- Добавление версионирования для публичного API.
Итого
- DTO в первую очередь предназначены для управления связями и композицией данных. Они помогают избежать раскрытия деталей внутренней реализации и позволяют формировать данные, адаптированные для конкретных потребителей.
- Не создавайте DTO просто так. Если внутренние модели и внешние потребители жёстко контролируются и ограничены, может быть удобно возвращать сущности напрямую.
- Остерегайтесь чрезмерных слоёв сопоставления. Они только добавляют ненужной сложности.
- Используйте шаблоны, вроде CQRS, чтобы по-другому думать о запросах и командах. Это помогает моделировать данные и действия таким образом, чтобы они естественным образом соответствовали DTO и композиции.
- Думайте о данных как о внутренних (частных) и внешних (публичный контракт). Такой образ мышления поможет решить, где необходимы DTO и как версионировать ваш API.
Источник: https://codeopinion.com/dtos-mapping-the-good-the-bad-and-the-excessive/
День 2320. #SystemDesign101
Прямой Прокси и Обратный Прокси
На схеме показаны различия между прямым и обратным прокси.
Прямой прокси — промежуточный сервер (программа) в компьютерных сетях, выполняющий роль посредника между пользователем и целевым сервером (при этом о посредничестве могут как знать, так и не знать обе стороны), позволяющий клиентам как выполнять косвенные запросы (принимая и передавая их через прокси-сервер) к другим сетевым сервисам, так и получать ответы.
Использование:
- доступ клиентов локальной сети к Интернет,
- кэширование и сжатие данных,
- защита локальной сети от внешнего доступа,
- обход ограничений,
- блокировка доступа к определённому контенту.
Обратный прокси ретранслирует запросы клиентов из внешней сети на один или несколько серверов, логически расположенных во внутренней сети. При этом для клиента это выглядит так, будто запрашиваемые ресурсы находятся непосредственно на прокси-сервере.
Использование:
- защита и сокрытие серверов локальной сети и их характеристик,
- защита от распространенных веб-атак, таких как DoS или DDoS,
- шифрование и дешифрование SSL-сообщений,
- балансировка нагрузки между несколькими серверами,
- кэширование статического и динамического контента,
- сжатие содержимого для уменьшения времени его загрузки,
Источники:
- https://github.com/ByteByteGoHq/system-design-101
- https://ru.wikipedia.org/wiki/Прокси-сервер
- https://ru.wikipedia.org/wiki/Обратный_прокси
Прямой Прокси и Обратный Прокси
На схеме показаны различия между прямым и обратным прокси.
Прямой прокси — промежуточный сервер (программа) в компьютерных сетях, выполняющий роль посредника между пользователем и целевым сервером (при этом о посредничестве могут как знать, так и не знать обе стороны), позволяющий клиентам как выполнять косвенные запросы (принимая и передавая их через прокси-сервер) к другим сетевым сервисам, так и получать ответы.
Использование:
- доступ клиентов локальной сети к Интернет,
- кэширование и сжатие данных,
- защита локальной сети от внешнего доступа,
- обход ограничений,
- блокировка доступа к определённому контенту.
Обратный прокси ретранслирует запросы клиентов из внешней сети на один или несколько серверов, логически расположенных во внутренней сети. При этом для клиента это выглядит так, будто запрашиваемые ресурсы находятся непосредственно на прокси-сервере.
Использование:
- защита и сокрытие серверов локальной сети и их характеристик,
- защита от распространенных веб-атак, таких как DoS или DDoS,
- шифрование и дешифрование SSL-сообщений,
- балансировка нагрузки между несколькими серверами,
- кэширование статического и динамического контента,
- сжатие содержимого для уменьшения времени его загрузки,
Источники:
- https://github.com/ByteByteGoHq/system-design-101
- https://ru.wikipedia.org/wiki/Прокси-сервер
- https://ru.wikipedia.org/wiki/Обратный_прокси
День 2321. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 54. Внедряя новые методы работы, делайте это мягко, но непрерывно
Никто не может по-настоящему изменить что-то, что, по мнению других, работает и даёт положительный результат. В конечном счёте каждый сам решает, готов ли он в будущем действовать иначе.
Руководство
Для того чтобы новые способы работы в организации, занимающейся разработкой ПО, внедрялись эффективно, руководители должны оказывать постоянное мягкое давление в нужном направлении. Чтобы задать желаемое направление, нужно чётко определить цели инициативы и довести их до сведения всех участников. Кроме того, они должны нести пользу бизнесу. Радикальные изменения всем даются с трудом. Постепенные изменения менее разрушительны, и к ним легче адаптироваться. Людям нужно время, чтобы усвоить новые практики и новый образ мышления.
Все участники должны понимать, какой вклад они могут внести в успех проекта. Ищите первых последователей, более восприимчивых к нововведениям, которые могут выступать в качестве пропагандистов изменений среди коллег.
Вот несколько советов, как мягко оказывать давление для достижения цели:
- Определите цели, мотивацию и основные желаемые результаты. Расплывчатые цели, такие как «до¬стичь мирового уровня» или «стать лидерами по продуктивности», бесполезны.
- Выберите метрики (ключевые показатели эффективности), которые будут показывать прогресс в достижении целей и влияние этих из¬менений на ускорение разработки, но имейте в виду, что последние показатели являются запаздывающими. Деятельность по изменению инициируется, поскольку мы уверены, что новые подходы помогут добиться лучших результатов. Однако для того, чтобы эти новые подходы повлияли на проекты, нужно время.
- Ставьте реалистичные и осмысленные цели и ожидания.
- Относитесь к изменениям как к проекту с конкретными действиями, результатами и обязанностями. Не забывайте о ресурсах. Предусмотрите дополнительное время, чтобы люди могли привыкать к новым подходам параллельно с выполнением своих обязанностей по разработке проекта.
- Регулярно информируйте о продвижении инициативы изменений.
- Призывайте людей ответственно относиться к их обязательствам по выполнению определённых мероприятий в рамках изменений.
- Стремитесь добиваться небольших успехов как можно раньше, чтобы показать, что инициатива приносит результаты.
- Обеспечьте обучение новым методам работы. Обучение — это инвестиции в будущие результаты.
Управление вышестоящими руководителями
Ценный навык для любого, кто осуществляет внедрение изменений. Если этим занимаетесь вы, научите своих руководителей говорить публично, какие модели поведения и результаты следует искать, а также какие результаты должны вознаграждаться или корректироваться. Это требует понимания корпоративной политики и умения находить подход к разным людям, представляя ценность изменений каждому руководителю так, чтобы найти у них отклик и получить поддержку.
Независимо от того, стремитесь вы к организации более зрелой модели процессов, такой как CMMI, или внедряете Agile-разработку во всей организации, руководителям важно понимать, как должны измениться их собственные действия и ожидания. Руководители, подающие пример, сами практикуют новые способы работы, публично поощряют их и посылают положительный сигнал всем остальным, что способствует изменению культуры.
Есть такое выражение: «Если вы когда-нибудь окажетесь в толпе людей, пытающихся войти туда же, куда надо и вам, просто перебирайте ногами — и толпа сама внесёт вас». То же самое верно и в отношении совершенствования процессов разработки ПО. Продолжайте двигаться в нужном направлении — и будете постепенно осваивать новые и лучшие способы работы.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 7.
Уроки 50 Лет Разработки ПО
Урок 54. Внедряя новые методы работы, делайте это мягко, но непрерывно
Никто не может по-настоящему изменить что-то, что, по мнению других, работает и даёт положительный результат. В конечном счёте каждый сам решает, готов ли он в будущем действовать иначе.
Руководство
Для того чтобы новые способы работы в организации, занимающейся разработкой ПО, внедрялись эффективно, руководители должны оказывать постоянное мягкое давление в нужном направлении. Чтобы задать желаемое направление, нужно чётко определить цели инициативы и довести их до сведения всех участников. Кроме того, они должны нести пользу бизнесу. Радикальные изменения всем даются с трудом. Постепенные изменения менее разрушительны, и к ним легче адаптироваться. Людям нужно время, чтобы усвоить новые практики и новый образ мышления.
Все участники должны понимать, какой вклад они могут внести в успех проекта. Ищите первых последователей, более восприимчивых к нововведениям, которые могут выступать в качестве пропагандистов изменений среди коллег.
Вот несколько советов, как мягко оказывать давление для достижения цели:
- Определите цели, мотивацию и основные желаемые результаты. Расплывчатые цели, такие как «до¬стичь мирового уровня» или «стать лидерами по продуктивности», бесполезны.
- Выберите метрики (ключевые показатели эффективности), которые будут показывать прогресс в достижении целей и влияние этих из¬менений на ускорение разработки, но имейте в виду, что последние показатели являются запаздывающими. Деятельность по изменению инициируется, поскольку мы уверены, что новые подходы помогут добиться лучших результатов. Однако для того, чтобы эти новые подходы повлияли на проекты, нужно время.
- Ставьте реалистичные и осмысленные цели и ожидания.
- Относитесь к изменениям как к проекту с конкретными действиями, результатами и обязанностями. Не забывайте о ресурсах. Предусмотрите дополнительное время, чтобы люди могли привыкать к новым подходам параллельно с выполнением своих обязанностей по разработке проекта.
- Регулярно информируйте о продвижении инициативы изменений.
- Призывайте людей ответственно относиться к их обязательствам по выполнению определённых мероприятий в рамках изменений.
- Стремитесь добиваться небольших успехов как можно раньше, чтобы показать, что инициатива приносит результаты.
- Обеспечьте обучение новым методам работы. Обучение — это инвестиции в будущие результаты.
Управление вышестоящими руководителями
Ценный навык для любого, кто осуществляет внедрение изменений. Если этим занимаетесь вы, научите своих руководителей говорить публично, какие модели поведения и результаты следует искать, а также какие результаты должны вознаграждаться или корректироваться. Это требует понимания корпоративной политики и умения находить подход к разным людям, представляя ценность изменений каждому руководителю так, чтобы найти у них отклик и получить поддержку.
Независимо от того, стремитесь вы к организации более зрелой модели процессов, такой как CMMI, или внедряете Agile-разработку во всей организации, руководителям важно понимать, как должны измениться их собственные действия и ожидания. Руководители, подающие пример, сами практикуют новые способы работы, публично поощряют их и посылают положительный сигнал всем остальным, что способствует изменению культуры.
Есть такое выражение: «Если вы когда-нибудь окажетесь в толпе людей, пытающихся войти туда же, куда надо и вам, просто перебирайте ногами — и толпа сама внесёт вас». То же самое верно и в отношении совершенствования процессов разработки ПО. Продолжайте двигаться в нужном направлении — и будете постепенно осваивать новые и лучшие способы работы.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 7.
День 2322. #ЧтоНовенького
Запись Логов по Требованию с Буферизацией. Начало
Обычно нужны подробные логи, когда что-то идёт не так, но когда всё хорошо, зачем платить за хранение логов, которые никогда не проверяются. Хотя выборка логов (log sampling) помогает сократить их объём, сохраняя только часть, иногда нужен другой подход.
Буферизация логов в .NET 9 временно сохраняет логи в памяти и позволяет позже решить, записывать ли их. Буферизация даёт контекстный контроль над тем, какие логи сохраняются на основе фактических результатов выполнения.
Концепция
Буферизация логов представляет новый шаблон потока:
1. Код генерирует логи обычным образом,
2. Нужные логи сохраняются в памяти, а не пишутся немедленно,
3. Позже, на основе определённых условий, вы решаете:
- Очистить буфер и записать все захваченные логи,
- Дать времени жизни буфера истечь, фактически отбрасывая ненужные логи.
Так, например, вы можете буферизировать подробные логи для транзакции, сохраняя их только в случае сбоя транзакции и отбрасывая в случае её успеха.
Две стратегии буферизации
.NET 9 предоставляет две стратегии буферизации: глобальная и по запросу.
1. Глобальная буферизация
Хранит логи в кольцевых буферах, общих для всего приложения. Это подходит для буферизации логов, связанных с длительными процессами или фоновыми задачами. Для базовых сценариев включите буферизацию:
Эта конфигурация буферизует все логи от уровня Information. Для более сложных сценариев определите конкретные правила:
- MaxBufferSizeInBytes – максимальный размер буфера.
- MaxLogRecordSizeInBytes – максимальный размер отдельных записей лога.
- AutoFlushDuration – как долго буферизация лога будет оставаться отключенной после запуска операции записи (flush). В примере выше буферизация будет оставаться отключенной в течение 30 секунд, что позволит приложению нормально писать все логи.
Запись глобального буфера
Чтобы записать буферизованные логи, внедрите класс GlobalLogBuffer и вызовите его метод Flush():
В этом примере подробные логи о процессе оплаты сохраняются только в случае возникновения исключения.
Окончание следует…
Источник: https://devblogs.microsoft.com/dotnet/emit-logs-on-demand-with-log-buffering/
Запись Логов по Требованию с Буферизацией. Начало
Обычно нужны подробные логи, когда что-то идёт не так, но когда всё хорошо, зачем платить за хранение логов, которые никогда не проверяются. Хотя выборка логов (log sampling) помогает сократить их объём, сохраняя только часть, иногда нужен другой подход.
Буферизация логов в .NET 9 временно сохраняет логи в памяти и позволяет позже решить, записывать ли их. Буферизация даёт контекстный контроль над тем, какие логи сохраняются на основе фактических результатов выполнения.
Концепция
Буферизация логов представляет новый шаблон потока:
1. Код генерирует логи обычным образом,
2. Нужные логи сохраняются в памяти, а не пишутся немедленно,
3. Позже, на основе определённых условий, вы решаете:
- Очистить буфер и записать все захваченные логи,
- Дать времени жизни буфера истечь, фактически отбрасывая ненужные логи.
Так, например, вы можете буферизировать подробные логи для транзакции, сохраняя их только в случае сбоя транзакции и отбрасывая в случае её успеха.
Две стратегии буферизации
.NET 9 предоставляет две стратегии буферизации: глобальная и по запросу.
1. Глобальная буферизация
Хранит логи в кольцевых буферах, общих для всего приложения. Это подходит для буферизации логов, связанных с длительными процессами или фоновыми задачами. Для базовых сценариев включите буферизацию:
builder.Logging
.AddGlobalLogBuffer(LogLevel.Information);
Эта конфигурация буферизует все логи от уровня Information. Для более сложных сценариев определите конкретные правила:
builder.Logging.AddGlobalBuffer(o =>
{
// Буферизация логов уровня Information, категории "PaymentService"
o.Rules.Add(
new LogBufferingFilterRule(
categoryName: "PaymentService",
logLevel: LogLevel.Information));
o.MaxBufferSizeInBytes = 100 * 1024 * 1024;
o.MaxLogRecordSizeInBytes = 50 * 1024;
o.AutoFlushDuration = TimeSpan.FromSeconds(30);
});
- MaxBufferSizeInBytes – максимальный размер буфера.
- MaxLogRecordSizeInBytes – максимальный размер отдельных записей лога.
- AutoFlushDuration – как долго буферизация лога будет оставаться отключенной после запуска операции записи (flush). В примере выше буферизация будет оставаться отключенной в течение 30 секунд, что позволит приложению нормально писать все логи.
Запись глобального буфера
Чтобы записать буферизованные логи, внедрите класс GlobalLogBuffer и вызовите его метод Flush():
public class PaymentService
{
private ILogger<PaymentService> _logger;
private GlobalLogBuffer _buffer;
public PaymentService(
ILogger<PaymentService> logger,
GlobalLogBuffer buffer)
{
_logger = logger;
_buffer = buffer;
}
public async Task ProcessPayment(
PaymentRequest req)
{
try
{
_logger.LogInformation(
"Старт обработки платежа для транзакции {TransId}", req.TransactionId);
// Обработка платежа...
_logger.LogInformation("Платёж обработан");
}
catch (Exception ex)
{
_logger.LogError(ex, "Сбой обработки");
// Записываем буфер для записи детальных логов
_buffer.Flush();
throw;
}
}
}
В этом примере подробные логи о процессе оплаты сохраняются только в случае возникновения исключения.
Окончание следует…
Источник: https://devblogs.microsoft.com/dotnet/emit-logs-on-demand-with-log-buffering/
День 2323. #ЧтоНовенького
Запись Логов по Требованию с Буферизацией. Окончание
Начало
2. Буферизация по запросу
Для веб-приложений ASP.NET Core буферизация по запросу обеспечивает более детальный контроль, поддерживая отдельные буферы для каждого HTTP-запроса. Подходит для отслеживания полного контекста отдельных взаимодействий пользователей.
Добавляем буферизацию по запросу:
Ключевое отличие от глобальной буферизации - когда HTTP-запрос завершается, буфер очищается и все его логи удаляются.
Очистка буфера по запросу
Чтобы выдать буферизованные логи для определённого запроса, внедрите класс PerRequestLogBuffer:
Запись (flush) буфера по запросу также записывает глобальный буфер, гарантируя, что все соответствующие логи будут записаны при возникновении ошибки.
Динамические обновления конфигурации
Если вы используете провайдер конфигурации, поддерживающий перезагрузки (например, File Configuration Provider), вы можете обновить правила буферизации без перезапуска приложения.
Это особенно ценно в производственных сценариях, где может потребоваться временно увеличить детализацию логов для диагностики проблемы, а затем вернуться к обычным уровням после её устранения.
Рекомендации
1. Стратегия буферизации
Буферизуйте подробные логи с уровня Information, но позвольте логам Error и Critical записываться немедленно. Это гарантирует, что важные проблемы всегда будут видны, а подробный контекст будет доступен только при необходимости.
2. Создайте явные триггеры записи (flush)
Определите конкретные условия, при которых контекст лога имеет значение, и добавьте явные вызовы записи:
- При исключениях или ошибках,
- Когда операции превышают ожидаемую продолжительность,
- Когда обнаружено что-то подозрительное.
3. Добавьте выборку логов для максимальной эффективности
Для приложений с большим объёмом логов используйте буферизацию и выборку:
- Фильтруйте обычные логи для уменьшения общего объёма,
- Буферизируйте подробные логи для записи контекста очистки при необходимости.
Ограничения
-.NET 9 и выше,
- Точный порядок логов может не сохраняться (временные метки сохраняются),
- Области (scope) логов не поддерживаются,
- Некоторые расширенные свойства записи логов (например, ActivitySpanId) не сохраняются.
Производительность
Буферизация логов — это компромисс между памятью и хранилищем:
- Буферизованные логи потребляют память, пока они не будут либо записаны, либо отброшены,
- Записывая логи только в определённых сценариях, вы можете радикально сократить расходы на хранение.
Для больших распределённых приложений накладные расходы памяти минимальны по сравнению с потенциальной экономией хранилища.
Источник: https://devblogs.microsoft.com/dotnet/emit-logs-on-demand-with-log-buffering/
Запись Логов по Требованию с Буферизацией. Окончание
Начало
2. Буферизация по запросу
Для веб-приложений ASP.NET Core буферизация по запросу обеспечивает более детальный контроль, поддерживая отдельные буферы для каждого HTTP-запроса. Подходит для отслеживания полного контекста отдельных взаимодействий пользователей.
Добавляем буферизацию по запросу:
builder.Services.AddPerIncomingRequestBuffer(o =>
{
// Логи Information от API контроллеров
o.Rules.Add(
new LogBufferingFilterRule(
categoryPrefix: "MyApp.Controllers",
logLevel: LogLevel.Information));
o.AutoFlushDuration = TimeSpan.FromSeconds(5);
});
Ключевое отличие от глобальной буферизации - когда HTTP-запрос завершается, буфер очищается и все его логи удаляются.
Очистка буфера по запросу
Чтобы выдать буферизованные логи для определённого запроса, внедрите класс PerRequestLogBuffer:
[ApiController]
[Route("[controller]")]
public class OrderController : ControllerBase
{
private ILogger<OrderController> _logger;
private PerRequestLogBuffer _buffer;
public OrderController(
ILogger<OrderController> logger,
PerRequestLogBuffer buffer)
{
_logger = logger;
_buffer = buffer;
}
[HttpPost]
public IActionResult CreateOrder(
OrderRequest request)
{
try
{
_logger.LogInformation("Заказ для клиента {CustomerId}", request.CustomerId);
// Обработка заказа…
_logger.LogInformation("Заказ {OrderId} создан", orderId);
return Ok(new { OrderId = orderId });
}
catch (Exception ex)
{
_logger.LogError(ex, "Сбой заказа");
_buffer.Flush();
return StatusCode(500, "Сбой создания заказа");
}
}
}
Запись (flush) буфера по запросу также записывает глобальный буфер, гарантируя, что все соответствующие логи будут записаны при возникновении ошибки.
Динамические обновления конфигурации
Если вы используете провайдер конфигурации, поддерживающий перезагрузки (например, File Configuration Provider), вы можете обновить правила буферизации без перезапуска приложения.
Это особенно ценно в производственных сценариях, где может потребоваться временно увеличить детализацию логов для диагностики проблемы, а затем вернуться к обычным уровням после её устранения.
Рекомендации
1. Стратегия буферизации
Буферизуйте подробные логи с уровня Information, но позвольте логам Error и Critical записываться немедленно. Это гарантирует, что важные проблемы всегда будут видны, а подробный контекст будет доступен только при необходимости.
2. Создайте явные триггеры записи (flush)
Определите конкретные условия, при которых контекст лога имеет значение, и добавьте явные вызовы записи:
- При исключениях или ошибках,
- Когда операции превышают ожидаемую продолжительность,
- Когда обнаружено что-то подозрительное.
3. Добавьте выборку логов для максимальной эффективности
Для приложений с большим объёмом логов используйте буферизацию и выборку:
- Фильтруйте обычные логи для уменьшения общего объёма,
- Буферизируйте подробные логи для записи контекста очистки при необходимости.
Ограничения
-.NET 9 и выше,
- Точный порядок логов может не сохраняться (временные метки сохраняются),
- Области (scope) логов не поддерживаются,
- Некоторые расширенные свойства записи логов (например, ActivitySpanId) не сохраняются.
Производительность
Буферизация логов — это компромисс между памятью и хранилищем:
- Буферизованные логи потребляют память, пока они не будут либо записаны, либо отброшены,
- Записывая логи только в определённых сценариях, вы можете радикально сократить расходы на хранение.
Для больших распределённых приложений накладные расходы памяти минимальны по сравнению с потенциальной экономией хранилища.
Источник: https://devblogs.microsoft.com/dotnet/emit-logs-on-demand-with-log-buffering/
День 2324. #TipsAndTricks
Развенчиваем Миф Производительности SQL "Сначала Фильтр Потом JOIN"
В интернете часто можно встретить описание «трюка, повышающего производительность запросов в SQL», который звучит "Сначала Фильтр Потом JOIN". В нём утверждается, что вместо того, чтобы сначала объединять таблицы, а затем применять фильтр к результатам, нужно делать наоборот.
Например, вместо:
использовать:
Смысл в том, что БД сначала уберёт ненужные данные из одной таблицы, а потом выполнит соединение меньшего объёма, экономя время и память. Звучит логично. Но дело в том, что для современных БД этот совет не имеет смысла.
Вот пример плана выполнения (EXPLAIN ANALYZE) обоих запросов в PostgreSQL над таблицами с 10000 записями в users и 5000000 в orders.
«Неоптимальный» план запроса:
«Оптимальный» план запроса:
Как видите, планы запросов идентичны, и «оптимизация» ничего не добилась.
Основные операции:
- Последовательное сканирование (Seq Scan) таблицы orders с применением фильтра;
- Последовательное сканирование таблицы users;
- Операция хеширования (Hash) меньшей таблицы (users);
- Хеш-соединение по user_id.
Оптимизаторы запросов умнее вас
Современные БД используют стоимостную оптимизацию. Оптимизатор имеет статистику о таблицах: количество строк, распределение данных, наличие индекса, селективность столбцов и т.п. – и использует её для оценки стоимости различных стратегий выполнения. Современные БД, такие как PostgreSQL, MySQL и SQL Server, уже автоматически выполняют «выталкивание предикатов» и переупорядочивание соединений. Т.е. оба запроса переписываются по одному и тому же оптимальному плану. Поэтому ручная оптимизация в подзапрос не ускоряет работу, а просто затрудняет чтение SQL-кода.
Итого
Пишите понятный, читаемый SQL. Позвольте оптимизатору делать свою работу. В непонятных ситуациях используйте EXPLAIN ANALYZE, чтобы понять, что на самом деле делает БД и действительно ли один запрос быстрее другого.
Источник: https://www.milanjovanovic.tech/blog/debunking-the-filter-early-join-later-sql-performance-myth
Развенчиваем Миф Производительности SQL "Сначала Фильтр Потом JOIN"
В интернете часто можно встретить описание «трюка, повышающего производительность запросов в SQL», который звучит "Сначала Фильтр Потом JOIN". В нём утверждается, что вместо того, чтобы сначала объединять таблицы, а затем применять фильтр к результатам, нужно делать наоборот.
Например, вместо:
SELECT *
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.total > 500;
использовать:
SELECT *
FROM (
SELECT * FROM orders WHERE total > 500
) o
JOIN users u ON u.id = o.user_id;
Смысл в том, что БД сначала уберёт ненужные данные из одной таблицы, а потом выполнит соединение меньшего объёма, экономя время и память. Звучит логично. Но дело в том, что для современных БД этот совет не имеет смысла.
Вот пример плана выполнения (EXPLAIN ANALYZE) обоих запросов в PostgreSQL над таблицами с 10000 записями в users и 5000000 в orders.
«Неоптимальный» план запроса:
Hash Join (cost=280.00..96321.92 rows=2480444 width=27) (actual time=1.014..641.202 rows=2499245 loops=1)
Hash Cond: (o.user_id = u.id)
-> Seq Scan on orders o (cost=0.00..89528.00 rows=2480444 width=14) (actual time=0.006..368.857 rows=2499245 loops=1)
Filter: (total > '500'::numeric)
Rows Removed by Filter: 2500755
-> Hash (cost=155.00..155.00 rows=10000 width=13) (actual time=0.998..0.999 rows=10000 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 577kB
-> Seq Scan on users u (cost=0.00..155.00 rows=10000 width=13) (actual time=0.002..0.341 rows=10000 loops=1)
Planning Time: 0.121 ms
Execution Time: 685.818 ms
«Оптимальный» план запроса:
Hash Join (cost=280.00..96321.92 rows=2480444 width=27) (actual time=1.019..640.613 rows=2499245 loops=1)
Hash Cond: (orders.user_id = u.id)
-> Seq Scan on orders (cost=0.00..89528.00 rows=2480444 width=14) (actual time=0.005..368.260 rows=2499245 loops=1)
Filter: (total > '500'::numeric)
Rows Removed by Filter: 2500755
-> Hash (cost=155.00..155.00 rows=10000 width=13) (actual time=1.004..1.005 rows=10000 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 577kB
-> Seq Scan on users u (cost=0.00..155.00 rows=10000 width=13) (actual time=0.003..0.348 rows=10000 loops=1)
Planning Time: 0.118 ms
Execution Time: 685.275 ms
Как видите, планы запросов идентичны, и «оптимизация» ничего не добилась.
Основные операции:
- Последовательное сканирование (Seq Scan) таблицы orders с применением фильтра;
- Последовательное сканирование таблицы users;
- Операция хеширования (Hash) меньшей таблицы (users);
- Хеш-соединение по user_id.
Оптимизаторы запросов умнее вас
Современные БД используют стоимостную оптимизацию. Оптимизатор имеет статистику о таблицах: количество строк, распределение данных, наличие индекса, селективность столбцов и т.п. – и использует её для оценки стоимости различных стратегий выполнения. Современные БД, такие как PostgreSQL, MySQL и SQL Server, уже автоматически выполняют «выталкивание предикатов» и переупорядочивание соединений. Т.е. оба запроса переписываются по одному и тому же оптимальному плану. Поэтому ручная оптимизация в подзапрос не ускоряет работу, а просто затрудняет чтение SQL-кода.
Итого
Пишите понятный, читаемый SQL. Позвольте оптимизатору делать свою работу. В непонятных ситуациях используйте EXPLAIN ANALYZE, чтобы понять, что на самом деле делает БД и действительно ли один запрос быстрее другого.
Источник: https://www.milanjovanovic.tech/blog/debunking-the-filter-early-join-later-sql-performance-myth
День 2325. #TipsAndTricks
Не Изобретайте Велосипед — Конфигурация
Часто в различных решениях dotnet core, можно встретить код вроде следующего:
EnvironmentHelper выглядит так:
С этим кодом есть проблема. Каждый раз, когда вызывается EnvironmentHelper.IsLocal, он создаёт новый экземпляр ConfigurationBuilder и считывает
Примечание: Вообще, идея регистрировать различные реализации в зависимости от того, в какой среде исполняется код, тоже не очень, но это уже другая история.
При регистрации сервисов можно использовать перегрузку, которая даёт доступ к провайдеру сервисов:
Здесь мы извлекаем IHostEnvironment из провайдера сервисов. Но это требует регистрации как MockClient, так и ClientService, поскольку мы используем контейнер для разрешения экземпляров.
Лучшим подходом является использование построителя. Он содержит свойство Environment:
По умолчанию фреймворк устанавливает среду на основе значения переменной среды
Бонус
Если же вы не можете избавиться от текущей реализации EnvironmentHelper из-за объёма рефакторинга, можно использовать такой «костыль», чтобы хотя бы не создавать ConfigurationBuilder при каждом обращении:
Источник: https://josef.codes/dont-reinvent-the-wheel-configuration-dotnet-core/
Не Изобретайте Велосипед — Конфигурация
Часто в различных решениях dotnet core, можно встретить код вроде следующего:
// Program.cs
…
if(EnvironmentHelper.IsLocal)
services
.AddSingleton<IClient, MockClient>();
else
services
.AddSingleton<IClient, ClientService>();
…
EnvironmentHelper выглядит так:
public static class EnvironmentHelper
{
public static bool IsLocal =>
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.Build();
return config.GetValue<bool>("IsDevelopmentEnvironment");
}
}
С этим кодом есть проблема. Каждый раз, когда вызывается EnvironmentHelper.IsLocal, он создаёт новый экземпляр ConfigurationBuilder и считывает
appsettings.json
с диска. Код используется по всей кодовой базе. Нехорошо. Мы можем избежать этого и использовать встроенные инструменты фреймворка вместо того, чтобы придумывать собственные решения.Примечание: Вообще, идея регистрировать различные реализации в зависимости от того, в какой среде исполняется код, тоже не очень, но это уже другая история.
При регистрации сервисов можно использовать перегрузку, которая даёт доступ к провайдеру сервисов:
builder.Services
.AddSingleton<IClient>(provider =>
{
var env = provider.GetRequiredService<IHostEnvironment>();
if(env.IsDevelopment())
return provider.GetRequiredService<MockClient>();
return provider.GetRequiredService<ClientService>();
});
Здесь мы извлекаем IHostEnvironment из провайдера сервисов. Но это требует регистрации как MockClient, так и ClientService, поскольку мы используем контейнер для разрешения экземпляров.
Лучшим подходом является использование построителя. Он содержит свойство Environment:
var builder = WebApplication.CreateBuilder(args);
if(builder.Environment.IsDevelopment())
builder.Services
.AddSingleton<IClient, MockClient>();
else
builder.Services
.AddSingleton<IClient, ClientService>();
По умолчанию фреймворк устанавливает среду на основе значения переменной среды
ASPNETCORE_ENVIRONMENT
/DOTNET_ENVIRONMENT
, поэтому нет никакой необходимости задействовать appsettings.json
вообще.Бонус
Если же вы не можете избавиться от текущей реализации EnvironmentHelper из-за объёма рефакторинга, можно использовать такой «костыль», чтобы хотя бы не создавать ConfigurationBuilder при каждом обращении:
public static class EnvironmentHelper
{
private static readonly Lazy<bool> _isLocal;
static EnvironmentHelper()
{
_isLocal = new Lazy<bool>(() => {
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.Build();
return config.GetValue<bool>("IsDevelopmentEnvironment");
},
LazyThreadSafetyMode.ExecutionAndPublication);
}
public static bool IsLocal => _isLocal.Value;
}
Источник: https://josef.codes/dont-reinvent-the-wheel-configuration-dotnet-core/
День 2326. #SystemDesign101
Распространённые Алгоритмы Балансировки Нагрузки
Статические алгоритмы
1. Круговой Алгоритм (Round Robin)
Клиентские запросы отправляются в различные экземпляры сервисов в последовательном порядке. Сервисы обычно должны быть без сохранения состояния.
Недостаток
- Эта простейшая версия алгоритма будет эффективно работать только в сферической среде в вакууме, где все серверы обладают почти одинаковой конфигурацией, а все входящие запросы (задачи, процессы) имеют одинаковые приоритет и продолжительность.
2. Закреплённый Круговой Алгоритм (Sticky Round Robin)
Усовершенствование кругового алгоритма. Если первый запрос Алисы отправляется в сервис A, следующие запросы также отправляются в сервис A.
3. Взвешенный Круговой Алгоритм (Weighted Round Robin)
Администратор может указать вес для каждого сервиса. Сервисы с более высоким весом обрабатывают больше запросов, чем другие.
4. Хэш IP/URL
Применяет хеш-функцию к IP или URL входящих запросов. Запросы направляются в соответствующие экземпляры на основе результата хеш-функции.
Преимущества
- Постоянство сессии – алгоритм гарантирует, что запросы от одного клиента всегда попадают на один и тот же сервер.
- Облегчает кеширование данных на стороне сервера для конкретных клиентов.
Недостатки
- Если много пользователей приходят из одного диапазона IP, один сервер может быть перегружен.
- Неэффективен в средах, где IP клиентов часто меняются (мобильные сети).
- Может привести к неравномерной нагрузке, если некоторые клиенты генерируют больше трафика.
Динамические алгоритмы
5. Наименьшее Количество Соединений (Least Connections)
Новый запрос отправляется в экземпляр сервиса с наименьшим количеством одновременных подключений.
Преимущества
- Более мощные серверы естественным образом будут обрабатывать больше запросов и, следовательно, иметь больше соединений. И напротив, менее мощные серверы будут получать меньше запросов, что предотвращает их перегрузку.
- Гибкость – если один сервер начинает работать медленнее, он будет получать меньше новых запросов.
Недостатки
- Алгоритм считает все соединения одинаковыми, не учитывая, что некоторые запросы могут быть более ресурсоёмкими.
- Вновь добавленный сервер может получить слишком много запросов, т.к. изначально у него 0 соединений.
6. Наименьшее Время Ответа (Least Time)
Новый запрос отправляется в экземпляр сервиса с самым быстрым временем ответа. Аналогичный алгоритм - Наименьший объем трафика (Least Bandwidth).
Преимущества
- Учёт текущей производительности серверов и динамическая адаптация обеспечивают оптимальный баланс и наилучший пользовательский опыт.
- Хорошо работает с серверами разной мощности и приложениями с разными характеристиками.
Недостаток
- Сложность реализации – требует постоянного мониторинга и анализа производительности серверов, отсюда повышенная нагрузка на балансировщик.
Источники:
- https://github.com/ByteByteGoHq/system-design-101
- https://proglib.io/p/6-glavnyh-algoritmov-balansirovki-nagruzki-2024-08-06
Распространённые Алгоритмы Балансировки Нагрузки
Статические алгоритмы
1. Круговой Алгоритм (Round Robin)
Клиентские запросы отправляются в различные экземпляры сервисов в последовательном порядке. Сервисы обычно должны быть без сохранения состояния.
Недостаток
- Эта простейшая версия алгоритма будет эффективно работать только в сферической среде в вакууме, где все серверы обладают почти одинаковой конфигурацией, а все входящие запросы (задачи, процессы) имеют одинаковые приоритет и продолжительность.
2. Закреплённый Круговой Алгоритм (Sticky Round Robin)
Усовершенствование кругового алгоритма. Если первый запрос Алисы отправляется в сервис A, следующие запросы также отправляются в сервис A.
3. Взвешенный Круговой Алгоритм (Weighted Round Robin)
Администратор может указать вес для каждого сервиса. Сервисы с более высоким весом обрабатывают больше запросов, чем другие.
4. Хэш IP/URL
Применяет хеш-функцию к IP или URL входящих запросов. Запросы направляются в соответствующие экземпляры на основе результата хеш-функции.
Преимущества
- Постоянство сессии – алгоритм гарантирует, что запросы от одного клиента всегда попадают на один и тот же сервер.
- Облегчает кеширование данных на стороне сервера для конкретных клиентов.
Недостатки
- Если много пользователей приходят из одного диапазона IP, один сервер может быть перегружен.
- Неэффективен в средах, где IP клиентов часто меняются (мобильные сети).
- Может привести к неравномерной нагрузке, если некоторые клиенты генерируют больше трафика.
Динамические алгоритмы
5. Наименьшее Количество Соединений (Least Connections)
Новый запрос отправляется в экземпляр сервиса с наименьшим количеством одновременных подключений.
Преимущества
- Более мощные серверы естественным образом будут обрабатывать больше запросов и, следовательно, иметь больше соединений. И напротив, менее мощные серверы будут получать меньше запросов, что предотвращает их перегрузку.
- Гибкость – если один сервер начинает работать медленнее, он будет получать меньше новых запросов.
Недостатки
- Алгоритм считает все соединения одинаковыми, не учитывая, что некоторые запросы могут быть более ресурсоёмкими.
- Вновь добавленный сервер может получить слишком много запросов, т.к. изначально у него 0 соединений.
6. Наименьшее Время Ответа (Least Time)
Новый запрос отправляется в экземпляр сервиса с самым быстрым временем ответа. Аналогичный алгоритм - Наименьший объем трафика (Least Bandwidth).
Преимущества
- Учёт текущей производительности серверов и динамическая адаптация обеспечивают оптимальный баланс и наилучший пользовательский опыт.
- Хорошо работает с серверами разной мощности и приложениями с разными характеристиками.
Недостаток
- Сложность реализации – требует постоянного мониторинга и анализа производительности серверов, отсюда повышенная нагрузка на балансировщик.
Источники:
- https://github.com/ByteByteGoHq/system-design-101
- https://proglib.io/p/6-glavnyh-algoritmov-balansirovki-nagruzki-2024-08-06
День 2327. #ЧтоНовенького
Вам не Хватало Vim в Windows? Представляем Edit!
Edit — это новый текстовый редактор командной строки в Windows. Edit имеет открытый исходный код. Вы можете посмотреть код на GitHub или установить его, выполнив следующую команду:
Edit будет доступен для предварительного просмотра в программе Windows Insider в ближайшие месяцы. После этого он будет поставляться как часть Windows 11.
Как использовать?
Введите
Возможности
Edit — это небольшой, легкий текстовый редактор. Он занимает менее 250 КБ. Он пока находится на ранней стадии разработки, но у него есть несколько готовых функций:
1. Поддержка режима мыши
Пункты меню другие элементы UI поддерживают использование мыши, а также горячие клавиши.
2. Открытие нескольких файлов
Вы можете открыть несколько файлов в Edit и переключаться между ними с помощью
3. Поиск и замена
Вы можете найти и заменить текст с помощью
4. Перенос по словам
Edit поддерживает перенос по словам. Чтобы использовать перенос по словам, вы можете использовать
Зачем нам ещё один CLI-редактор?
Edit - текстовый редактор CLI по умолчанию в 64-разрядных версиях Windows. 32-разрядные версии Windows поставляются с редактором MS-DOS, но в 64-разрядных версиях редактор CLI не установлен.
В Microsoft решили, что нужен немодальный редактор для Windows (в отличие от модального редактора, в котором новым пользователям пришлось бы запоминать различные режимы работы и как переключаться между ними). К сожалению, это ограничило выбор списком редакторов, которые либо не имели собственной поддержки для Windows, либо были слишком большими, чтобы включать их в каждую версию ОС. В результате родился Edit.
Источник: https://devblogs.microsoft.com/commandline/edit-is-now-open-source/
Вам не Хватало Vim в Windows? Представляем Edit!
Edit — это новый текстовый редактор командной строки в Windows. Edit имеет открытый исходный код. Вы можете посмотреть код на GitHub или установить его, выполнив следующую команду:
winget install Microsoft.Edit
Edit будет доступен для предварительного просмотра в программе Windows Insider в ближайшие месяцы. После этого он будет поставляться как часть Windows 11.
Как использовать?
Введите
edit
в командной строке или edit <your-file-name>
, чтобы открыть файл. Теперь вы можете редактировать файлы непосредственно в командной строке без переключения контекста.Возможности
Edit — это небольшой, легкий текстовый редактор. Он занимает менее 250 КБ. Он пока находится на ранней стадии разработки, но у него есть несколько готовых функций:
1. Поддержка режима мыши
Пункты меню другие элементы UI поддерживают использование мыши, а также горячие клавиши.
2. Открытие нескольких файлов
Вы можете открыть несколько файлов в Edit и переключаться между ними с помощью
Ctrl+P
(или щелкнув на список файлов в правом нижнем углу).3. Поиск и замена
Вы можете найти и заменить текст с помощью
Ctrl+R
или выбрать Edit > Replace в меню. Также есть поддержка регистра и регулярных выражений.4. Перенос по словам
Edit поддерживает перенос по словам. Чтобы использовать перенос по словам, вы можете использовать
Alt+Z
или выбрать View > Word Wrap в меню.Зачем нам ещё один CLI-редактор?
Edit - текстовый редактор CLI по умолчанию в 64-разрядных версиях Windows. 32-разрядные версии Windows поставляются с редактором MS-DOS, но в 64-разрядных версиях редактор CLI не установлен.
В Microsoft решили, что нужен немодальный редактор для Windows (в отличие от модального редактора, в котором новым пользователям пришлось бы запоминать различные режимы работы и как переключаться между ними). К сожалению, это ограничило выбор списком редакторов, которые либо не имели собственной поддержки для Windows, либо были слишком большими, чтобы включать их в каждую версию ОС. В результате родился Edit.
Источник: https://devblogs.microsoft.com/commandline/edit-is-now-open-source/
День 2328. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 55. У вас нет времени, чтобы совершить все ошибки, сделанные до вас
Получать знания от других людей гораздо эффективнее, чем обретать их самостоятельно. Все профессионалы должны тратить часть своего времени на обретение знаний и расширение навыков в постоянно развивающейся области: чтение книг, статей, посещение конференций или прослушивание подкастов, - и думать, как можно применять эти навыки в работе.
Кривая обучения
Кривая обучения описывает, как человек обретает навыки выполнения новой задачи или применения нового приёма в зависимости от своего опыта. В жизни мы постоянно сталкиваемся с бесчисленными кривыми обучения. Всякий раз, пытаясь сделать что-то новое, мы встаём на новую кривую. Не нужно ожидать, что весь потенциал метода раскроется с первой попытки. Когда проектные группы пробуют использовать незнакомые методы, в их планах должно быть предусмотрено время, необходимое на то, чтобы освоиться. Если им не удастся освоить новую практику, то затраченное время будет потеряно навсегда.
Вы, несомненно, заинтересованы в повышении общей продуктивности, которой позволяет добиться ваш набор приёмов. На картинке выше показано, что в самом начале вы имеете определенный уровень продуктивности, который хотите повысить с помощью усовершенствованного процесса, практики, метода или инструмента. Первый шаг — обучение и обретение некоего опыта. Ваша продуктивность немедленно падает, поскольку в периоды обучения вы не выполняете полезную работу. Продуктивность продолжает снижаться, пока вы тратите время на создание новых процессов, пытаетесь понять, как заставить работать новую технику, приобретаете новый инструмент и учитесь использовать его и т. д. По мере осваивания нового способа работы вы начинаете замечать первые успехи, а также некоторые неудачи — пилообразная часть кривой роста продуктивности. Если все пойдет хорошо, то в итоге ваши вложения окупятся и вы почувствуете, что эффективность, результативность и качество возросли. Помните о реальности кривой обучения, внедряя новые практики, и не поддавайтесь искушению сдаться до того, как вложения в обучение начнут окупаться.
Хорошие практики
Забавно, когда кто-то жалуется на другого: «Он всегда думает, что его способ лучше». Конечно, он так думает! Зачем кому-то намеренно делать что-то, выбирая плохие способы? Это было бы глупо. Проблема не в том, что кто-то считает свой способ лучшим, а в том, если он не допускает мысли, что другие могут знать лучшие способы, и не желает учиться у них.
Рецензирование коллегами даёт хорошую возможность наблюдать за способами работы, которые используют другие. В ходе таких встреч можно увидеть, как кто-то использует незнакомые функции, хитрые приёмы программирования или что-то ещё, что зажигает в вашем мозгу лампочку. Это простой способ учиться и совершенствоваться.
Люди часто заводят разговоры о лучших практиках, которые сразу же перерастают в споры о том, чья практика лучше для той или иной цели, в том или ином контексте. Всё это хорошо, но «лучшая практика» — слишком строгий термин.
Совет - собирайте арсенал хороших практик. Чтобы попасть в него, приём просто должен быть лучше, чем тот, который вы используете сейчас. По мере накопления инструментов и методов придерживайтесь тех, которые вы успешно использовали в прошлом. Заменяйте текущую технику новой, только когда новая позволяет получить превосходные результаты во всех случаях. Часто техники могут мирно сосуществовать, и тогда у вас есть возможность выбирать между ними в зависимости от ситуации. Так что овладейте обеими техниками и используйте самую простую из них, позволяющую выполнять работу.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 7.
Уроки 50 Лет Разработки ПО
Урок 55. У вас нет времени, чтобы совершить все ошибки, сделанные до вас
Получать знания от других людей гораздо эффективнее, чем обретать их самостоятельно. Все профессионалы должны тратить часть своего времени на обретение знаний и расширение навыков в постоянно развивающейся области: чтение книг, статей, посещение конференций или прослушивание подкастов, - и думать, как можно применять эти навыки в работе.
Кривая обучения
Кривая обучения описывает, как человек обретает навыки выполнения новой задачи или применения нового приёма в зависимости от своего опыта. В жизни мы постоянно сталкиваемся с бесчисленными кривыми обучения. Всякий раз, пытаясь сделать что-то новое, мы встаём на новую кривую. Не нужно ожидать, что весь потенциал метода раскроется с первой попытки. Когда проектные группы пробуют использовать незнакомые методы, в их планах должно быть предусмотрено время, необходимое на то, чтобы освоиться. Если им не удастся освоить новую практику, то затраченное время будет потеряно навсегда.
Вы, несомненно, заинтересованы в повышении общей продуктивности, которой позволяет добиться ваш набор приёмов. На картинке выше показано, что в самом начале вы имеете определенный уровень продуктивности, который хотите повысить с помощью усовершенствованного процесса, практики, метода или инструмента. Первый шаг — обучение и обретение некоего опыта. Ваша продуктивность немедленно падает, поскольку в периоды обучения вы не выполняете полезную работу. Продуктивность продолжает снижаться, пока вы тратите время на создание новых процессов, пытаетесь понять, как заставить работать новую технику, приобретаете новый инструмент и учитесь использовать его и т. д. По мере осваивания нового способа работы вы начинаете замечать первые успехи, а также некоторые неудачи — пилообразная часть кривой роста продуктивности. Если все пойдет хорошо, то в итоге ваши вложения окупятся и вы почувствуете, что эффективность, результативность и качество возросли. Помните о реальности кривой обучения, внедряя новые практики, и не поддавайтесь искушению сдаться до того, как вложения в обучение начнут окупаться.
Хорошие практики
Забавно, когда кто-то жалуется на другого: «Он всегда думает, что его способ лучше». Конечно, он так думает! Зачем кому-то намеренно делать что-то, выбирая плохие способы? Это было бы глупо. Проблема не в том, что кто-то считает свой способ лучшим, а в том, если он не допускает мысли, что другие могут знать лучшие способы, и не желает учиться у них.
Рецензирование коллегами даёт хорошую возможность наблюдать за способами работы, которые используют другие. В ходе таких встреч можно увидеть, как кто-то использует незнакомые функции, хитрые приёмы программирования или что-то ещё, что зажигает в вашем мозгу лампочку. Это простой способ учиться и совершенствоваться.
Люди часто заводят разговоры о лучших практиках, которые сразу же перерастают в споры о том, чья практика лучше для той или иной цели, в том или ином контексте. Всё это хорошо, но «лучшая практика» — слишком строгий термин.
Совет - собирайте арсенал хороших практик. Чтобы попасть в него, приём просто должен быть лучше, чем тот, который вы используете сейчас. По мере накопления инструментов и методов придерживайтесь тех, которые вы успешно использовали в прошлом. Заменяйте текущую технику новой, только когда новая позволяет получить превосходные результаты во всех случаях. Часто техники могут мирно сосуществовать, и тогда у вас есть возможность выбирать между ними в зависимости от ситуации. Так что овладейте обеими техниками и используйте самую простую из них, позволяющую выполнять работу.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 7.