TL;DR. Один foo.GetAsync().Result внутри middleware превращает ASP.NET Core, державший 50k RPS на p99 = 40 мс, в сервис на 12k RPS с p99 = 4 с при CPU 8 %. Виноват не блокирующий вызов сам по себе.
Виноват hill-climbing: фидбэк-луп в ThreadPool, внутри которого живёт дискретное преобразование Фурье.
Разбираемся по исходникам CoreCLR, как это работает, воспроизводим эффект на ~80 строках кода и показываем, почему SetMinThreads это не лечение, а анестезия.
https://habr.com/ru/articles/1040804/
Please open Telegram to view this post
VIEW IN TELEGRAM
🐳 «Используй Testcontainers вместо in-memory» - это только половина правды
Все уже выучили: EF Core InMemory provider - не интеграционный тест.
Он не ловит:
- баги в LINQ-трансляции
- ограничения БД
- коллации
- реальные типы колонок
- поведение конкретного SQL-провайдера
Окей, заменили на реальный PostgreSQL через Testcontainers. Победа? Не совсем.
Вот что начинается дальше.
1. Вы получили «медленное враньё» вместо «быстрого»
Поднимать контейнер на каждый тест-класс - быстрый способ превратить CI из 30 секунд в 8 минут.
Нормальный вариант:
- один контейнер на всю тестовую сессию
- изоляция данных между тестами через Respawn
- без пересоздания базы и контейнера каждый раз
Respawn чистит таблицы с учётом графа foreign keys за миллисекунды.
2. Транзакционный откат ≠ реальный сценарий
Трюк «обернули тест в транзакцию и откатили» красиво выглядит, но ломается, когда в коде есть:
- свои транзакции
- несколько SaveChanges
- фоновые операции
- поведение, завязанное на commit
В итоге тестируется сценарий, которого в проде нет.
3. Самая коварная ловушка - общий DbContext
Если тест и код используют один экземпляр DbContext, EF может вернуть данные из change tracker, а не из базы.
Тест зелёный, но он врёт: реальный SQL-запрос мог вообще не выполниться.
Между Act и Assert стоит чистить трекер:
4. Бонус, который теряют 90% команд - тест миграций
Реальная БД позволяет прогнать EF-миграции на чистой схеме.
Если миграция падает или схема разъехалась с моделью, вы узнаёте об этом в CI, а не в проде в пятницу вечером.
Пример базового подхода:
Testcontainers - это не галочка «best practice», а смена философии.
Без нормальной изоляции данных вы просто пересели с быстрого вранья на медленное.
А как вы изолируете состояние БД между интеграционными тестами - Respawn, транзакции или пересоздание контейнера?
#dotnet #csharp #testing #efcore
Все уже выучили: EF Core InMemory provider - не интеграционный тест.
Он не ловит:
- баги в LINQ-трансляции
- ограничения БД
- коллации
- реальные типы колонок
- поведение конкретного SQL-провайдера
Окей, заменили на реальный PostgreSQL через Testcontainers. Победа? Не совсем.
Вот что начинается дальше.
1. Вы получили «медленное враньё» вместо «быстрого»
Поднимать контейнер на каждый тест-класс - быстрый способ превратить CI из 30 секунд в 8 минут.
Нормальный вариант:
- один контейнер на всю тестовую сессию
- изоляция данных между тестами через Respawn
- без пересоздания базы и контейнера каждый раз
Respawn чистит таблицы с учётом графа foreign keys за миллисекунды.
2. Транзакционный откат ≠ реальный сценарий
Трюк «обернули тест в транзакцию и откатили» красиво выглядит, но ломается, когда в коде есть:
- свои транзакции
- несколько SaveChanges
- фоновые операции
- поведение, завязанное на commit
В итоге тестируется сценарий, которого в проде нет.
3. Самая коварная ловушка - общий DbContext
Если тест и код используют один экземпляр DbContext, EF может вернуть данные из change tracker, а не из базы.
Тест зелёный, но он врёт: реальный SQL-запрос мог вообще не выполниться.
Между Act и Assert стоит чистить трекер:
Db.ChangeTracker.Clear();
4. Бонус, который теряют 90% команд - тест миграций
Реальная БД позволяет прогнать EF-миграции на чистой схеме.
Если миграция падает или схема разъехалась с моделью, вы узнаёте об этом в CI, а не в проде в пятницу вечером.
Пример базового подхода:
public class IntegrationTestBase : IAsyncLifetime
{
private static readonly PostgreSqlContainer _db =
new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.Build();
private Respawner _respawner = null!;
protected AppDbContext Db = null!;
public async Task InitializeAsync()
{
await _db.StartAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(_db.GetConnectionString())
.Options;
Db = new AppDbContext(options);
// Реальные миграции - заодно проверяем, что они накатываются
await Db.Database.MigrateAsync();
await using var conn = new NpgsqlConnection(_db.GetConnectionString());
await conn.OpenAsync();
_respawner = await Respawner.CreateAsync(conn, new RespawnerOptions
{
DbAdapter = DbAdapter.Postgres,
SchemasToInclude = ["public"]
});
}
// Сброс данных перед каждым тестом - без пересоздания контейнера
protected async Task ResetAsync()
{
await using var conn = new NpgsqlConnection(_db.GetConnectionString());
await conn.OpenAsync();
await _respawner.ResetAsync(conn);
// Иначе тест может читать из кеша, а не из БД
Db.ChangeTracker.Clear();
}
public Task DisposeAsync() => Task.CompletedTask;
}
Testcontainers - это не галочка «best practice», а смена философии.
Без нормальной изоляции данных вы просто пересели с быстрого вранья на медленное.
А как вы изолируете состояние БД между интеграционными тестами - Respawn, транзакции или пересоздание контейнера?
#dotnet #csharp #testing #efcore
Если в приложении нужно обрабатывать поток данных от продюсеров к потребителям, первым делом вспоминают про очереди или ConcurrentQueue. Но в асинхронном мире этого мало. Поэтому в .NET есть каналы.
Канал устроен как пара из продюсера и потребителя. Когда продюсер пишет элемент в канал, он либо сразу уходит к потребителю, либо ждёт, если очередь переполнена. Потребитель, в свою очередь, может ждать новые элементы без блокировки потоков — всё работает на async/await.
Главная сила каналов — балансировка нагрузки.
Ограниченные каналы позволяют держать под контролем количество элементов. Это защищает приложение от перегрузки: если продюсер работает быстрее, чем потребитель, система сама притормозит поток данных.
Неограниченные каналы — вариант попроще, они всегда принимают новые элементы, но это может обернуться непредсказуемым ростом памяти.
Мини-пример:
var channel = Channel.CreateBounded<int>(5);
// producer
_ = Task.Run(async () =>
{
for (int i = 0; i < 10; i++)
{
await channel.Writer.WriteAsync(i);
Console.WriteLine($"Produced {i}");
}
channel.Writer.Complete();
});
// consumer
await foreach (var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine($"Consumed {item}");
}
Почему это лучше, чем ConcurrentQueue? Потому что Channel создан сразу с учётом асинхронности. Вам не нужно городить блокировки и таймеры ожидания — достаточно написать
await reader.ReadAsync(), и код будет сам по себе масштабироваться без блокировки потоков.#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача по .NET: зачем нужен IHttpClientFactory?
Есть сервис, который ходит во внешний API:
Регистрация:
Вопрос: почему это лучше, чем создавать
Потому что
А
Есть сервис, который ходит во внешний API:
public sealed class PaymentApiClient
{
private readonly HttpClient _http;
public PaymentApiClient(HttpClient http)
{
_http = http;
}
public async Task<string> GetStatusAsync(string id, CancellationToken ct)
{
using var response = await _http.GetAsync($"/payments/{id}", ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(ct);
}
}
Регистрация:
builder.Services.AddHttpClient<PaymentApiClient>(client =>
{
client.BaseAddress = new Uri("https://payments.example.com");
client.Timeout = TimeSpan.FromSeconds(10);
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
MaxConnectionsPerServer = 50
});
Вопрос: почему это лучше, чем создавать
new HttpClient() на каждый запрос?Потому что
IHttpClientFactory переиспользует HttpMessageHandler и соединения под капотом. Это снижает риск socket exhaustion, даёт централизованную настройку таймаутов, ретраев, логирования и авторизации.А
PooledConnectionLifetime нужен, чтобы соединения не жили вечно и приложение могло корректно подхватывать изменения DNS.Аллокации, которых нет в коде: охота на скрытый боксинг в .NET 10
Самая дорогая аллокация в вашем сервисе та, которой нет в исходниках. Вы написали struct ради zero-allocation, прошли code review, а в проде Gen0-коллекции все равно идут косяком. Потому что между вашим кодом и машинным кодом стоит компилятор, и он молча упаковывает ваш value-тип в кучу там, где вы этого не просили — а на код-ревью этого не видно.
TL;DR. Боксинг (boxing) в .NET - это не только object o = 42. Он прячется в вызовах интерфейсных методов на struct, в дефолтном ValueType.Equals, в params object[]-аргументах, в foreach по интерфейсу и в замыканиях. При этом часть “классических” примеров боксинга из старых гайдов на современном рантайме уже не аллоцирует — JIT научился их вырезать, и слепо копировать советы десятилетней давности вредно. Ниже — карта мест, где боксинг живёт и сейчас, отдельный разбор того, что рантайм уже оптимизировал, реальный мини-кейс, воспроизводимый бенчмарк на BenchmarkDotNet с MemoryDiagnoser, способ ловить упаковку через DOTNET_JitDisasm и dotnet-gcdump, и паттерны лечения без потери читаемости.
О версиях и числах. Всё прверялось на .NET 10 (текущий LTS) и C# 13/14-уровне компилятора, Release, без отладчика, BenchmarkDotNet с MemoryDiagnoser. На .NET 8/9 поведение в основном такое же, но отдельные оптимизации JIT отличаются между мажорными версиями — поэтому главный принцип статьи: не верьте на слово (в том числе мне), гоняйте MemoryDiagnoser на своей версии рантайма. Числа в таблицах ниже - иллюстративные, порядок величины, а не точные замеры с вашего железа.
Пролог: “у нас же всё на struct, откуда Gen0?”
Сервис на горячем пути считает метрики: миллионы маленьких readonly struct-значений в секунду, никакого new, никаких классов в hot path. По задумке — ноль аллокаций. На дашборде — стабильный поток Gen0-коллекций раз в несколько секунд под нагрузкой.
Профайлер показывает аллокации, но стек ведёт в метод, где в коде нет ни одного new. Там цикл по интерфейсу, пара вызовов .Equals(), передача значения в params-метод лога. Глазами — чисто. В машинном коде — box-инструкции на каждой итерации.
Это и есть скрытый боксинг: компилятор C# и JIT упаковывают ваш struct в объект на куче, потому что в конкретной точке кода value-тип нужно представить как ссылочный. Симптом — Gen0-коллекции “из ниоткуда”, и его не видно ни в code review, ни в дампе, пока не посмотришь на IL или дизасм.
Если тема близка - я регулярно разбираю такие штуки по C# и .NET (внутренности рантайма, перформанс, неочевидные грабли с замерами и дизасмом) в своём Telegram-канале: t.me/csharp_ci. Заходите, если интересно копаться глубже.
Что такое боксинг и почему он стоит дорого
Боксинг — это упаковка value-типа (struct, enum, примитив) в объект на управляемой куче. Рантайму нужно выделить заголовок объекта, скопировать туда значение и вернуть ссылку. Анбоксинг - обратная операция с проверкой типа.
Цена не в самой инструкции, а в последствиях: каждая упаковка - это аллокация в Gen0. Много мелких аллокаций на горячем пути означают частые Gen0-коллекции, паузы (пусть и короткие), вытеснение полезных данных из кэша и общий рост CPU на ровном месте. На сервисе с SLA по p99 это бьёт по хвосту латентности так же, как и любая другая лишняя аллокация.
В IL боксинг виден явно - инструкция box. Именно её мы и будем искать.
Читать дальше: https://habr.com/ru/articles/1049236/
Самая дорогая аллокация в вашем сервисе та, которой нет в исходниках. Вы написали struct ради zero-allocation, прошли code review, а в проде Gen0-коллекции все равно идут косяком. Потому что между вашим кодом и машинным кодом стоит компилятор, и он молча упаковывает ваш value-тип в кучу там, где вы этого не просили — а на код-ревью этого не видно.
TL;DR. Боксинг (boxing) в .NET - это не только object o = 42. Он прячется в вызовах интерфейсных методов на struct, в дефолтном ValueType.Equals, в params object[]-аргументах, в foreach по интерфейсу и в замыканиях. При этом часть “классических” примеров боксинга из старых гайдов на современном рантайме уже не аллоцирует — JIT научился их вырезать, и слепо копировать советы десятилетней давности вредно. Ниже — карта мест, где боксинг живёт и сейчас, отдельный разбор того, что рантайм уже оптимизировал, реальный мини-кейс, воспроизводимый бенчмарк на BenchmarkDotNet с MemoryDiagnoser, способ ловить упаковку через DOTNET_JitDisasm и dotnet-gcdump, и паттерны лечения без потери читаемости.
О версиях и числах. Всё прверялось на .NET 10 (текущий LTS) и C# 13/14-уровне компилятора, Release, без отладчика, BenchmarkDotNet с MemoryDiagnoser. На .NET 8/9 поведение в основном такое же, но отдельные оптимизации JIT отличаются между мажорными версиями — поэтому главный принцип статьи: не верьте на слово (в том числе мне), гоняйте MemoryDiagnoser на своей версии рантайма. Числа в таблицах ниже - иллюстративные, порядок величины, а не точные замеры с вашего железа.
Пролог: “у нас же всё на struct, откуда Gen0?”
Сервис на горячем пути считает метрики: миллионы маленьких readonly struct-значений в секунду, никакого new, никаких классов в hot path. По задумке — ноль аллокаций. На дашборде — стабильный поток Gen0-коллекций раз в несколько секунд под нагрузкой.
Профайлер показывает аллокации, но стек ведёт в метод, где в коде нет ни одного new. Там цикл по интерфейсу, пара вызовов .Equals(), передача значения в params-метод лога. Глазами — чисто. В машинном коде — box-инструкции на каждой итерации.
Это и есть скрытый боксинг: компилятор C# и JIT упаковывают ваш struct в объект на куче, потому что в конкретной точке кода value-тип нужно представить как ссылочный. Симптом — Gen0-коллекции “из ниоткуда”, и его не видно ни в code review, ни в дампе, пока не посмотришь на IL или дизасм.
Если тема близка - я регулярно разбираю такие штуки по C# и .NET (внутренности рантайма, перформанс, неочевидные грабли с замерами и дизасмом) в своём Telegram-канале: t.me/csharp_ci. Заходите, если интересно копаться глубже.
Что такое боксинг и почему он стоит дорого
Боксинг — это упаковка value-типа (struct, enum, примитив) в объект на управляемой куче. Рантайму нужно выделить заголовок объекта, скопировать туда значение и вернуть ссылку. Анбоксинг - обратная операция с проверкой типа.
Цена не в самой инструкции, а в последствиях: каждая упаковка - это аллокация в Gen0. Много мелких аллокаций на горячем пути означают частые Gen0-коллекции, паузы (пусть и короткие), вытеснение полезных данных из кэша и общий рост CPU на ровном месте. На сервисе с SLA по p99 это бьёт по хвосту латентности так же, как и любая другая лишняя аллокация.
В IL боксинг виден явно - инструкция box. Именно её мы и будем искать.
Читать дальше: https://habr.com/ru/articles/1049236/
Что выведет код?
А —
Б —
В —
Г —
Правильный ответ: В — A1AB2.
Почему: IEnumerable и LINQ выполняются лениво. First() запускает перебор один раз и доходит только до первого элемента: печатает A, потом 1. Count() запускает перебор заново: снова A, потом B, и в конце печатает 2.
using System;
using System.Collections.Generic;
using System.Linq;
static IEnumerable<int> GetNumbers()
{
Console.Write("A");
yield return 1;
Console.Write("B");
yield return 2;
}
var query = GetNumbers().Where(x => x > 0);
Console.Write(query.First());
Console.Write(query.Count());
А —
A12Б —
A1B2В —
A1AB2Г —
ErrorПочему: IEnumerable и LINQ выполняются лениво. First() запускает перебор один раз и доходит только до первого элемента: печатает A, потом 1. Count() запускает перебор заново: снова A, потом B, и в конце печатает 2.
Продвинутый C#-трюк: обновляй `Dictionary` без двойного поиска
Многие пишут так:
Проблема: ты сначала ищешь ключ через
В hot path это лишняя работа.
Есть более взрослый вариант:
Что происходит:
1. C# получает ссылку прямо на значение внутри
2. ключ ищется один раз
3. значение можно менять без повторного обращения
4. меньше лишних операций в tight loop
Где это полезно:
1. счётчики событий
2. парсеры
3. агрегации
4. обработка логов
5. high-performance backend code
Но есть важный нюанс:
не меняй структуру словаря, пока держишь
То есть не делай
Это не трюк для каждого CRUD-сервиса.
Это инструмент для мест, где C# уже упёрся в производительность, и ты начинаешь выжимать лишние аллокации и лишние lookup’и.
Многие пишут так:
if (dict.TryGetValue(key, out var value))
{
dict[key] = value + 1;
}
else
{
dict[key] = 1;
}
Проблема: ты сначала ищешь ключ через
TryGetValue, а потом снова лезешь в словарь через dict[key].В hot path это лишняя работа.
Есть более взрослый вариант:
using System.Runtime.InteropServices;
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(
dict,
key,
out var exists
);
if (!exists)
{
count = 0;
}
count++;
Что происходит:
1. C# получает ссылку прямо на значение внутри
Dictionary2. ключ ищется один раз
3. значение можно менять без повторного обращения
4. меньше лишних операций в tight loop
Где это полезно:
1. счётчики событий
2. парсеры
3. агрегации
4. обработка логов
5. high-performance backend code
Но есть важный нюанс:
не меняй структуру словаря, пока держишь
ref.То есть не делай
Add, Remove, Clear рядом с этой ссылкой.Это не трюк для каждого CRUD-сервиса.
Это инструмент для мест, где C# уже упёрся в производительность, и ты начинаешь выжимать лишние аллокации и лишние lookup’и.
Методы, их перегрузка и расширения. Бесплатный урок специализации «C#-разработчик»
Методы — одна из базовых вещей в C#, без которой невозможно нормально писать, читать и поддерживать код. Но у начинающих разработчиков часто всё смешивается: где обычный метод, где перегрузка, как работает сигнатура, зачем нужны параметры по умолчанию и в каких случаях использовать params.
На открытом уроке 2 июля в 20:00 разберём, что такое метод в C#, как писать собственные методы и как использовать перегрузку без хаоса в коде. Поговорим о сигнатуре метода, параметрах по умолчанию, ключевом слове params и методах-расширениях. На примерах покажем, как эти механики помогают делать код понятнее, гибче и удобнее для повторного использования.
Урок не для тех, кто хочет просто «выучить синтаксис» без понимания, как методы влияют на структуру программы.
👉 Записаться: https://otus.pw/wkKs/?erid=2W5zFG7dRZ3
Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
Методы — одна из базовых вещей в C#, без которой невозможно нормально писать, читать и поддерживать код. Но у начинающих разработчиков часто всё смешивается: где обычный метод, где перегрузка, как работает сигнатура, зачем нужны параметры по умолчанию и в каких случаях использовать params.
На открытом уроке 2 июля в 20:00 разберём, что такое метод в C#, как писать собственные методы и как использовать перегрузку без хаоса в коде. Поговорим о сигнатуре метода, параметрах по умолчанию, ключевом слове params и методах-расширениях. На примерах покажем, как эти механики помогают делать код понятнее, гибче и удобнее для повторного использования.
Урок не для тех, кто хочет просто «выучить синтаксис» без понимания, как методы влияют на структуру программы.
👉 Записаться: https://otus.pw/wkKs/?erid=2W5zFG7dRZ3
Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
Record-типы в C#: когда модель данных не должна быть обычным class
Типичный пример:
Компилятор сам сгенерирует конструктор,
Обычный
Это делает
Ещё одна удобная вещь -
Но есть важный нюанс:
Такой
Но когда тип нужен как чистая модель данных,
record в C# удобен там, где объект описывает данные, а не поведение и идентичность.Типичный пример:
public record User(string Name, int Age);
Компилятор сам сгенерирует конструктор,
Equals, GetHashCode, ToString и деконструкцию. Но главное отличие не в сокращении кода, а в семантике.Обычный
class сравнивается по ссылке:
var a = new UserClass("Alice", 25);
var b = new UserClass("Alice", 25);
Console.WriteLine(a == b); // false
record сравнивается по значению:
var a = new User("Alice", 25);
var b = new User("Alice", 25);
Console.WriteLine(a == b); // true
Это делает
record хорошим выбором для DTO, read models, value objects, событий, результатов запросов и моделей, где важны значения полей.Ещё одна удобная вещь -
with. Можно создать копию объекта, изменив только нужные поля:
var user = new User("Alice", 25);
var updated = user with { Age = 26 };
Console.WriteLine(updated);
// User { Name = Alice, Age = 26 }
Но есть важный нюанс:
with делает поверхностную копию. Если внутри есть изменяемая коллекция, она не станет автоматически immutable.
public record Team(string Name, List<string> Members);
Такой
record всё ещё может меняться через Members.Add(...). Поэтому для реально неизменяемых моделей лучше использовать immutable-коллекции или аккуратно закрывать доступ к изменяемому состоянию.record не заменяет class везде. Если у объекта есть жизненный цикл, identity, состояние и бизнес-поведение, обычный класс часто будет честнее.Но когда тип нужен как чистая модель данных,
record убирает шум и делает намерение в коде очевидным.⚡️ GPT-5.6 РЕЛИЗ
OpenAI выкатили сразу три новые модели.
• Sol - заявлено, что модель мощнее Mythos. Доступ для платных пользователей обещают в течение 24 часов.
На Terminal Bench 2.1 с настройкой Ultra модель выбивает рекордные 91,9%.
Первые тестеры отдельно отмечают сильную работу с интерфейсами: она уверенно собирает UI для приложений и сайтов, а не просто генерирует сырой код.
• Terra - уровень Fable 5. Будет доступна бесплатно.
• Luna - еще одна бесплатная модель для всех.
Помимо самой модели, показали 3 крупных продуктовых обновления:
1. ChatGPT Work
2. новое desktop-приложение ChatGPT
3. hosted sites, то есть размещение сайтов прямо через Chatgpt
https://openai.com/ru-RU/live/
OpenAI выкатили сразу три новые модели.
• Sol - заявлено, что модель мощнее Mythos. Доступ для платных пользователей обещают в течение 24 часов.
На Terminal Bench 2.1 с настройкой Ultra модель выбивает рекордные 91,9%.
Первые тестеры отдельно отмечают сильную работу с интерфейсами: она уверенно собирает UI для приложений и сайтов, а не просто генерирует сырой код.
• Terra - уровень Fable 5. Будет доступна бесплатно.
• Luna - еще одна бесплатная модель для всех.
Помимо самой модели, показали 3 крупных продуктовых обновления:
1. ChatGPT Work
2. новое desktop-приложение ChatGPT
3. hosted sites, то есть размещение сайтов прямо через Chatgpt
https://openai.com/ru-RU/live/
Полезный Docker-трюк для .NET: кэшируйте NuGet-пакеты через BuildKit, а не скачивайте их заново при каждой сборке.
NuGet-кэш не попадает в финальный image, но между сборками сохраняется. В итоге
# syntax=docker/dockerfile:1.7
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY *.csproj .
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
COPY . .
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish -c Release -o /app
NuGet-кэш не попадает в финальный image, но между сборками сохраняется. В итоге
restore работает быстрее, особенно в CI, где зависимости обычно весят больше, чем сам код.🔥 Хочешь быстрее расти в IT? Хватит учиться в одиночку
В IT прокачивается тот, кто каждый день видит сильные идеи, новые инструменты, реальные задачи, вакансии и разборы.
Окружение решает больше, чем кажется.
Собрал папки и каналы, где можно быстрее влиться в нужное направление, следить за трендами и не вариться в своём пузыре.
AI: t.me/ai_machinelearning_big_data
Python: t.me/pythonl
Linux: t.me/linuxacademiya
Хакинг: t.me/linuxkalii
DevOps: t.me/DevOPSitsec
Docker: t.me/DevopsDocker
Golang: t.me/Golang_google
Rust: t.me/rust_code
C++: t.me/cpluspluc
C#: t.me/csharp_1001_notes
Java: t.me/java_library
JavaScript: t.me/javascriptv
React: t.me/react_tg
Frontend: t.me/front
PHP: t.me/phpshka
Android: t.me/android_its
Мобильная разработка: t.me/mobdevelop
Базы данных: t.me/sqlhub
Data Science: t.me/data_analysis_ml
Big Data: t.me/bigdatai
Математика: t.me/data_math
Физика: t.me/fizmat
Kubernetes: t.me/kubernetc
GameDev: https://xn--r1a.website/gamedev
Haskell: t.me/haskell_tg
Собеседования и карьера:
DS собеседования: t.me/machinelearning_interview
Python собеседования: t.me/python_job_interview
Папка с вакансиями: t.me/addlist/_zyy_jQ_QUsyM2Vi
Папка Go разработчика: t.me/addlist/MUtJEeJSxeY2YTFi
Папка Python разработчика: t.me/addlist/eEPya-HF6mkxMGIy
Папка ML: https://xn--r1a.website/addlist/2Ls-snqEeytkMDgy
Папка Frontend: https://xn--r1a.website/addlist/mzMMG3RPZhY2M2Iy
Полезное сверху:
ИТ-мемы: t.me/memes_prog
Английский для программистов: t.me/english_forprogrammers
ИИ и технологии: t.me/vistehno
954 ГБ open-source курсов: @courses
ИТ-книги бесплатно: https://xn--r1a.website/addlist/BkskQciUW_FhNjEy
Max Ai: https://max.ru/ai_machinelearning_big_data
Max python: https://max.ru/pythonl
ТЕХНО: https://max.ru/vistehno
Max Go: https://max.ru/Golang_google
Max Linux: https://max.ru/linuxkalii
Devops: https://max.ru/DevOPSitsec
C#: https://max.ru/csharp_ci
C++: https://max.ru/cpluspluc
SQL: https://max.ru/sqlhub
Java: https://max.ru/javatg
Подписывайся на нужные направления и собирай себе ленту, которая реально двигает вперёд.
Пока кто-то листает шум, ты будешь видеть инструменты, задачи и идеи, которые помогают расти в профессии.
В IT прокачивается тот, кто каждый день видит сильные идеи, новые инструменты, реальные задачи, вакансии и разборы.
Окружение решает больше, чем кажется.
Собрал папки и каналы, где можно быстрее влиться в нужное направление, следить за трендами и не вариться в своём пузыре.
AI: t.me/ai_machinelearning_big_data
Python: t.me/pythonl
Linux: t.me/linuxacademiya
Хакинг: t.me/linuxkalii
DevOps: t.me/DevOPSitsec
Docker: t.me/DevopsDocker
Golang: t.me/Golang_google
Rust: t.me/rust_code
C++: t.me/cpluspluc
C#: t.me/csharp_1001_notes
Java: t.me/java_library
JavaScript: t.me/javascriptv
React: t.me/react_tg
Frontend: t.me/front
PHP: t.me/phpshka
Android: t.me/android_its
Мобильная разработка: t.me/mobdevelop
Базы данных: t.me/sqlhub
Data Science: t.me/data_analysis_ml
Big Data: t.me/bigdatai
Математика: t.me/data_math
Физика: t.me/fizmat
Kubernetes: t.me/kubernetc
GameDev: https://xn--r1a.website/gamedev
Haskell: t.me/haskell_tg
Собеседования и карьера:
DS собеседования: t.me/machinelearning_interview
Python собеседования: t.me/python_job_interview
Папка с вакансиями: t.me/addlist/_zyy_jQ_QUsyM2Vi
Папка Go разработчика: t.me/addlist/MUtJEeJSxeY2YTFi
Папка Python разработчика: t.me/addlist/eEPya-HF6mkxMGIy
Папка ML: https://xn--r1a.website/addlist/2Ls-snqEeytkMDgy
Папка Frontend: https://xn--r1a.website/addlist/mzMMG3RPZhY2M2Iy
Полезное сверху:
ИТ-мемы: t.me/memes_prog
Английский для программистов: t.me/english_forprogrammers
ИИ и технологии: t.me/vistehno
954 ГБ open-source курсов: @courses
ИТ-книги бесплатно: https://xn--r1a.website/addlist/BkskQciUW_FhNjEy
Max Ai: https://max.ru/ai_machinelearning_big_data
Max python: https://max.ru/pythonl
ТЕХНО: https://max.ru/vistehno
Max Go: https://max.ru/Golang_google
Max Linux: https://max.ru/linuxkalii
Devops: https://max.ru/DevOPSitsec
C#: https://max.ru/csharp_ci
C++: https://max.ru/cpluspluc
SQL: https://max.ru/sqlhub
Java: https://max.ru/javatg
Подписывайся на нужные направления и собирай себе ленту, которая реально двигает вперёд.
Пока кто-то листает шум, ты будешь видеть инструменты, задачи и идеи, которые помогают расти в профессии.
Каждый год в середине лета мы с друзьями собираемся на E-CODE. Ещё относительно молодая конфа Ozon Tech стала уже своего рода легендой. Не удивительно: хардовость официальной части здесь не уступает громкости афтерпати.
На E-CODE 2026 нас по части бэкэнда ждут:
• Runtime Async с его + и –
• трассировка с eBPF
• ускорение на SIMD
• построение кастомной real-time системы видеоаналитики
• сравнительный анализ алгоритмов сборки мусора
Фишка этого года — брейнринг разработчиков и ИИ. Эксперты разберут сложные кейсы и сравнят свои подходы с тем, что предложит машина. Победителя выберет зал.
Регистрируйтесь сейчас, чтобы услышать это всё вживую: https://ecode.ozon.tech/
12 и 13 сентября, Москва. До встречи на E-CODE!
На E-CODE 2026 нас по части бэкэнда ждут:
• Runtime Async с его + и –
• трассировка с eBPF
• ускорение на SIMD
• построение кастомной real-time системы видеоаналитики
• сравнительный анализ алгоритмов сборки мусора
Фишка этого года — брейнринг разработчиков и ИИ. Эксперты разберут сложные кейсы и сравнят свои подходы с тем, что предложит машина. Победителя выберет зал.
Регистрируйтесь сейчас, чтобы услышать это всё вживую: https://ecode.ozon.tech/
12 и 13 сентября, Москва. До встречи на E-CODE!
.NET 11 готовят под эпоху AI
Microsoft собрала ключевые .NET-сессии с Build 2026. Фокус - AI, агенты и развитие C#.
Что показали:
- union types в C#;
- улучшения runtime, SDK и производительности в .NET 11;
- инструменты для добавления AI в C#-приложения;
- agentic web в ASP.NET Core и Blazor;
- локальные AI-модели и on-device inference через .NET MAUI;
- новый
Важно: .NET 11 пока находится в preview, а часть возможностей ещё развивается.
https://devblogs.microsoft.com/dotnet/dotnet-at-microsoft-build-2026/
Microsoft собрала ключевые .NET-сессии с Build 2026. Фокус - AI, агенты и развитие C#.
Что показали:
- union types в C#;
- улучшения runtime, SDK и производительности в .NET 11;
- инструменты для добавления AI в C#-приложения;
- agentic web в ASP.NET Core и Blazor;
- локальные AI-модели и on-device inference через .NET MAUI;
- новый
dotnetup для установки и обновления SDK/runtime.Важно: .NET 11 пока находится в preview, а часть возможностей ещё развивается.
https://devblogs.microsoft.com/dotnet/dotnet-at-microsoft-build-2026/
This media is not supported in your browser
VIEW IN TELEGRAM
🚀 Claude Opus 5 за 24 часа собрал собственный космический симулятор
Разработчик запустил модель на одну непрерывную сессию и получил The Long Silence - полноценную браузерную игру об исследовании космоса. По словам автора, весь код, визуал и звук создал Claude.
Внутри:
• процедурные звёзды, планеты, кольца и туманности
• посадка на поверхность планет
• перелёты между системами и fold drive
• сканирование объектов, станции и заброшенные корабли
• карта галактики, сюжет и архив найденных данных
• динамическое масштабирование графики для стабильных 60 FPS
Игра работает на WebGL2 без готовых ассетов: миры генерируются из seed, графика написана на GLSL, а музыка и звук создаются процедурно.
Это ещё не Starfield по масштабу, но для проекта, собранного моделью за сутки, результат выглядит безумно.
🎮 Код и запуск: https://github.com/achimala/TheLongSilence
🌐 Запустить в браузере: https://longsilence.anshu.dev/
Разработчик запустил модель на одну непрерывную сессию и получил The Long Silence - полноценную браузерную игру об исследовании космоса. По словам автора, весь код, визуал и звук создал Claude.
Внутри:
• процедурные звёзды, планеты, кольца и туманности
• посадка на поверхность планет
• перелёты между системами и fold drive
• сканирование объектов, станции и заброшенные корабли
• карта галактики, сюжет и архив найденных данных
• динамическое масштабирование графики для стабильных 60 FPS
Игра работает на WebGL2 без готовых ассетов: миры генерируются из seed, графика написана на GLSL, а музыка и звук создаются процедурно.
Это ещё не Starfield по масштабу, но для проекта, собранного моделью за сутки, результат выглядит безумно.
🎮 Код и запуск: https://github.com/achimala/TheLongSilence
🌐 Запустить в браузере: https://longsilence.anshu.dev/
🔥 EF Core migrations - это компилятор схемы базы, а не кнопка «создать таблицу»
Опытный C#-разработчик не оставляет бизнес-инварианты только внутри
Проверку в приложении можно обойти прямым SQL, импортом данных, фоновым процессом или второй версией сервиса. Поэтому правила, нарушение которых делает данные некорректными, стоит закреплять в самой базе.
Уникальный индекс решает другую важную проблему - гонку между запросами.
Два параллельных запроса могут одновременно пройти
Использовать для уникальности исходный
После изменения модели создаём миграцию:
Но миграцию нельзя принимать вслепую. EF Core сравнивает текущую модель с
Например, обычное переименование свойства может быть распознано как удаление старого столбца и создание нового:
В production это означает потерю данных. Правильную операцию нужно указать вручную:
Для крупных таблиц полезен подход
Сначала добавляется nullable-столбец:
Затем данные заполняются порциями отдельной задачей. После обновления всех строк новая версия приложения начинает записывать оба значения. И только в следующем релизе столбец переводится в
Так миграция не держит долгую блокировку и остаётся совместимой сразу с несколькими версиями приложения.
В CI стоит проверять, не забыл ли разработчик создать миграцию:
Для production лучше генерировать проверяемый SQL:
Либо собирать отдельный executable:
Опытный C#-разработчик не оставляет бизнес-инварианты только внутри
SaveChangesAsync().Проверку в приложении можно обойти прямым SQL, импортом данных, фоновым процессом или второй версией сервиса. Поэтому правила, нарушение которых делает данные некорректными, стоит закреплять в самой базе.
public sealed class ProductConfiguration
: IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.ToTable("Products", table =>
{
table.HasCheckConstraint(
"CK_Products_Price_Range",
"[Price] > 0 AND [Price] <= 10000000");
});
builder.Property(x => x.Name)
.HasMaxLength(200)
.IsRequired();
builder.Property(x => x.NormalizedName)
.HasMaxLength(200)
.IsRequired();
builder.Property(x => x.Price)
.HasPrecision(18, 2);
builder.HasIndex(x => x.NormalizedName)
.IsUnique()
.HasDatabaseName("UX_Products_NormalizedName");
}
}
HasPrecision(18, 2) управляет физическим представлением decimal, но не проверяет допустимый диапазон цены. За это отвечает CHECK.Уникальный индекс решает другую важную проблему - гонку между запросами.
if (!await db.Products.AnyAsync(x => x.Name == name))
{
db.Products.Add(product);
await db.SaveChangesAsync();
}
Два параллельных запроса могут одновременно пройти
AnyAsync() и попытаться создать одинаковые записи. Только уникальное ограничение базы гарантированно остановит второй запрос.Использовать для уникальности исходный
Name тоже опасно. Результат будет зависеть от collation базы: Laptop, LAPTOP и Laptop могут считаться одинаковыми или разными. Поэтому часто сохраняют нормализованное значение:
product.NormalizedName = product.Name
.Trim()
.ToUpperInvariant();
После изменения модели создаём миграцию:
dotnet ef migrations add HardenProductSchema
Но миграцию нельзя принимать вслепую. EF Core сравнивает текущую модель с
ModelSnapshot и генерирует предполагаемый переход между двумя состояниями.Например, обычное переименование свойства может быть распознано как удаление старого столбца и создание нового:
migrationBuilder.DropColumn(
name: "Name",
table: "Products");
migrationBuilder.AddColumn<string>(
name: "DisplayName",
table: "Products",
nullable: false);
В production это означает потерю данных. Правильную операцию нужно указать вручную:
migrationBuilder.RenameColumn(
name: "Name",
table: "Products",
newName: "DisplayName");
Для крупных таблиц полезен подход
expand -> backfill -> contract.Сначала добавляется nullable-столбец:
migrationBuilder.AddColumn<string>(
name: "NormalizedName",
table: "Products",
type: "nvarchar(200)",
nullable: true);
Затем данные заполняются порциями отдельной задачей. После обновления всех строк новая версия приложения начинает записывать оба значения. И только в следующем релизе столбец переводится в
NOT NULL, добавляется индекс и удаляется устаревшая логика.Так миграция не держит долгую блокировку и остаётся совместимой сразу с несколькими версиями приложения.
В CI стоит проверять, не забыл ли разработчик создать миграцию:
dotnet ef migrations has-pending-model-changes
Для production лучше генерировать проверяемый SQL:
dotnet ef migrations script \
--idempotent \
--output migrations.sql
Либо собирать отдельный executable:
dotnet ef migrations bundle \
--self-contained \
-r linux-x64
efbundle можно запускать в deployment job без исходников проекта и установленного EF CLI.🚀 C# 15 меняет подход к `unsafe`: указатели больше не означают автоматически опасный код
Раньше в C# сам факт использования указателя требовал:
Теперь язык движется к более точной модели:
опасным считается не наличие указателя, а операция, которая реально работает с unmanaged-памятью.
В preview-версии C# 15 уже не требуют
✅ объявление pointer-типа
✅ получение адреса через
✅
✅ преобразование
✅
Пример:
Но вот это всё ещё требует
Главная идея:
**создать инструмент можно безопасно.
Опасно — когда начинаешь напрямую менять память.**
Это первый этап большой переработки модели безопасности C#.
Дальше ожидаются:
-
- opt-in на уровне assembly;
- новый
C# постепенно делает unsafe-код более точечным: меньше блоков вокруг всего файла, больше внимания к реально опасным операциям.
#csharp #dotnet #programming
Раньше в C# сам факт использования указателя требовал:
unsafe
{
int* ptr;
}
Теперь язык движется к более точной модели:
опасным считается не наличие указателя, а операция, которая реально работает с unmanaged-памятью.
В preview-версии C# 15 уже не требуют
unsafe:✅ объявление pointer-типа
✅ получение адреса через
& ✅
fixed для закрепления памяти ✅ преобразование
stackalloc в указатель ✅
sizeof для unmanaged-типов Пример:
int number = 42;
int* pointer = &number;
int[] numbers = [10, 20, 30];
fixed (int* first = numbers)
{
// само закрепление памяти безопасно
}
Но вот это всё ещё требует
unsafe:
*pointer; // dereference
pointer->Value; // доступ через указатель
pointer[0]; // работа с элементами
delegate*<> // вызов function pointer
Главная идея:
**создать инструмент можно безопасно.
Опасно — когда начинаешь напрямую менять память.**
Это первый этап большой переработки модели безопасности C#.
Дальше ожидаются:
-
requires-unsafe для отдельных членов;- opt-in на уровне assembly;
- новый
safe keyword.C# постепенно делает unsafe-код более точечным: меньше блоков вокруг всего файла, больше внимания к реально опасным операциям.
#csharp #dotnet #programming
⚡️ C#-приём: не привязывай метаданные к объектам через обычный `Dictionary` — иногда нужен `ConditionalWeakTable`.
Допустим, библиотека кеширует данные о типах:
В обычном приложении это может годами работать без проблем.
Но если используются plugins, dynamic assemblies или unloadable
И получить memory leak.
Для таких случаев в .NET есть малоизвестный:
Пример:
Ключ здесь хранится слабо.
Если
Это особенно полезно для:
* reflection-кешей;
* proxy/framework-кода;
* source/runtime metadata;
* plugin-систем;
* приложений с
🔥 Если видишь:
или
в коде, который работает с динамически загружаемыми сборками, стоит проверить:
не удерживает ли этот кеш сборки в памяти навсегда?
Иногда правильный ответ —
#CSharp #DotNet #Backend #Programming #CLR
Допустим, библиотека кеширует данные о типах:
private static readonly Dictionary<Type, Metadata> Cache = new();
В обычном приложении это может годами работать без проблем.
Но если используются plugins, dynamic assemblies или unloadable
AssemblyLoadContext, такой словарь способен удерживать Type, а вместе с ним — всю загруженную сборку.И получить memory leak.
Для таких случаев в .NET есть малоизвестный:
ConditionalWeakTable<TKey, TValue>
Пример:
private static readonly ConditionalWeakTable<Type, Metadata> Cache = new();
static Metadata GetMetadata(Type type)
{
return Cache.GetValue(type, BuildMetadata);
}
Ключ здесь хранится слабо.
Если
Type больше нигде не нужен, GC может его собрать, а связанная запись автоматически исчезнет из таблицы.Это особенно полезно для:
* reflection-кешей;
* proxy/framework-кода;
* source/runtime metadata;
* plugin-систем;
* приложений с
AssemblyLoadContext.🔥 Если видишь:
static Dictionary<Type, ...>
или
static ConcurrentDictionary<Type, ...>
в коде, который работает с динамически загружаемыми сборками, стоит проверить:
не удерживает ли этот кеш сборки в памяти навсегда?
Иногда правильный ответ —
ConditionalWeakTable.#CSharp #DotNet #Backend #Programming #CLR
Rate Limiting и Throttling API в .NET
В ASP.NET Core есть встроенные механизмы, которые помогают защитить API от перегрузки и слишком частых запросов.
Можно ограничивать трафик:
— по IP
— по пользователю
— по endpoint
— по фиксированному окну
— через sliding window
— token bucket
— concurrency limiter
Это полезно не только против DDoS. Rate limiting помогает не дать одному клиенту забить весь сервис, защитить дорогие операции и стабилизировать нагрузку на БД и внешние API.
В .NET это можно настроить прямо в middleware без сторонних библиотек.
Хороший разбор с примерами:
https://tundehub.dev/rate-limiting-and-throttling-apis-in-net
#aspnetcore #dotnet
В ASP.NET Core есть встроенные механизмы, которые помогают защитить API от перегрузки и слишком частых запросов.
Можно ограничивать трафик:
— по IP
— по пользователю
— по endpoint
— по фиксированному окну
— через sliding window
— token bucket
— concurrency limiter
Это полезно не только против DDoS. Rate limiting помогает не дать одному клиенту забить весь сервис, защитить дорогие операции и стабилизировать нагрузку на БД и внешние API.
В .NET это можно настроить прямо в middleware без сторонних библиотек.
Хороший разбор с примерами:
https://tundehub.dev/rate-limiting-and-throttling-apis-in-net
#aspnetcore #dotnet