.NET Разработчик
6.72K subscribers
480 photos
4 videos
14 files
2.41K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2729. #ЗаметкиНаПолях
Сравнение Чисел в .NET. Окончание

Начало

+0.0 и -0.0: равны, но не идентичны
В стандарте IEEE 754 используется знаковый ноль. +0.0 и -0.0 сравниваются как равные, поэтому == и Equals возвращают true:
double pz = +0.0;
double nz = -0.0;

Console.WriteLine(pz == nz); // True
Console.WriteLine(pz.Equals(nz)); // True

Однако знак всё же имеет значение для некоторых операций:
Console.WriteLine(1.0 / +0.0); // +Infinity
Console.WriteLine(1.0 / -0.0); // -Infinity

Таким образом, «равенство» не всегда означает «взаимозаменяемость в каждом выражении».

Примечание: значения NaN также имеют знак. Такие методы, как float.IsNegative, возвращают true для -NaN.

CompareTo обеспечивает упорядочивание, включая обработку NaN
Операторы сравнения с NaN намеренно неудобны, поскольку NaN не упорядочен. Для сортировки .NET предоставляет возможность упорядочивания через CompareTo.

Для Half, double и float:
- NaN равно NaN;
- NaN меньше, чем не-NaN.
Это поведение явно закодировано в реализациях CompareTo в исходном коде среды выполнения.

Хэши нормализованы для NaN и знакового нуля
Еще один тонкий, но важный момент: GetHashCode() намеренно канонизирует значения. В методах Half.GetHashCode, Double.GetHashCode и Single.GetHashCode .NET гарантирует, что:
- все шаблоны битов NaN дают одинаковый хэш;
- +0.0 и -0.0 дают одинаковый хэш.
Это необходимо для согласованности с Equals, когда значения используются в качестве ключей.

Особый случай точности: значения, которые кажутся равными, могут быть не равными
Отдельным источником путаницы является представление. Многие десятичные значения не могут быть точно выражены в двоичном представлении с плавающей запятой, поэтому прямое сравнение на равенство часто не работает:
Console.WriteLine(0.1 + 0.2 == 0.3); // False

Тип decimal решает эту проблему, поскольку хранит целое число в десятичной системе счисления, а не дробь в двоичной системе. Такие значения, как 0.1m, 0.2m и 0.3m, точно воспроизводимы, поэтому:
Console.WriteLine(0.1m + 0.2m == 0.3m); // True

Точность decimal составляет от 28 до 29 значащих цифр. Внутри используется 96-битное целое число плюс коэффициент масштабирования (от 0 до 28 десятичных знаков) и знак.

Важные ограничения:
- decimal не является числом с произвольной точностью. Оно по-прежнему имеет конечный диапазон и может переполняться;
- Операции, которые дают более 28–29 значащих цифр, округляются;
- decimal относится к точному десятичному представлению, а не к поведению чисел с плавающей запятой по стандарту IEEE. В нём нет значений NaN или Infinity.

Для числовых алгоритмов сравнивайте с допуском (абсолютным + относительным), а не с Double.Epsilon:
static bool NearlyEqual(
double a,
double b,
double relTol = 1e-12,
double absTol = 1e-15)
{
if (a == b)
return true; // нули

if (double.IsNaN(a) || double.IsNaN(b))
return false;

if (double.IsInfinity(a) || double.IsInfinity(b))
return false;

double diff = Math.Abs(a - b);
double scale = Math.Max(Math.Abs(a), Math.Abs(b));
return diff <= Math.Max(absTol, relTol * scale);
}


Практические рекомендации
Используйте:
- ==, когда явно нужна семантика сравнения IEEE;
- Equals, когда нужна семантика равенства .NET (особенно в коллекциях);
- Half.IsNaN, double.IsNaN или float.IsNaN для проверки на NaN;
- сравнение с погрешностью для вычисленных результатов с плавающей запятой;
- decimal, когда нужны точные десятичные дроби (например, деньги) и когда достаточно 28-29 значащих цифр.

Итого
И ==, и Equals являются корректными. Они отвечают на разные вопросы:
- ==: «Равны ли эти значения согласно правилам сравнения чисел с плавающей запятой IEEE?»
- Equals: «Следует ли считать эти два значения .NET равными для контрактов равенства объектов?»
Как только вы разделите эти два намерения, крайние случаи, связанные с NaN, знаковым нулем и бесконечностью, станут предсказуемыми.

Источник:
https://www.meziantou.net/number-comparison-in-dotnet-equals-and-ieee-754-edge-cases.htm
👍4
День 2730. #Курсы
Курс «Модернизация .NET для Начинающих»

В Microsoft разработали курс, который поможет вам разобраться в процессе модернизации приложения, созданного на устаревшей платформе .NET. Инструментарий модернизации в GitHub Copilot поможет модернизировать ваш код .NET. А курс научит использовать эти инструменты.

https://github.com/microsoft/dotnet-modernization-for-beginners

Этот бесплатный, открытый, практический курс шаг за шагом проведёт через процесс модернизации реального устаревшего приложения ASP.NET до .NET 10 с использованием агента модернизации GitHub Copilot. В процессе обучения вы узнаете, как инструмент создаёт оценки и планы до внесения изменений в код, и как вы можете модифицировать их, чтобы точно указать агенту кодирования, что нужно сделать.

В этом курсе вы не просто будете читать теорию о модернизации с GitHub Copilot, но и получите практические упражнения (потребуется подписка на GitHub Copilot).

Модернизация в GitHub Copilot работает иначе, чем ИИ-агенты, с которыми вы, возможно, работали раньше. Она не просто переписывает ваш код за кулисами и возвращает вам то, что нужно будет править. Она создаёт прозрачные, редактируемые артефакты, которые вы можете читать, задавать по ним вопросы и корректировать: assessment.md, plan.md и tasks.md. Эти файлы становятся вашим руководством и источником истины. Вы можете их просмотреть, отредактировать, и только потом начинается работа. Агент помогает, но вы принимаете решения.

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

Глава 1 – Оценка
Вы начинаете с того, что указываете агенту модернизации на устаревшее решение и позволяете ему проанализировать существующий код. Вы изучаете сгенерированный файл assessment.md, чтобы понять риски, зависимости и уровень необходимых усилий.

Глава 2 – Планирование
Далее вы превращаете эти данные в план. Агент создает файл plan.md с рекомендуемыми целевыми платформами, последовательностью шагов обновления и оценкой трудозатрат. Вы научитесь проверять его и настраивать под свой контекст.

Глава 3 – Обновление и выполнение
Здесь происходит агентская разработка. Агент выполняет план, а вы отслеживаете прогресс в файле tasks.md. Вы увидите, как система выполняет основную работу, пока вы принимаете важные решения.

Глава 4 – Azure
Модернизация не завершена, пока ваше приложение не будет работать там, где ему положено. В заключительной главе вы публикуете модернизированное приложение в Azure App Service и рассматриваете следующие шаги по его эксплуатации и развитию, чтобы завершить процесс созданием работающего приложения, а не просто компилируемого.

Вам потребуется Visual Studio 2022 (17.10 или более поздняя версия) или Visual Studio 2026 с подпиской GitHub Copilot.
Клонируйте репозиторий и начните с введения в главе 0.

Источник: https://devblogs.microsoft.com/dotnet/announcing-dotnet-modernization-for-beginners/
👍3👎1
День 2731. #TipsAndTricks
Добавление Статического Свойства Благодаря Расширениям
Вот простой сценарий: у вас есть StringComparer, и вы хотите добавить свой собственный компаратор — например, порядок «естественной сортировки», чтобы «file2» сортировался перед «file10», а не после.

Начиная с C#14 мы можем расширить семейство компараторов и сделать это естественным!

Пре-C#14
Итак, мы хотим добавить и использовать что-то вроде:
public class NaturalSortComparer : StringComparer
{
public override int Compare(string? x, string? y)
=> …
public override bool Equals(string? x, string? y)
=> …
public override int GetHashCode(string obj)
=> …
}


До C#14 у нас были только методы расширения, поэтому мы могли сделать только что-то такое:
public static class StringComparerExtensions
{
public static StringComparer NaturalSort(
this StringComparer comparer)
=> new NaturalSortComparer();
}

Что было бы странно использовать: StringComparer.NaturalSort();. Если вы посмотрите на аналогичные члены, вроде StringComparer.Ordinal или StringComparer.OrdinalIgnoreCase, то это не совсем то, что мы бы хотели.

В качестве альтернативы мы могли создать отдельный класс:
public static class NaturalSort
{
public static StringComparer Instance { get; }
= new NaturalSortComparer();
}

// Использование:
var files = Directory.GetFiles(path)
.OrderBy(f => f, NaturalSort.Instance);

Тоже выглядит не совсем естественно.

C#14
«Расширенные» (простите за каламбур) расширения в C#14 могут также добавлять статические члены, не только методы, но и свойства. Кроме того, в перечислении CompareOptions был добавлен элемент NumericOrdering для натуральной сортировки. Поэтому мы можем создать такое статическое свойство-расширение:
public static class StringComparerExtensions
{
extension(StringComparer)
{
public static StringComparer NaturalSort =>
StringComparer.Create(
CultureInfo.CurrentCulture,
CompareOptions.NumericOrdering
);
}
}


Теперь мы можем использовать его так, как и хотелось:
var files = Directory.GetFiles(path)
.OrderBy(f => f, StringComparer.NaturalSort);


Источник: https://steven-giesel.com/blogPost/605274d2-719b-4b1f-b0d5-3ad9001d9b56/adding-static-getter-thanks-to-extensions
👍11
День 2732. #ЗаметкиНаПолях
Решаем Проблему Паники в Кэше
В приложении ASP.NET Core API кэшировался довольно ресурсоёмкий отчёт на 60 секунд. При обычной нагрузке всё было нормально: один запрос перестраивал кэш, все остальные читали из него. При пиковой нагрузке десятки запросов поступали одновременно, все видели промах кэша и параллельно выполняли один и тот же ресурсоёмкий запрос. Слой кэширования не помогал. Это называется «паникой в кэше» (cache stampede).

Почему это происходит
IMemoryCache.GetOrCreate (и его асинхронный аналог) сами по себе не добавляют блокировок:
public async Task<Report> GetReportAsync(string key)
{
if (_cache.TryGetValue(key, out Report cached))
return cached;

var report =
await _service.BuildReportAsync(key);

_cache.Set(key, report, TimeSpan.FromSeconds(60));

return report;
}

Если 10 запросов придут одновременно после истечения срока действия кэша, для всех TryGetValue вернёт false, и все они вызовут BuildReportAsync. Это не специфично для IMemoryCache. Распределённые кэши, такие как Redis, имеют ту же проблему. Сам кэш не знает и не заботится о том, что параллельные вызывающие процессы собираются запросить тот же ключ.

Решение 1: блокировка для каждого ключа с помощью SemaphoreSlim
Самое простое решение — заставить одновременные вызывающие процессы для одного и того же ключа ждать завершения первого:
private static readonly 
ConcurrentDictionary<string, SemaphoreSlim> _locks = new();

public async Task<Report> GetReportAsync(string key)
{
if (_cache.TryGetValue(key, out Report cached))
return cached;

var keyLock = _locks.GetOrAdd(key,
_ => new SemaphoreSlim(1, 1));

await keyLock.WaitAsync();
try
{
// Перепроверка: другой запрос мог уже
// задать значение, пока мы ждали семафора
if (_cache.TryGetValue(key, out cached))
return cached;

var report = await _service.BuildReportAsync(key);
_cache.Set(key, report, TimeSpan.FromSeconds(60));
return report;
}
finally
{
keyLock.Release();
}
}

Примечание: ConcurrentDictionary<string, SemaphoreSlim> будет бесконечно расти, если его не чистить. Для небольшого набора ключей это нормально. Иначе - удаляйте неиспользуемые семафоры.

Решение 2: HybridCache сделает это за вас
Microsoft.Extensions.Caching.Hybrid.HybridCache координирует одновременные вызовы для одного и того же ключа, так что одновременно выполняется только один вызов:
// регистрация
services.AddHybridCache(options =>
{
opts.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromSeconds(60),
LocalCacheExpiration = TimeSpan.FromSeconds(60)
};
});

// использование
public async Task<Report> GetReportAsync(string key)
{
return await _cache.GetOrCreateAsync(
key,
async ct => await _service.BuildReportAsync(key, ct),
cancellationToken: default);
}

HybridCache также предоставляет двухуровневый кэш. Защита от «паники» применяется на локальном уровне на каждом узле; она не предотвращает одновременную перестройку одного и того же ключа двумя разными узлами в кластере, поскольку отсутствует межмашинная блокировка. Для этого потребуется распределённая блокировка или придётся смириться с периодическим двойным перестроением на разных узлах как с менее масштабной версией той же проблемы.

Решение 3: не допускать одновременного истечения срока действия всего кэша
Даже при блокировке по ключу, проблема может возникать, если срок действия множества разных ключей истекает одновременно. Решение - добавить разброс (jitter):
var jitter = TimeSpan.FromSeconds(Random.Shared.Next(0, 10));

_cache.Set(key, report, TimeSpan.FromSeconds(60) + jitter);


Источник:
https://steven-giesel.com/blogPost/605274d2-719b-4b1f-b0d5-3ad9001d9b56/adding-static-getter-thanks-to-extensions
👍10
День 2733. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

41. Контейнеры Docker и .NET
«Расскажите, как Docker можно использовать для улучшения разработки, тестирования и развёртывания приложений .NET. Как вы бы настроили и развернули приложение .NET с использованием Docker».

Хороший ответ
Использование Docker значительно улучшает разработку, тестирование и развёртывание, обеспечивая согласованную среду от разработки до производства. Это устраняет проблему «работает на моей машине», когда ошибки не возникают в среде разработки, но появляются в производственной среде из-за разных настроек среды или разного установленного программного обеспечения. Контейнеризация также способствует более надёжному развёртыванию. Вот как Docker можно интегрировать с приложением .NET.

1. Начнём с создания файла Dockerfile в корне проекта .NET. Этот файл описывает процесс сборки образа Docker и указывает среду, в которой работает приложение:
# Используем официальный образ SDK .NET 10 от Microsoft
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build-env
WORKDIR /app

# Копируем csproj и восстанавливаем зависимости
COPY *.csproj ./
RUN dotnet restore

# Копируем файлы проекта и публикуем
COPY . ./
RUN dotnet publish -c Release -o out

# Создаём образ среды выполнения
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "MyApp.dll"]


2. Создадим Docker-образ, используя Dockerfile:
docker build -t myapp:latest .


3. Запустим приложение в контейнере Docker:
docker run -d -p 8080:80 --name myrunningapp myapp:latest


4. Используем Docker Compose для управления многоконтейнерными приложениями в Docker. Здесь мы определяем сервисы, параметры сети и дисковые тома в файле docker-compose.yml:
version: '3.4'

services:
webapp:
image: myapp:latest
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:80"
environment:
ASPNETCORE_ENVIRONMENT: Development
volumes:
- .:/app
- ~/.aspnet/https:/https:ro

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

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

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

Источник:
https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍5👎3
День 2734. #Оффтоп
На воскресенье решил поделиться с вами интересной околокомпьютерной историей. Как обычно, неведомые алгоритмы ютуба подсунули канал America's Strangest History, рассказывающий о самых странных историях Америки из совершенно разных сфер. И я, конечно, залип. Вот одной из них хочу поделиться.

2 ноября 2000 года кто-то зашёл на небольшой интернет-форум и в мельчайших технических деталях описал машину, способную перемещать человека во времени. Он представился солдатом из 2036 года.

Его миссия: найти компьютер IBM 5100 1975 года выпуска со скрытой функцией, о существовании которой знали лишь немногие, за исключением нескольких отставных инженеров.

За четыре месяца пользователь под именем Джон Титор опубликовал 151 сообщение. Он описал машину времени вплоть до компонентов последовательного соединения. Разместил фотографии и, по-видимому, страницы из руководства по эксплуатации. Приводил ссылки на реальные теоретические законы физики. Он ответил на сотни вопросов с терпением и последовательностью, которые указывали на то, что это не мистификация.

Затем 24 марта 2001 года он написал последнее предложение и исчез. Никто, использовавший это имя, больше ничего не публиковал. Четыре года спустя журналист разыскал одного из инженеров, работавших над созданием IBM 5100. Боб Дубке подтвердил, что скрытая функция действительно существует!

Это был его личный вклад в разработку машины, и IBM это скрыла…

Полная история в видео https://youtu.be/s_t-RI01Gac
Оригинал на английском, но есть автодубляж на русский. Приятного просмотра.
👍5
День 2735. #ЗаметкиНаПолях #Cancellation
Токен Отмены: Ошибки, Которые Незаметно Приводят к Проблемам. Начало

Пользователь закрывает вкладку во время выполнения запроса. Клиент отключается. На панели управления этот запрос должен отображаться как отменённый в течение миллисекунд. Вместо этого он продолжает выполняться ещё секунды — обращается к БД, API, записывает результат, который никто никогда не прочтёт. При тысячах прерванных запросов в день вы платите за вычислительные ресурсы, которые никому не служат. Это чаще всего токен отмены, который корректно создаётся в начале конвейера обработки запросов, а затем незаметно перестаёт передаваться дальше. ASP.NET Core предоставляет возможность отмены практически бесплатно. Токен интегрирован в конвейер, и фреймворк отменяет его в момент отключения клиента. Все ошибки происходят в коде, который забывает о существовании токена.

Каждый HTTP-запрос в ASP.NET Core содержит токен, который вы можете запросить напрямую:
app.MapGet("/reports/{id}", async (int id, HttpContext http, ReportService service) =>
{
// Этот токен отменяется при отключении клиента
// или на старте процедуры завершения работы сервера
CancellationToken ct = http.RequestAborted;
var report = await service.BuildReportAsync(id, ct);
return Results.Ok(report);
});

Редко требуется явно использовать HttpContext.RequestAborted — обработчики в минимальных API и методы-действия в MVC могут просто принимать параметр CancellationToken, и фреймворк автоматически привязывает RequestAborted к нему:
app.MapGet("/reports/{id}", async (int id, ReportService service, CancellationToken ct) =>
{
var report = await service.BuildReportAsync(id, ct);
return Results.Ok(report);
});

Вот и весь контракт: токен передаётся вам, а ваша задача — передать его каждому ожидаемому вызову ниже. Описанные ниже ошибки — это варианты нарушения этой цепочки.

Ошибка 1: «Проглатывание» токена на уровне сервиса
Наиболее распространённое нарушение - сигнатура метода сервиса игнорирует параметр:
public class ReportService
{
// …

public async Task<Report> BuildReportAsync(int id)
{
var order = await _db.Orders.FindAsync(id);
var pricing = await _prcClient.GetAsync($"/price/{id}");
return Map(order, pricing);
}
}

Здесь ничего не вызывает ошибок. На код-ревью всё выглядит нормально, если только вы специально это не проверяете. Но с этого момента запрос становится неотменяемым — клиент может исчезнуть, a запрос к БД и HTTP-вызов всё равно завершатся. Верный вариант:
public async Task<Report> BuildReportAsync(
int id, CancellationToken ct)
{
var order = await _db.Orders.FindAsync(new object[] { id }, ct);
var pricing = await _prcClient.GetAsync($"/price/{id}", ct);
return Map(order, pricing);
}

Правило: если метод ожидает чего-либо, он обязан принимать CancellationToken. Анализатор, вроде CA2016 (часть встроенных анализаторов .NET), отметит большинство таких случаев — включите его как предупреждение, а не просто как рекомендацию.

Ошибка №2: Task.Run без токена или с неправильным токеном
Task.Run имеет две перегрузки, и легко вызвать ту, которая делает вашу работу неотменяемой:
public async Task<byte[]> GenerateExportAsync(
int reportId, CancellationToken ct)
{
return await Task.Run(() => BuildReport(reportId), ct);
}

Передача ct в Task.Run останавливает запуск задачи только в том случае, если она уже отменена, но ничего не делает, если делегат уже запущен, т.к. BuildReport не может отслеживать отмену. Решение – передавать токен в исполняемый метод, а не только в Task.Run:
public async Task<byte[]> GenerateExportAsync(
int reportId, CancellationToken ct)
{
return await Task.Run(() => BuildReport(reportId, ct), ct);
}

private byte[] BuildReport(
int id, CancellationToken ct)
{
for (int page = 0; page < TotalPages(id); page++)
{
// периодически проверяем отмену
ct.ThrowIfCancellationRequested();
RenderPage(id, page);
}

}

Для циклов, интенсивно использующих процессор, вызывайте ct.ThrowIfCancellationRequested() между итерациями, а не только один раз в начале. Цикл, который проверяет токен один раз, а затем работает десять секунд, не может быть корректно отменён.

Продолжение следует…

Источник:
https://thecodeman.net/posts/cancellation-tokens-in-aspnet-core-mistakes
👍20
День 2736. #ЗаметкиНаПолях #Cancellation
Токен Отмены: Ошибки, Которые Незаметно Приводят к Проблемам. Продолжение

Начало

Ошибка №3: Игнорирование OperationCanceledException
Отмена в .NET работает путём генерации исключений. Когда срабатывает токен, следующая ожидаемая операция генерирует исключение OperationCanceledException (или его подкласс TaskCanceledException). Блок catch, перехватывающий все исключения, перехватит и это — и теперь ваши логи полны «сбоев», которые на самом деле являются просто закрытием вкладок пользователями:
try
{
await service.BuildReportAsync(id, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Генерация отчёта не удалась");
throw;
}

Решение – ожидать отмену:
try
{
await service.BuildReportAsync(id, ct);
}
catch (OperationCanceledException)
when (ct.IsCancellationRequested)
{
_logger.LogDebug("Генерация отчёта №{Id} отменена клиентом", id);
throw; // позволяем ASP.NET Core перехватить это и вернуть правильный ответ
}
catch (Exception ex)
{
_logger.LogError(ex, "Генерация отчёта не удалась");
throw;
}

ASP.NET Core умеет обрабатывать необработанное OperationCanceledException, связанное с RequestAborted — оно завершает запрос без записи кода 500.

Ошибка №4: Предположение, что фоновый сервис получает токен запроса
Эта ошибка сбивает с толку, потому что выглядит как противоположная проблема. У фонового сервиса есть собственный токен отмены, и он не имеет отношения к HTTP-запросу. Он срабатывает только при завершении работы хоста:
public class OutboxProcessor : BackgroundService
{
private readonly IServiceProvider _services;

public OutboxProcessor(IServiceProvider services)
=> _services = services;

protected override async Task ExecuteAsync(
CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
using var scope = _services.CreateScope();
var db = scope.ServiceProvider
.GetRequiredService<AppDbContext>();

// stopToken здесь означает «приложение закрывается»,
// а не «эта часть работы отменена кем-то»
await ProcessAsync(db, stopToken);
await Task.Delay(TimeSpan.FromSeconds(5), stopToken);
}
}
}

Если вы запускаете фоновые процессы внутри обработчика запросов и передаёте в него RequestAborted, вы создаёте ошибку: фоновые процессы прекращаются в тот же момент, когда отправляется HTTP-ответ (потому что RequestAborted также срабатывает в этих случаях при некоторых конфигурациях хоста) или когда клиент отключается. Фоновым процессам, которые должны продолжаться после завершения запроса, нужен собственный токен — обычно IHostApplicationLifetime.ApplicationStopping, а не токен запроса:
app.MapPost("/reports/{id}/export", async (
int id,
ReportService svc,
IHostApplicationLifetime lt) =>
{
// НЕ RequestAborted – сервис должен продолжить
// работу, даже когда ответ клиенту отправлен
_ = svc.GenerateAsync(id, lt.ApplicationStopping);
return Results.Accepted();
});

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

Окончание следует…

Источник:
https://thecodeman.net/posts/cancellation-tokens-in-aspnet-core-mistakes
👍10
День 2737. #ЗаметкиНаПолях #Cancellation
Токен Отмены: Ошибки, Которые Незаметно Приводят к Проблемам. Окончание

Начало
Продолжение

Ошибка № 5: Понимание необходимости связывания токенов
Иногда одной операции необходимо учитывать сразу два источника отмены — токен запроса и тайм-аут, который вы добавляете. Если забыть их объединить, один из двух источников молча ничего не сделает:
public async Task<Pricing> GetPricingAsync(
 int id, CancellationToken requestCt)
{
  using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
  using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCt, timeoutCts.Token);
 
  // linkedCts.Token отменяется при первом из событий:
  // клиент отключился или после 3х секунд
  var resp = await
   _prcClient.GetAsync($"/price/{id}", linkedCts.Token);
 
  return await resp.Content
   .ReadFromJsonAsync<Pricing>(linkedCts.Token);
}

Без связывания, отдельный токен таймаута игнорирует разрывы соединения с клиентом, а отдельный токен запроса игнорирует таймаут. CreateLinkedTokenSource — решение, когда отмена должна происходить из нескольких мест.
 
Правила
Не каждому методу нужен параметр CancellationToken. Синхронное вычисление ничего не ожидает, поэтому отменять нечего. Вот некоторые правила:
1. Всё, что ожидает ввода-вывода (БД, HTTP, файл, очередь) — принимает токен, передаёт его дальше, без исключений.
2. Циклы, сильно нагружающие CPU, длящиеся более нескольких миллисекунд — периодически проверяйте ThrowIfCancellationRequested() внутри цикла, а не только при входе.
3. Работа «fire-and-forget» — никогда не используйте RequestAborted. Используйте ApplicationStopping или лучше, передайте запрос в соответствующую фоновую очередь вместо _ = SomeAsync(…).
4. Логирование отмены — LogDebug или ниже, никогда не LogError. Это не ошибка.
 
FAQ
1. В чем разница между CancellationToken.None и не передачей токена?
На практике это приводит к одному результату — операцию нельзя отменить. Преднамеренная передача CancellationToken.None должна быть редкой и осознанной, например, в фоновый сервис, работающий вне контекста запроса, который действительно должен завершить работу независимо от обстоятельств.
 
2. Использовать HttpContext.RequestAborted напрямую или параметр CancellationToken?
Параметр. Привязка модели ASP.NET Core автоматически заполняет параметр значением RequestAborted, и это выглядит чище, чем обращение к HttpContext. Используйте HttpContext.RequestAborted в коде, не имеющем прямого доступа к параметру, например, для фильтра или промежуточного ПО.
 
3. Почему мой BackgroundService останавливается посреди выполнения пакета при повторном развертывании?
Потому что токен остановки срабатывает при ApplicationStopping, а таймаут завершения работы хоста по умолчанию короткий. Если пакет должен завершиться корректно, либо уменьшите размер пакета так, чтобы одна единица работы поместилась в окно завершения, либо увеличьте HostOptions.ShutdownTimeout — но не полагайтесь на неограниченный таймаут завершения, поскольку хост (или ваш оркестратор) в итоге принудительно завершит процесс независимо от этого.
 
4. Перехват исключения OperationCanceledException без повторного выбрасывания что-либо ломает?
Зависит от места. Внутри обработчика запроса, если проигнорировать его без повторного выбрасывания, ASP.NET Core никогда не получит сигнал об отмене запроса, поэтому может попытаться отправить ответ на уже разорванное соединение, что вызовет собственное исключение на более высоком уровне. Повторно выбросьте исключение и позвольте фреймворку обработать жизненный цикл ответа; перехватывайте его локально только для добавления отладочного лога или очистки ресурса.

Источник:
https://thecodeman.net/posts/cancellation-tokens-in-aspnet-core-mistakes
👍6
День 2738. #ЗаметкиНаПолях
Отладка Дампов в Visual Studio
Дамп памяти — это файл, содержащий копию памяти конкретного процесса. Файлы дампов в Windows обычно имеют расширение .dmp. Это полезно, когда у вас есть ошибка, которая воспроизводятся только в рабочей среде. И да, это, как правило, крайняя мера! Обычно ошибки обнаруживаются путём воспроизведения проблемы локально, а затем вы можете просто запустить код в отладчике. Но если вам не удаётся найти/воспроизвести ошибку за разумное время, создание дампа — отличный способ получить дополнительную информацию.

Создание дампа
Лучший инструмент для этого — ProcDump: можно создавать дампы по запросу или по триггеру мониторинга (сбой процесса, высокая загрузка ЦП/памяти и т.д.). Однако ProcDump требует доступа к машине, поэтому он отлично подходит только для локальных или виртуальных развёртываний. Если вы работаете с веб-приложением Azure, вы можете создавать дампы памяти и там.

Загрузка дампа

Вы можете просто перетащить файл dbg в Visual Studio. Но, прежде чем это сделать, вот пара советов по настройке VS для наилучшего результата:
1. Just my code
Снимите флажок Just my code (Только мой код). Удобнее видеть полную картину при отладке файла дампа.

2. Символы
Включите загрузку символов (например, файлов .pdb). VS использует символы для расшифровки скомпилированного кода, особенно если он был скомпилирован в режиме Release. Символы часто необходимы для понимания стека. Лучший вариант — указать VS загружать все символы при загрузке файла дампа. Обычно настройку лучше выключать, т.к. загрузка этих символов занимает много времени. Но для при отладке дампа она пригодится.
Также нужно указать, откуда загружать файлы символов. Два основных источника — это Microsoft и NuGet (symbols.nuget.org). Microsoft предоставляет символы для своей ОС (как минимум); сервер символов NuGet — это общее место для символов библиотек с открытым кодом.
Наконец, хорошо бы настроить локальный кэш символов. Это локальная папка, которая будет хранить все эти файлы pdb.

3. Исходные файлы
Включите поддержку сервера исходных файлов. Это способ для Visual Studio загружать точную версию исходных файлов, фактически использованных для компиляции программы. В настоящее время серверы исходных файлов в основном используются для неуправляемого кода, хотя некоторые устаревшие библиотеки .NET могут их использовать.
Также лучше включить диспетчер учётных данных Git для Source Link. Source Link — это современная замена серверам исходного кода, по крайней мере, в контексте .NET. Это позволит получать исходные файлы из вашего репозитория исходного кода.

Теперь VS готова загрузить файл дампа; просто перетащите его прямо в VS. Затем сходите за чашкой кофе; загрузка всех этих символов в первый раз потребует некоторое время! По мере заполнения кэша файлы дампов будут загружаться быстрее; первая загрузка обычно самая медленная.

VS спросит, какой тип отладчика вы хотите запустить; лучше выбрать Mixed (смешанный) — если вы не знаете, в чём проблема, лучше иметь возможность видеть всё. Затем вы перейдёте в режим, похожий на отладку. Конечно, вы не сможете снять паузу или пошагово отладить, но сможете покопаться в настройках. Как правило, окна отладки «Параллельные стеки», «Потоки», «Стек вызовов» и «Модули» являются хорошими отправными точками для попытки разобраться в происходящем.

Источник:
https://blog.stephencleary.com/2025/12/debug-dumps-in-visual-studio.html
👍8
День 2739. #Карьера
21 Урок После 14 Лет в Google. Начало
Автор оригинала: Addy Osmani

Когда я пришел в Google около 14 лет назад, я думал, что работа заключается в написании отличного кода. Отчасти я был прав. Но чем дольше я там работал, тем больше понимал, что преуспевают не обязательно лучшие программисты, а те, кто научился ориентироваться во всём, что связано с кодом: в людях, в политике, в согласованности действий, в неопределённости. Эти советы о закономерностях, которые постоянно проявляются, проект за проектом, команда за командой.

1. Лучшие инженеры одержимы решением проблем пользователей.
Заманчиво влюбиться в технологию и искать места для её применения. Все это делали. Но инженеры, создающие наибольшую ценность, работают наоборот: они одержимы пониманием проблем пользователей и позволяют решениям возникать из этого понимания.
Это означает тратить время на обработку заявок в службу поддержки, на общение с пользователями, на наблюдение за их трудностями, на вопросы «почему», пока не доберёшься до сути. Инженер, который действительно понимает проблему, часто обнаруживает, что элегантное решение проще, чем кто-либо ожидал. Инженер, который начинает с готового решения, склонен к усложнению в поисках оправдания.

2. Быть правым легко. Настоящая работа — совместное движение к правде.
Вы можете выиграть каждый технический спор и потерять проект. Я наблюдал, как блестящие инженеры накапливали молчаливое негодование, всегда будучи самым умным человеком в комнате. Цена проявляется позже в виде «загадочных проблем с исполнением» и «странного сопротивления».
Навык не в том, чтобы быть правым, а в том, чтобы в дискуссии находить решение проблемы, оставляя пространство для других и оставаться скептически настроенным к собственной уверенности.

3. Ориентация на действие. Выпуск продукта. Вы можете отредактировать плохую страницу, но не можете отредактировать пустую.
Стремление к совершенству парализует. Я наблюдал, как инженеры неделями обсуждали идеальную архитектуру для чего-то, чего они никогда не создавали. Идеальное решение редко рождается из одних лишь размышлений — оно рождается из контакта с реальностью. ИИ во многом может помочь в этом.
Сначала сделайте как-нибудь, затем правильно, затем улучшайте. Покажите пользователям некрасивый прототип. Напишите неряшливый первый черновик проектной документации. Выпустите MVP, который немного вас смущает. Вы узнаете больше из одной недели реальной обратной связи, чем из месяца теоретических дебатов.

4. Опыт – это ясность. Умничание — это лишние затраты.
Инстинкт писать умный код почти универсален среди инженеров. Это воспринимается как доказательство компетентности. Но разработка ПО — это работа с дедлайнами и другими программистами. В такой среде ясность — это не споры о стиле, а снижение операционных рисков.
Ваш код — это руководство для незнакомцев, которые будут поддерживать его в 2 часа ночи во время сбоя. Оптимизируйте код для их понимания, а не для своего эго. Самые уважаемые сеньоры научились всегда жертвовать остроумием ради ясности.

5. Новизна — это кредит, который вы возвращаете сбоями, наймом персонала и когнитивными издержками.
Относитесь к выбору технологий как к организации с небольшим бюджетом на «инновационные токены». Тратьте один каждый раз, когда внедряете что-то существенно нестандартное. Вы не можете позволить себе много.
Это не значит «никогда не внедряйте новинки». Это значит «внедрять инновации только там, где вам платят именно за это». Всё остальное должно быть скучным, потому что у скучного есть известные причины неудач.
«Лучший инструмент для работы» часто оказывается «наименее худшим инструментом для большинства задач», потому что содержание зоопарка становится затратным.

6. За вас говорит не ваш код, а люди.
В начале своей карьеры я считал, что отличная работа говорит сама за себя. Нет. Код молча лежит в репозитории. Ваш менеджер упоминает вас на совещании, или нет. Коллега рекомендует для проекта вас или кого-то ещё.
В крупных организациях решения принимаются на совещаниях, на которые вас не приглашают, на основе отзывов, которые писали не вы, людьми, у которых есть пять минут и двенадцать приоритетов. Если никто не может рассказать за вас о вашем вкладе, ваш вклад по сути незначителен.
Речь не только о саморекламе, но и о том, чтобы сделать цепочку создания ценности понятной для всех, включая вас самих.

7. Лучший код — тот, который вам никогда не приходилось писать.
В инженерной культуре мы ценим созидание. Никто не получает повышения за удаление кода, хотя это улучшает систему больше, чем добавление. Каждая строка кода, которую вы не написали, — это строка, которую вам никогда не придётся отлаживать, поддерживать или объяснять.
Прежде чем начать разработку, задайте себе вопрос: «Что произойдет, если мы просто… не будем этого делать?» Иногда ответ — «ничего плохого», вот и решение.
Проблема не в том, что инженеры не умеют писать код или использовать для этого ИИ. Проблема в том, что мы настолько хорошо пишем код, что забываем спросить себя, стоит ли это делать.

Продолжение следует…

Источник:
https://addyosmani.com/blog/21-lessons/
👍6👎2
День 2740. #Карьера
21 Урок После 14 Лет в Google. Продолжение

Начало

8. В больших масштабах даже у ваших ошибок есть пользователи.
При достаточном количестве пользователей каждое наблюдаемое поведение становится зависимостью — независимо от того, что вы обещали. Кто-то парсит ваш API, автоматизирует ваши «особенности», кэширует ваши баги.
Нельзя рассматривать работу по обеспечению совместимости как «поддержку», а новые функции как «настоящую работу». Совместимость — это продукт.
Планируйте устаревание как миграции с учётом времени, инструментов и эмпатии. Большая часть «проектирования API» на самом деле является «прекращением поддержки устаревших функций».

9. Большинство «медленных» команд на самом деле являются проблемой координации.
Когда проект затягивается, инстинктивно винят исполнение: люди работают недостаточно усердно, технология неправильная, инженеров недостаточно. Обычно ничто из этого не является настоящей проблемой.
В крупных компаниях затраты на координацию растут в геометрической прогрессии по мере увеличения количества команд. Большая часть замедления на самом деле является результатом нарушения согласованности — люди создают неправильные вещи или правильные вещи несовместимыми способами.

10. Сосредоточьтесь на том, что вы можете контролировать. Игнорируйте то, что не можете контролировать.
В крупной компании бесчисленное множество переменных находятся вне вашего контроля — организационные изменения, решения руководства, сдвиги рынка, изменения в продукте. Зацикливание на этом порождает тревогу без возможности влиять на ситуацию.
Вы не можете контролировать, произойдёт ли реорганизация компании. Вы можете контролировать качество своей работы, то, как вы реагируете, и то, чему вы учитесь. Столкнувшись с неопределённостью, разбейте проблемы на части и определите конкретные действия, доступные вам.
Это не пассивное принятие, а стратегическая фокусировка. Энергия, потраченная на то, что вы не можете изменить, — это энергия, украденная у того, что вы можете изменить.

11. Абстракции не устраняют сложность. Они переносят её на тот день, когда вы дежурите.
Каждая абстракция — это ставка на то, что вам не нужно разбираться, что лежит в основе. Иногда эта ставка играет. Но часто получается, что что-то просачивается, и когда это происходит, хорошо бы про это знать.
Сеньоры продолжают изучать «низкоуровневые» вещи не из ностальгии, а чтобы быть готовым к моменту, когда абстракция даёт сбой, и вы остаётесь наедине с системой в 3 часа ночи. Используйте свой стек, но имейте в голове представление об основных его слабых местах.

12. Письменное изложение способствует ясности. Самый быстрый способ лучше чему-то научиться — это попытаться научить этому других.
Когда я объясняю концепцию другим — в документе, докладе, комментарии к коду, даже просто общаясь с ИИ — я обнаруживаю пробелы в собственном понимании. Сам акт превращения чего-либо в понятное для другого человека делает это более понятным для меня.
Если вы думаете, что что-то понимаете, попытайтесь объяснить это простым языком. Вы споткнётесь там, где ваше понимание поверхностно. Обучение — это отладка собственных ментальных моделей.

13. Работа, которая делает возможной работу других, бесценна… и незаметна.
Работа по обеспечению взаимодействия: документация, адаптация новых сотрудников, межкомандная координация, улучшение процессов — жизненно важна. Но если вы делаете это неосознанно, это может затормозить ваш технический прогресс и привести к выгоранию. Не делайте это просто, чтоб «быть полезным». Это должно быть явное, ограниченное, видимое воздействие.
Ограничьте помощь по времени. Чередуйте с другой работой. Превратите её в артефакты: документы, шаблоны, автоматизацию. И сделайте её заметной как воздействие, а не как черту вашего характера. Полезное, но невидимое — опасное сочетание для вашей карьеры.

14. Если вы выигрываете все споры, вы, вероятно, накапливаете скрытое сопротивление.
Я научился с подозрением относиться к собственной уверенности. Когда я слишком легко «побеждаю», обычно что-то не так. Люди бросают бороться с вами не потому, что вы их убедили, а потому, что они перестают пытаться – и они выражают это несогласие на практике, а не на совещаниях.
Настоящее согласование требует времени. Нужно действительно понимать другие точки зрения, учитывать обратную связь и иногда публично менять свое мнение.
Кратковременное чувство собственной правоты стоит гораздо меньше, чем долгосрочная уверенность при создании чего-то вместе с заинтересованными партнёрами.

Окончание следует…

Источник:
https://addyosmani.com/blog/21-lessons/
👍5👎2
День 2741. #Карьера
21 Урок После 14 Лет в Google. Окончание

Начало
Продолжение

15. Когда показатель становится целью, он перестаёт измерять.
Каждый показатель, который вы предоставляете руководству, в итоге будет использован не по назначению. Не из-за злого умысла, а потому что люди оптимизируют то, что измеряется.
Если вы отслеживаете количество строк кода, вы получите больше строк, скорость - получите завышенные оценки.
Рекомендация для руководителей: отвечайте на каждый запрос метрики парой значений - скорости и качества/риска. Настаивайте на отслеживании тенденций, а не абсолютных значений. Цель — понимание, а не слежка.

16. Признание того, чего вы не знаете, более безопасно, чем притворство, что вы знаете.
Сеньоры, которые говорят: «Я не знаю», - не проявляют слабость — они дают разрешение. Когда лидер признаёт неуверенность, это сигнал, что другим безопасно делать то же самое. Альтернатива — культура, где все делают вид, что понимают, и проблемы остаются скрытыми, пока не рванёт.
В команде, где самый старший сотрудник никогда не признаёт, что чего-то не знает, не задаются вопросы, предположения не подвергаются сомнению, джуны молчат, потому что считают, что все остальные всё понимают.
Проявляйте любознательность, и вы получите команду, которая действительно учится.

17. Ваши связи переживут любые места работы.
Инженеры, которые вкладывали силы в отношения — внутри и вне компании — пожинали плоды на протяжении десятилетий. Они первыми узнавали о возможностях, быстрее налаживали связи, получали рекомендации на должности и становились соучредителями предприятий с людьми, с которыми они годами выстраивали доверительные отношения.
Ваша работа не вечна, а ваша сеть контактов — да. Относитесь к ней с любопытством и открытостью, а как в деловой рутине. Когда придёт время двигаться дальше, именно отношения часто открывают двери.

18. Большинство успехов в повышении производительности достигается за счёт сокращения работы, а не добавления умных фич.
Когда системы начинают тормозить, инстинктивно возникает желание добавить: слои кэширования, параллельную обработку, более интеллектуальные алгоритмы. Иногда это правильно. Но я видел больше улучшений производительности, когда задавался вопросом: «Что мы вычисляем, что нам не нужно?»
Удаление ненужной работы почти всегда оказывает большее влияние, чем ускорение выполнения необходимой работы. Самый быстрый код — это код, который никогда не запускается. Прежде чем оптимизировать, задайте себе вопрос, должна ли эта работа вообще существовать.

19. Процесс существует для уменьшения неопределённости, а не для создания бумажной волокиты.
Лучший процесс упрощает координацию и снижает затраты на устранение сбоев. Худший процесс — это бюрократический театр — он существует не для того, чтобы помогать, а для того, чтобы переложить вину, когда что-то пойдёт не так.
Если вы не можете объяснить, как процесс снижает риски или повышает ясность, это, вероятно, просто лишняя работа. И если люди тратят больше времени на документирование своей работы, чем на её выполнение, что-то пошло очень не так.

20. В итоге время ценнее денег. Действуйте соответственно.
В начале своей карьеры вы обмениваете время на деньги — и это нормально. Но в какой-то момент они меняются местами. Вы начинаете понимать, что время — это невозобновляемый ресурс.
Я наблюдал, как сеньоры выгорали, гонясь за следующим повышением с зарплату на несколько процентов выше. Некоторые из них добились успеха. Большинство же потом задавались вопросом, стоило ли это того, что они потеряли. Это не значит «работать на отвали», но важно знать, чем вы жертвуете, и делать это обдуманно.

21. Нет коротких путей, но есть эффект накопления.
Экспертность приходит благодаря осознанной практике — выход за границы своих текущих навыков, размышления, повторения. Годами. Короткого пути нет.
Хорошая новость: опыт накапливается, когда создаёшь новые вещи, а не просто повторяешь за кем-то. Создавайте переиспользуемые примитивы. Собирайте «набитые шишки» в заметки.
Инженер, который относится к своей карьере как к системе со сложными процентами, а не как к лотерее, как правило, добивается гораздо большего.

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

Источник:
https://addyosmani.com/blog/21-lessons/
👍5👎1
День 2742. #TipsAndTricks
Получаем Название Символа 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
👍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/
👎2
День 2745. #ЗаметкиНаПолях #Architecture
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/
👍7
День 2747. #TipsAndTricks #VisualStudio
Загадочный Флажок .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
👍3👎3
День 2749. #BestPractices #SQL
Как Оптимизировать 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