День 2742. #TipsAndTricks
Получаем Название Символа Unicode из Руны
Я уже писал в обзоре новинок .NET 11, что в C# добавлена поддержка рун. Однако, если у вас есть руна и вы хотите получить название этого Unicode-символа (например,
Библиотека ICU (International Components for Unicode) предоставляет функцию
Использование:
Источник: https://www.meziantou.net/get-a-unicode-character-name-from-a-rune-in-dotnet.htm
Получаем Название Символа Unicode из Руны
Я уже писал в обзоре новинок .NET 11, что в C# добавлена поддержка рун. Однако, если у вас есть руна и вы хотите получить название этого Unicode-символа (например,
GRINNING FACE для 😀), то в .NET на сегодняшний день нет встроенного API.Библиотека ICU (International Components for Unicode) предоставляет функцию
u_charName, которая возвращает название Unicode-символа. Обычно, начиная с .NET 5, вам не требуется обращаться к ней напрямую, т.к. .NET нативно её поддерживает. Но поддержка названий Unicode-символов пока не добавлена, поэтому придётся вызвать её напрямую:using System;
using System.Runtime.InteropServices;
using System.Text;
public static class IcuNative
{
private const string IcuLib = "icuuc";
private enum UCharNameChoice
{
U_UNICODE_CHAR_NAME = 0,
}
private enum UErrorCode
{
U_ZERO_ERROR = 0,
}
[DllImport(IcuLib, CallingConvention = CallingConvention.Cdecl,
EntryPoint = "u_charName")]
private static extern int u_charName(
int codepoint,
UCharNameChoice nameChoice,
byte[] buffer,
int bufferLength,
ref UErrorCode errorCode);
public static string GetCharName(Rune rune)
{
var buffer = new byte[128];
var error = UErrorCode.U_ZERO_ERROR;
int length = u_charName(
rune.Value,
UCharNameChoice.U_UNICODE_CHAR_NAME,
buffer,
buffer.Length,
ref error);
if (error != UErrorCode.U_ZERO_ERROR)
throw new InvalidOperationException(
$"Ошибка ICU: {error}");
return Encoding.ASCII
.GetString(buffer, 0, length);
}
}
Использование:
var rune = new Rune(0x1F600); // 😀
Console.WriteLine(IcuNative.GetCharName(rune));
// GRINNING FACE
var rune = new Rune(0x1F600); // 🌍
Console.WriteLine(IcuNative.GetCharName(rune));
// EARTH GLOBE EUROPE-AFRICA
Источник: https://www.meziantou.net/get-a-unicode-character-name-from-a-rune-in-dotnet.htm
👍1
День 2743. #ЗаметкиНаПолях #Microservices
Стоит ли Делить Это на Микросервисы?
Одни команды внедряют микросервисы, другие отказываются от них. Почти во всех случаях отказа решение о разделении принималось раньше, чем находились причины. Приложение может когда-нибудь масштабироваться. Монолит кажется всё более запутанным с каждым спринтом. В докладе на конференции сказали, что независимые развёртывания — это легко. Ничто из этого не является причиной для перехода на распределённую систему. Между тем, реальная цена высока: микросервисы обменивают локальную сложность на распределённую. Вызов метода становится сетевым. Транзакция становится сагой. Трассировка стека - распределённой трассировкой по трём сервисам и очереди. Иногда этот обмен стоит того. Ответьте на вопросы ниже, и решение делить или нет станет очевидным.
1. Действительно ли у разных частей системы разные потребности в масштабировании?
Не потребности, которые могут возникнуть когда-нибудь, а те, которые можно измерить сегодня: одна часть системы обрабатывает в 100 раз больший трафик или требует графического процессора, или потребляет память так, что приходится рассчитывать масштаб всего развёртывания под пиковые нагрузки этой части.
Это реальная причина. Выделение «горячего» пути, позволяющего масштабироваться (и падать) независимо, — один из лучших аргументов в пользу выделения сервиса.
Но сначала проверьте: большинство монолитов в .NET хорошо масштабируются за балансировщиком нагрузки. Если всё приложение комфортно работает на трёх экземплярах, у вас нет проблемы масштабирования, ради которой стоило бы выделять сервисы.
2. Действительно ли команды мешают друг другу?
Несколько команд, одна кодовая база и процесс релиза, где наполовину готовая функция команды А задерживает выпуск исправления командой Б. Развёртывания усложняются, релизы откладываются, постоянные конфликты слияния и т.п. Если это ваша реальность, то независимое развёртывание имеет реальную ценность.
Если у вас команда из 6 человек, то нет. Одна команда не настолько сильно мешает сама себе, чтобы оправдать эксплуатацию распределённой системы. Если у вас меньше двух полных команд, организационная польза от микросервисов равна нулю.
3. Можно ли разграничить данные?
Каждый сервис должен полностью владеть своими данными. Владение базой данных означает, что ни один другой сервис не будет напрямую считывать её таблицы, даже для одного удобного join'а. Если двум потенциальным сервисам постоянно требуются данные друг друга для ответа на базовые запросы, это не два сервиса, а один который вы собираетесь разорвать пополам.
Граница, которая выглядит чистой на организационной диаграмме, может быть безнадёжно запутана на уровне данных. Запутанность не исчезнет, когда вы добавите сеть между половинами. Она усугубится, потому что теперь каждое «соединение» — это вызов API, и сохранение целостности границ данных становится распределённой проблемой.
4. Требует ли что-либо независимого отказа или выпуска?
Некоторые части системы предъявляют требования, которых нет у остальных:
- Платёжный поток, который должен оставаться работоспособным даже при сбое модуля отчётности;
- Компонент, который обязан соответствовать государственным стандартам, и необходимо максимально уменьшить площадь проверяемого кода;
- Интеграция, которая выпускается еженедельно, в то время как ядро — ежеквартально;
и т.п.
Это законные требования к изоляции, и выделение сервиса — это чёткий способ их выразить. Обратите внимание, насколько они конкретны. Обычное желание изолировать сервис в этом списке отсутствует.
5. Можете ли вы позволить себе «налог на платформу»?
Прежде чем первый микросервис начнёт приносить какую-либо пользу, вам необходимы: платформа контейнеров, CI/CD для каждого сервиса, централизованное логирование, распределённая трассировка, брокер сообщений и паттерны надёжности, обеспечивающие безопасное взаимодействие между сервисами («Исходящие», «Идемпотентные потребители», «Повторные попытки»).
Это вступительный взнос, оплачиваемый в инженерных человеко-месяцах, ещё до получения первого преимущества.
Команда, которая не может выделить такие ресурсы, не получит более дешёвую версию в виде микросервисов. Она получит распределённый монолит без всех преимуществ и со всеми его издержками.
Оценка
- 4 или 5 ответов «да»: разделите проект и начните с одного сервиса. Выделите часть с наиболее чёткими границами и запустите её в продакшене на квартал, прежде чем выделять следующую.
- 2 или 3: вам нужны модули, а не сервисы. Модульный монолит даст границы, командное владение кодом и возможность разделения позже без платы за платформу. Границы, которые вы устанавливаете сейчас, — это именно то, что делает последующую миграцию механической, а не героической.
- 0 или 1: сохраните монолит и вложите сэкономленную энергию в его совершенствование.
Команды, которые пожалели о переходе на микросервисы, почти никогда не ошибались в выборе технологии. Они ошиблись полтора года назад на совещании, где было принято решение о разделении, ещё до того, как стали известны причины. Проведите опрос по пяти вопросам перед подобным совещанием у вас.
Источник: https://milanjovanovic.tech/blog/should-you-split-that-into-microservices-ask-these-5-questions-first
Стоит ли Делить Это на Микросервисы?
Одни команды внедряют микросервисы, другие отказываются от них. Почти во всех случаях отказа решение о разделении принималось раньше, чем находились причины. Приложение может когда-нибудь масштабироваться. Монолит кажется всё более запутанным с каждым спринтом. В докладе на конференции сказали, что независимые развёртывания — это легко. Ничто из этого не является причиной для перехода на распределённую систему. Между тем, реальная цена высока: микросервисы обменивают локальную сложность на распределённую. Вызов метода становится сетевым. Транзакция становится сагой. Трассировка стека - распределённой трассировкой по трём сервисам и очереди. Иногда этот обмен стоит того. Ответьте на вопросы ниже, и решение делить или нет станет очевидным.
1. Действительно ли у разных частей системы разные потребности в масштабировании?
Не потребности, которые могут возникнуть когда-нибудь, а те, которые можно измерить сегодня: одна часть системы обрабатывает в 100 раз больший трафик или требует графического процессора, или потребляет память так, что приходится рассчитывать масштаб всего развёртывания под пиковые нагрузки этой части.
Это реальная причина. Выделение «горячего» пути, позволяющего масштабироваться (и падать) независимо, — один из лучших аргументов в пользу выделения сервиса.
Но сначала проверьте: большинство монолитов в .NET хорошо масштабируются за балансировщиком нагрузки. Если всё приложение комфортно работает на трёх экземплярах, у вас нет проблемы масштабирования, ради которой стоило бы выделять сервисы.
2. Действительно ли команды мешают друг другу?
Несколько команд, одна кодовая база и процесс релиза, где наполовину готовая функция команды А задерживает выпуск исправления командой Б. Развёртывания усложняются, релизы откладываются, постоянные конфликты слияния и т.п. Если это ваша реальность, то независимое развёртывание имеет реальную ценность.
Если у вас команда из 6 человек, то нет. Одна команда не настолько сильно мешает сама себе, чтобы оправдать эксплуатацию распределённой системы. Если у вас меньше двух полных команд, организационная польза от микросервисов равна нулю.
3. Можно ли разграничить данные?
Каждый сервис должен полностью владеть своими данными. Владение базой данных означает, что ни один другой сервис не будет напрямую считывать её таблицы, даже для одного удобного join'а. Если двум потенциальным сервисам постоянно требуются данные друг друга для ответа на базовые запросы, это не два сервиса, а один который вы собираетесь разорвать пополам.
Граница, которая выглядит чистой на организационной диаграмме, может быть безнадёжно запутана на уровне данных. Запутанность не исчезнет, когда вы добавите сеть между половинами. Она усугубится, потому что теперь каждое «соединение» — это вызов API, и сохранение целостности границ данных становится распределённой проблемой.
4. Требует ли что-либо независимого отказа или выпуска?
Некоторые части системы предъявляют требования, которых нет у остальных:
- Платёжный поток, который должен оставаться работоспособным даже при сбое модуля отчётности;
- Компонент, который обязан соответствовать государственным стандартам, и необходимо максимально уменьшить площадь проверяемого кода;
- Интеграция, которая выпускается еженедельно, в то время как ядро — ежеквартально;
и т.п.
Это законные требования к изоляции, и выделение сервиса — это чёткий способ их выразить. Обратите внимание, насколько они конкретны. Обычное желание изолировать сервис в этом списке отсутствует.
5. Можете ли вы позволить себе «налог на платформу»?
Прежде чем первый микросервис начнёт приносить какую-либо пользу, вам необходимы: платформа контейнеров, CI/CD для каждого сервиса, централизованное логирование, распределённая трассировка, брокер сообщений и паттерны надёжности, обеспечивающие безопасное взаимодействие между сервисами («Исходящие», «Идемпотентные потребители», «Повторные попытки»).
Это вступительный взнос, оплачиваемый в инженерных человеко-месяцах, ещё до получения первого преимущества.
Команда, которая не может выделить такие ресурсы, не получит более дешёвую версию в виде микросервисов. Она получит распределённый монолит без всех преимуществ и со всеми его издержками.
Оценка
- 4 или 5 ответов «да»: разделите проект и начните с одного сервиса. Выделите часть с наиболее чёткими границами и запустите её в продакшене на квартал, прежде чем выделять следующую.
- 2 или 3: вам нужны модули, а не сервисы. Модульный монолит даст границы, командное владение кодом и возможность разделения позже без платы за платформу. Границы, которые вы устанавливаете сейчас, — это именно то, что делает последующую миграцию механической, а не героической.
- 0 или 1: сохраните монолит и вложите сэкономленную энергию в его совершенствование.
Команды, которые пожалели о переходе на микросервисы, почти никогда не ошибались в выборе технологии. Они ошиблись полтора года назад на совещании, где было принято решение о разделении, ещё до того, как стали известны причины. Проведите опрос по пяти вопросам перед подобным совещанием у вас.
Источник: https://milanjovanovic.tech/blog/should-you-split-that-into-microservices-ask-these-5-questions-first
👍9
День 2744. #ЧтоНовенького #VisualStudio
Заставьте Вашу Модель Думать Больше
Не каждый вопрос требует одинакового уровня обдумывания. Переименование переменной — это не то же самое, что отладка утечки памяти, и для них требуется разный уровень мышления.
Начиная с Visual Studio 18.9 Insiders 2 поддерживаемые модели теперь имеют регулятор Thinking (уровня мыслительных усилий), поэтому вы можете увеличить уровень рассуждений, когда задача действительно сложная, и уменьшить его, когда она простая. Результат настройки - более подходящие ответы и больший контроль над тем, сколько ресурсов вы тратите на их получение.
Параметр Thinking регулирует, сколько рассуждений выполняет модель, прежде чем дать ответ. Он отображается в виде набора именованных уровней, и какие именно уровни вы получите, зависит от модели (см. картинку ниже):
- Низкий — быстрые ответы с минимальными рассуждениями. Отлично подходит для простых вопросов и повседневных подсказок по коду, а также потребляет меньше токенов.
- Средний — сбалансированное мышление и скорость, обычно используется по умолчанию. Отлично подходит для типичных задач программирования.
- Высокий — более глубокое мышление для сложных проблем. Используйте его, когда сталкиваетесь со сложным алгоритмом, архитектурным решением или неуловимой ошибкой.
- Экстра высокий и Максимальный — максимальное мышление, предлагаемое некоторыми моделями, для самых сложных задач, где вы хотите, чтобы модель использовала все доступные возможности.
Какие уровни отображаются, зависит от выбранной вами модели, поэтому отображаемый список адаптирован к тому, что эта модель фактически поддерживает. Некоторые модели вообще не отображают этот элемент управления, и это тоже нормально: они будут показывать прочерк и продолжат работать как обычно.
Настройка выполняется быстро. В окне чата GitHub Copilot раскройте список выбора модели и выберите Manage models (Управление моделями), чтобы открыть расширенное окно управления моделями (на картинке ниже), и отрегулируйте уровень мышления для каждой модели.
В окне управления уровень мыслительной нагрузки находится в отдельном столбце рядом с возможностями каждой модели, размером контекста и стоимостью. Таким образом, то же место, где вы сравнивали модели, теперь используется для настройки их мыслительных процессов.
Более высокие уровни мыслительной нагрузки требуют больше рассуждений, что потребляет больше токенов. И наоборот. Это означает, что вы можете оставить модель на низком уровне для десятков небольших задач, которые заполняют обычную сессию, а затем повысить его до высокого уровня для одной сложной задачи, которая действительно в этом нуждается. Одна и та же модель, один и тот же диалог, каждый раз нужное количество размышлений.
Источник: https://devblogs.microsoft.com/visualstudio/tell-your-model-when-to-think-harder/
Заставьте Вашу Модель Думать Больше
Не каждый вопрос требует одинакового уровня обдумывания. Переименование переменной — это не то же самое, что отладка утечки памяти, и для них требуется разный уровень мышления.
Начиная с Visual Studio 18.9 Insiders 2 поддерживаемые модели теперь имеют регулятор Thinking (уровня мыслительных усилий), поэтому вы можете увеличить уровень рассуждений, когда задача действительно сложная, и уменьшить его, когда она простая. Результат настройки - более подходящие ответы и больший контроль над тем, сколько ресурсов вы тратите на их получение.
Параметр Thinking регулирует, сколько рассуждений выполняет модель, прежде чем дать ответ. Он отображается в виде набора именованных уровней, и какие именно уровни вы получите, зависит от модели (см. картинку ниже):
- Низкий — быстрые ответы с минимальными рассуждениями. Отлично подходит для простых вопросов и повседневных подсказок по коду, а также потребляет меньше токенов.
- Средний — сбалансированное мышление и скорость, обычно используется по умолчанию. Отлично подходит для типичных задач программирования.
- Высокий — более глубокое мышление для сложных проблем. Используйте его, когда сталкиваетесь со сложным алгоритмом, архитектурным решением или неуловимой ошибкой.
- Экстра высокий и Максимальный — максимальное мышление, предлагаемое некоторыми моделями, для самых сложных задач, где вы хотите, чтобы модель использовала все доступные возможности.
Какие уровни отображаются, зависит от выбранной вами модели, поэтому отображаемый список адаптирован к тому, что эта модель фактически поддерживает. Некоторые модели вообще не отображают этот элемент управления, и это тоже нормально: они будут показывать прочерк и продолжат работать как обычно.
Настройка выполняется быстро. В окне чата GitHub Copilot раскройте список выбора модели и выберите Manage models (Управление моделями), чтобы открыть расширенное окно управления моделями (на картинке ниже), и отрегулируйте уровень мышления для каждой модели.
В окне управления уровень мыслительной нагрузки находится в отдельном столбце рядом с возможностями каждой модели, размером контекста и стоимостью. Таким образом, то же место, где вы сравнивали модели, теперь используется для настройки их мыслительных процессов.
Более высокие уровни мыслительной нагрузки требуют больше рассуждений, что потребляет больше токенов. И наоборот. Это означает, что вы можете оставить модель на низком уровне для десятков небольших задач, которые заполняют обычную сессию, а затем повысить его до высокого уровня для одной сложной задачи, которая действительно в этом нуждается. Одна и та же модель, один и тот же диалог, каждый раз нужное количество размышлений.
Источник: https://devblogs.microsoft.com/visualstudio/tell-your-model-when-to-think-harder/
👎2
День 2745. #ЗаметкиНаПолях #Architecture
5 Архитектурных Ошибок, Которые Затрудняют Изменение Системы. Начало
Вы можете следовать любым лучшим практикам, использовать чистую архитектуру, event-sourcing, микросервисы или что-либо ещё популярное. В итоге вы всё равно оказываетесь в той же ситуации. У вас система, которую очень сложно изменить. Когда вы всё-таки вносите изменения, вы боитесь что-нибудь сломать. В конце концов всё чаще возникает мысль: «Лучше бы это переписать».
Кодовая база может даже выглядеть не так уж плохо. Она может быть организованной и относительно простой для понимания. Но почему-то очень трудно что-то изменить. Причина, вероятно, кроется в одной из 5 архитектурных ошибок. В каждом случае вы принимаете дорогостоящее решение, прежде чем по-настоящему поймёте бизнес или проблемы, которые пытаетесь решить. Вопрос не в том, какую архитектуру использовать? Вопрос должен звучать: «Что мы понимаем о проблеме, что оправдывает архитектурное решение, которое мы собираемся принять?»
1. Выбор архитектуры до понимания предметной области
Если в начале обсуждения проекта речь идёт о необходимости микросервисов, событийного моделирования, CQRS, Kafka и Kubernetes, но никто не может объяснить бизнес-процессы или рабочие процессы, вы делаете всё наоборот.
Может кто-нибудь объяснить ограничения? Какие проблемы с согласованностью могут возникнуть? Какие возможные режимы отказов? Какие части системы часто меняются? Где допустимы задержки?
Архитектурные шаблоны и инструменты сопряжены с компромиссами и дополнительной сложностью:
- Нужна независимая развёртываемость? Теперь у вас распределённые операции.
- Масштабировать части системы независимо? Придётся бороться со сбоями в сети.
- Автономия команды? Потребуется межсервисное и межкомандное взаимодействие.
- Изоляция между границами? Получите проблемы согласованности между этими границами.
Всегда есть компромисс. У каждого решения есть своя цена. Основное внимание следует уделить факторам, влияющим на вашу систему, и проблемам, которые вы пытаетесь решить.
Есть ли проблемы с согласованностью? Есть ли в системе часть, где правила быстро меняются и которую следует изолировать? Содержит ли рабочий процесс задержки, которые необходимо учитывать в последующих процессах?
Если вы сначала принимаете технические решения, вы лишь предполагаете, что когда-нибудь столкнётесь с проблемой, которую эти решения должны решить.
2. Создание сервисов сущностей, управляемых CRUD-операциями
При этом система полностью управляется своей моделью данных, а не поведением. Код может выглядеть отлично, быть хорошо организован. Но сущности на самом деле представляют собой просто таблицы или графы объектов, отображающие клиентов, заказы, товары и т.п.
Вот простой заказ:
Что нам говорит эта модель? Что какие-то данные существуют. Она ничего не говорит нам о том, как ведёт себя заказ. Можно ли его отменить? Возможно да, но как? Просто изменить статус? Когда заказ может быть отправлен? Должен ли он находиться в определённом статусе? Можно ли изменить статус после того, как товар зарезервирован? Модель предоставляет нам только данные. Вся бизнес-логика при этом обычно разбросана повсюду: в контроллерах, обработчиках сообщений, хранимых процедурах или даже во фронтэнде.
Сами по себе анемичные модель не плохи. В системе всегда есть части, которые ими по сути являются. Проблема в том, когда всё становится анемичными моделями, а разработка сводится к операциям над обновляемой записью:
В UpdateOrder вы просто изменяете свойства заказа. В CancelOrder вы явно сообщаете о намерении отменить заказ и указываете причину. Это выражает поведение и бизнес-намерение.
Вопрос, который вы должны задать себе: «Сообщает ли выполняемая мной операция бизнес-намерение?»
В этом примере цель в отмене заказа. Как только эта цель становится чётко выраженной, можно начать задавать полезные вопросы:
- Можно ли отменить заказ, учитывая, как давно он был размещён?
- Был ли уже зарезервирован товар на складе?
- Был ли заказ отправлен?
- Нужен ли клиенту возврат средств?
Эти вопросы вытекают из цели операции. Когда всё рассматривается как обновление, эта цель теряется. Также теряется естественное место, где должны существовать бизнес-правила, проверка и решения по рабочим процессам.
Окончание следует…
Источник: https://codeopinion.com/5-software-architecture-mistakes/
5 Архитектурных Ошибок, Которые Затрудняют Изменение Системы. Начало
Вы можете следовать любым лучшим практикам, использовать чистую архитектуру, event-sourcing, микросервисы или что-либо ещё популярное. В итоге вы всё равно оказываетесь в той же ситуации. У вас система, которую очень сложно изменить. Когда вы всё-таки вносите изменения, вы боитесь что-нибудь сломать. В конце концов всё чаще возникает мысль: «Лучше бы это переписать».
Кодовая база может даже выглядеть не так уж плохо. Она может быть организованной и относительно простой для понимания. Но почему-то очень трудно что-то изменить. Причина, вероятно, кроется в одной из 5 архитектурных ошибок. В каждом случае вы принимаете дорогостоящее решение, прежде чем по-настоящему поймёте бизнес или проблемы, которые пытаетесь решить. Вопрос не в том, какую архитектуру использовать? Вопрос должен звучать: «Что мы понимаем о проблеме, что оправдывает архитектурное решение, которое мы собираемся принять?»
1. Выбор архитектуры до понимания предметной области
Если в начале обсуждения проекта речь идёт о необходимости микросервисов, событийного моделирования, CQRS, Kafka и Kubernetes, но никто не может объяснить бизнес-процессы или рабочие процессы, вы делаете всё наоборот.
Может кто-нибудь объяснить ограничения? Какие проблемы с согласованностью могут возникнуть? Какие возможные режимы отказов? Какие части системы часто меняются? Где допустимы задержки?
Архитектурные шаблоны и инструменты сопряжены с компромиссами и дополнительной сложностью:
- Нужна независимая развёртываемость? Теперь у вас распределённые операции.
- Масштабировать части системы независимо? Придётся бороться со сбоями в сети.
- Автономия команды? Потребуется межсервисное и межкомандное взаимодействие.
- Изоляция между границами? Получите проблемы согласованности между этими границами.
Всегда есть компромисс. У каждого решения есть своя цена. Основное внимание следует уделить факторам, влияющим на вашу систему, и проблемам, которые вы пытаетесь решить.
Есть ли проблемы с согласованностью? Есть ли в системе часть, где правила быстро меняются и которую следует изолировать? Содержит ли рабочий процесс задержки, которые необходимо учитывать в последующих процессах?
Если вы сначала принимаете технические решения, вы лишь предполагаете, что когда-нибудь столкнётесь с проблемой, которую эти решения должны решить.
2. Создание сервисов сущностей, управляемых CRUD-операциями
При этом система полностью управляется своей моделью данных, а не поведением. Код может выглядеть отлично, быть хорошо организован. Но сущности на самом деле представляют собой просто таблицы или графы объектов, отображающие клиентов, заказы, товары и т.п.
Вот простой заказ:
public class Order
{
public Guid Id { get; set; }
public Guid CustomerId { get; set; }
public string Status { get; set; }
public decimal Total { get; set; }
}
Что нам говорит эта модель? Что какие-то данные существуют. Она ничего не говорит нам о том, как ведёт себя заказ. Можно ли его отменить? Возможно да, но как? Просто изменить статус? Когда заказ может быть отправлен? Должен ли он находиться в определённом статусе? Можно ли изменить статус после того, как товар зарезервирован? Модель предоставляет нам только данные. Вся бизнес-логика при этом обычно разбросана повсюду: в контроллерах, обработчиках сообщений, хранимых процедурах или даже во фронтэнде.
Сами по себе анемичные модель не плохи. В системе всегда есть части, которые ими по сути являются. Проблема в том, когда всё становится анемичными моделями, а разработка сводится к операциям над обновляемой записью:
UpdateOrder(order);
// или
CancelOrder(order, reason);
В UpdateOrder вы просто изменяете свойства заказа. В CancelOrder вы явно сообщаете о намерении отменить заказ и указываете причину. Это выражает поведение и бизнес-намерение.
Вопрос, который вы должны задать себе: «Сообщает ли выполняемая мной операция бизнес-намерение?»
В этом примере цель в отмене заказа. Как только эта цель становится чётко выраженной, можно начать задавать полезные вопросы:
- Можно ли отменить заказ, учитывая, как давно он был размещён?
- Был ли уже зарезервирован товар на складе?
- Был ли заказ отправлен?
- Нужен ли клиенту возврат средств?
Эти вопросы вытекают из цели операции. Когда всё рассматривается как обновление, эта цель теряется. Также теряется естественное место, где должны существовать бизнес-правила, проверка и решения по рабочим процессам.
Окончание следует…
Источник: https://codeopinion.com/5-software-architecture-mistakes/
👍9
День 2746. #ЗаметкиНаПолях #Architecture
5 Архитектурных Ошибок, Которые Затрудняют Изменение Системы. Окончание
Начало
3. Использование схемы БД в качестве точки интеграции
Представьте две части системы: отдел продаж и склад. Они используют один экземпляр БД, но база в некоторой степени разделена на данные, относящиеся к продажам, и данные, относящиеся к складу. Отдел продаж взаимодействует с данными, которыми он владеет. Но также запрашивает данные, которые, как кажется, принадлежат складу. Или хуже – изменяет данные склада. Кому на самом деле принадлежат эти данные?
В этот момент нет реального разделения и чёткого права собственности. Схема БД становится точкой интеграции. Некоторые скажут, что им всё равно. Они хотят использовать БД напрямую. Это может быть нормально, если вы понимаете последствия.
Когда данные записываются, кто контролирует, как они записываются? Кто является владельцем этого изменения? Какие другие части системы могут сломаться?
Одно из решений — предоставить API склада. Этот API становится контрактом, который отдел продаж может использовать для отправки запросов к складу или получения информации.
Представления (view) базы данных также могут быть контрактом. Они могут явно определять, к каким данным разрешён доступ другим частям системы. Совместное использование экземпляра БД не является автоматически проблемой. Разные части системы могут владеть отдельными схемами в рамках одного экземпляра базы.
Проблема в отсутствии права собственности. Когда БД доступна для всех, и что угодно может читать или изменять любые данные, в итоге начинают возникать проблемы. Вдруг заказ перешёл в недопустимое состояние. Как? Понятия не имеем. Что угодно могло его изменить. Обновление могло не пройти через необходимый процесс, бизнес-правила и проверку, требуемые для корректного перехода в новое состояние, потому что никто явно не отвечал за процесс его изменения.
4. Создание абстракций без понимания, что именно меняется
Обычно все начинается с разумной идеи: «Возможно, нам понадобится что-то заменить позже». Вы начинаете с общего сервиса. Затем получается универсальный рабочий процесс. «А что, если придётся заменить БД или брокер сообщений?» - создаётся абстракция и вокруг них. Только после всего этого вы начинаете создавать само приложение.
Звучит логично. Нас всех учат ценить повторное использование. Также постоянно возникает вопрос: «А что, если…?».
Проблема в том, что если у вас только одна реализация создаваемой абстракции, то, вероятно, у вас нет и самой абстракции. У вас недостаточно информации. Вы не понимаете другие конкретные реализации, где они пересекаются, а где различаются. Вы пытаетесь обобщить что-то, имея только один пример.
RabbitMQ и Azure Service Bus имеют существенные различия. Kafka — ещё больше отличий. Все они могут казаться связанными с отправкой и получением сообщений, но у них разная семантика. Если вы построите абстракцию вокруг одной, эта абстракция будет полностью сформирована единственной известной вам реализацией. Вы не устранили зависимость. Вы скрыли её за интерфейсом.
Плохая абстракция притворяется, что устраняет зависимость, хотя в реальности затрудняет понимание системы, т.к. разработчикам теперь нужно понимать как абстракцию, так и конкретную технологию, скрытую за ней. Создавайте абстракции, основываясь на реальных, понятных вам вариациях, а не на тех, которые, как вам кажется, будут существовать в будущем.
5. Создание системы с учетом масштабируемости, которой пока нет
Важное слово — «пока». Т.е. нужно оплатить всю стоимость создания системы такого уровня или типа масштабируемости, о котором вы даже не подозреваете.
Это не значит проектировать систему, которая не сможет масштабироваться. Но не нужно оплачивать всю стоимость авансом, основываясь на гипотетических сценариях:
- У нас могут быть миллионы пользователей.
- Может потребоваться заменить БД.
- В итоге система может стать глобальной.
Что на самом деле означают все эти утверждения? Система должна масштабироваться - это больше пользователей, больше данных, больше транзакций или несколько языков?
Также необходимо понимать, что логические границы и физические границы — это не одно и то же. Определяя логические границы и избегая ненужной их взаимосвязи, вы значительно повышаете свои шансы на масштабирование различных частей системы при необходимости. Поэтому модульный монолит и микросервисы часто масштабируются одинаково. Вы можете определить осмысленные границы, не превращая сразу каждую границу в отдельно развёртываемый сервис. Начните с границ. Определитесь с моделью физического развёртывания, когда у вас будет достаточно информации, чтобы это оправдать.
Итого
В каждом случае проблема одна и та же. Вы принимаете дорогостоящее решение, не имея достаточной информации. Архитектура не должна начинаться с шаблонов, фреймворков или инфраструктуры. Она должна начинаться с понимания бизнеса, рабочих процессов, ограничений и реальных проблем, которые система должна решать. Тогда вы сможете принять подходящее архитектурное решение.
Источник: https://codeopinion.com/5-software-architecture-mistakes/
5 Архитектурных Ошибок, Которые Затрудняют Изменение Системы. Окончание
Начало
3. Использование схемы БД в качестве точки интеграции
Представьте две части системы: отдел продаж и склад. Они используют один экземпляр БД, но база в некоторой степени разделена на данные, относящиеся к продажам, и данные, относящиеся к складу. Отдел продаж взаимодействует с данными, которыми он владеет. Но также запрашивает данные, которые, как кажется, принадлежат складу. Или хуже – изменяет данные склада. Кому на самом деле принадлежат эти данные?
В этот момент нет реального разделения и чёткого права собственности. Схема БД становится точкой интеграции. Некоторые скажут, что им всё равно. Они хотят использовать БД напрямую. Это может быть нормально, если вы понимаете последствия.
Когда данные записываются, кто контролирует, как они записываются? Кто является владельцем этого изменения? Какие другие части системы могут сломаться?
Одно из решений — предоставить API склада. Этот API становится контрактом, который отдел продаж может использовать для отправки запросов к складу или получения информации.
Представления (view) базы данных также могут быть контрактом. Они могут явно определять, к каким данным разрешён доступ другим частям системы. Совместное использование экземпляра БД не является автоматически проблемой. Разные части системы могут владеть отдельными схемами в рамках одного экземпляра базы.
Проблема в отсутствии права собственности. Когда БД доступна для всех, и что угодно может читать или изменять любые данные, в итоге начинают возникать проблемы. Вдруг заказ перешёл в недопустимое состояние. Как? Понятия не имеем. Что угодно могло его изменить. Обновление могло не пройти через необходимый процесс, бизнес-правила и проверку, требуемые для корректного перехода в новое состояние, потому что никто явно не отвечал за процесс его изменения.
4. Создание абстракций без понимания, что именно меняется
Обычно все начинается с разумной идеи: «Возможно, нам понадобится что-то заменить позже». Вы начинаете с общего сервиса. Затем получается универсальный рабочий процесс. «А что, если придётся заменить БД или брокер сообщений?» - создаётся абстракция и вокруг них. Только после всего этого вы начинаете создавать само приложение.
Звучит логично. Нас всех учат ценить повторное использование. Также постоянно возникает вопрос: «А что, если…?».
Проблема в том, что если у вас только одна реализация создаваемой абстракции, то, вероятно, у вас нет и самой абстракции. У вас недостаточно информации. Вы не понимаете другие конкретные реализации, где они пересекаются, а где различаются. Вы пытаетесь обобщить что-то, имея только один пример.
RabbitMQ и Azure Service Bus имеют существенные различия. Kafka — ещё больше отличий. Все они могут казаться связанными с отправкой и получением сообщений, но у них разная семантика. Если вы построите абстракцию вокруг одной, эта абстракция будет полностью сформирована единственной известной вам реализацией. Вы не устранили зависимость. Вы скрыли её за интерфейсом.
Плохая абстракция притворяется, что устраняет зависимость, хотя в реальности затрудняет понимание системы, т.к. разработчикам теперь нужно понимать как абстракцию, так и конкретную технологию, скрытую за ней. Создавайте абстракции, основываясь на реальных, понятных вам вариациях, а не на тех, которые, как вам кажется, будут существовать в будущем.
5. Создание системы с учетом масштабируемости, которой пока нет
Важное слово — «пока». Т.е. нужно оплатить всю стоимость создания системы такого уровня или типа масштабируемости, о котором вы даже не подозреваете.
Это не значит проектировать систему, которая не сможет масштабироваться. Но не нужно оплачивать всю стоимость авансом, основываясь на гипотетических сценариях:
- У нас могут быть миллионы пользователей.
- Может потребоваться заменить БД.
- В итоге система может стать глобальной.
Что на самом деле означают все эти утверждения? Система должна масштабироваться - это больше пользователей, больше данных, больше транзакций или несколько языков?
Также необходимо понимать, что логические границы и физические границы — это не одно и то же. Определяя логические границы и избегая ненужной их взаимосвязи, вы значительно повышаете свои шансы на масштабирование различных частей системы при необходимости. Поэтому модульный монолит и микросервисы часто масштабируются одинаково. Вы можете определить осмысленные границы, не превращая сразу каждую границу в отдельно развёртываемый сервис. Начните с границ. Определитесь с моделью физического развёртывания, когда у вас будет достаточно информации, чтобы это оправдать.
Итого
В каждом случае проблема одна и та же. Вы принимаете дорогостоящее решение, не имея достаточной информации. Архитектура не должна начинаться с шаблонов, фреймворков или инфраструктуры. Она должна начинаться с понимания бизнеса, рабочих процессов, ограничений и реальных проблем, которые система должна решать. Тогда вы сможете принять подходящее архитектурное решение.
Источник: https://codeopinion.com/5-software-architecture-mistakes/
👍7
День 2747. #TipsAndTricks #VisualStudio
Загадочный Флажок .dev.localhost
При создании нового проекта ASP.NET Core в Visual Studio есть флажок, который легко пропустить: Use the .dev.localhost TLD in the application URL (Использовать домен верхнего уровня .dev.localhost в URL приложения). Давайте разберёмся, что он делает.
Проблема
Когда вы работаете над несколькими локальными веб-проектами, все они располагаются по одному и тому же адресу
Есть вторая, менее заметная проблема: поскольку все используют имя
Что такое .dev.localhost?
Начиная с .NET 10, ASP.NET Core развивает эту идею и добавляет полноценную поддержку поддомена
вы получите:
Для этого и флажок. Настройка также доступна из командной строки, если вы не используете мастер Visual Studio:
Примечание: Kestrel распознаёт адреса
Зачем?
- Вы сможете различать свои приложения. Название проекта отображается прямо в адресной строке, а не в номере порта, который вам нужно запоминать.
- Никаких конфликтов кук и хранилищ. Поскольку каждое приложение получает свой собственный поддомен, хранилище браузера, привязанное к домену, больше не является общим для всех ваших локальных проектов.
- HTTPS по-прежнему работает «из коробки». Сертификат разработчика ASP.NET Core уже указывает
- Ничего не сломается. Kestrel продолжает прослушивать обычный
Единственная загвоздка: Safari
Safari на macOS не разрешает имена
То же самое относится и к клиентам, не являющимся браузерами. Некоторые HTTP-клиенты и инструменты разрешают имена
Источник: https://bartwullems.blogspot.com/2026/07/the-mysterious-devlocalhost-checkbox.html
Загадочный Флажок .dev.localhost
При создании нового проекта ASP.NET Core в Visual Studio есть флажок, который легко пропустить: Use the .dev.localhost TLD in the application URL (Использовать домен верхнего уровня .dev.localhost в URL приложения). Давайте разберёмся, что он делает.
Проблема
Когда вы работаете над несколькими локальными веб-проектами, все они располагаются по одному и тому же адресу
localhost. Различить их можно только по номеру порта. Если у вас в работе 3 проекта, то в браузере может быть localhost:5001, localhost:5215, localhost:7099 — и вы не будете знать, какой из них какой, пока не посмотрите на страницу.Есть вторая, менее заметная проблема: поскольку все используют имя
localhost, файлы cookie и другие хранилища браузера, привязанные к домену, также используются всеми вашими локальными приложениями. Это обычно нежелательно при тестировании.Что такое .dev.localhost?
.localhost — это зарезервированный домен верхнего уровня, определённый в RFC2606 и RFC6761 специально для локального тестирования. Современные браузеры уже разрешают всё, что заканчивается на .localhost, напрямую в локальный адрес (127.0.0.1/::1), поэтому myapp.localhost ведет себя точно так же, как localhost, если только вы не отредактировали файл .hosts или настройки DNS.Начиная с .NET 10, ASP.NET Core развивает эту идею и добавляет полноценную поддержку поддомена
.dev.localhost. Шаблоны проектов для ASP.NET Core Empty и Blazor Web App могут объединить имя вашего проекта с этим суффиксом, поэтому вместо:https://localhost:7099
вы получите:
https://myapp.dev.localhost:7099
Для этого и флажок. Настройка также доступна из командной строки, если вы не используете мастер Visual Studio:
dotnet new web -n MyApp --localhost-tld
Примечание: Kestrel распознаёт адреса
.localhost. Когда ваш профиль запуска или ASPNETCORE_URLS указывает на имя .dev.localhost, Kestrel привязывается только к локальному адресу (127.0.0.1/::1), а не ко всем интерфейсам. Он также регистрирует как адрес .localhost, так и обычный localhost при запуске, поэтому оба варианта по-прежнему работают.Зачем?
- Вы сможете различать свои приложения. Название проекта отображается прямо в адресной строке, а не в номере порта, который вам нужно запоминать.
- Никаких конфликтов кук и хранилищ. Поскольку каждое приложение получает свой собственный поддомен, хранилище браузера, привязанное к домену, больше не является общим для всех ваших локальных проектов.
- HTTPS по-прежнему работает «из коробки». Сертификат разработчика ASP.NET Core уже указывает
*.dev.localhost в качестве альтернативного имени субъекта. Вам не нужно ничего дополнительно генерировать — dotnet dev-certs https уже это обеспечивает. Сертификат с подстановочным знаком для *.localhost сам по себе недействителен для домена верхнего уровня, именно поэтому существует поддомен .dev.- Ничего не сломается. Kestrel продолжает прослушивать обычный
localhost, поэтому инструменты или скрипты, которые обращаются к localhost:7099, продолжат работать.Единственная загвоздка: Safari
Safari на macOS не разрешает имена
*.localhost автоматически. Если вы тестируете в Safari, используйте обычный адрес localhost.То же самое относится и к клиентам, не являющимся браузерами. Некоторые HTTP-клиенты и инструменты разрешают имена
.localhost через обычный стек DNS вместо обработки их в особом порядке, и, если ваш DNS не знает, что с этим делать, запрос просто завершится неудачей. В таких случаях продолжайте использовать обычный localhost.Источник: https://bartwullems.blogspot.com/2026/07/the-mysterious-devlocalhost-checkbox.html
👍12👎1
День 2748. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
42. Шаблоны взаимодействия в микросервисах
«Можете ли вы рассказать о различных шаблонах взаимодействия, используемых в микросервисной архитектуре, и как бы вы реализовали их в приложении .NET?»
Хороший ответ
Эффективная коммуникация между микросервисами имеет решающее значение для успеха архитектуры. Существует несколько распространённых шаблонов коммуникации, каждый из которых подходит для разных сценариев:
- HTTP/REST/gRPC: наиболее распространённый метод синхронной коммуникации, при котором сервисы используют HTTP-запросы для связи. Он прост и не имеет состояния.
- Очереди сообщений: используются для децентрализованной, надёжной асинхронной коммуникации. Они помогают справляться с пиковыми нагрузками и обеспечивают механизм, гарантирующий, что данные не будут потеряны при передаче.
- Событийно-ориентированная модель «публикация/подписка»: эта модель усиливает децентрализацию сервисов, позволяя сервисам подписываться на определённые события, не зная источника этих событий.
Преимущества
- Децентрализация: сервисы не зависят друг от друга напрямую, что повышает отказоустойчивость и масштабируемость.
- Масштабируемость: асинхронные и событийно-ориентированные подходы позволяют системам эффективно обрабатывать изменяющиеся нагрузки.
- Надёжность: очереди сообщений гарантируют доставку сообщений даже если части системы выходят из строя.
В таких реализациях крайне важно обрабатывать сбои, повторные попытки и идемпотентность, особенно в асинхронных сценариях, чтобы обеспечить надёжность и согласованность системы.
Часто встречающийся ошибочный ответ
«Для связи между микросервисами используются HTTP-запросы. Это просто и гарантирует, что сервисы могут общаться в режиме реального времени».
Почему это неправильно
- Чрезмерная зависимость от синхронной связи: этот подход игнорирует преимущества асинхронных моделей связи. Хотя HTTP прост и эффективен для определённых сценариев, он может создавать тесную взаимосвязь и плохо масштабируется при высокой нагрузке или в сложных системах.
- Игнорирование преимуществ очередей сообщений: не используя очереди сообщений или архитектуры, управляемые событиями, ответ упускает из виду устойчивость, которую предлагают эти модели, особенно с точки зрения надёжности, слабой связанности и асинхронной обработки.
- Риск системных сбоев: упор исключительно на HTTP-запросы может привести к сбоям, если какая-либо отдельная часть системы станет недоступной, что повлияет на доступность всей системы.
Эта ошибка часто возникает из-за недостаточного понимания лучших архитектурных практик в микросервисах или чрезмерного упрощения потребностей в коммуникации в распределённых системах. Это отражает необходимость более глубокого знания различных стратегий коммуникации и соответствующих сценариев их использования.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
42. Шаблоны взаимодействия в микросервисах
«Можете ли вы рассказать о различных шаблонах взаимодействия, используемых в микросервисной архитектуре, и как бы вы реализовали их в приложении .NET?»
Хороший ответ
Эффективная коммуникация между микросервисами имеет решающее значение для успеха архитектуры. Существует несколько распространённых шаблонов коммуникации, каждый из которых подходит для разных сценариев:
- HTTP/REST/gRPC: наиболее распространённый метод синхронной коммуникации, при котором сервисы используют HTTP-запросы для связи. Он прост и не имеет состояния.
- Очереди сообщений: используются для децентрализованной, надёжной асинхронной коммуникации. Они помогают справляться с пиковыми нагрузками и обеспечивают механизм, гарантирующий, что данные не будут потеряны при передаче.
- Событийно-ориентированная модель «публикация/подписка»: эта модель усиливает децентрализацию сервисов, позволяя сервисам подписываться на определённые события, не зная источника этих событий.
Преимущества
- Децентрализация: сервисы не зависят друг от друга напрямую, что повышает отказоустойчивость и масштабируемость.
- Масштабируемость: асинхронные и событийно-ориентированные подходы позволяют системам эффективно обрабатывать изменяющиеся нагрузки.
- Надёжность: очереди сообщений гарантируют доставку сообщений даже если части системы выходят из строя.
В таких реализациях крайне важно обрабатывать сбои, повторные попытки и идемпотентность, особенно в асинхронных сценариях, чтобы обеспечить надёжность и согласованность системы.
Часто встречающийся ошибочный ответ
«Для связи между микросервисами используются HTTP-запросы. Это просто и гарантирует, что сервисы могут общаться в режиме реального времени».
Почему это неправильно
- Чрезмерная зависимость от синхронной связи: этот подход игнорирует преимущества асинхронных моделей связи. Хотя HTTP прост и эффективен для определённых сценариев, он может создавать тесную взаимосвязь и плохо масштабируется при высокой нагрузке или в сложных системах.
- Игнорирование преимуществ очередей сообщений: не используя очереди сообщений или архитектуры, управляемые событиями, ответ упускает из виду устойчивость, которую предлагают эти модели, особенно с точки зрения надёжности, слабой связанности и асинхронной обработки.
- Риск системных сбоев: упор исключительно на HTTP-запросы может привести к сбоям, если какая-либо отдельная часть системы станет недоступной, что повлияет на доступность всей системы.
Эта ошибка часто возникает из-за недостаточного понимания лучших архитектурных практик в микросервисах или чрезмерного упрощения потребностей в коммуникации в распределённых системах. Это отражает необходимость более глубокого знания различных стратегий коммуникации и соответствующих сценариев их использования.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍3👎3
День 2749. #BestPractices #SQL
Как Оптимизировать SQL-запросы. Часть 1
Медленный SQL-запрос — один из самых простых способов испортить быстрое приложение. У вас может быть чистая архитектура, отличный кэш и мощный сервер — и всё равно страница может зависать из-за того, что один запрос сканирует миллион строк без индекса. Большинство успехов достигается за счёт одного и того же небольшого набора методов, применяемых снова и снова. Некоторые из них очевидны. Некоторые противоречат советам, которые вы, вероятно, уже слышали. Советы можно условно разделить на 6 групп.
Замечание: здесь мы рассматриваем PostgreSQL. Те же принципы применимы и к другим БД, хотя точный синтаксис может отличаться.
Группа I. Написание запросов, удобных для индексации
Индекс полезен только в том случае, если ваш запрос позволяет БД его использовать.
1. Разумно используйте индексы
Индексы — самый мощный метод повышения производительности чтения. Это отсортированная структура данных, которая позволяет БД находить строки, не сканируя всю таблицу, подобно тому, как оглавление книги избавляет вас от необходимости пролистывать все страницы.
Создавайте индексы по столбцам, по которым вы чаще всего выполняете фильтрацию, соединение, сортировку и группировку — столбцам в
Этот индекс ускоряет запросы, фильтрующие по статусу, а также по статусу и дате заказа. Порядок столбцов имеет значение: индекс по
Вы также можете создать покрывающий индекс, который хранит дополнительные значения столбцов внутри индекса. Это полезно для небольших частых запросов на поиск, когда запросу нужны только столбцы, доступные в индексе, чтобы БД могла избежать чтения фактических строк таблицы:
Замечание: индексы не бесплатны. Каждый индекс необходимо обновлять при каждой вставке, обновлении и удалении, и это занимает место на диске. Индексируйте столбцы, которые фактически используются вашими запросами, а не каждый столбец.
2. Избегайте функций в WHERE
Обёртывание столбца в функцию — один из наиболее распространённых способов случайно отключить индекс. Когда вы вызываете функцию для столбца, БД должна вычислить значение функции для каждой строки, прежде чем сможет сравнить его, поэтому она не может использовать индекс для исходного столбца:
Перепишите условие так, чтобы оно сравнивало исходный столбец с диапазоном:
Оба запроса возвращают одни и те же строки, но только второй может использовать индекс по
То же правило применяется к
3. Избегайте символов подстановки в начале запроса LIKE
Шаблон LIKE, начинающийся с символа подстановки, не может использовать обычный индекс. БД считывает индекс слева направо, поэтому ей необходимо знать начало значения. Шаблон типа
Если вам действительно нужно выполнить contains-поиск в тексте, используйте полнотекстовый поиск или триграммный индекс (расширение pg_trgm в PostgreSQL), созданный специально для этой задачи.
4. Точное соответствие типов данных
Сравнение двух разных типов данных заставляет БД преобразовывать один из них, и это преобразование может незаметно отключить индекс.
Если столбец является целым числом, но вы сравниваете его со строкой, или соединяете int-ключ с bigint-ключом, БД добавляет неявное приведение типов — и индекс по исходному столбцу может быть пропущен. Сохраняйте одинаковые типы с обеих сторон каждого соединения и фильтрации:
Определите
Продолжение следует…
https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
Как Оптимизировать SQL-запросы. Часть 1
Медленный SQL-запрос — один из самых простых способов испортить быстрое приложение. У вас может быть чистая архитектура, отличный кэш и мощный сервер — и всё равно страница может зависать из-за того, что один запрос сканирует миллион строк без индекса. Большинство успехов достигается за счёт одного и того же небольшого набора методов, применяемых снова и снова. Некоторые из них очевидны. Некоторые противоречат советам, которые вы, вероятно, уже слышали. Советы можно условно разделить на 6 групп.
Замечание: здесь мы рассматриваем PostgreSQL. Те же принципы применимы и к другим БД, хотя точный синтаксис может отличаться.
Группа I. Написание запросов, удобных для индексации
Индекс полезен только в том случае, если ваш запрос позволяет БД его использовать.
1. Разумно используйте индексы
Индексы — самый мощный метод повышения производительности чтения. Это отсортированная структура данных, которая позволяет БД находить строки, не сканируя всю таблицу, подобно тому, как оглавление книги избавляет вас от необходимости пролистывать все страницы.
Создавайте индексы по столбцам, по которым вы чаще всего выполняете фильтрацию, соединение, сортировку и группировку — столбцам в
WHERE, JOIN, ORDER BY и GROUP BY. Когда используется несколько столбцов одновременно, один составной индекс, охватывающий их, намного лучше, чем отдельные индексы по одному столбцу:-- Составной индекс для частой фильтрации по статусу и дате
CREATE INDEX idx_orders_status_order_date
ON orders (status, order_date);
Этот индекс ускоряет запросы, фильтрующие по статусу, а также по статусу и дате заказа. Порядок столбцов имеет значение: индекс по
(status, order_date) помогает запросам, которые сначала фильтруют по статусу, но не запросам, которые фильтруют только по дате заказа.Вы также можете создать покрывающий индекс, который хранит дополнительные значения столбцов внутри индекса. Это полезно для небольших частых запросов на поиск, когда запросу нужны только столбцы, доступные в индексе, чтобы БД могла избежать чтения фактических строк таблицы:
-- Добавляем часто читаемые данные
CREATE INDEX idx_orders_status_order_date_covering
ON orders (status, order_date)
INCLUDE (customer_id, total_amount);
SELECT customer_id, total_amount
FROM orders
WHERE status = 'paid'
AND order_date >= DATE '2026-01-01';
Замечание: индексы не бесплатны. Каждый индекс необходимо обновлять при каждой вставке, обновлении и удалении, и это занимает место на диске. Индексируйте столбцы, которые фактически используются вашими запросами, а не каждый столбец.
2. Избегайте функций в WHERE
Обёртывание столбца в функцию — один из наиболее распространённых способов случайно отключить индекс. Когда вы вызываете функцию для столбца, БД должна вычислить значение функции для каждой строки, прежде чем сможет сравнить его, поэтому она не может использовать индекс для исходного столбца:
-- Плохо: функция по order_date отключает сканирование по индексу
SELECT * FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2025;
Перепишите условие так, чтобы оно сравнивало исходный столбец с диапазоном:
-- Хорошо: диапазон по чистому значению столбца
SELECT * FROM orders
WHERE order_date >= '2025-01-01'
AND order_date < '2026-01-01';
Оба запроса возвращают одни и те же строки, но только второй может использовать индекс по
order_date.То же правило применяется к
LOWER(email), CAST(…) и арифметическим операциям по столбцу. Если вам часто нужно фильтровать по вычисляемому значению, создайте вместо этого функциональный индекс для этого конкретного выражения.3. Избегайте символов подстановки в начале запроса LIKE
Шаблон LIKE, начинающийся с символа подстановки, не может использовать обычный индекс. БД считывает индекс слева направо, поэтому ей необходимо знать начало значения. Шаблон типа
'%son' скрывает начало и заставляет выполнять полное сканирование:-- Плохо: полное сканирование таблицы
SELECT * FROM customers
WHERE last_name LIKE '%son';
-- Хорошо: сканирование индекса по известному префиксу
SELECT * FROM customers
WHERE last_name LIKE 'Anders%';
Если вам действительно нужно выполнить contains-поиск в тексте, используйте полнотекстовый поиск или триграммный индекс (расширение pg_trgm в PostgreSQL), созданный специально для этой задачи.
4. Точное соответствие типов данных
Сравнение двух разных типов данных заставляет БД преобразовывать один из них, и это преобразование может незаметно отключить индекс.
Если столбец является целым числом, но вы сравниваете его со строкой, или соединяете int-ключ с bigint-ключом, БД добавляет неявное приведение типов — и индекс по исходному столбцу может быть пропущен. Сохраняйте одинаковые типы с обеих сторон каждого соединения и фильтрации:
CREATE TABLE logs (
log_id int PRIMARY KEY,
event_date timestamptz NOT NULL,
user_id int NOT NULL
);
Определите
logs.user_id так, чтобы он соответствовал типу users.id, и тогда соединение будет использовать индекс с обеих сторон. Продолжение следует…
https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
👍10
День 2750. #BestPractices #SQL
Как оптимизировать SQL-запросы. Часть 2
Часть 1
Часть II. Извлекайте только необходимые данные
Быстрее всего обрабатываются данные, которые вы не читаете. Каждый столбец и каждая строка, которые вы извлекаете, требуют операций ввода-вывода на диске, памяти и сетевого времени. Следующие методы позволяют максимально сократить результирующий набор данных на ранней стадии.
1. Прекратите использовать SELECT *
Явное указание столбцов также безопаснее. Ваш запрос не изменит свою форму и не сломается незаметно, когда кто-то добавит или изменит порядок столбцов.
2. Фильтрация на ранних этапах
Чем меньше набор данных, с которым вы работаете, тем быстрее происходит обработка данных на последующих этапах. Возможно, вы слышали распространённый совет: «Применяйте наиболее избирательные фильтры в начале, чтобы сократить количество строк до того, как их обработают соединения и агрегирования».
Замечание: в большинстве случаев не нужно размещать эти фильтры вручную. Современный стоимостной планировщик сам размещает предикаты в
3. Keyset-пагинация вместо OFFSET, когда возможно
Стоимость каждой страницы одинакова, будь то страница 2 или страница 2000, поскольку индекс переходит непосредственно к
Компромисс: keyset-пагинация обеспечивает быструю навигацию по следующей и предыдущей страницам, но вы не можете реализовать случайные переходы к любому номеру страницы.
4. Запрашивайте только то, что изменилось
Повторное чтение всей таблицы при каждом запуске неэффективно, если изменилось всего несколько строк. Отслеживайте «водяной знак» — последнюю обработанную точку — и извлекайте только строки, более новые, чем он:
Это превращает полное сканирование таблицы в небольшое чтение диапазона с помощью индекса. Индексируйте
Продолжение следует…
Источник: https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
Как оптимизировать SQL-запросы. Часть 2
Часть 1
Часть II. Извлекайте только необходимые данные
Быстрее всего обрабатываются данные, которые вы не читаете. Каждый столбец и каждая строка, которые вы извлекаете, требуют операций ввода-вывода на диске, памяти и сетевого времени. Следующие методы позволяют максимально сократить результирующий набор данных на ранней стадии.
1. Прекратите использовать SELECT *
SELECT * извлекает все столбцы, включая те, которые вам не нужны. Это означает больше данных для чтения с диска, больше данных для передачи по сети и больше памяти для хранения — всё это для столбцов, которые ваш код игнорирует. Это также блокирует покрывающие индексы, когда индекс сам по себе может ответить на запрос, не затрагивая таблицу:-- Плохо: извлечение всех столбцов
SELECT * FROM customers;
-- Хорошо: только используемые столбцы
SELECT customer_id, first_name, last_name
FROM customers;
Явное указание столбцов также безопаснее. Ваш запрос не изменит свою форму и не сломается незаметно, когда кто-то добавит или изменит порядок столбцов.
2. Фильтрация на ранних этапах
Чем меньше набор данных, с которым вы работаете, тем быстрее происходит обработка данных на последующих этапах. Возможно, вы слышали распространённый совет: «Применяйте наиболее избирательные фильтры в начале, чтобы сократить количество строк до того, как их обработают соединения и агрегирования».
SELECT o.order_id, o.total
FROM orders o
WHERE o.completed = true
AND o.order_date >= '2026-01-01'
AND o.order_date < '2026-02-01';
Замечание: в большинстве случаев не нужно размещать эти фильтры вручную. Современный стоимостной планировщик сам размещает предикаты в
WHERE как можно раньше и самостоятельно переупорядочивает соединения. Изменение порядка в тексте предложения WHERE редко меняет план. На самом деле помогает предоставление планировщику селективного фильтра и индекса для его применения. Поэтому сосредоточьтесь на том, чтобы сделать фильтр удобным для индекса, а не на том, где он в запросе.3. Keyset-пагинация вместо OFFSET, когда возможно
OFFSET кажется простым способом реализации пагинации, но чем глубже вы углубляетесь, тем медленнее она становится. Чтобы вернуть OFFSET 100000, БД прочитает и отбросит 100 000 строк перед ней. Keyset-пагинация (также называемая поисковой пагинацией) запоминает последнее увиденное значение и переходит непосредственно за него:-- Keyset-пагинация по индексированному столбцу
SELECT * FROM orders
WHERE order_id > 1000
ORDER BY order_id
LIMIT 10;
Стоимость каждой страницы одинакова, будь то страница 2 или страница 2000, поскольку индекс переходит непосредственно к
order_id > 1000.Компромисс: keyset-пагинация обеспечивает быструю навигацию по следующей и предыдущей страницам, но вы не можете реализовать случайные переходы к любому номеру страницы.
4. Запрашивайте только то, что изменилось
Повторное чтение всей таблицы при каждом запуске неэффективно, если изменилось всего несколько строк. Отслеживайте «водяной знак» — последнюю обработанную точку — и извлекайте только строки, более новые, чем он:
-- Читаем только записи, обновлённые с последнего запуска
SELECT * FROM records
WHERE modified_date > '2026-08-10 00:00';
Это превращает полное сканирование таблицы в небольшое чтение диапазона с помощью индекса. Индексируйте
modified_date, сохраняйте новую точку последнего изменения после каждого запуска, и ваша задача синхронизации останется быстрой даже при росте таблицы.Продолжение следует…
Источник: https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
👍2
День 2751. #BestPractices #SQL
Как оптимизировать SQL-запросы. Часть 3
Часть 1
Часть 2
Часть III. Упростите операции соединения и подзапросы
Это место, где запросы становятся дорогостоящими и где скрываются самые большие возможности улучшения. Цель в том, чтобы заставить БД выполнять меньше работы и представить эту работу в форме, с которой она лучше всего справляется.
1. Уменьшайте сложность операций соединения
Каждая операция соединения — это дополнительная работа. Чем меньше таблиц базе нужно coединить, тем быстрее запрос. Распространённая ошибка — соединение таблицы, из которой вы фактически не читаете данные:
Удалите ненужное соединение:
Прочитайте столбцы в
2. Выбирайте правильный тип соединения
Тип соединения влияет как на результат, так и на стоимость. Используйте
3. Заменяйте избыточные подзапросы соединениями (JOIN) или CTE
Подзапрос выполняется для каждой строки, что может быть крайне неэффективно при работе с большим набором результатов. В данном случае подзапрос выполняется для каждого заказа, просто чтобы найти имя клиента:
Простое соединение выполнит ту же работу один раз:
Когда один и тот же подзапрос требуется несколько раз в одном запросе, преобразуйте его в общее табличное выражение (CTE) с помощью оператора
А вот более быстрый запрос, использующий CTE:
4. EXISTS лучше, чем IN
EXISTS может привести к прерыванию обработки: он останавливается на первой совпадающей строке, в то время как IN может сначала сформировать полный список значений:
Примечание: не следует воспринимать «
Продолжение следует…
Источник: https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
Как оптимизировать SQL-запросы. Часть 3
Часть 1
Часть 2
Часть III. Упростите операции соединения и подзапросы
Это место, где запросы становятся дорогостоящими и где скрываются самые большие возможности улучшения. Цель в том, чтобы заставить БД выполнять меньше работы и представить эту работу в форме, с которой она лучше всего справляется.
1. Уменьшайте сложность операций соединения
Каждая операция соединения — это дополнительная работа. Чем меньше таблиц базе нужно coединить, тем быстрее запрос. Распространённая ошибка — соединение таблицы, из которой вы фактически не читаете данные:
-- Плохо: соединение с suppliers, которая не используется
SELECT p.product_name, c.category_name
FROM products p
JOIN categories c ON p.category_id = c.category_id
JOIN suppliers s ON p.supplier_id = s.supplier_id;
Удалите ненужное соединение:
SELECT p.product_name, c.category_name
FROM products p
JOIN categories c ON p.category_id = c.category_id;
Прочитайте столбцы в
SELECT и WHERE и удалите все соединения с таблицами, на которые нет ссылок.2. Выбирайте правильный тип соединения
Тип соединения влияет как на результат, так и на стоимость. Используйте
INNER JOIN, когда нужны совпадающие строки с обеих сторон, LEFT JOIN только тогда, когда действительно нужны и несовпадающие строки, и EXISTS, когда просто нужно узнать, существует ли совпадение:-- Проверка существования: EXISTS останавливается на первом совпадении
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
AND o.total > 100
);
LEFT JOIN часто используется там, где подошло бы INNER JOIN — оно заставляет базу сохранять несовпадающие строки, которые будут проигнорированы позже.3. Заменяйте избыточные подзапросы соединениями (JOIN) или CTE
Подзапрос выполняется для каждой строки, что может быть крайне неэффективно при работе с большим набором результатов. В данном случае подзапрос выполняется для каждого заказа, просто чтобы найти имя клиента:
-- Плохо: подзапрос на каждую строку
SELECT o.order_id,
(SELECT c.name FROM customers c
WHERE c.customer_id = o.customer_id) AS customer_name
FROM orders o;
Простое соединение выполнит ту же работу один раз:
-- Хорошо: одно соединение
SELECT o.order_id, c.name AS customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
Когда один и тот же подзапрос требуется несколько раз в одном запросе, преобразуйте его в общее табличное выражение (CTE) с помощью оператора
WITH, чтобы он был написан один раз и его было легче читать. Вот пример медленного запроса:-- Плохо: подзапрос повторяется
SELECT
c.customer_id,
c.name,
(
SELECT SUM(o.total_amount)
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date >= DATE '2026-01-01'
) AS total_spent,
(
SELECT COUNT(*)
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date >= DATE '2026-01-01'
) AS order_count
FROM customers c;
А вот более быстрый запрос, использующий CTE:
-- Хорошо: считаем результат один раз и переиспользуем
WITH customer_order_totals AS (
SELECT
customer_id,
SUM(total_amount) AS total_spent,
COUNT(*) AS order_count
FROM orders
WHERE order_date >= DATE '2026-01-01'
GROUP BY customer_id
)
SELECT
c.customer_id, c.name,
COALESCE(t.total_spent, 0) AS total_spent,
COALESCE(t.order_count, 0) AS order_count
FROM customers c
LEFT JOIN customer_order_totals t ON t.customer_id = c.customer_id;
4. EXISTS лучше, чем IN
EXISTS может привести к прерыванию обработки: он останавливается на первой совпадающей строке, в то время как IN может сначала сформировать полный список значений:
SELECT p.product_id, p.product_name
FROM products p
WHERE EXISTS (
SELECT 1 FROM order_details od
WHERE od.product_id = p.product_id
);
Примечание: не следует воспринимать «
EXISTS всегда лучше IN» как жёсткое правило. В современных версиях PostgreSQL запросы IN, EXISTS и даже некоторые соединения часто переписываются в один и тот же план выполнения, поэтому они могут работать идентично. Однако проблема всё ещё возникает с NOT IN в подзапросе, который может возвращать NULL — это приводит к неожиданным результатам и худшему плану выполнения, поэтому в этом случае предпочтительнее использовать NOT EXISTS. Как всегда, проверяйте план выполнения, а не гадайте.Продолжение следует…
Источник: https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
День 2752. #BestPractices #SQL
Как оптимизировать SQL-запросы. Части 4-5
Части 1, 2, 3
Часть IV. Разрабатывайте схему для чтения
Некоторые запросы работают медленно, независимо от способа их написания, потому что они постоянно пересчитывают один и тот же ресурсоёмкий результат. Решение заключается в изменении структуры данных.
1. Нормализуйте данные с умом
Нормализация поддерживает чистоту и согласованность данных и является правильным вариантом по умолчанию. Однако полностью нормализованные данные могут медленно читаться, когда часто выполняемый запрос должен соединять и агрегировать одни и те же таблицы при каждом запросе. Для путей с интенсивным чтением допустимо денормализовывать данные: предварительно агрегировать данные и сохранять их.
Теперь чтение представляет собой простой поиск, а не агрегацию в реальном времени по всей таблице
2. Использование материализованных представлений
Материализованное представление физически хранит результат запроса, поэтому чтение обращается к предварительно вычисленным строкам, а не пересчитывает их. Это вариант сводной таблицы, описанной выше:
Запросы к
Часть V. Повышение эффективности операций записи и транзакций
Медленная запись и длительные транзакции вызывают конфликты блокировок, заставляя остальные запросы ждать.
1. Пакетная обработка больших операций
Выполнение оператора для каждой строки приводит к перегрузке БД запросами и транзакционными издержками. Но один оператор, затрагивающий миллионы строк, также представляет проблему — он удерживает блокировки в течение длительного времени и может привести к переполнению журнала предварительной записи (WAL).
Промежуточным решением является пакетная обработка: обработка фиксированного фрагмента за раз. Следующий запрос перемещает строки в архивную таблицу по 1000 за раз, удаляя каждый фрагмент после его копирования:
Запустите его в цикле, пока он не станет затрагивать 0 строк. Каждая партия фиксируется быстро, удерживает мало блокировок и поддерживает отзывчивость системы во время выполнения основной задачи.
2. Сокращайте транзакции
Транзакция удерживает блокировки до момента фиксации, и все другие запросы, которым нужны эти строки, должны ждать. Чем дольше транзакция остаётся открытой, тем больше конкуренции она создаёт. Держите транзакцию открытой только для операций записи, а медленные операции — вызовы API, файловый ввод-вывод, ресурсоёмкие вычисления — выполняйте вне её:
Эта транзакция открывается, выполняет две связанные операции записи и немедленно фиксируется. Никогда не оставляйте транзакцию открытой, ожидая ввода пользователя или ответа по сети.
Окончание следует…
Источник: https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
Как оптимизировать SQL-запросы. Части 4-5
Части 1, 2, 3
Часть IV. Разрабатывайте схему для чтения
Некоторые запросы работают медленно, независимо от способа их написания, потому что они постоянно пересчитывают один и тот же ресурсоёмкий результат. Решение заключается в изменении структуры данных.
1. Нормализуйте данные с умом
Нормализация поддерживает чистоту и согласованность данных и является правильным вариантом по умолчанию. Однако полностью нормализованные данные могут медленно читаться, когда часто выполняемый запрос должен соединять и агрегировать одни и те же таблицы при каждом запросе. Для путей с интенсивным чтением допустимо денормализовывать данные: предварительно агрегировать данные и сохранять их.
-- Сохраняем агрегированные данные в сводной таблице
CREATE TABLE sales_summary AS
SELECT product_id, SUM(quantity) AS total_sold
FROM order_details
GROUP BY product_id;
Теперь чтение представляет собой простой поиск, а не агрегацию в реальном времени по всей таблице
order_details. Компромисс заключается в необходимости синхронизации сводной таблицы — её обновления по расписанию или при изменении исходных данных. Денормализацию следует проводить целенаправленно, для конкретных часто используемых запросов, а не повсеместно.2. Использование материализованных представлений
Материализованное представление физически хранит результат запроса, поэтому чтение обращается к предварительно вычисленным строкам, а не пересчитывает их. Это вариант сводной таблицы, описанной выше:
-- Храним агрегированные данные
CREATE MATERIALIZED VIEW mv_total_sales AS
SELECT product_id, SUM(quantity) AS total_qty
FROM order_details
GROUP BY product_id;
-- Уникальный индекс позволяет представлению обновляться без блокирования чтения
CREATE UNIQUE INDEX idx_mv_total_sales_product
ON mv_total_sales (product_id);
-- Обновление по расписанию; читатели будут получать старые данные до завершения обновления
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_total_sales;
Запросы к
mv_total_sales выполняются быстро, потому что агрегация уже была выполнена. Данные актуальны только на момент последнего обновления, поэтому используйте материализованные представления для ресурсоёмких агрегаций, которые могут допускать небольшое устаревание — панели мониторинга, отчёты и таблицы лидеров.Часть V. Повышение эффективности операций записи и транзакций
Медленная запись и длительные транзакции вызывают конфликты блокировок, заставляя остальные запросы ждать.
1. Пакетная обработка больших операций
Выполнение оператора для каждой строки приводит к перегрузке БД запросами и транзакционными издержками. Но один оператор, затрагивающий миллионы строк, также представляет проблему — он удерживает блокировки в течение длительного времени и может привести к переполнению журнала предварительной записи (WAL).
Промежуточным решением является пакетная обработка: обработка фиксированного фрагмента за раз. Следующий запрос перемещает строки в архивную таблицу по 1000 за раз, удаляя каждый фрагмент после его копирования:
WITH batch AS (
DELETE FROM source_table
WHERE ctid IN (
SELECT ctid
FROM source_table
WHERE processed = false
LIMIT 1000
)
RETURNING col1, col2
)
INSERT INTO archive_table (col1, col2)
SELECT col1, col2 FROM batch;
Запустите его в цикле, пока он не станет затрагивать 0 строк. Каждая партия фиксируется быстро, удерживает мало блокировок и поддерживает отзывчивость системы во время выполнения основной задачи.
2. Сокращайте транзакции
Транзакция удерживает блокировки до момента фиксации, и все другие запросы, которым нужны эти строки, должны ждать. Чем дольше транзакция остаётся открытой, тем больше конкуренции она создаёт. Держите транзакцию открытой только для операций записи, а медленные операции — вызовы API, файловый ввод-вывод, ресурсоёмкие вычисления — выполняйте вне её:
BEGIN;
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
INSERT INTO transactions (account_id, amount)
VALUES (1, -100);
COMMIT;
Эта транзакция открывается, выполняет две связанные операции записи и немедленно фиксируется. Никогда не оставляйте транзакцию открытой, ожидая ввода пользователя или ответа по сети.
Окончание следует…
Источник: https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
👍3