A state of .NET
186 subscribers
328 photos
1 video
860 links
A State of .NET — всё для .NET-разработчика: новости, релизы, статьи о C#, .NET, ASP.NET Core, EF Core, и прочих технологиях.

Ежедневно — новости, релизы, обзоры статей и книг, лучшие практики и инструменты.
Download Telegram
Native AOT заставил меня переписать весь .NET проект

#dotnet #NativeAOT #csharp

✅
Переход на Native AOT — это не флаг в .csproj, а отказ от reflection-зависимостей по всему проекту.

📈
Чтобы поставлять C#-утилиту нативным бинарником без установки .NET Runtime, придётся заменить всё, что решает поведение в рантайме, на генерацию кода при сборке.

🔧 Reflection-зависимости пришлось выпилить одну за другой: System.CommandLine.Hosting, Scrutor (декораторы переехали на keyed services с [FromKeyedServices]), а сериализация — на source-generated JsonSerializerContext с явным списком CLR-типов.

⚠️ Убрав хост, потеряли Dispose service provider’а — процесс завершался до того, как асинхронный вывод логов долетал до консоли. Лечится через using var serviceProvider.

Читаем:
В статье разобрана финальная модель поставки в .NET 10 — пять NuGet-пакетов за одним именем команды, порядок публикации, managed fallback через RID any и почему Native AOT требует отдельной GitHub Actions job на каждую ОС.

https://habr.com/ru/articles/1085386/

@aStateOfNet
❤1
❤1
LLM предлагает, код исполняет: как разделить reasoning и side effects в production‑пайплайне
#LLM #AIAgents #DotNet

✅
Модель имеет право предлагать, код — делать, а между ними всегда остаётся строка в логе, понятная инженеру через месяц без самой модели.

📈
Вынос side effects из agent loop за typed proposal и явные гейты даёт проверяемую границу ответственности при разборе инцидентов.

🔁 Timeout сам по себе не разрешает retry: его делят минимум на четыре исхода, и при «ответ потерян» / «частичный ответ отдан» слепой повтор запрещён даже с idempotency key — сначала read‑back или reconciliation.

📊 Порог confidence калибруют на размеченных данных: в их детекторе 0,2 давал precision 1,0, а 0,5 терял около половины recall — переносить это число нельзя.

Читаем:
Внутри — код гейтов handle_refund, полная state machine записи с состоянием outcome_unknown, разбор четырёх контуров (offline eval / release gate / runtime gate / monitoring) и провальные итерации вроде инспектора tool‑call, блокировавшего вызовы по имени инструмента.

https://habr.com/ru/companies/agima/articles/1086332/

@aStateOfNet
❤3
System Design для начинающих: всё, что вам нужно. Часть 1

#SystemDesign #DistributedSystems #Backend

✅
В распределённой системе сетевое разделение неизбежно, поэтому реальный выбор всегда между CP и AP, а не между всеми тремя гарантиями CAP.

📈
Знаешь, что «P» присутствует всегда — и на собеседовании обосновываешь выбор БД через компромисс между согласованностью и доступностью под конкретный домен.

🔹 Банкинг, платежи, биржи требуют consistency (нельзя показывать противоречивые данные); соцсети выбирают availability (расхождение в счётчике лайков допустимо).

🔹 Оценка ресурсов «на коленке»: 10k RPS × 10 мс = 100 000 мс/с, при 1000 мс/с на ядро это 100 ядер, то есть 25 серверов по 4 ядра.

Читаем:
В статье разобраны конкретные примеры расчёта нагрузки и хранилища на кейсе Twitter (DAU, объём в ПБ/день), различия вертикального и горизонтального масштабирования и механика автоскейлинга по порогу загрузки CPU.

https://habr.com/ru/articles/873388/

@aStateOfNet
❤2
Как я перенёс сборку лексера в compile-time

#dotnet #roslyn #sourcegenerators

✅
Один source generator может передать результат другому через post-initialization output — эта фаза добавляет исходники в компиляцию до запуска обычных генераторов, поэтому [GeneratedRegex] видит сгенерированную константу.

📈
Описываешь токен один раз, регулярка собирается сама — без ручного копирования строки между файлами и теста на «забыл скопировать».

⚙️ Порядок альтернатив в регулярке — это правило языка: FloatLiteral с приоритетом 2 должен идти раньше IntegerLiteral с приоритетом 3, иначе `12.5` распадётся на `12` и точку. Отдельного maximal munch нет.

🐛 Генератор падал с CS8032 при `dotnet build /p:UseSharedCompilation=false` (так собирает BenchmarkDotNet). Лечится заменой ProjectReference с OutputItemType="Analyzer" на подключение исходников через Compile — но тогда определения токенов обязаны собираться и под netstandard2.0.

Читаем:
В статье — разбор, как через static abstract member интерфейса отдать регулярку из Infrastructure в Domain без разворота зависимостей слоёв, плюс бенчмарки Compiled vs Generated и трассировка лексера через флаг --dump.

https://habr.com/ru/articles/1087280/

@aStateOfNet
Как и зачем я сделал свой маппер

#dotnet #csharp #sourcegenerator

✅
Незаполненное поле в цели маппинга должно ломать сборку, а не молча уезжать в прод как default.

📈
Изменение модели ломает билд, а не отдаёт пользователям `0001-01-01T00:00:00` вместо реальной даты.

🔧 `required` не спасает с рантайм-мапперами: AutoMapper, Mapster создают объекты через рефлексию мимо object initializer, поэтому CS9035 не срабатывает. Ловит только Mapperly, генерирующий настоящий object initializer.

⚡ FusionMapper совмещает source generator и рантайм-фоллбек: где типы известны на компиляции — работают C# Interceptors (ошибка сборки при незаполненном required), где типы приходят из generic-кода — маппинг строится в рантайме на первом вызове.

Читаем:
Разбор, почему интерсепторы нельзя применять внутри expression tree и как это ломало компилятор без сообщения об ошибке, плюс бенчмарки против ручного маппинга, Mapperly, Mapster и AutoMapper на плоских и вложенных графах.

https://habr.com/ru/articles/1087338/

@aStateOfNet
OLAP OVER HTTP: как отдавать большие аналитические данные через API и не положить сервис

#ClickHouse #DotNet #HighLoad

✅
OLAP-базу не стоит ставить под каждый пользовательский HTTP-запрос — она захлёбывается на RPS, а не на объёме данных.

📈
Разнеси нагрузку по компонентам: hot storage для частых запросов к свежим данным, S3 для готовых отчётов, ClickHouse для тяжёлой аналитики.

🔹 ClickHouse на стенде (4 ГБ RAM, 6 ядер) держал ~60 RPS при ~80 ms, а на 80 RPS полностью захлёбывался — каждый запрос жрёт максимум ядер, отсюда Thread Contention и Noisy Neighbor.

🔹 Перенос горячих данных (99% запросов) в PostgreSQL дал ~240 RPS и 12 ms против 80 ms — за счёт Index Scan по 0,02% строк на селлера, а кеш отчётов в S3/MinIO вытянул ~750 RPS.

Читаем:
В статье разобраны конкретные подводные камни каждого подхода (пересоздание битого отчёта, 404 против генерации на лету, отсутствие пула соединений для ClickHouse) и расчёт, при каком числе запросов кеширование начинает выигрывать.

https://habr.com/ru/companies/ozontech/articles/1084762/

@aStateOfNet
Поменяли object на Lock, и два потока встретились в критической секции

#dotnet #csharp #concurrency

✅
С C# 13 тип выражения в lock решает семантику, а не только скорость: object разворачивается в Monitor, System.Threading.Lock — в EnterScope, и это два несовместимых замка на одном объекте.

📈
Один объект Lock, прочитанный через object или через generic-параметр, тихо возвращает вас к Monitor — и взаимное исключение исчезает без единой ошибки компиляции.

🔧 Утечка происходит не в самом lock, а в строке, где экземпляр кладут в поле типа object или в Dictionary<string, object>: два потока по 150 000 проходов встретились в критической секции 106 309 раз, счётчик потерял 4487 инкрементов.

⚠️ Обёртка `lock (gate)` в универсальном методе `LockAny<T>` компилируется в Monitor молча — предупреждения нет, а `where T : Lock` невозможен, потому что Lock объявлен sealed.

Читаем:
В статье разбор того, где именно компилятор молчит, почему Monitor.IsEntered начинает врать, что происходит с Monitor.Wait/Pulse, замеры памяти (Lock +16 байт на экземпляр против раздувания Monitor) и порядок безопасной миграции через CS9216 как ошибку сборки.

https://habr.com/ru/companies/otus/articles/1087292/

@aStateOfNet
❤1
Media is too big
VIEW IN TELEGRAM
Уточнение: после помечания вершины просмотренной, мы выбираем следующую ближайшую вершину.
❤1
AS4 в .NET без Java-шлюза: ebMS 3.0, WS-Security и неотрекаемые квитанции шагом маршрута

#dotnet #AS4 #ebMS

✅
AS4 защищает сам документ, а не только канал: получатель возвращает подписанную квитанцию, чьи дайджесты криптографически привязаны к тому, что вы подписали — доказательство доставки именно этого документа, байт в байт.

📈
Нативный .NET-коннектор ставит AS4 внутрь маршрута (From → To), убирая отдельный Java-MSH или коммерческий шлюз с их inbox-каталогом, джобой-подборщиком и отдельной командой сопровождения.

🔐 Крипто-слой пришлось писать вручную: SignedXml из .NET не разрешает ссылки cid: на MIME-вложения, а EncryptedXml не умеет ни AES-GCM, ни OAEP с MGF1-SHA256 (поддерживает только OAEP-SHA1) — ровно те «дыры» в API, из-за которых готовой реализации в мейнстриме .NET нет.

🔁 Повтор по AS4 — это те же байты, тот же MessageId и та же подпись: Domibus на повтор отдаёт сохранённую квитанцию первой передачи, и пересборка сообщения с новой подписью сломала бы сверку дайджестов.

Читаем:
В статье — разбор профиля eDelivery AS4 1.16 (что принимается, что отвергается с какими EBMS-кодами), механика идемпотентности и дедупликации при рестарте и за балансировщиком, защита от signature wrapping и replay, потоковая обработка больших вложений с лимитами, результаты интеропа против Holodeck B2B и Domibus, а также честные границы (нет Pull, асинхронных квитанций, AS4 2.0).

https://habr.com/ru/articles/1089204/

@aStateOfNet
Kafka гарантирует порядок? Разбираем 3 инцидента с Outbox

#Kafka #PostgreSQL #Debezium

✅
Один order_id на все события даёт размещение в партиции, но не причинную последовательность: ключ не определяет, в каком порядке события дошли до брокера.

📈
«Порядок» распадается на разные свойства, и за каждое отвечает своё звено — пока это не записано в требовании, упорядоченность остаётся надеждой.

🔑 Ключ даёт размещение, aggregate_version — позицию в истории, бизнес-правило — допустимость. Если сборка закоммитилась раньше оплаты, номер честно зафиксирует порядок, но история будет недопустимой.

⚠️ exactly-once не чинит перестановку событий от разных процессов и не объединяет PostgreSQL с Kafka в одну транзакцию: повтор и перестановка — разные проблемы с разными решениями.

Читаем:
Внутри — разбор трёх инцидентов по дампу партиции (включая случай, где у одного инцидента две независимых причины, и случай, где событие потеряно, а не переставлено), почему `last offset + 1` ломает прогресс чтения, и как переписать НФТ так, чтобы оно пережило смену базы и брокера.

https://habr.com/ru/companies/otus/articles/1077958/

@aStateOfNet
❤2
О пользе ограничений в MSSQL
#MSSQL #EntityFramework #SQLPerformance

✅
CHECK-ограничения не только защищают целостность данных — они дают оптимизатору информацию о домене значений и напрямую влияют на план выполнения.

📈
Явно описанный диапазон значений в столбце может сократить чтения в разы без добавления новых индексов.

🔹 Добавление CHECK (State IN (0..10)) запустило упрощение предиката: NOT IN (0,6,8) превратилось в IN (1,2,3,4,5,7,9,10), и логические чтения упали с 11268 до 2301, а прочитанные строки — с 659796 до 114496.

🔹 Запрос по заведомо недопустимому значению (State IN (11)) оптимизатор свёл к Constant scan — обращение к данным исключено полностью из-за обнаруженного противоречия.

Читаем:
В статье — разбор, почему без ограничений сервер строит широкие открытые диапазоны по B-Tree, как это настроить в EF Core code-first через HasCheckConstraint и EFCore.CheckConstraints, и какую цену вы платите на каждом INSERT/UPDATE.

https://habr.com/ru/companies/skbkontur/articles/1082258/

@aStateOfNet
❤1👍1
429 — это не про скорость. Это про бюджет, которого ты не видишь
#Redis #RateLimiting #TokenBucket

✅
Рейт-лимит — это общий бюджет токена, а не скорость твоего цикла, поэтому лимитер не может жить внутри процесса.

📈
Три воркера с локальным ведром по 30 токенов шлют 90 msg/s при лимите 30 — лимитер нужно вынести в Redis, где видно всех потребителей.

🔁 429 с полем retry_after — это расписание, а не повод для бэкоффа: обращения во время лимита продлевают сам лимит, превращая паузу в 32 секунды в несколько минут. Вместо sleep сообщение перепланируют через ZADD с ключом по времени доставки.

⚠️ INCR + EXPIRE не атомарен (процесс умирает между командами — ключ застревает без TTL) и даёт фиксированное окно, которое пропускает двойной лимит на границе: 30 сообщений в 12:00:00.999 и ещё 30 в 12:00:01.001. Token bucket на Lua-скрипте оперирует непрерывным уровнем, а не окном.

Читаем:
В статье разобраны head-of-line blocking через дорожки по чатам, цена 403 от заблокировавших бота, рамповое размораживание очереди вместо рубильника и четыре метрики, которые реально показывают близость к стене — плюс честный контраргумент о том, где планировщик превращается в оверинжиниринг.

https://habr.com/ru/articles/1083632/

@aStateOfNet
Компактация лога в Apache Kafka повреждала данные. Вот как мы это исправили

#ApacheKafka #DistributedSystems #DataIntegrity

✅
Удаление tombstone и COMMIT/ABORT маркеров по таймеру, а не по прогрессу реплик — это гонка, из-за которой логи реплик расходятся навсегда.

📈
Если брокер в офлайне дольше delete.retention.ms (по умолчанию 24ч), он навсегда теряет маркер, и консьюмер видит разные данные в зависимости от того, какой брокер стал лидером.

🐛 Баг стабильно воспроизводится в Kafka 3.9–4.2 в четырёх вариантах: удалённые данные возвращаются, ABORT становится COMMIT, COMMIT становится ABORT, и партиция зависает для read_committed до истечения producer.id.expiration.ms.

🔧 Redpanda решает это координированной компактацией: лидер собирает MCCO/MXFO с каждой реплики и вычисляет MTRO/MXRO как минимум по всем репликам (включая офлайн), запрещая удаление маркера, пока самая медленная реплика не догонит.

Читаем:
В статье — пошаговый сценарий воспроизведения на Docker Compose со скриптами из GitHub-репозитория, разбор edge cases протокола (смена лидера, изменение состава реплик, запоздавшие RPC) и объяснение, почему MCCO новой реплики инициализируется значением выше конца её лога.

https://habr.com/ru/companies/otus/articles/1086222/

@aStateOfNet