.NET Разработчик
前往频道在 Telegram
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#. Для связи: @SBenzenko Поддержать канал: - https://boosty.to/netdeveloperdiary - https://patreon.com/user?u=52551826 - https://pay.cloudtips.ru/p/70df3b3b
显示更多6 702
订阅者
-124 小时
-37 天
+1930 天
帖子存档
6 702
День 2634. #TipsAndTricks #Git
Команды Git, Которые Я Запускаю Перед Чтением Кода
Автор оригинала: Ally Piechowski
Я занимаюсь аудитом кодовых баз моих клиентов. Первое, что я обычно делаю, когда берусь за новую кодовую базу, открываю терминал и запускаю несколько команд Git. Я хочу понять, кто основные авторы, где сосредоточены проблемы, уверенно ли команда выпускает продукт или ходит по минному полю.
1. Что меняется чаще всего
git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20
20 самых часто изменяемых файлов за последний год. Файл, находящийся вверху списка, почти всегда тот, о котором говорят: «К нему все боятся прикасаться».
Высокая активность в файле - иногда это просто активная разработка. Но если авторов много — это явный признак проблемы. Это файл, где каждое изменение — это исправление исправления. Я беру 5 файлов из этого списка и сопоставляю их с командой поиска ошибок (см. п. 3). Файл с высокой активностью и большим количеством ошибок — это основной источник риска.
2. Кто авторы
git shortlog -sn --no-merges
Авторы по количеству коммитов. Если на одного человека приходится 60%+, это ваш «автобусный фактор». Если он ушёл полгода назад, это кризис. Если ведущий автор из списка не появляется в течение 6 месяцев (git shortlog -sn --no-merges --since="6 months ago"), я немедленно сообщаю об этом клиенту.
Я также смотрю на хвост. 30 авторов, но только 3 были активны в течение последнего года. Т.е. люди, которые создали эту систему, не те, кто её поддерживает.
Замечание: рабочие процессы объединения коммитов сжимают информацию об авторстве. Если команда объединяет каждый пул-реквест в один коммит, этот результат отражает, кто объединил, а не кто написал. Стоит уточнить стратегию объединения, прежде чем делать выводы.
3. Места ошибок
git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20
Та же структура, что и у 1й команды, но отфильтрована по коммитам с ключевыми словами, связанными с ошибками. Файлы, которые появляются в обоих списках, — это код с самым высоким риском: они постоянно ломаются и постоянно исправляются, но никогда не исправляются должным образом.
Это зависит от дисциплины в сообщениях коммитов. Если команда пишет случайные сообщения коммитов, вы ничего не получите. Но даже приблизительная карта плотности ошибок лучше, чем её отсутствие.
4. Проект ускоряется или умирает?
git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c
Количество коммитов по месяцам. Стабильный ритм — это хорошо. Но что, если количество коммитов падает в 2 раза за месяц? Обычно - кто-то ушёл. Снижение в течение 6-12 месяцев говорит о том, что команда теряет темп. Периодические всплески, за которыми следуют спокойные месяцы, означают, что команда работает над релизами партиями, а не выпускает их непрерывно.
5. Как часто «тушат пожары»?
git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'
Частота откатов и хотфиксов. Несколько откатов за год нормально. Каждые пару недель - команда не доверяет своему процессу развёртывания. Это свидетельство более глубокой проблемы: ненадёжные тесты, отсутствие приёмочных тестов или конвейер развертывания, который усложняет откаты. Отсутствие результатов – сигнал, что либо команда стабильна, либо никто не пишет подробные сообщения о коммитах.
Итого
Выполнение этих команд займет пару минут. Они не покажут вам всего. Но вы будете знать, какой код читать в первую очередь и что искать, когда дойдёте до него. В этом разница между методичным изучением кодовой базы с первого дня и бесцельным блужданием по коду.
Источник: https://piechowski.io/post/git-commands-before-reading-code/6 702
День 2633. #TipsAndTricks
Вызываем Файлы из Репозитория Действий в GitHub Actions
GitHub Actions позволяет создавать многократно используемые действия. Один из самых простых способов сделать это — использовать составные действия (Composite Actions), которые позволяют объединять несколько шагов в одно действие. Это уменьшает дублирование кода в ваших рабочих процессах.
При создании составного действия вам может потребоваться сослаться на файлы, хранящиеся в репозитории действий, например, на скрипт PowerShell или Bash.
По умолчанию, когда выполняется действие, рабочий каталог — это корень репозитория, использующего действие. Это означает, что относительные пути разрешаются в репозиторий вызывающего объекта, а не в репозиторий действия.
Для доступа к файлам из репозитория действий используйте контекстную переменную github.action_path, которая содержит путь к каталогу, где находится ваше действие.
Вот пример того, как использовать её в файле
action.yml:
name: 'My Composite Action'
description: 'An example'
runs:
using: "composite"
steps:
- name: Run script
run: ${{ github.action_path }}/script.sh
shell: bash
В этом примере файл script.sh находится в корневом каталоге репозитория действий. Использование ${{ github.action_path }}/script.sh гарантирует, что исполнитель выполнит скрипт из правильного места.
Дополнительные ресурсы:
- Контексты GitHub - github.action_path
- Создание составного действия
Источник: https://www.meziantou.net/accessing-files-from-the-action-repository-in-a-github-composite-action.htm6 702
День 2632. #МоиИнструменты #PG
Инструменты Оптимизации Запросов в PostgreSQL. Часть 6
6. SQLFluff (SQL-линтер для производительности)
Что даёт: выявляет антипаттерны производительности до того, как запросы попадут в прод.
Зачем нужен: Большинство проблем с производительностью SQL возникают из-за распространённых ошибок: SELECT *, ненужные DISTINCT, отсутствие WHERE, неэффективные соединения. SQLFluff выявляет их на этапе разработки, до того, как они вызовут проблемы в продакшене.
Установка:
pip install sqlfluff
# Создаём конфиг
cat > .sqlfluff <<EOF
[sqlfluff]
dialect = postgres
templater = dbt
[sqlfluff:rules:L042]
# Запрет SELECT * в проде
select_
_targets = qualified
[sqlfluff:rules:L045]
# Обязательные алиасы полей
aliasing = explicit
[sqlfluff:rules:L052]
# Требование явных объединений
join_types = explicit
EOF
Использование
1. Линтинг SQL-файлов:
sqlfluff lint models/*.sql
Пример вывода:
== [models/orders.sql] FAIL L: 5 | P: 8 | L042 | SELECT * is not allowed in production. L: 12 | P: 15 | L031 | Avoid DISTINCT where possible (use GROUP BY). L: 18 | P: 1 | L044 | Query is missing WHERE clause (full table scan). L: 23 | P: 22 | L052 | JOIN should specify type (INNER, LEFT, etc.).2. Автоматические исправления:
sqlfluff fix models/*.sql
До SQLFluff:
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE order_date > '2024-01-01';
Проблемы:
1. SELECT * (возвращает ненужные поля);
2. Неявное объединение (неясное намерение);
3. Сравнение даты без явного приведения типа.
После SQLFluff:
SELECT
o.order_id,
o.order_date,
o.total,
c.customer_name
FROM orders AS o
INNER JOIN customers AS c
ON o.customer_id = c.id
WHERE o.order_date > CAST('2024-01-01' AS DATE);
Когда использовать
- dbt-проекты (отлично интегрируется);
- Несколько инженеров, пишущих SQL-запросы (обеспечение согласованности);
- Предотвращение распространённых ошибок;
- Конвейер CI/CD для SQL.
Когда отказаться
- Разработчик-одиночка (меньшая ценность);
- ad-hoc запросы (избыточно);
- Нестандартный диалект SQL (ограниченная поддержка).
Скрытая функция
Пользовательские правила для антипаттернов в вашей команде:
# custom_rules/no_cross_joins.py
from sqlfluff.core.rules import BaseRule, LintResult
class Rule_Custom_NoCrossJoin(BaseRule):
"""Запрет CROSS JOIN (почти всегда необязательны)."""
def _eval(self, segment, **kwargs):
if segment.is_type("join_clause"):
if "CROSS" in segment.raw_upper:
return LintResult(
anchor=segment,
description="Найден CROSS JOIN. Почти всегда необязателен.",
fixes=[...] # Опционально: предложение исправления
)
Добавить в .sqlfluff.
С осторожностью
Слишком агрессивный линтинг убивает продуктивность:
# Слишком строго (инженеры возненавидят)
sqlfluff lint --rules all
# Лучше начать с критических правил
sqlfluff lint --rules L042,L044,L052
Постепенно добавляйте правила со временем. Соблюдайте баланс между отловом проблем и сопротивлением разработчиков.
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d976 702
День 2631. #ЗаметкиНаПолях
Проверяем Конфигурацию при Запуске. Окончание
Начало
Доступ к другим сервисам внутри валидатора
Поскольку валидатор является обычным классом, зарегистрированным в DI, вы можете внедрять зависимости. Это одно из ключевых преимуществ по сравнению с аннотациями данных. Кроме того, аннотации данных не могут выражать зависимости между свойствами. В валидаторе вы можете добавить любую логику.
public class DatabaseOptionsValidator
: IValidateOptions<DatabaseOptions>
{
private readonly IHostEnvironment _env;
public DatabaseOptionsValidator(IHostEnvironment env)
=> _env = env;
public ValidateOptionsResult Validate(
string? name, DatabaseOptions opts)
{
var errors = new List<string>();
// Более строгие правила вне среды разработки
if (!_env.IsDevelopment())
{
…
}
…
}
}
Именованные конфигурации
IValidateOptions<T> поддерживает именованные конфигурации через параметр name. Это важно при регистрации нескольких экземпляров одного типа конфигурации — например, двух разных клиентов API:
public class ApiClientOptionsValidator
: IValidateOptions<ApiClientOptions>
{
public ValidateOptionsResult Validate(
string? name, ApiClientOptions opts)
{
…
// … Валидация общих свойств …
// Более строгие правила для API Payments
if (name == "Payments"
&& opts.Timeout > 30)
errors.Add($"Таймаут клиента [{name}] не должен превышать 30 сек.");
…
}
}
Регистрация именованных настроек:
builder.Services
.AddSingleton<IValidateOptions<ApiClientOptions>, ApiClientOptionsValidator>();
builder.Services
.AddOptions<ApiClientOptions>("Payments")
.BindConfiguration("ApiClients:Payments")
.ValidateOnStart();
builder.Services
.AddOptions<ApiClientOptions>("Shipping")
.BindConfiguration("ApiClients:Shipping")
.ValidateOnStart();
Использование в сочетании с OptionsBuilder<T>
API OptionsBuilder<T> (возвращаемый функцией AddOptions<T>()) имеет перегрузку метода .Validate(), которая принимает делегат. Это подходит для простых случаев. Для чего-либо более сложного предпочтительнее использовать реализацию IValidateOptions<T> — она позволяет тестировать логику проверки и не размещать её в Program.cs:
// Делегат — для простых проверок
builder.Services
.AddOptions<FeatureFlagOptions>()
.BindConfiguration("FeatureFlags")
.Validate(opts => opts.Percentage is >= 0 and <= 100,
"Percentage должен быть от 0 до 100")
.ValidateOnStart();
// Класс – для сложных проверок
builder.Services
.AddSingleton<IValidateOptions<SmtpOptions>, SmtpOptionsValidator>()
.AddOptions<SmtpOptions>()
.BindConfiguration("Smtp")
.ValidateOnStart();
Вы можете объединить оба варианта в одном типе параметров — фреймворк запускает все зарегистрированные валидаторы и агрегирует ошибки.
Источник: https://bartwullems.blogspot.com/2026/03/validating-configuration-at-startup.html6 702
День 2630. #ЗаметкиНаПолях
Проверяем Конфигурацию при Запуске. Начало
.NET-приложения со строго типизированной конфигурацией IOptions<T> и его вариантами предоставляют удобный способ привязки разделов appsettings.json к классам C#. Но привязка — это не то же самое, что проверка: отсутствие обязательного значения или число, выходящее за пределы допустимого диапазона, без проблем привяжутся и незаметно сломают приложение во время выполнения. IValidateOptions<T> — механизм, предоставляемый .NET для решения этой проблемы.
Рассмотрим типичный класс параметров:
public class SmtpOptions
{
public string Host { get; set; } = "";
public int Port { get; set; }
public string FromAddress { get; set; } = "";
}
Если в файле appsettings.json отсутствует Host, приложение запустится нормально. Ошибка проявится только при отправке email. Аннотации данных ([Required], [Range] и т.п.) в сочетании с вызовом ValidateDataAnnotations() помогают, но с ними есть 2 проблемы.
1. Они используют рефлексию, что приводит к:
- Затратам на производительность, что важно в сценариях с высокой нагрузкой и требованиям к быстрому запуску.
- Несовместимости с AOT и агрессивным триммингом (<PublishTrimmed>true</PublishTrimmed>). Могут быть исключены метаданные, от которых зависит рефлексия, что приводит к предупреждениям IL2026/IL3050 или к сбоям во время выполнения.
Это можно исправить, используя генератор кода в .NET8+. Для активации генератора для конкретного класса валидатора параметров потребуется:
- Класс параметров, помеченный атрибутами Data Annotation.
- Частичный класс, реализующий IValidateOptions<T> и помеченный [OptionsValidator], с пустым телом:
using Microsoft.Extensions.Options;
[OptionsValidator]
public partial class ValidateSmtpOptions
: IValidateOptions<SmtpOptions>
{
}
Генератор заполнит тело класса при сборке.
2. Но иногда аннотация данных вовсе недостаточно, когда необходимы:
- Проверка нескольких свойств объекта конфигурации;
- Асинхронные или основанные на данных из БД проверки;
- Условная логика в зависимости от среды;
- Многократно используемые валидаторы, общие для нескольких типов конфигурации.
Здесь пригодится реализовать IValidateOptions<T>. Это интерфейс в Microsoft.Extensions.Options с единственным методом:
public interface IValidateOptions<TOptions>
where TOptions : class
{
ValidateOptionsResult Validate(
string? name, TOptions options);
}
Вы реализуете этот интерфейс, регистрируете его в DI-контейнере, и инфраструктура Options вызывает его автоматически:
- лениво (при первом обращении),
- немедленно при запуске, если используется ValidateOnStart().
Вот простой валидатор для класса, описанного выше:
public class SmtpOptionsValidator
: IValidateOptions<SmtpOptions>
{
public ValidateOptionsResult Validate(
string? name, SmtpOptions opts)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(opts.Host))
errors.Add("Требуется Host");
if (opts.Port is < 1 or > 65535)
errors.Add($"Port должен быть от 1 до 65535");
if (string.IsNullOrWhiteSpace(opts.FromAddress))
errors.Add("Требуется FromAddress");
return errors.Count > 0
? ValidateOptionsResult.Fail(errors)
: ValidateOptionsResult.Success;
}
}
Регистрируем в Program.cs:
builder.Services
.Configure<SmtpOptions>(
builder.Configuration.GetSection("Smtp"))
.AddSingleton<IValidateOptions<SmtpOptions>, SmtpOptionsValidator>()
.AddOptions<SmtpOptions>()
.ValidateOnStart();
Если проверка не проходит, во время выполнения app.Run() генерируется исключение OptionsValidationException, поэтому сервис никогда не запустится с некорректной конфигурацией. Этот вариант «быстрого сбоя» обычно предпочтительнее, чем исключение NullReferenceException во время выполнения, возникающее где-то в глубинах бизнес-логики.
Окончание следует…
Источник: https://bartwullems.blogspot.com/2026/03/validating-configuration-at-startup.html6 702
День 2629. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
30. Лидерство и наставничество
«Можете ли вы рассказать о своем подходе к лидерству и наставничеству в команде разработчиков? Как вы обеспечиваете положительное влияние вашего лидерства на результаты проекта и на развитие членов команды?»
Хороший ответ
Мой подход к лидерству и наставничеству в команде разработчиков основан на личном примере, развитии открытой коммуникации и активной поддержке профессионального роста членов команды. Вот как я реализую эти принципы.
- Лидерство личным примером: Я верю в демонстрацию поведения и практик, которые ожидаю от своей команды. Это включает в себя соблюдение лучших практик кодирования, поддержание высокого стандарта качества кода и демонстрацию приверженности целям проекта. Например, я обязательно участвую в проверках кода, вношу свой вклад в задачи кодирования и остаюсь в курсе последних технологий .NET, чтобы эффективно руководить командой.
- Развитие открытой коммуникации: Я поощряю регулярные командные встречи и индивидуальные консультации, чтобы обеспечить постоянную открытость каналов связи. Это помогает решать любые проблемы на ранних этапах, обмениваться идеями и совместно находить решения проблем.
- Профессиональный рост и наставничество: Я привержен профессиональному развитию членов своей команды. Это включает в себя регулярные сессии наставничества, поощрение посещения семинаров и конференций, а также предоставление членам команды возможностей принимать новые вызовы. Начинающих разработчиков я часто объединяю в пары с более опытными коллегами и чередую обязанности в проектах, чтобы расширить их кругозор и навыки.
- Обратная связь и признание: Я стараюсь предоставлять конструктивную обратную связь и отмечать достижения. Позитивное подкрепление и конструктивная критика помогают укрепить уверенность и улучшить навыки, что, в свою очередь, повышает производительность команды и моральный дух.
Внедряя эти стратегии, я стремлюсь создать благоприятную среду, которая не только способствует успеху проекта, но и развитию каждого члена команды.
Часто встречающийся неправильный ответ
«Как лидер, я принимаю все ключевые решения и определяю направление проекта. Я ожидаю, что моя команда будет следовать моему руководству и сосредоточится на своих задачах, чтобы проект был завершен вовремя».
Почему это неправильно
- Авторитарный подход: Этот ответ указывает на нисходящий, командно-административный подход к лидерству, который может подавлять творчество и снижать вовлеченность команды. Современное лидерство в разработке программного обеспечения часто требует инклюзивности и сотрудничества.
- Отсутствие делегирования: Не вовлекая команду в процесс принятия решений, лидер упускает ценные идеи и лишает членов команды возможности расти, преодолевая трудности и принимая решения.
- Пренебрежение наставничеством: Ответ не затрагивает ни один аспект наставничества или личностного развития членов команды, которые имеют решающее значение для поддержания мотивированной и квалифицированной команды.
Эта ошибка обычно проистекает из традиционного взгляда на лидерство, который может не соответствовать коллаборативной и динамичной природе современных команд разработчиков программного обеспечения, особенно в средах, поощряющих инновации и непрерывное обучение.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
6 702
День 2628. #SystemDesign101
Что Происходит, Когда вы Вводите google.com в Браузере?
1. Вы вводите адрес веб-сайта в адресную строку браузера.
2. Браузер сначала проверяет свой кэш. Если адрес не найден в кэше, он должен найти IP-адрес.
3. Начинается поиск DNS (представьте, что вы ищете номер телефона). Запрос проходит через различные DNS-серверы (корневой, TLD - сервер имен доменов верхнего уровня - и авторитативный). Наконец, извлекается IP-адрес.
4. Браузер инициирует TCP-соединение, начиная с рукопожатия. Например, в случае HTTP 1.1 клиент и сервер выполняют трёхстороннее TCP-рукопожатие с сообщениями SYN, SYN-ACK и ACK.
5. Браузер отправляет HTTP-запрос на сервер, и сервер отвечает файлами HTML, CSS и JS.
6. Браузер обрабатывает ответ. Он анализирует HTML-документ и создаёт деревья DOM и CSSOM.
7. Браузер выполняет код JavaScript и отображает страницу, проходя через различные этапы (токенизатор, парсер, дерево рендеринга, компоновка и отрисовка).
8. Веб-страница появляется на экране.
Источник: https://bytebytego.com/guides/what-happens-when-you-type-google/
6 702
День 2627. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Окончание
Часть 1
Часть 2
Часть 3
Часть 4
Ресурсы
Существует множество видео, курсов и книг по теме. Платные курсы рекламировать не буду, решайте сами, стоят ли они того.
Книги:
1) «System Design. Подготовка к сложному интервью» Сюй А.
- Красивые диаграммы;
- Более 15 вариантов системного проектирования.
2) «Высоконагруженные приложения. Программирование, масштабирование, поддержка» Клеппман М.
- Глубокий технический анализ;
- Читать после изучения основ;
- Отличный справочник.
Сдвиг мышления, который изменил всё
Перед изучением системного проектирования.
Синдром самозванца:
- «Я недостаточно опытен»
- «Это слишком сложно»
- «Я никогда не пойму распределённые системы»
Тревога перед собеседованием:
- Надеюсь, я сталкивался с этой проблемой;
- Паника, когда застреваю;
- Заучивание решений.
Карьерный застой:
- Ограничен задачами по программированию;
- Неучастие в принятии архитектурных решений;
- Мимо руководящих должностей.
После освоения системного проектирования
Уверенность:
- Возможность обсуждать архитектуру с кем угодно;
- Понимание компромиссов;
- Мышление в масштабе.
Успех на собеседованиях:
- Возможность ответить на любой вопрос по проектированию;
- Сосредоточенность на размышлении, а не на запоминании.
На собеседованиях по системному проектированию (и в реальной работе):
Плохой ответ: «Мы будем использовать микросервисы и Kafka».
Хороший ответ: «Мы могли бы использовать микросервисы для независимого масштабирования, но, учитывая размер нашей команды (5 инженеров) и текущий масштаб (10 000 пользователей), хорошо структурированный монолит с чёткими границами модулей будет проще в обслуживании. Мы можем выделить сервисы позже, когда достигнем 100 тыс пользователей или нам потребуется независимое развёртывание».
Что изменилось:
- Продемонстрировал понимание компромиссов;
- Учел команду и масштаб;
- Предоставил обоснование;
- Обсудил эволюцию.
Интервьюерам не нужны идеальные проекты. Им нужно увидеть, как вы мыслите.
Итого
Проектирование систем — это навык, а не талант.
Вам не нужны:
- 10 лет опыта;
- Степень в области компьютерных наук;
- Фотографическая память;
- Природный талант.
Нужно только:
- Любопытство изучать, как всё работает;
- Готовность к систематическому обучению;
- Практика проектирования реальных систем;
- Терпение.
Проектирование систем — это не магия. Это навык, которому можно научиться, как приготовление пищи, вождение или игра на гитаре. Начните с основ. Практикуйтесь постоянно. Развивайте свои знания. Помните: каждый архитектор, каждый сеньор, каждый системный проектировщик начинал с того места, где вы сейчас.
Источник: https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
6 702
День 2626. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Продолжение
Часть 1
Часть 2
Часть 3
6. Применение в работе (реальные системы и ограничения)
Теория бесполезна, пока вы не создадите что-то реальное. На работе я был в команде, разрабатывавшей систему обработки платежей:
- Большой объём транзакций;
- Требуется строгая согласованность данных;
- Нулевая терпимость к потере данных;
- Сложная бизнес-логика.
Что я применил
1) Архитектурное решение - разделить монолит на сервисы:
- платежи;
- уведомления;
- бухгалтерия;
- обнаружение мошенничества.
Почему: Каждый сервис имеет разные потребности в масштабировании.
2) Стратегия коммуникаций:
- Синхронная: REST API для операций, ориентированных на пользователя.
- Асинхронная: Kafka для внутренних событий:
Платеж обработан
→ Уведомить пользователя;
→ Обновить учётную запись;
→ Провести проверку на мошенничество.
3) Согласованность данных:
Проблема: деньги не могут быть дублированы или потеряны.
Решение:
- Ключи идемпотентности (предотвращение дублирования транзакций);
- Паттерн Saga для распределённых транзакций;
- Паттерн Источники Событий для аудита.
4) Надёжность:
- Прерыватели цепи (предотвращение каскадных сбоев)
- Повторные попытки с экспоненциальной задержкой;
- Очереди недоставленных сообщений.
5) Мониторинг:
- Отслеживание задержек (P50, P95, P99);
- Оповещения об уровне ошибок;
- Уровень успешности транзакций;
- Панель мониторинга в реальном времени.
Создание реальных систем научило меня:
1) Идеальных проектов не существует
- Каждый выбор — это компромисс;
- Бизнес-ограничения имеют значение;
- Технический долг неизбежен.
2) Эксплуатация важнее проектирования
- Мониторинг и оповещения критически важны;
- Отладка распределённых систем сложна;
- Необходимо планирование отката.
3) Важна команда
- Самый лучший проект ничего не значит, если команда не может его поддерживать;
- Документация имеет решающее значение;
- Привлечение новых инженеров занимает время.
Этот реальный опыт сделал меня в 10 раз лучше на собеседованиях по проектированию систем. Я перестал давать шаблонные ответы. Я начал обсуждать реальные компромиссы, основанные на реальном опыте.
7. Я обучал других (лучший способ учиться)
Обучение выявляет пробелы в понимании.
Что я начал делать
1) Наставлял младших инженеров:
- Объяснял кэширование, базы данных, масштабирование;
- Разбирал реальные системные проекты;
- Отвечал на их вопросы «почему».
2) Проводил внутренние технические презентации:
- «Как работает наша платежная система»;
- «Введение в очереди сообщений»;
- «Базы данных: когда что использовать».
3) Писал посты в блоге с диаграммами:
- Помогает систематизировать мысли;
- Приходится ясно объяснять;
- Появляется обратная связь от читателей.
4) Проводил пробные собеседования с коллегами:
- Практика в формулировании мыслей;
- Умение справляться с трудностями;
- Разные подходы к решению.
Почему обучение сработало?
Если вы не можете объяснить что-то просто, значит, вы недостаточно хорошо это понимаете.
Каждый раз, когда я обучал кого-то:
- Я прояснял собственное мышление;
- Я обнаруживал пробелы в своих знаниях;
- Я учился на вопросах, на которые не мог ответить.
Преподавание превращает понимание в мастерство.
Окончание следует…
Источник: https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
6 702
День 2625. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Продолжение
Начало
Продолжение
5. Я проектировал реальные системы (на бумаге, а затем в коде)
Теория без практики бесполезна. Освоив основы, я перестал просто наблюдать. Я начал проектировать системы. Каждое воскресенье я выбирал реальную систему: WhatsApp, YouTube, Uber, Instagram, TinyURL, Netflix, Twitter, Dropbox.
Шаблон разработки системы
1) Функциональные требования (Что нужно делать?)
Пример (Twitter):
- Публиковать твиты (280 символов);
- Подписываться на пользователей;
- Просматривать ленту (твиты от людей, на которых вы подписаны);
- Лайки/ретвиты.
2) Нефункциональные требования (Масштабируемость, производительность, доступность)
- 300 млн активных пользователей;
- 500 млн твитов в день;
- Загрузка ленты <300 мс;
- 99,95% времени безотказной работы.
3) Оценка (приблизительные расчёты)
Хранилище:
- 500 млн твитов в день × 280 символов × 365 дней = 51 ТБ/год (только текст);
- Фотографии: 200 млн/день × 200 КБ = 40 ТБ/день;
- Видео: отдельный расчёт.
Трафик:
500 млн твитов/день ÷ 86400 секунд = ~5800 твитов/секунду.
Пик: 5x среднее значение = 29000 твитов/секунду
4) Высокоуровневый дизайн (нарисуйте архитектуру)
Клиент (веб/мобильное приложение) -> Балансировщик нагрузки -> API-серверы (сервис твитов, сервис ленты, сервис пользователей) -> Кэш (Redis) + БД (PostgreSQL, Cassandra) -> Объектное хранилище (S3 для медиафайлов).
5) Детальный дизайн (подробный анализ компонентов)
Как сгенерировать ленту?
- Подход 1: Пул-модель (интенсивное чтение)
Когда пользователь открывает приложение, запросить все подписки, получить последние твиты, объединить и отсортировать по времени.
Проблема: медленно для пользователей, подписанных на тысячи человек.
- Подход 2: Пуш-модель (интенсивная запись)
Когда кто-то публикует твит, отправить его во все ленты подписчиков. Быстрое чтение (предварительно вычислено).
Проблема: дорого для знаменитостей (миллионы подписчиков).
- Подход 3: Гибридный
Пул для твитов знаменитостей. Пуш для обычных пользователей.
6) Масштабирование и оптимизация
- Добавить кэширование (Redis);
- Шардирование базы данных (по user_id);
- Асинхронная обработка (очереди сообщений);
- CDN для медиафайлов.
7) Обработка сбоев
- Что если БД выйдет из строя? (Репликация)
- Что если сервер приложения выйдет из строя? (Проверки работоспособности балансировщика нагрузки)
- Что если кэш выйдет из строя? (стратегия Cache-aside).
Я проектировал по одной системе в неделю в течение 12 недель. К 5й системе я перестал гуглить решения, а стал мыслить самостоятельно. К 10й я смог сравнивать различные подходы и обосновывать свой выбор. В этом разница между запоминанием и пониманием. Я также читал решения распространённых задач проектирования систем, чтобы выявить недостатки своего решения и улучшить его.
Продолжение следует…
Источник: https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
6 702
День 2624. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Продолжение
Начало
3. Я перестал смотреть обучающие видео и начал смотреть, как люди думают
Переломный момент: пробные собеседования. Вместо того чтобы смотреть, как кто-то объясняет концепции, я смотрел, как люди решают задачи в реальном времени.
Обучающие видео учат вас, что делать. Пробные собеседования учат, как думать. Кандидаты на пробных собеседованиях:
- Задавали уточняющие вопросы;
- Совершали ошибки и исправляли их;
- Обсуждали компромиссы;
- Меняли подходы в процессе проектирования.
Я научился процессу, а не только ответам.
Что я узнал, наблюдая?
1) Всегда задавайте уточняющие вопросы
- Каков масштаб? (1000 пользователей? 1 миллиард?)
- Каковы основные функции?
- Что важнее: скорость или стабильность?
- Для чего мы оптимизируем (время или размер)?
2) Чётко определите требования
Функциональные:
- Публикация фото;
- Подписка на других;
- Просмотр ленты.
Нефункциональные:
- 100 миллионов активных пользователей в день;
- Загрузка ленты менее чем за 200 мс;
- 99,9% времени безотказной работы;
- 10 миллионов фотографий загружается ежедневно.
3) Всегда обсуждайте компромиссы
Никогда не говорите: «Мы будем использовать Redis для кэширования».
Говорите: «Мы будем использовать Redis для кэширования, потому что он работает в оперативной памяти (быстро), но нам понадобится стратегия резервного копирования, поскольку данные изменчивы».
4. Я начал рисовать (хотя не умею)
Неожиданное открытие: эскизы помогли во всём разобраться.
Я не художник. Мои диаграммы — это просто прямоугольники и стрелки. Но рисунок «клиент → балансировщик нагрузки → серверы приложений → база данных» позволяет наглядно представить абстрактные понятия.
1) Поток запроса стал реальным
Когда я нарисовал путь запроса пользователя, стало:
- Видно, где возникают узкие места;
- Понятно, почему помогает кэширование;
- Очевидны точки отказа.
2) Компоненты обрели смысл
Рисование поможет понять:
- Зачем размещать кэш между приложением и БД;
- Где разместить очередь сообщений;
- Когда следует разделять на микросервисы.
3) Компромиссы стали очевидны
Рисование двух подходов рядом покажет:
- Какой дизайн проще;
- Какой масштабируется лучше;
- Какой стоит дороже.
Процесс рисования
1) Нарисуйте счастливый сценарий (всё работает);
2) Добавьте сценарии сбоев (что ломается?);
3) Добавьте решения (кэширование, репликация, балансировка нагрузки);
4) Оцените показатели (запросы в секунду, хранилище, пропускная способность).
Даже сегодня, когда я застреваю, я беру бумагу и ручку. 5-минутный набросок часто даст больше ясности, чем 2 часа чтения.
Продолжение следует…
Источник: https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
6 702
День 2623. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Начало
Путь от провала на собеседовании до проектирования масштабируемых производственных систем.
Автор оригинала: Soma Sharma
Собеседование, которое изменило всё
Google. Второй раунд. Собеседование по системному проектированию: «Спроектируйте ленту Instagram для 2 миллиардов пользователей». У меня в голове всё помутнело. Я начал мямлить что-то о REST API. Упомянул MySQL. Поговорил о кэшировании… А потом тишина. Я понятия не имел, как это делать.
Шесть месяцев спустя я успешно прошёл собеседования по проектированию систем в Meta, Amazon и Netflix. Вот путь от полной растерянности до уверенного в себе системного проектировщика.
1. Я признал, что ничего не знал (и это нормально)
Годами я избегал проектирования систем. Я видел видео под названием «Разрабатываем Твиттер» и думал: «Это для архитекторов, а не для разработчиков, как я». Нет. Проектирование систем — это для всех, кто хочет понять, как ПО работает в масштабе. Я перестал спрашивать: «Достаточно ли я умён для этого?» и начал спрашивать: «Что нужно изучить в первую очередь?»
Проектирование систем — это набор концепций:
1) Инфраструктура:
- Как запросы проходят через интернет;
- Что происходит, когда вы вводите URL-адрес;
- DNS, балансировщики нагрузки, CDN.
2) Данные:
- Где хранить различные типы данных;
- Как сделать БД быстрыми;
- Когда использовать SQL, а когда NoSQL.
3) Масштабирование:
- Обработка 1 тыс пользователей против 1 млн;
- Стратегии кэширования;
- Горизонтальное и вертикальное масштабирование.
4) Надёжность:
- Что происходит при сбоях серверов;
- Как избежать единой точки отказа;
- Стратегии резервного копирования и восстановления.
2. Я разбил «проектирование систем» на части
1) Основы (недели 1–2)
Что происходит, когда вы набираете google.com?
- Поиск DNS (домен → IP-адрес);
- Установление TCP-соединения;
- Отправка HTTP-запроса;
- Обработка запроса сервером;
- Отправка ответа;
- Отображение страницы браузером.
Изученные концепции:
- DNS (телефонная книга интернета);
- Балансировка нагрузки (распределение трафика);
- CDN (сети доставки контента);
- Протоколы HTTP/HTTPS.
2) Данные и хранилища (недели 3–4)
SQL или NoSQL — когда что использовать?
Изученные концепции:
- SQL: структурированные данные, связи, ACID-транзакции;
- NoSQL: гибкая схема, горизонтальное масштабирование, итоговая согласованность;
- Индексирование: ускорение запросов в 100 раз;
- Репликация: копирование данных для повышения надёжности;
- Шардинг: разделение данных между несколькими БД.
Вывод: нет «лучшей» БД, есть подходящая для конкретного случая.
3) Методы масштабирования (недели 5–6)
Как системы обрабатывают миллионы пользователей?
- Вертикальное масштабирование: большие серверы (ограниченно, дорого);
- Горизонтальное масштабирование: больше серверов (неограниченно, сложно).
Кэширование:
- Хранение часто используемых данных в памяти (Redis, Memcached);
- 90% запросов попадают в кэш, а не в БД;
- Ускоряет работу систем в 100 раз.
Балансировка нагрузки:
- Распределение запросов между несколькими серверами: по очереди, по наименьшему количеству соединений, по хэшу IP;
- Предотвращает перегрузку какого-либо отдельного сервера.
Очереди сообщений:
- Разделение сервисов;
- Обработка пиковых нагрузок;
- Асинхронная обработка задач.
4) Архитектурные шаблоны (недели 7–8)
Монолитная архитектура:
✅ Простота разработки и развёртывания;
✅ Лёгкость понимания;
❌ Сложность масштабирования отдельных частей независимо друг от друга;
❌ Сбой в одном месте ведёт к сбою всей системы.
Микросервисы:
✅ Независимое масштабирование компонентов;
✅ Разные команды отвечают за разные сервисы;
❌ Сложное развёртывание;
❌ Проблемы распределённых систем.
Архитектура, управляемая событиями:
- Сервисы взаимодействуют посредством событий;
- Слабая связанность;
- Kafka, RabbitMQ для передачи сообщений.
Архитектура — это не вопрос «что лучше», а вопрос «что подходит для вашего масштаба и команды».
Продолжение следует…
Источник: https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
6 702
День 2629. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
29. Командная работа
«Расскажите, как вы развивали командную работу в ваших проектах? Какие стратегии и инструменты вы использовали для обеспечения эффективной коммуникации и интеграции между членами команды?»
Хороший ответ
По моему опыту, эффективное командное взаимодействие в проектах разработки включает в себя сочетание чёткой коммуникации, общности целей и стратегического использования инструментов для совместной работы. Вот некоторые важные аспекты.
- Регулярная коммуникация: я выступаю за ежедневные совещания или встречи в виртуальном формате. Эти встречи помогают всем быть в курсе прогресса проекта и любых препятствий, которые могут потребовать решения. Например, во время недавнего проекта мы использовали Microsoft Teams для ежедневных митингов, что поддерживало вовлечённость и информированность удалённых членов команды.
- Проверки кода: внедрение надёжного процесса проверки кода имеет жизненно важное значение. Мы используем пул-реквесты, которые не только облегчают коллегиальную проверку, но и интегрируют проверки в конвейер CI/CD, где код автоматически собирается и запускаются тесты. Этот процесс гарантирует, что код соответствует стандартам качества до слияния, и способствует обмену знаниями и наставничеству внутри команды.
- Инструменты для совместной работы: использование таких инструментов, как Git, для контроля версий и ветвления функций позволяет нам работать над различными аспектами проекта, не мешая друг другу. Мы регулярно интегрируем наши ветки, чтобы уменьшить проблемы интеграции.
- Общая документация: мы храним документацию по проекту в централизованном ресурсе, что помогает новым членам команды быстрее адаптироваться и служит справочным материалом для всей команды.
- Обратная связь: регулярные ретроспективы позволяют команде анализировать, что работало хорошо, а что нет. Эта непрерывная обратная связь помогает улучшать процессы и взаимодействие, обеспечивая более эффективное сотрудничество в будущих спринтах.
Сочетание этих стратегий и инструментов помогает делать команды продуктивными, сплочёнными и способными эффективно решать сложные проекты.
Часто встречающийся плохой ответ
«Пока каждый выполняет свою работу и соблюдает сроки, нет особой необходимости в дополнительном сотрудничестве или частых встречах. Мы просто отправляем дневные отчёты через e-mail или общаемся в чатах, чтобы держать всех в курсе».
Почему это неправильно:
- Отсутствие активного участия: ответ демонстрирует пассивный подход к сотрудничеству, полагающийся на индивидуальные усилия и минимальное взаимодействие. Эффективное командное сотрудничество требует более активных стратегий вовлечения для управления зависимостями и эффективной интеграции работы.
- Недооценка ценности взаимодействия: утверждение, что достаточно отчётов о проделанной работе по e-mail, игнорирует преимущества общения в режиме реального времени и интерактивных обсуждений, которые имеют решающее значение для быстрого и эффективного решения сложных проблем.
- Игнорирование командной динамики: такой подход не способствует формированию командной культуры, которая поощряет общую ответственность и коллективное решение проблем. Он может привести к изоляции среди членов команды и потенциально затруднить эффективное реагирование команды на изменения и вызовы.
Эта ошибка часто возникает из-за недостаточного понимания важности акцента на командной динамике и коммуникации. Она также может быть вызвана предыдущим опытом работы в средах, где приоритет отдавался индивидуальному вкладу, а не командному сотрудничеству, либо в небольших проектах, что менее эффективно в более крупных или сложных проектах.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
6 702
День 2621. #ЧтоНовенького #МоиИнструменты
Bookmark Studio Закладки на Стеройдах в Visual Studio
Закладки в Visual Studio всегда были простой и надёжной функцией. Многие разработчики регулярно ими пользуются. Он полезны, но имеют несколько недостатков, которые мешали им быть настолько эффективными и актуальными, насколько они могли бы быть.
Навигация - одна из самых больших проблем. Можно было перемещаться между закладками, но не было простого способа перейти непосредственно к конкретной закладке с помощью клавиатуры. Это неудобно, когда закладок больше нескольких штук. Ещё один распространённый запрос пользователей – возможность делиться закладками с коллегами или повторно использовать их в разных репозиториях, ветках или пул-реквестах.
Bookmark Studio - новое экспериментальное расширение Visual Studio от Мэдса Кристенсена, которое развивает существующий опыт работы с закладками. Вот его основные функции.
1. Навигация по слотам. Закладки можно назначать слотам с 1 по 9 и переходить к ним напрямую с помощью простых сочетаний клавиш, таких как
Alt+Shift+1 – Alt+Shift+9. Это делает закладки более продуманными и удобными для быстрого доступа к нескольким важным разделам. Новые закладки автоматически назначаются следующему доступному слоту, если это возможно, поэтому быстрая навигация часто работает без дополнительной настройки. Bookmark Studio также интегрируется с существующими командами закладок Visual Studio, т.е. текущие сочетания клавиш продолжат работать.
2. Менеджер закладок (см. картинку). Отображает все закладки в одном месте и упрощает просмотр, поиск и навигацию между ними. Вы можете фильтровать по имени, файлу, местоположению, цвету или слоту и переходить непосредственно к закладке двойным щелчком или навигацией с помощью клавиатуры. Он разработан для того, чтобы упростить повторное обращение к закладкам, особенно при переключении контекста или возвращении к коду позже.
3. Метки, цвета и папки для закладок. Ничего из этого не требуется, и вы можете продолжать использовать закладки по-прежнему. Но когда вы отлаживаете, рефакторите, проверяете код или исследуете незнакомые области кодовой базы, этот дополнительный контекст поможет сделать закладки более полезными и упростить понимание их работы. Все метаданные закладок хранятся для каждого решения, поэтому они остаются с вашей работой между сессиями.
4. Экспорт. Закладки часто наиболее ценны, когда они отражают намерение, а не просто местоположение. Bookmark Studio позволяет легко экспортировать закладки в виде обычного текста, Markdown или CSV. Т.е. вы можете включать закладки в пул-реквесты, делиться с коллегами или перемещать полезные наборы закладок между репозиториями.
5. Отслеживание. Bookmark Studio отслеживает закладки по мере перемещения текста во время редактирования, поэтому они остаются прикрепленными к соответствующему коду, а не смещаются на неправильную строку.
Итого
Если вы уже используете закладки в Visual Studio, Bookmark Studio покажется вам знакомым за считанные минуты. А если вы когда-либо хотели, чтобы закладки могли делать немного больше, это расширение стоит посмотреть.
Источник: https://devblogs.microsoft.com/visualstudio/bookmark-studio-evolving-bookmarks-in-visual-studio/6 702
День 2620. #ЗаметкиНаПолях
Паттерн Адаптер: Упрощаем Интеграцию со Сторонними Сервисами. Окончание
Начало
Пример из реальной жизни: интеграция с облачным хранилищем
Представьте себе систему, поддерживающую несколько облачных провайдеров:
- Amazon S3
- Azure Blob Storage
- Google Cloud Storage
У каждого провайдера свои SDK и API. Без адаптера код становится тесно связанным с конкретной реализацией.
1. Определение общего интерфейса
public interface ICloudStorage
{
Task UploadAsync(string container,
string file, Stream stream);
Task<Stream> DownloadAsync(
string container, string file);
Task DeleteAsync(string container,
string file);
}
Интерфейс представляет контракт вашей системы и стабильную абстракцию. Ваше приложение должно зависеть только от него.
2. Реализуем адаптер (пример для Google Cloud)
public class GoogleCloudStorageAdapter
: ICloudStorage
{
private readonly StorageClient _client;
public GoogleCloudStorageAdapter(
StorageClient client)
{
_client = client;
}
public async Task UploadAsync(
string container, string file, Stream stream)
{
await _client.UploadObjectAsync(
container, file, null, stream);
}
public async Task<Stream> DownloadAsync(
string container, string file)
{
MemoryStream stream = new();
await _client.DownloadObjectAsync(
container, file, stream);
stream.Position = 0;
return stream;
}
public async Task DeleteAsync(
string container, string file)
{
await _client.DeleteObjectAsync(container, file);
}
}
Адаптер:
- оборачивает SDK от Google,
- приводит вызовы к вашему интерфейсу,
- скрывает детали реализации.
3. Настройка внедрения зависимостей
builder.Services
.AddTransient<Func<string, ICloudStorage>>(
sp => provider =>
{
return provider switch
{
"Azure" => sp.GetRequiredService<AzureBlobStorageAdapter>(),
"Google" => sp.GetRequiredService<GoogleCloudStorageAdapter>(),
"AWS" => sp.GetRequiredService<S3StorageAdapter>(),
_ => throw new ArgumentException("Неподдерживаемый провайдер")
};
});
Пояснение
- адаптер выбирается динамически,
- обеспечивается гибкость системы,
- есть возможность переключаться во время выполнения.
Когда использовать Адаптер
- интеграция сторонних API,
- работа с устаревшими системами,
- стандартизация нескольких провайдеров,
- переключение реализаций.
Когда не использовать
- интерфейсы уже совместимы,
- преобразование тривиально,
- производительность крайне важна,
- абстракция добавляет ненужную сложность.
Источник: https://thecodeman.net/posts/simplifying-integration-with-adapter-pattern6 702
День 2619. #ЗаметкиНаПолях
Паттерн Адаптер: Упрощаем Интеграцию со Сторонними Сервисами. Начало
Интеграция сторонних сервисов в ваше приложение может быстро стать сложной задачей. Разные API имеют разные форматы запросов, механизмы аутентификации и структуры ответов. Со временем это приводит к тесно связанному коду, который трудно поддерживать, тестировать и расширять. Именно здесь проявляется преимущество паттерна Адаптер.
Паттерн Адаптер позволяет несовместимым интерфейсам работать вместе. Он выступает в качестве моста между вашим приложением и внешней системой, преобразуя один интерфейс в другой, ожидаемый вашим приложением, что позволяет:
- отделить вашу бизнес-логику от внешних систем;
- легко переключаться между провайдерами;
- улучшить тестируемость;
- сократить количество критических изменений.
Сценарий
Есть приложение, использующее следующий интерфейс:
public interface IPaymentProcessor
{
void ProcessPayment(decimal amount);
}
Всё приложение использует эту абстракцию. Однако, есть существующий сервис, производящий оплату:
public class LegacyPaymentService
{
public void MakePayment(string amount)
{
// обработка оплаты
}
}
Проблемы:
- сигнатуры методов отличаются;
- типы параметров отличаются;
- изменить существующий сервис невозможно.
Решение
1. Создаём адаптер
public class PaymentAdapter(
LegacyPaymentService legacyService) :
IPaymentProcessor
{
public void ProcessPayment(decimal amount)
{
var amntStr = amount.ToString("F2");
legacyService.MakePayment(amntStr);
}
}
Адаптер:
- реализует интерфейс приложения (IPaymentProcessor);
- преобразует десятичное число в строку;
- делегирует вызов устаревшему сервису.
Приложение остаётся чистым и не знает об устаревшей реализации.
2. Используем адаптер
LegacyPaymentService legacySvc = new();
IPaymentProcessor processor =
new PaymentAdapter(legacySvc);
processor.ProcessPayment(123.4567868m);
Результат:
- Приложение использует только IPaymentProcessor;
- Адаптер обрабатывает все преобразования;
- Устаревшая система полностью изолирована.
Окончание следует…
Источник: https://thecodeman.net/posts/simplifying-integration-with-adapter-pattern6 702
День 2618. #МоиИнструменты #PG
Инструменты Оптимизации Запросов в PostgreSQL. Часть 5
5. EXPLAIN ANALYZE (для всех SQL баз данных)
Что даёт: точное понимание, как БД выполняет запрос.
Тип: встроенная команда (для всех основных БД)
Зачем: показывает план выполнения запроса — как БД фактически обрабатывает SQL-запрос. Без этого оптимизация — это гадание.
Использование в разных базах данных:
-- PostgreSQL
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT … FROM orders WHERE …;
-- MySQL
EXPLAIN ANALYZE
SELECT … FROM orders WHERE …;
-- SQL Server
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT … FROM orders WHERE …;
Вывод (пример Postgres):
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE customer_id = 12345
AND order_date >= '2024-01-01';
Seq Scan on orders (cost=0.00..185234.25 rows=1 width=120)
(actual time=0.045..2341.234 rows=247 loops=1)
Filter: ((customer_id = 12345) AND (order_date >= '2024-01-01'::date))
Rows Removed by Filter: 9847234
Buffers: shared hit=47234 read=138000
Planning Time: 0.234 ms
Execution Time: 2341.567 ms
Что это значит:
- "Seq Scan" – полное сканирование таблицы (ПЛОХО – не используется индекс)
- "Rows Removed by Filter: 9847234" - Просканировано 9.8млн строк, возвращено 247
- "Execution Time: 2341ms" - 2.3 секунды
Решение – добавить индекс на поля customer_id и order_date:
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
После:
Index Scan using idx_orders_customer_date on orders (cost=0.43..8.45 rows=1 width=120) (actual time=0.023..0.087 rows=247 loops=1) Index Cond: ((customer_id = 12345) AND (order_date >= '2024-01-01'::date)) Buffers: shared hit=5 Planning Time: 0.123 ms Execution Time: 0.112 msЧто это значит: - "Index Scan" – Используется индекс (ХОРОШО) - "Buffers: shared hit=5" – только 5 блоков прочитано (против 185234 в предыдущем случае) - "Execution Time: 0.112ms" – в 20000 раз быстрее Основные шаги оптимизации запросов 1. Выполнить EXPLAIN ANALYZE 2. Обнаружить узкое место (seq scan, дорогой join или сортировку и т.п.) 3. Исправить (добавить индекс, переписать запрос, изменить порядок объединения) 4. Ещё раз выполнить EXPLAIN ANALYZE для проверки Когда использовать - Анализ любого медленного запроса; - Перед написанием сложных запросов (прогноз производительности); - После изменений схемы (проверка влияния); - При добавлении индексов (обоснование использования). Когда отказаться Особых причин не использовать нет. Скрытая функция Сравнение планов запросов 1. Сохранить в файл
psql -c "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT …" > plan_before.json
2. Сделать изменения
3. Сохранить новый план
psql -c "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT …" > plan_after.json
4. Использовать утилиту визуализации плана:
- https://explain.dalibo.com/
- https://explain.depesz.com/
5. Использовать утилиту визуального сравнения (например, https://winmerge.org/)
С осторожностью
EXPLAIN с параметром ANALYZE на самом деле выполняет запрос.
-- Удалит данные!!!
EXPLAIN ANALYZE DELETE FROM orders WHERE …;
-- Безопасное тестирование
BEGIN;
EXPLAIN ANALYZE DELETE FROM orders WHERE …;
ROLLBACK;
-- Либо без ANALYZE (только оценка)
EXPLAIN DELETE FROM orders WHERE …;
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d976 702
День 2617. #Шпаргалка
Виды Классов в C#
Сегодня рассмотрим различные виды классов в C# и как они работают.
Абстрактный
Базовый класс, экземпляр которого нельзя создать. Он может содержать абстрактные и неабстрактные члены и предназначен для наследования от него.
public abstract class Vehicle
{
public abstract int Wheels { get; }
public abstract void TurnOn();
public bool Started { get; protected set; }
}
Создать экземпляр класса Vehicle нельзя, поэтому унаследуем от него. При этом класс-наследник обязан переопределить абстрактные члены:
public class Car : Vehicle
{
public override int Wheels => 4;
public override void TurnOn()
=> Started = true;
}
Изолированный
Специальный тип класса, который ограничивает иерархию наследования. Это предотвращает создание производных типов, что повышает безопасность кода и позволяет компилятору применять оптимизации производительности.
Статический
Экземпляр статического класса нельзя создать, и от статического класса нельзя унаследовать. Все члены должны быть помечены статическими.
public static class SpeedConverter
{
public static decimal ToMph(decimal kph)
=> return kph / 1.6093m;
}
Частичный
В одном и том же пространстве имён нельзя создавать несколько классов с одним именем. Частичный класс позволяет разделить объявление класса на несколько файлов. При компиляции объявления объединяются в один класс. Нельзя дублировать члены с одной сигнатурой в разных объявлениях частичного класса:
public partial class Team
{
public Team() { }
public string Name { get; set; }
}
public partial class Team
{
public int Players { get; set; }
}
Небезопасный
Небезопасный класс позволяет использовать код с указателями:
public unsafe class MemoryReader
{
public void Read(int* value)
{
Console.WriteLine(*value);
}
}
Если вам необходимо работать с указателями, нужно включить небезопасный код в файле .csproj, установив параметр AllowUnsafeBlocks в значение true.
Запись
Запись — ссылочный тип, предназначенный для данных, а не для поведения, и по умолчанию является неизменяемой:
public record class Team(string Name, int Players);
См. подробнее про записи.
Модификаторы доступа
- public – доступен отовсюду;
- internal – доступен внутри сборки;
- private – доступен только внутри включающего его типа;
- protected – доступен внутри включающего его типа и всех его наследников;
- file – доступен только внутри содержащего его файла исходного кода.
Источник: https://www.roundthecode.com/dotnet-tutorials/c-sharp-class-types-explained-examples6 702
День 2616. #SQLServer
SQL Server Незаметно Переименовывает Пользователя
Автор оригинала: Bart Wullems
Учитывая кучу существующих ИИ-помощников, можно было бы ожидать, что мы больше не будем тратить время на глупые проблемы. К сожалению, до этого ещё далеко. Сегодня я потерял немало времени из-за поведения SQL Server, которое кажется очевидным, если вы об этом знаете, а если не знаете —ужасно раздражает.
У меня был скрипт, который должен был быть идемпотентным: создать пользователя БД, если его не существует, или обновить его, если существует. Стандартная процедура. Вот упрощённая версия:
USE [ONT_SampleDB];
GO
IF EXISTS (SELECT * FROM sys.database_principals WHERE name = N'usr_DB_reader')
BEGIN
ALTER USER [usr_DB_reader] WITH LOGIN = [lg_DB_dev_reader];
END
ELSE
BEGIN
CREATE USER [usr_DB_reader] FOR LOGIN [lg_DB_dev_reader];
END
GO
ALTER ROLE [db_datareader] ADD MEMBER [usr_DB_reader];
GO
Выглядит нормально. Запустил один раз — работает. Запустил второй раз, и SQL Server выдаёт ошибку, что не может найти пользователя usr_DB_reader. Пользователя, которого мы только что создали. В той же базе данных. Этим же скриптом.
Что происходит на самом деле?
Когда вы запускаете ALTER USER […] WITH LOGIN = […], SQL Server переименовывает пользователя в соответствии с логином — по умолчанию, без предупреждений.
Таким образом, после первого запуска пользователя usr_DB_reader больше не существует. Он переименован в lg_DB_dev_reader, чтобы соответствовать логину. При втором запуске проверка IF EXISTS ищет usr_DB_reader, ничего не находит и переходит в ELSE — где CREATE USER завершается с ошибкой, потому что такой логин уже сопоставлен с пользователем c другим именем.
Это одно из тех явлений, которое становится понятным, как только вы разберётесь в модели данных, но никаким образом не сообщает вам, что так происходит.
Решение
Явно укажите SQL Server сохранить имя пользователя как есть, добавив NAME = к оператору ALTER USER:
ALTER USER [usr_DB_reader]
WITH NAME = [usr_DB_reader],
LOGIN = [VLM\lg_DB_dev_reader];
Добавление WITH NAME = [usr_DB_reader] говорит SQL Server: да, измени логин, но не трогай имя пользователя. Теперь скрипт действительно идемпотентный.
Почему SQL Server так делает?
SQL Server различает логины (субъекты уровня сервера) и пользователей (субъекты уровня БД). Когда вы связываете пользователя с логином с помощью команды ALTER USER … WITH LOGIN, не указывая имя, SQL Server предполагает, что вы хотите синхронизировать их — поэтому он переименовывает пользователя в соответствии с логином. Это следует соглашениям, но это не то, чего вы ожидаете, если привыкли рассматривать имена пользователей как стабильные идентификаторы.
Источник: https://bartwullems.blogspot.com/2026/03/sql-server-silently-renames-your-user.html6 702
День 2615. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
28. Методы оценки
«Можете ли вы описать методы оценки, которые вы использовали в своих проектах, и объяснить, как они помогли вам обеспечить выполнение проектов в установленные сроки?»
Хороший ответ
В моём опыте управления проектами .NET я использовал комбинацию методов оценки для обеспечения точного прогнозирования и эффективного управления проектом. Среди них экспертная оценка (Expert Judgment), покер планирования (Planning Poker) и анализ точек использования (Case Point Analysis).
1. Экспертная оценка предполагает консультации с опытными членами команды для получения оценок на основе их прошлого опыта. Например, при работе над веб-сервисом, включающим ASP.NET Core и Entity Framework Core, я обсуждал задачи со старшими разработчиками, имеющими аналогичный опыт работы над проектами, чтобы получить реалистичное представление о трудозатратах и сроках.
2. Покер планирования - метод оценки в Agile, использующий консенсус для оценки задач. Каждый член команды предоставляет оценку, используя пронумерованные карточки, и затем следуют обсуждения, пока команда не достигнет консенсуса. Этот метод особенно полезен для вовлечения всей команды в процесс оценки, что повышает вовлечённость и точность оценок. Суть метода:
- Каждый разработчик получает карточки с номерами, представляющими собой стори-пойнты.
- Владелец продукта описывает функцию.
- Разработчики выбирают карточку, представляющую их оценку, не раскрывая её.
- Все карточки раскрываются одновременно, и разногласия обсуждаются до достижения консенсуса.
3. Анализ вариантов использования - адаптирован для оценки трудозатрат, необходимых для пользовательских историй, включающих взаимодействие с пользователями. Он учитывает сложность вариантов использования и корректирует её с учётом технических и организационных факторов.
Суть метода в том, что каждый вариант использования классифицируется как простой, средний или сложный, а затем присваивается вес на основе этих категорий. Это особенно эффективно для проектов с чёткими функциональными требованиями, таких как веб-сервис с несколькими конечными точками.
Комбинация этих методов позволяет предоставить более надёжные оценки, учитывающие неопределённости и позволяющие лучше планировать и управлять рисками. Такой подход помогает гарантировать, что проекты будут реализованы в срок и в рамках заданного объёма, даже при возникновении непредвиденных сложностей.
Часто встречающийся плохой ответ
«Я примерно по опыту оцениваю время, которое потребуется на написание кода и добавляю к нему некоторый процент на непредвиденные проблемы. Обычно этого достаточно.»
Почему это неправильно
- Чрезмерное упрощение: Этот подход чрезмерно упрощён и не учитывает детальные аспекты выполняемых задач. Оценка — это не просто завышение необходимого времени, а понимание сложности и связанных с этим рисков.
- Неточность и неэффективность: Простое увеличение предполагаемого времени кодирования неточно отражает реальные необходимые усилия, особенно для задач, сложность или трудозатраты которых могут быть нелинейными. Этот метод может привести к неэффективному распределению ресурсов и либо к переоценке, либо к недооценке фактического необходимого времени.
- Отсутствие вовлечения заинтересованных сторон: Этот метод не предполагает вовлечения других членов команды и заинтересованных сторон, которые могут предложить ценные идеи для процесса оценки. Он не использует коллективный опыт и знания команды, что может привести к нарушению сроков проекта.
Эта ошибка часто возникает из-за недостатка опыта работы с формальными методами оценки или из-за работы в условиях, где детальное планирование не было приоритетом. Это подчёркивает необходимость структурированного подхода к оценке в управлении проектами разработки ПО, особенно для крупных или сложных проектов.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
